/** One file the conversion relocates, rewrites, or both. */ export interface ConversionFileChange { /** Path relative to the project root, before conversion. */ from: string; /** Path relative to the project root, after conversion. */ to: string; /** Declared SysML package before conversion, when the file declares one. */ fromPackage?: string; /** Declared SysML package after conversion. */ toPackage?: string; /** Full file text after conversion. Undefined for binary payloads. */ content?: string; /** Why this file changed, for the diff header. */ reasons: string[]; } /** A file the conversion creates that had no pre-conversion counterpart. */ export interface ConversionNewFile { path: string; content: string; reason: string; } /** * A refusal. A plan carrying collisions is never applied. * * Collisions are reported as a complete list rather than thrown one at a time: * a user converting a real project wants to see every clash in one pass, not to * discover the second one after fixing the first. */ export interface ConversionCollision { code: 'destination-conflict' | 'package-name-conflict' | 'destination-occupied' | 'no-project-prefix' | 'referenced-superseded-entrypoint' | 'unparsed-package'; message: string; files: string[]; } export interface ConversionWarning { code: 'package-not-mirroring' | 'unclassified-view' | 'vendored-reusable-package' | 'legacy-settings-file' | 'superseded-entrypoint' | 'unresolved-import'; message: string; file?: string; } export interface ConversionPlan { projectRoot: string; /** Package-name prefix every project package shares, e.g. `memo_examples_gpca_pump`. */ projectPrefix: string; /** True when the project is already in the target layout: applying is a no-op. */ alreadyConverted: boolean; changes: ConversionFileChange[]; newFiles: ConversionNewFile[]; /** Legacy semantic settings files the conversion consumes and deletes. */ removals: string[]; /** old package name → new package name, for every renamed package. */ packageRenames: Map; collisions: ConversionCollision[]; warnings: ConversionWarning[]; } /** * The package a file declares, and the constructs it holds. * * This is deliberately a light regex read rather than a Langium parse. The * converter's job is textual — move a file, rename a package, follow the * references — and it must work on files that do not fully parse, which is * exactly the state a stale pre-conversion project is in. Anything that needs * real semantics (which viewpoint a view belongs to) is read as a declaration, * not evaluated. */ interface SourceFacts { packageName?: string; /** `view : ` usages declared in the file. */ viewUsages: string[]; /** `viewpoint ` usages declared in the file. */ viewpointUsages: string[]; /** Name bound by `part :>> viewpointDefinition = ;`. */ viewpointDefinition?: string; /** Package names this file imports. */ imports: string[]; /** * True when the package body holds imports and nothing else — a re-export * wrapper rather than model content. */ declaresOnlyImports: boolean; } export declare function readSourceFacts(text: string): SourceFacts; /** * Longest `_`-separated prefix shared by every project package. * * Derived rather than configured, because the prefix is already a fact about * the project's source: whatever the packages agree on is the project's * namespace. Trailing structural segments are dropped so that a project whose * packages are all called `x_model_catalog_*` yields `x`, not `x_model_catalog` * — those segments describe where files sit, and the whole point of the * conversion is that the directory tree carries that instead. */ export declare function deriveProjectPrefix(packageNames: string[], sharedDirSegments?: string[]): string | undefined; /** * The package name a file at `catalogRelPath` should declare. * * Namespace mirroring, mechanically: prefix, then the directory chain, then the * basename — except where the basename only restates the directory it sits in. */ export declare function derivePackageName(prefix: string, catalogRelPath: string): string; /** * Whether an in-place file's declared package already mirrors its location. * * Two forms are accepted: the canonical one `derivePackageName` emits, and the * same name with the trailing basename segment dropped. Both place the package * at or under its directory's namespace, which is what mirroring means. The * short form is accepted rather than "corrected" because rewriting a package * name is a real edit to a user's model, and MEMO's own hand-authored catalog * uses it — a converter that churned those files would fail its own * idempotence test for a purely cosmetic reason. */ export declare function isMirroringPackageName(name: string, prefix: string, catalogRelPath: string): boolean; export type FileRole = 'catalog' | 'view' | 'viewpoint' | 'entrypoint' | 'vendored-reusable' | 'legacy-entrypoint'; /** * Where a source file belongs after conversion. * * The classification is structural — a file's current directory and the * constructs it declares — and never guesses at ontology layer. Deciding that * `gpca_risk.sysml` "is" safety/risk content would mean the converter forming * an opinion about model semantics, and getting that wrong silently relocates a * user's model into a namespace they did not choose. Files already inside the * catalog therefore keep their place; only files outside it, whose location is * unambiguously wrong under section 6.2, are moved. */ export declare function classifyFile(relPath: string, facts: SourceFacts, viewpointGroup: (viewpointUsage: string) => string): { role: FileRole; to: string; }; /** * Directory name for the viewpoint a view is governed by. * * Prefers the directory the reusable viewpoint's own source sits in, so a * project's `viewpoints/risk/` lines up with the ontology's * `src/viewpoints/risk/`. Falls back to the usage name with its `Viewpoint` * suffix removed. */ export declare function viewpointGroupName(usageName: string, declaringFileByUsage: Map): string; /** * Rewrite package references in `text` according to `renames`. * * Word-boundary anchored and applied longest-name-first, because package names * nest: renaming `a_b` before `a_b_c` would corrupt every reference to the * longer name. Comments and doc strings are rewritten too — deliberately, since * a stale package name in a comment is a documentation defect the conversion * would otherwise create. */ export declare function rewritePackageReferences(text: string, renames: Map): string; /** * Rewrite the file's own `package ` declaration. * * Separate from reference rewriting because a file's declaration is renamed * even when nothing else refers to it, and because the declaration is the one * occurrence that must change exactly once. */ export declare function rewritePackageDeclaration(text: string, toPackage: string): string; /** * Rewrite model-owned payload URIs to follow their payload files. * * Artifact identities carry a project-relative URI (`imageUri`, `uri`, * `payloadUri`, `sourceUri`). When the payload moves, an unrewritten URI leaves * the artifact pointing at nothing, and the hash check that would have caught * it passes vacuously because the file is simply absent. This is the quietest * failure the conversion could produce, so it is handled explicitly rather than * left to the package-reference pass. */ export declare function rewriteArtifactUris(text: string, moves: Map): string; export interface PlanOptions { /** * Viewpoint usage name → file that declares it, across the resolved * reusable closure. Supplied by the command, which has already resolved the * project; the planner stays free of resolution so it can be tested on a * directory alone. */ viewpointDeclarations?: Map; /** Package name → the reusable package that declares that viewpoint usage. */ viewpointPackages?: Map; /** * Also rename packages of files that do not move but whose declared name * does not mirror their location. * * Off by default. Renaming a package a user chose is a real edit to their * model with no functional payoff — the file already sits where it belongs, * and only the spelling of its namespace is off. Reporting it as a warning * lets the owner decide. MEMO's own corpus turns it on, because a shipped * example whose packages still spell `…_model_catalog_…` is demonstrating * the layout the catalog replaced. */ normalizeNames?: boolean; } /** * Describe, without writing anything, the conversion of `projectRoot`. * * Every disk write in this module happens in `applyConversion`. That split is * what makes `--dry-run` trustworthy: dry-run is not a flag threaded through * the write path that might be missed on one branch, it is the absence of a * call to the only function that writes. */ export declare function planConversion(projectRoot: string, options?: PlanOptions): ConversionPlan; /** * A human-readable description of the plan. * * Deliberately not a unified content diff by default: the conversion's changes * are overwhelmingly moves and one-token renames, and a line-level diff of * twenty relocated files buries the two facts a reviewer needs — what moved, * and what got renamed. `--diff` adds the content hunks on top of this. */ export declare function renderPlan(plan: ConversionPlan, options?: { diff?: boolean; }): string; export interface ApplyResult { written: string[]; moved: string[]; created: string[]; removed: string[]; } /** * The only function in this module that writes. * * It refuses a plan carrying collisions, and it refuses to duplicate: a move * writes the destination and removes the source, so a half-applied conversion * leaves one copy of each file rather than two divergent ones. Directories left * empty by the moves are pruned, because an empty `model/views/` beside * `model/catalog/viewpoints/` is exactly the superseded layout the conversion * exists to eliminate. */ export declare function applyConversion(plan: ConversionPlan): ApplyResult; export {}; //# sourceMappingURL=project-conversion.d.ts.map