@topogram/cli 0.3.78 → 0.3.80

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (100) hide show
  1. package/CHANGELOG.md +20 -0
  2. package/package.json +2 -2
  3. package/src/agent-brief.js +29 -23
  4. package/src/agent-ops/query-builders/change-risk/{import-plan.js → extract-plan.js} +1 -1
  5. package/src/agent-ops/query-builders/change-risk/review-packets.js +5 -5
  6. package/src/agent-ops/query-builders/change-risk.js +1 -1
  7. package/src/agent-ops/query-builders/common.js +2 -2
  8. package/src/agent-ops/query-builders/multi-agent.js +1 -1
  9. package/src/agent-ops/query-builders/workflow-context-shared.js +4 -4
  10. package/src/catalog/provenance.js +1 -1
  11. package/src/cli/catalog-alias.d.ts +2 -0
  12. package/src/cli/catalog-alias.js +2 -2
  13. package/src/cli/command-parser.js +2 -0
  14. package/src/cli/command-parsers/core.js +9 -5
  15. package/src/cli/command-parsers/extractor.js +40 -0
  16. package/src/cli/command-parsers/import.js +11 -17
  17. package/src/cli/command-parsers/project.js +0 -3
  18. package/src/cli/commands/catalog/copy.js +3 -3
  19. package/src/cli/commands/catalog/help.js +1 -2
  20. package/src/cli/commands/catalog/list.js +7 -4
  21. package/src/cli/commands/catalog/show.js +4 -4
  22. package/src/cli/commands/copy.js +356 -0
  23. package/src/cli/commands/doctor.js +1 -1
  24. package/src/cli/commands/extractor.js +451 -0
  25. package/src/cli/commands/import/adopt.js +9 -9
  26. package/src/cli/commands/import/check.js +15 -15
  27. package/src/cli/commands/import/diff.js +6 -6
  28. package/src/cli/commands/import/help.js +45 -34
  29. package/src/cli/commands/import/paths.js +3 -3
  30. package/src/cli/commands/import/plan.js +8 -8
  31. package/src/cli/commands/import/refresh.js +25 -24
  32. package/src/cli/commands/import/status-history.js +4 -4
  33. package/src/cli/commands/import/workspace.js +24 -18
  34. package/src/cli/commands/import-runner.js +10 -7
  35. package/src/cli/commands/import.js +4 -1
  36. package/src/cli/commands/init.js +67 -0
  37. package/src/cli/commands/query/{import-adopt.js → extract-adopt.js} +2 -2
  38. package/src/cli/commands/query/runner/change.js +2 -2
  39. package/src/cli/commands/query/runner/{import-adopt.js → extract-adopt.js} +9 -9
  40. package/src/cli/commands/query/runner/index.js +1 -1
  41. package/src/cli/commands/query/runner/workflow.js +7 -7
  42. package/src/cli/commands/query/workspace.js +4 -4
  43. package/src/cli/commands/release-status.js +2 -2
  44. package/src/cli/commands/source.js +2 -2
  45. package/src/cli/commands/template/check.js +2 -2
  46. package/src/cli/commands/template/list-show.js +4 -4
  47. package/src/cli/dispatcher.js +32 -3
  48. package/src/cli/help-dispatch.js +33 -8
  49. package/src/cli/help.js +79 -52
  50. package/src/cli/migration-guidance.js +9 -0
  51. package/src/cli/options.js +17 -0
  52. package/src/extractor/check.js +155 -0
  53. package/src/extractor/packages.js +295 -0
  54. package/src/extractor/registry.js +196 -0
  55. package/src/extractor-policy.js +249 -0
  56. package/src/generator/check.js +24 -87
  57. package/src/generator/context/bundle.js +14 -7
  58. package/src/generator/context/diff.js +8 -1
  59. package/src/generator/context/digest.js +10 -1
  60. package/src/generator/context/shared/domain-sdlc.js +5 -1
  61. package/src/generator/context/shared/relationships.js +20 -5
  62. package/src/generator/context/shared/summaries.js +26 -0
  63. package/src/generator/context/shared.d.ts +1 -0
  64. package/src/generator/context/shared.js +1 -0
  65. package/src/generator/context/slice/core.js +9 -5
  66. package/src/generator/context/slice/sdlc.js +31 -2
  67. package/src/generator/context/task-mode.js +3 -3
  68. package/src/generator/registry/index.js +16 -75
  69. package/src/generator-policy.js +9 -57
  70. package/src/import/core/registry.d.ts +3 -0
  71. package/src/import/core/registry.js +82 -8
  72. package/src/import/core/runner/reports.js +4 -4
  73. package/src/import/core/runner/run.js +2 -0
  74. package/src/import/core/runner/tracks.js +66 -4
  75. package/src/import/provenance.js +18 -17
  76. package/src/init-project.js +215 -0
  77. package/src/new-project/constants.js +1 -1
  78. package/src/new-project/create.js +2 -2
  79. package/src/new-project/project-files.js +7 -7
  80. package/src/package-adapters/adapter.js +64 -0
  81. package/src/package-adapters/file-map.js +30 -0
  82. package/src/package-adapters/index.js +27 -0
  83. package/src/package-adapters/manifest.js +108 -0
  84. package/src/package-adapters/policy.js +81 -0
  85. package/src/package-adapters/spec.js +51 -0
  86. package/src/reconcile/journeys.js +8 -3
  87. package/src/record-blocks.js +125 -0
  88. package/src/resolver/index.js +3 -0
  89. package/src/resolver/journeys.js +74 -0
  90. package/src/resolver/normalize.js +25 -0
  91. package/src/sdlc/adopt.js +1 -1
  92. package/src/validator/common.js +34 -1
  93. package/src/validator/index.js +4 -0
  94. package/src/validator/kinds.d.ts +2 -0
  95. package/src/validator/kinds.js +34 -1
  96. package/src/validator/per-kind/journey.js +233 -0
  97. package/src/workflows/docs-generate.js +4 -1
  98. package/src/workflows/reconcile/bundle-core/index.js +4 -2
  99. package/src/workflows/reconcile/canonical-surface.js +4 -1
  100. package/src/cli/commands/new.js +0 -94
@@ -3,46 +3,57 @@
3
3
  import { TOPOGRAM_IMPORT_FILE } from "../../../import/provenance.js";
4
4
  import { TOPOGRAM_IMPORT_ADOPTIONS_FILE } from "./paths.js";
5
5
 
6
- export function printImportHelp() {
7
- console.log("Usage: topogram import <app-path> --out <target> [--from <track[,track]>] [--json]");
8
- console.log(" or: topogram import refresh [path] [--from <app-path>] [--dry-run] [--json]");
9
- console.log(" or: topogram import diff [path] [--json]");
10
- console.log(" or: topogram import check [path] [--json]");
11
- console.log(" or: topogram import plan [path] [--json]");
12
- console.log(" or: topogram import adopt --list [path] [--json]");
13
- console.log(" or: topogram import adopt <selector> [path] [--dry-run|--write] [--force --reason <text>] [--json]");
14
- console.log(" or: topogram import status [path] [--json]");
15
- console.log(" or: topogram import history [path] [--verify] [--json]");
6
+ export function printExtractHelp() {
7
+ console.log("Usage: topogram extract <app-path> --out <target> [--from <track[,track]>] [--extractor <id-or-package-or-path>] [--json]");
8
+ console.log(" or: topogram extract refresh [path] [--from <app-path>] [--dry-run] [--json]");
9
+ console.log(" or: topogram extract diff [path] [--json]");
10
+ console.log(" or: topogram extract check [path] [--json]");
11
+ console.log(" or: topogram extract plan [path] [--json]");
12
+ console.log(" or: topogram extract status [path] [--json]");
13
+ console.log(" or: topogram extract history [path] [--verify] [--json]");
16
14
  console.log("");
17
- console.log("Creates an editable Topogram workspace from a brownfield app without modifying the app.");
15
+ console.log("Extracts reviewable Topogram candidates from a brownfield app without modifying the app.");
18
16
  console.log("");
19
17
  console.log("Behavior:");
20
- console.log(" - writes raw import candidates under topo/candidates/app");
18
+ console.log(" - writes raw extraction candidates under topo/candidates/app");
21
19
  console.log(" - writes reconcile proposal bundles under topo/candidates/reconcile");
22
20
  console.log(" - writes topogram.project.json with maintained ownership and no generated stack binding");
23
- console.log(` - writes ${TOPOGRAM_IMPORT_FILE} with source file hashes from import time`);
24
- console.log(" - imported Topogram artifacts are project-owned after creation");
21
+ console.log(` - writes ${TOPOGRAM_IMPORT_FILE} with source file hashes from extraction time`);
22
+ console.log(" - extracted Topogram artifacts are project-owned after creation");
25
23
  console.log(" - refresh rewrites only candidate/reconcile artifacts and source provenance");
26
- console.log(" - adoption previews never write canonical Topogram files unless --write is passed");
27
- console.log(" - adoption writes refuse dirty brownfield source provenance unless --force is passed");
28
- console.log(` - adoption writes append audit receipts to ${TOPOGRAM_IMPORT_ADOPTIONS_FILE}`);
29
- console.log(" - forced adoption writes require --reason <text>");
30
24
  console.log("");
31
25
  console.log("Examples:");
32
- console.log(" topogram import ./existing-app --out ./imported-topogram");
33
- console.log(" topogram import ./existing-app --out ./imported-topogram --from db,api,ui");
34
- console.log(" topogram import ./existing-cli --out ./imported-topogram --from cli");
35
- console.log(" topogram import diff ./imported-topogram");
36
- console.log(" topogram import refresh ./imported-topogram --from ./existing-app --dry-run");
37
- console.log(" topogram import refresh ./imported-topogram --from ./existing-app");
38
- console.log(" topogram import check ./imported-topogram");
39
- console.log(" topogram import plan ./imported-topogram");
40
- console.log(" topogram import adopt --list ./imported-topogram");
41
- console.log(" topogram import adopt bundle:task ./imported-topogram --dry-run");
42
- console.log(" topogram import adopt bundle:task ./imported-topogram --write");
43
- console.log(" topogram import adopt bundle:task ./imported-topogram --write --force --reason \"Reviewed source drift\"");
44
- console.log(" topogram import status ./imported-topogram");
45
- console.log(" topogram import history ./imported-topogram");
46
- console.log(" topogram import history ./imported-topogram --verify");
47
- console.log(" topogram import check --json");
26
+ console.log(" topogram extract ./existing-app --out ./extracted-topogram");
27
+ console.log(" topogram extract ./existing-app --out ./extracted-topogram --from db,api,ui");
28
+ console.log(" topogram extract ./existing-cli --out ./extracted-topogram --from cli");
29
+ console.log(" topogram extract ./existing-cli --out ./extracted-topogram --from cli --extractor @topogram/extractor-node-cli");
30
+ console.log(" topogram extract ./existing-app --out ./extracted-topogram --extractor-policy ./topogram.extractor-policy.json");
31
+ console.log(" topogram extract diff ./extracted-topogram");
32
+ console.log(" topogram extract refresh ./extracted-topogram --from ./existing-app --dry-run");
33
+ console.log(" topogram extract refresh ./extracted-topogram --from ./existing-app");
34
+ console.log(" topogram extract check ./extracted-topogram");
35
+ console.log(" topogram extract plan ./extracted-topogram");
36
+ console.log(" topogram extract status ./extracted-topogram");
37
+ console.log(" topogram extract history ./extracted-topogram");
38
+ console.log(" topogram extract history ./extracted-topogram --verify");
39
+ console.log(" topogram extract check --json");
40
+ }
41
+
42
+ export function printAdoptHelp() {
43
+ console.log("Usage: topogram adopt --list [path] [--json]");
44
+ console.log(" or: topogram adopt <selector> [path] [--dry-run|--write] [--force --reason <text>] [--json]");
45
+ console.log("");
46
+ console.log("Promotes reviewed extraction candidates into canonical topo/ files.");
47
+ console.log("");
48
+ console.log("Behavior:");
49
+ console.log(" - previews never write canonical Topogram files unless --write is passed");
50
+ console.log(" - writes refuse dirty brownfield source provenance unless --force is passed");
51
+ console.log(` - writes append audit receipts to ${TOPOGRAM_IMPORT_ADOPTIONS_FILE}`);
52
+ console.log(" - forced writes require --reason <text>");
53
+ console.log("");
54
+ console.log("Examples:");
55
+ console.log(" topogram adopt --list ./extracted-topogram");
56
+ console.log(" topogram adopt bundle:task ./extracted-topogram --dry-run");
57
+ console.log(" topogram adopt bundle:task ./extracted-topogram --write");
58
+ console.log(" topogram adopt bundle:task ./extracted-topogram --write --force --reason \"Reviewed source drift\"");
48
59
  }
@@ -9,7 +9,7 @@ import { TOPOGRAM_IMPORT_FILE } from "../../../import/provenance.js";
9
9
  import { shellCommandArg } from "../catalog.js";
10
10
  import { resolveTopoRoot, resolveWorkspaceContext } from "../../../workspace-paths.js";
11
11
 
12
- export const TOPOGRAM_IMPORT_ADOPTIONS_FILE = ".topogram-import-adoptions.jsonl";
12
+ export const TOPOGRAM_IMPORT_ADOPTIONS_FILE = ".topogram-adoptions.jsonl";
13
13
 
14
14
  /**
15
15
  * @typedef {Record<string, any>} AnyRecord
@@ -219,7 +219,7 @@ export function readImportAdoptionReceipts(projectRoot) {
219
219
  try {
220
220
  return JSON.parse(line);
221
221
  } catch (error) {
222
- throw new Error(`Invalid import adoption receipt JSON at ${historyPath}:${index + 1}.`);
222
+ throw new Error(`Invalid adoption receipt JSON at ${historyPath}:${index + 1}.`);
223
223
  }
224
224
  });
225
225
  }
@@ -265,5 +265,5 @@ export function importProjectCommandPath(projectRoot) {
265
265
  * @returns {string}
266
266
  */
267
267
  export function importAdoptCommand(projectRoot, selector, write = false) {
268
- return `topogram import adopt ${selector} ${importProjectCommandPath(projectRoot)} ${write ? "--write" : "--dry-run"}`;
268
+ return `topogram adopt ${selector} ${importProjectCommandPath(projectRoot)} ${write ? "--write" : "--dry-run"}`;
269
269
  }
@@ -76,7 +76,7 @@ export function readImportAdoptionArtifacts(inputPath) {
76
76
  reconcileReport: path.join(reconcileRoot, "report.json")
77
77
  };
78
78
  if (!fs.existsSync(paths.adoptionPlanAgent)) {
79
- throw new Error(`No import adoption plan found under '${reconcileRoot}'. Run 'topogram import <app-path> --out <target>' first.`);
79
+ throw new Error(`No extraction adoption plan found under '${reconcileRoot}'. Run 'topogram extract <app-path> --out <target>' first.`);
80
80
  }
81
81
  return {
82
82
  projectRoot,
@@ -182,10 +182,10 @@ export function summarizeImportAdoption(adoptionPlan, adoptionStatus, projectRoo
182
182
  risks: [
183
183
  ...(blockedCount > 0 ? [`${blockedCount} adoption item(s) are blocked.`] : []),
184
184
  ...(((adoptionPlan.requires_human_review || []).length || surfaces.some((/** @type {AnyRecord} */ item) => item.human_review_required))
185
- ? ["Imported proposal items require human review before adoption."]
185
+ ? ["Extracted proposal items require human review before adoption."]
186
186
  : [])
187
187
  ],
188
- nextCommand: nextBundle ? nextBundle.nextCommand : `topogram import status ${importProjectCommandPath(projectRoot)}`
188
+ nextCommand: nextBundle ? nextBundle.nextCommand : `topogram extract status ${importProjectCommandPath(projectRoot)}`
189
189
  };
190
190
  }
191
191
 
@@ -209,8 +209,8 @@ export function buildBrownfieldImportPlanPayload(inputPath) {
209
209
  },
210
210
  ...adoption,
211
211
  commands: {
212
- check: `topogram import check ${importProjectCommandPath(artifacts.projectRoot)}`,
213
- status: `topogram import status ${importProjectCommandPath(artifacts.projectRoot)}`,
212
+ check: `topogram extract check ${importProjectCommandPath(artifacts.projectRoot)}`,
213
+ status: `topogram extract status ${importProjectCommandPath(artifacts.projectRoot)}`,
214
214
  next: adoption.nextCommand
215
215
  }
216
216
  };
@@ -221,7 +221,7 @@ export function buildBrownfieldImportPlanPayload(inputPath) {
221
221
  * @returns {void}
222
222
  */
223
223
  export function printBrownfieldImportPlan(payload) {
224
- console.log(`Import adoption plan for ${payload.projectRoot}`);
224
+ console.log(`Extraction adoption plan for ${payload.projectRoot}`);
225
225
  console.log(`Proposal items: ${payload.summary.proposalItemCount}`);
226
226
  console.log(`Bundles: ${payload.summary.bundleCount}`);
227
227
  for (const bundle of payload.bundles) {
@@ -279,9 +279,9 @@ export function buildBrownfieldImportAdoptListPayload(inputPath) {
279
279
  * @returns {void}
280
280
  */
281
281
  export function printBrownfieldImportAdoptList(payload) {
282
- console.log(`Import adoption selectors for ${payload.projectRoot}`);
282
+ console.log(`Adoption selectors for ${payload.projectRoot}`);
283
283
  if (payload.selectors.length === 0) {
284
- console.log("No adoption selectors are available. Run `topogram import plan` to inspect reconcile artifacts.");
284
+ console.log("No adoption selectors are available. Run `topogram extract plan` to inspect reconcile artifacts.");
285
285
  return;
286
286
  }
287
287
  for (const selector of payload.selectors) {
@@ -34,12 +34,12 @@ import { verifyImportAdoptionReceipts } from "./status-history.js";
34
34
  export function readTopogramImportRecord(projectRoot) {
35
35
  const importPath = path.join(normalizeProjectRoot(projectRoot), TOPOGRAM_IMPORT_FILE);
36
36
  if (!fs.existsSync(importPath)) {
37
- throw new Error(`No brownfield import provenance found at '${importPath}'. Run 'topogram import <app-path> --out <target>' first.`);
37
+ throw new Error(`No brownfield extraction provenance found at '${importPath}'. Run 'topogram extract <app-path> --out <target>' first.`);
38
38
  }
39
39
  try {
40
40
  return { path: importPath, record: JSON.parse(fs.readFileSync(importPath, "utf8")) };
41
41
  } catch (error) {
42
- throw new Error(`Invalid brownfield import provenance JSON at '${importPath}'.`);
42
+ throw new Error(`Invalid brownfield extraction provenance JSON at '${importPath}'.`);
43
43
  }
44
44
  }
45
45
 
@@ -48,8 +48,9 @@ export function readTopogramImportRecord(projectRoot) {
48
48
  * @returns {string|null}
49
49
  */
50
50
  export function importTrackValueFromRecord(importRecord) {
51
- const tracks = Array.isArray(importRecord.import?.tracks)
52
- ? importRecord.import.tracks.map((/** @type {any} */ track) => String(track).trim()).filter(Boolean)
51
+ const source = importRecord.extract || importRecord.import || {};
52
+ const tracks = Array.isArray(source.tracks)
53
+ ? source.tracks.map((/** @type {any} */ track) => String(track).trim()).filter(Boolean)
53
54
  : [];
54
55
  return tracks.length ? [...new Set(tracks)].join(",") : null;
55
56
  }
@@ -283,7 +284,7 @@ export function buildBrownfieldImportRefreshAnalysis(inputPath, options = {}) {
283
284
  const projectRoot = normalizeProjectRoot(inputPath);
284
285
  const topogramRoot = normalizeTopogramPath(projectRoot);
285
286
  if (!fs.existsSync(topogramRoot) || !fs.statSync(topogramRoot).isDirectory()) {
286
- throw new Error(`No workspace folder found for imported workspace '${inputPath}'.`);
287
+ throw new Error(`No workspace folder found for extracted workspace '${inputPath}'.`);
287
288
  }
288
289
 
289
290
  const { record: importRecord } = readTopogramImportRecord(projectRoot);
@@ -291,21 +292,21 @@ export function buildBrownfieldImportRefreshAnalysis(inputPath, options = {}) {
291
292
  ? options.sourcePath
292
293
  : importRecord.source?.path;
293
294
  if (!sourcePath) {
294
- throw new Error("No brownfield source path was provided or recorded. Use 'topogram import refresh <workspace> --from <app-path>'.");
295
+ throw new Error("No brownfield source path was provided or recorded. Use 'topogram extract refresh <workspace> --from <app-path>'.");
295
296
  }
296
297
  const sourceRoot = path.resolve(sourcePath);
297
298
  if (!fs.existsSync(sourceRoot) || !fs.statSync(sourceRoot).isDirectory()) {
298
299
  throw new Error(`Cannot refresh from missing app directory '${sourcePath}'.`);
299
300
  }
300
301
  if (sourceRoot === projectRoot) {
301
- throw new Error("Refusing to refresh import from the imported Topogram workspace itself.");
302
+ throw new Error("Refusing to refresh extraction from the extracted Topogram workspace itself.");
302
303
  }
303
304
 
304
305
  const sourceComparison = compareImportRecordToSource(projectRoot, importRecord, sourceRoot);
305
306
  const trackValue = importTrackValueFromRecord(importRecord);
306
307
  const importResult = runWorkflow("import-app", sourceRoot, { from: trackValue });
307
308
  const candidateCounts = importCandidateCounts(importResult.summary);
308
- const candidateCountDeltas = buildCountDeltas(importRecord.import?.candidateCounts || {}, candidateCounts);
309
+ const candidateCountDeltas = buildCountDeltas((importRecord.extract || importRecord.import)?.candidateCounts || {}, candidateCounts);
309
310
  const removedCandidateFiles = {
310
311
  rawCandidateFiles: countFilesRecursive(path.join(topogramRoot, "candidates", "app")),
311
312
  reconcileFiles: countFilesRecursive(path.join(topogramRoot, "candidates", "reconcile"))
@@ -324,7 +325,7 @@ export function buildBrownfieldImportRefreshAnalysis(inputPath, options = {}) {
324
325
  topogramRoot,
325
326
  sourcePath: sourceRoot,
326
327
  provenancePath: path.join(projectRoot, TOPOGRAM_IMPORT_FILE),
327
- importedAt: importRecord.importedAt || null,
328
+ importedAt: importRecord.extractedAt || importRecord.importedAt || null,
328
329
  previousImportStatus: sourceComparison.status,
329
330
  sourceDiff: {
330
331
  status: sourceComparison.status,
@@ -354,13 +355,13 @@ export function buildBrownfieldImportRefreshAnalysis(inputPath, options = {}) {
354
355
  /**
355
356
  * @param {string} inputPath
356
357
  * @param {{ sourcePath?: string|null, dryRun?: boolean }} [options]
357
- * @returns {{ ok: boolean, dryRun: boolean, projectRoot: string, workspaceRoot: string, topogramRoot: string, sourcePath: string, provenancePath: string, previousImportStatus: string, currentImportStatus: string, tracks: string[], sourceFiles: number, sourceDiff: Record<string, any>, removedCandidateFiles: Record<string, number>, rawCandidateFiles: number, reconcileFiles: number, writtenFiles: string[], plannedFiles: string[], candidateCounts: Record<string, number>, candidateCountDeltas: Record<string, any>, adoptionPlanDeltas: Record<string, any>, receiptVerification: Record<string, any>, refreshMetadata: Record<string, any>|null, nextCommands: string[] }}
358
+ * @returns {{ ok: boolean, dryRun: boolean, projectRoot: string, workspaceRoot: string, topogramRoot: string, sourcePath: string, provenancePath: string, previousExtractStatus: string, currentExtractStatus: string, tracks: string[], sourceFiles: number, sourceDiff: Record<string, any>, removedCandidateFiles: Record<string, number>, rawCandidateFiles: number, reconcileFiles: number, writtenFiles: string[], plannedFiles: string[], candidateCounts: Record<string, number>, candidateCountDeltas: Record<string, any>, adoptionPlanDeltas: Record<string, any>, receiptVerification: Record<string, any>, refreshMetadata: Record<string, any>|null, nextCommands: string[] }}
358
359
  */
359
360
  export function buildBrownfieldImportRefreshPayload(inputPath, options = {}) {
360
361
  const analysis = buildBrownfieldImportRefreshAnalysis(inputPath, options);
361
362
  const dryRun = Boolean(options.dryRun);
362
363
  let provenancePath = analysis.provenancePath;
363
- let currentImportStatus = dryRun ? analysis.previousImportStatus : "unknown";
364
+ let currentExtractStatus = dryRun ? analysis.previousImportStatus : "unknown";
364
365
  /** @type {string[]} */
365
366
  let writtenFiles = [];
366
367
  /** @type {AnyRecord|null} */
@@ -391,7 +392,7 @@ export function buildBrownfieldImportRefreshPayload(inputPath, options = {}) {
391
392
  files: collectImportSourceFileRecords(analysis.sourcePath, { excludeRoots: [analysis.projectRoot] })
392
393
  });
393
394
  provenancePath = provenance.path;
394
- currentImportStatus = buildTopogramImportStatus(analysis.projectRoot).status;
395
+ currentExtractStatus = buildTopogramImportStatus(analysis.projectRoot).status;
395
396
  writtenFiles = [
396
397
  TOPOGRAM_IMPORT_FILE,
397
398
  ...rawCandidateFiles.map((filePath) => `${DEFAULT_TOPO_FOLDER_NAME}/${filePath}`),
@@ -402,15 +403,15 @@ export function buildBrownfieldImportRefreshPayload(inputPath, options = {}) {
402
403
  analysis.reconcileFiles = reconcileFiles.length;
403
404
  }
404
405
  return {
405
- ok: dryRun || currentImportStatus === "clean",
406
+ ok: dryRun || currentExtractStatus === "clean",
406
407
  dryRun,
407
408
  projectRoot: analysis.projectRoot,
408
409
  workspaceRoot: analysis.topogramRoot,
409
410
  topogramRoot: analysis.topogramRoot,
410
411
  sourcePath: analysis.sourcePath,
411
412
  provenancePath,
412
- previousImportStatus: analysis.previousImportStatus,
413
- currentImportStatus,
413
+ previousExtractStatus: analysis.previousImportStatus,
414
+ currentExtractStatus,
414
415
  tracks: analysis.tracks,
415
416
  sourceFiles: analysis.sourceFiles,
416
417
  sourceDiff: analysis.sourceDiff,
@@ -426,11 +427,11 @@ export function buildBrownfieldImportRefreshPayload(inputPath, options = {}) {
426
427
  refreshMetadata,
427
428
  nextCommands: [
428
429
  dryRun
429
- ? `topogram import refresh ${importProjectCommandPath(analysis.projectRoot)}`
430
- : `topogram import check ${importProjectCommandPath(analysis.projectRoot)}`,
431
- `topogram import plan ${importProjectCommandPath(analysis.projectRoot)}`,
432
- `topogram import status ${importProjectCommandPath(analysis.projectRoot)}`,
433
- `topogram import history ${importProjectCommandPath(analysis.projectRoot)} --verify`
430
+ ? `topogram extract refresh ${importProjectCommandPath(analysis.projectRoot)}`
431
+ : `topogram extract check ${importProjectCommandPath(analysis.projectRoot)}`,
432
+ `topogram extract plan ${importProjectCommandPath(analysis.projectRoot)}`,
433
+ `topogram extract status ${importProjectCommandPath(analysis.projectRoot)}`,
434
+ `topogram extract history ${importProjectCommandPath(analysis.projectRoot)} --verify`
434
435
  ]
435
436
  };
436
437
  }
@@ -440,12 +441,12 @@ export function buildBrownfieldImportRefreshPayload(inputPath, options = {}) {
440
441
  * @returns {void}
441
442
  */
442
443
  export function printBrownfieldImportRefresh(payload) {
443
- console.log(`${payload.dryRun ? "Previewed" : "Refreshed"} brownfield import candidates for ${payload.projectRoot}.`);
444
+ console.log(`${payload.dryRun ? "Previewed" : "Refreshed"} brownfield extraction candidates for ${payload.projectRoot}.`);
444
445
  console.log(`Source: ${payload.sourcePath}`);
445
446
  console.log(`Topogram: ${payload.topogramRoot}`);
446
- console.log(`Import provenance: ${payload.provenancePath}`);
447
- console.log(`Previous source status: ${payload.previousImportStatus}`);
448
- console.log(`Current source status: ${payload.currentImportStatus}`);
447
+ console.log(`Extraction provenance: ${payload.provenancePath}`);
448
+ console.log(`Previous source status: ${payload.previousExtractStatus}`);
449
+ console.log(`Current source status: ${payload.currentExtractStatus}`);
449
450
  console.log(`Source diff: changed=${payload.sourceDiff.counts.changed}, added=${payload.sourceDiff.counts.added}, removed=${payload.sourceDiff.counts.removed}`);
450
451
  console.log(`Tracked source files: ${payload.sourceFiles}`);
451
452
  console.log(`Raw candidate files: ${payload.rawCandidateFiles}`);
@@ -33,7 +33,7 @@ export function buildBrownfieldImportStatusPayload(inputPath) {
33
33
  projectRoot: artifacts.projectRoot,
34
34
  workspaceRoot: artifacts.topogramRoot,
35
35
  topogramRoot: artifacts.topogramRoot,
36
- import: importCheck.import,
36
+ extract: importCheck.extract,
37
37
  topogram: importCheck.topogram,
38
38
  adoption: {
39
39
  status: adoptionStatus,
@@ -52,7 +52,7 @@ export function buildBrownfieldImportStatusPayload(inputPath) {
52
52
  * @returns {void}
53
53
  */
54
54
  export function printBrownfieldImportStatus(payload) {
55
- console.log(`Import status: ${payload.import.status}`);
55
+ console.log(`Extraction status: ${payload.extract.status}`);
56
56
  console.log(`Topogram check: ${payload.topogram.ok ? "passed" : "failed"}`);
57
57
  console.log(`Adoption: ${payload.adoption.summary.appliedItemCount} applied, ${payload.adoption.summary.pendingItemCount} pending, ${payload.adoption.summary.blockedItemCount} blocked`);
58
58
  const next = payload.adoption.nextCommand;
@@ -134,7 +134,7 @@ export function verifyImportAdoptionReceipts(projectRoot, receipts) {
134
134
  summary,
135
135
  files,
136
136
  auditOnly: true,
137
- note: "History verification is audit-only. Imported/adopted Topogram files are project-owned, and edits do not make the workspace invalid."
137
+ note: "History verification is audit-only. Extracted/adopted Topogram files are project-owned, and edits do not make the workspace invalid."
138
138
  };
139
139
  }
140
140
 
@@ -175,7 +175,7 @@ export function buildBrownfieldImportHistoryPayload(inputPath, options = {}) {
175
175
  * @returns {void}
176
176
  */
177
177
  export function printBrownfieldImportHistory(payload) {
178
- console.log(`Import adoption history for ${payload.projectRoot}`);
178
+ console.log(`Adoption history for ${payload.projectRoot}`);
179
179
  console.log(`Receipts: ${payload.summary.receiptCount}`);
180
180
  console.log(`Forced writes: ${payload.summary.forcedWriteCount}`);
181
181
  if (!payload.exists) {
@@ -79,23 +79,23 @@ function importedProjectConfig() {
79
79
  */
80
80
  function importedWorkspaceReadme(sourceRoot, targetRoot, importSummary) {
81
81
  return [
82
- "# Imported Topogram Workspace",
82
+ "# Extracted Topogram Workspace",
83
83
  "",
84
- "This workspace was created from a brownfield app import.",
84
+ "This workspace was created from a brownfield app extraction.",
85
85
  "",
86
- `- Imported source: \`${sourceRoot}\``,
86
+ `- Extracted source: \`${sourceRoot}\``,
87
87
  `- Target workspace: \`${targetRoot}\``,
88
88
  `- Tracks: ${(importSummary.tracks || []).join(", ") || "none"}`,
89
89
  `- Provenance: \`${TOPOGRAM_IMPORT_FILE}\``,
90
90
  "",
91
- "Imported Topogram artifacts are project-owned after creation. Edit them directly, promote candidates deliberately, and run `topogram check` before generation or maintained-app work.",
91
+ "Extracted Topogram artifacts are project-owned after creation. Edit them directly, promote candidates deliberately, and run `topogram check` before generation or maintained-app work.",
92
92
  "",
93
93
  "Useful commands:",
94
94
  "",
95
95
  "```sh",
96
- "topogram import check",
96
+ "topogram extract check",
97
97
  "topogram check",
98
- `topogram query import-plan ./${DEFAULT_TOPO_FOLDER_NAME}`,
98
+ `topogram query extract-plan ./${DEFAULT_TOPO_FOLDER_NAME}`,
99
99
  "```",
100
100
  ""
101
101
  ].join("\n");
@@ -149,24 +149,29 @@ export function countFilesRecursive(rootPath) {
149
149
  /**
150
150
  * @param {string} sourcePath
151
151
  * @param {string} targetPath
152
- * @param {{ from?: string|null }} [options]
152
+ * @param {{ from?: string|null, extractorSpecs?: string[], extractorPolicyPath?: string|null, cwd?: string|null }} [options]
153
153
  * @returns {{ ok: boolean, sourcePath: string, targetPath: string, workspaceRoot: string, topogramRoot: string, projectConfigPath: string, provenancePath: string, tracks: string[], sourceFiles: number, rawCandidateFiles: number, reconcileFiles: number, writtenFiles: string[], candidateCounts: Record<string, number>, nextCommands: string[] }}
154
154
  */
155
155
  export function buildBrownfieldImportWorkspacePayload(sourcePath, targetPath, options = {}) {
156
156
  const sourceRoot = path.resolve(sourcePath);
157
157
  const targetRoot = path.resolve(targetPath);
158
158
  if (!fs.existsSync(sourceRoot) || !fs.statSync(sourceRoot).isDirectory()) {
159
- throw new Error(`Cannot import missing app directory '${sourcePath}'.`);
159
+ throw new Error(`Cannot extract missing app directory '${sourcePath}'.`);
160
160
  }
161
161
  if (sourceRoot === targetRoot) {
162
- throw new Error("Refusing to import into the same directory as the brownfield app.");
162
+ throw new Error("Refusing to extract into the same directory as the brownfield app.");
163
163
  }
164
164
  ensureEmptyImportTarget(targetRoot);
165
165
 
166
166
  const topogramRoot = path.join(targetRoot, DEFAULT_TOPO_FOLDER_NAME);
167
167
  fs.mkdirSync(topogramRoot, { recursive: true });
168
168
  const sourceFiles = collectImportSourceFileRecords(sourceRoot, { excludeRoots: [targetRoot] });
169
- const importResult = runWorkflow("import-app", sourceRoot, { from: options.from || null });
169
+ const importResult = runWorkflow("import-app", sourceRoot, {
170
+ from: options.from || null,
171
+ extractorSpecs: options.extractorSpecs || [],
172
+ extractorPolicyPath: options.extractorPolicyPath || null,
173
+ cwd: options.cwd || process.cwd()
174
+ });
170
175
  const rawCandidateFiles = writeRelativeFiles(topogramRoot, importResult.files || {});
171
176
 
172
177
  const projectConfigPath = path.join(targetRoot, "topogram.project.json");
@@ -182,6 +187,7 @@ export function buildBrownfieldImportWorkspacePayload(sourcePath, targetPath, op
182
187
  tracks: importResult.summary.tracks || [],
183
188
  findingsCount: importResult.summary.findings_count || 0,
184
189
  candidateCounts,
190
+ extractorPackages: importResult.summary.package_extractors || [],
185
191
  files: sourceFiles
186
192
  });
187
193
  const writtenFiles = [
@@ -206,12 +212,12 @@ export function buildBrownfieldImportWorkspacePayload(sourcePath, targetPath, op
206
212
  writtenFiles,
207
213
  candidateCounts,
208
214
  nextCommands: [
209
- "topogram import check",
210
- "topogram import plan",
211
- "topogram import adopt bundle:task --dry-run",
212
- "topogram import status",
215
+ "topogram extract check",
216
+ "topogram extract plan",
217
+ "topogram adopt bundle:task --dry-run",
218
+ "topogram extract status",
213
219
  "topogram check",
214
- `topogram query import-plan ./${DEFAULT_TOPO_FOLDER_NAME}`
220
+ `topogram query extract-plan ./${DEFAULT_TOPO_FOLDER_NAME}`
215
221
  ]
216
222
  };
217
223
  }
@@ -221,15 +227,15 @@ export function buildBrownfieldImportWorkspacePayload(sourcePath, targetPath, op
221
227
  * @returns {void}
222
228
  */
223
229
  export function printBrownfieldImportWorkspace(payload) {
224
- console.log(`Imported brownfield app to ${payload.targetPath}.`);
230
+ console.log(`Extracted brownfield app to ${payload.targetPath}.`);
225
231
  console.log(`Source: ${payload.sourcePath}`);
226
232
  console.log(`Topogram: ${payload.topogramRoot}`);
227
233
  console.log(`Project config: ${payload.projectConfigPath}`);
228
- console.log(`Import provenance: ${payload.provenancePath}`);
234
+ console.log(`Extraction provenance: ${payload.provenancePath}`);
229
235
  console.log(`Tracked source files: ${payload.sourceFiles}`);
230
236
  console.log(`Raw candidate files: ${payload.rawCandidateFiles}`);
231
237
  console.log(`Reconcile proposal files: ${payload.reconcileFiles}`);
232
- console.log("Imported Topogram artifacts are project-owned after creation; source hashes record the app evidence trusted at import time.");
238
+ console.log("Extracted Topogram artifacts are project-owned after creation; source hashes record the app evidence trusted at extraction time.");
233
239
  console.log("");
234
240
  console.log("Next steps:");
235
241
  console.log(` cd ${shellCommandArg(path.relative(process.cwd(), payload.targetPath) || ".")}`);
@@ -11,6 +11,7 @@ import {
11
11
  buildBrownfieldImportRefreshPayload,
12
12
  buildBrownfieldImportStatusPayload,
13
13
  buildBrownfieldImportWorkspacePayload,
14
+ printAdoptHelp,
14
15
  printBrownfieldImportAdopt,
15
16
  printBrownfieldImportAdoptList,
16
17
  printBrownfieldImportCheck,
@@ -20,11 +21,11 @@ import {
20
21
  printBrownfieldImportRefresh,
21
22
  printBrownfieldImportStatus,
22
23
  printBrownfieldImportWorkspace,
23
- printImportHelp
24
+ printExtractHelp
24
25
  } from "./import.js";
25
26
 
26
27
  /**
27
- * @param {{ commandArgs: Record<string, any>, inputPath: string|null|undefined, outPath?: string|null, fromValue?: string|null, reasonValue?: string|null, refreshAdopted?: boolean, dryRun?: boolean, write?: boolean, force?: boolean, json?: boolean }} context
28
+ * @param {{ commandArgs: Record<string, any>, inputPath: string|null|undefined, outPath?: string|null, fromValue?: string|null, extractorSpecs?: string[], extractorPolicyPath?: string|null, reasonValue?: string|null, refreshAdopted?: boolean, dryRun?: boolean, write?: boolean, force?: boolean, json?: boolean }} context
28
29
  * @returns {number}
29
30
  */
30
31
  export function runImportCommand(context) {
@@ -33,6 +34,8 @@ export function runImportCommand(context) {
33
34
  inputPath,
34
35
  outPath = null,
35
36
  fromValue = null,
37
+ extractorSpecs = [],
38
+ extractorPolicyPath = null,
36
39
  reasonValue = null,
37
40
  refreshAdopted = false,
38
41
  dryRun = false,
@@ -45,10 +48,10 @@ export function runImportCommand(context) {
45
48
  if (command === "workspace") {
46
49
  if (!outPath) {
47
50
  console.error("Missing required --out <target>.");
48
- printImportHelp();
51
+ printExtractHelp();
49
52
  return 1;
50
53
  }
51
- const payload = buildBrownfieldImportWorkspacePayload(inputPath || "", outPath, { from: fromValue });
54
+ const payload = buildBrownfieldImportWorkspacePayload(inputPath || "", outPath, { from: fromValue, extractorSpecs, extractorPolicyPath, cwd: process.cwd() });
52
55
  if (json) {
53
56
  console.log(stableStringify(payload));
54
57
  } else {
@@ -110,12 +113,12 @@ export function runImportCommand(context) {
110
113
  if (command === "adopt") {
111
114
  if (!commandArgs.importAdoptSelector || commandArgs.importAdoptSelector.startsWith("-")) {
112
115
  console.error("Missing required <selector>.");
113
- printImportHelp();
116
+ printAdoptHelp();
114
117
  return 1;
115
118
  }
116
119
  if (write && dryRun) {
117
120
  console.error("Use either --dry-run or --write, not both.");
118
- printImportHelp();
121
+ printAdoptHelp();
119
122
  return 1;
120
123
  }
121
124
  const payload = buildBrownfieldImportAdoptPayload(commandArgs.importAdoptSelector, inputPath || ".", {
@@ -153,5 +156,5 @@ export function runImportCommand(context) {
153
156
  return payload.ok ? 0 : 1;
154
157
  }
155
158
 
156
- throw new Error(`Unknown import command '${command}'`);
159
+ throw new Error(`Unknown extract/adopt command '${command}'`);
157
160
  }
@@ -1,6 +1,9 @@
1
1
  // @ts-check
2
2
 
3
- export { printImportHelp } from "./import/help.js";
3
+ export {
4
+ printAdoptHelp,
5
+ printExtractHelp
6
+ } from "./import/help.js";
4
7
  export {
5
8
  buildBrownfieldImportWorkspacePayload,
6
9
  printBrownfieldImportWorkspace
@@ -0,0 +1,67 @@
1
+ // @ts-check
2
+
3
+ import path from "node:path";
4
+
5
+ import { stableStringify } from "../../format.js";
6
+ import { initTopogramProject } from "../../init-project.js";
7
+
8
+ /**
9
+ * @param {string} projectRoot
10
+ * @param {string} cwd
11
+ * @returns {string}
12
+ */
13
+ function displayProjectRoot(projectRoot, cwd) {
14
+ const relative = path.relative(cwd, projectRoot);
15
+ return !relative || relative.startsWith("..")
16
+ ? projectRoot
17
+ : relative.split(path.sep).join("/");
18
+ }
19
+
20
+ /**
21
+ * @param {ReturnType<typeof initTopogramProject>} result
22
+ * @param {string} cwd
23
+ * @returns {void}
24
+ */
25
+ export function printInitProjectResult(result, cwd) {
26
+ console.log(`Initialized Topogram workspace at ${result.projectRoot}.`);
27
+ console.log("Workspace: topo/");
28
+ console.log("Project config: topogram.project.json");
29
+ console.log("Output ownership: maintained (.)");
30
+ console.log("Template: none");
31
+ console.log("Generated app output: none");
32
+ console.log(`SDLC: ${result.sdlc.enabled ? "adopted/enforced" : "not adopted"}`);
33
+ if (result.created.length > 0) {
34
+ console.log(`Created: ${result.created.join(", ")}`);
35
+ }
36
+ if (result.skipped.length > 0) {
37
+ console.log(`Skipped existing files: ${result.skipped.join(", ")}`);
38
+ }
39
+ console.log("");
40
+ console.log("Next steps:");
41
+ console.log(` cd ${displayProjectRoot(result.projectRoot, cwd)}`);
42
+ console.log(" topogram agent brief --json");
43
+ console.log(" topogram check --json");
44
+ console.log(" topogram query list --json");
45
+ if (result.sdlc.enabled) {
46
+ console.log(" topogram sdlc policy explain --json");
47
+ console.log(" topogram sdlc prep commit . --json");
48
+ } else {
49
+ console.log(" topogram sdlc policy init .");
50
+ }
51
+ console.log(" topogram emit <target> ./topo --json");
52
+ }
53
+
54
+ /**
55
+ * @param {string} inputPath
56
+ * @param {{ json?: boolean, cwd?: string, withSdlc?: boolean }} [options]
57
+ * @returns {number}
58
+ */
59
+ export function runInitProjectCommand(inputPath, options = {}) {
60
+ const result = initTopogramProject({ targetPath: inputPath || ".", withSdlc: Boolean(options.withSdlc) });
61
+ if (options.json) {
62
+ console.log(stableStringify(result));
63
+ } else {
64
+ printInitProjectResult(result, options.cwd || process.cwd());
65
+ }
66
+ return 0;
67
+ }
@@ -38,7 +38,7 @@ export function buildImportPlanForContext(context, queryFamily) {
38
38
  }
39
39
  const adoptionPlan = readJson(planPath);
40
40
  const ast = parsePath(context.inputPath);
41
- const taskModeResult = buildTaskMode(ast, {}, "import-adopt");
41
+ const taskModeResult = buildTaskMode(ast, {}, "extract-adopt");
42
42
  if (!resultOk(taskModeResult)) {
43
43
  return taskModeResult;
44
44
  }
@@ -74,7 +74,7 @@ export function buildImportAdoptAgentContext(context, queryFamily) {
74
74
  const topogramRoot = normalizeTopogramPath(context.inputPath);
75
75
  const artifacts = requireReconcileArtifacts(topogramRoot, queryFamily);
76
76
  const ast = parsePath(context.inputPath);
77
- const taskModeResult = buildTaskMode(ast, {}, "import-adopt");
77
+ const taskModeResult = buildTaskMode(ast, {}, "extract-adopt");
78
78
  if (!resultOk(taskModeResult)) {
79
79
  return taskModeResult;
80
80
  }