@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
@@ -0,0 +1,451 @@
1
+ // @ts-check
2
+
3
+ import fs from "node:fs";
4
+ import path from "node:path";
5
+
6
+ import { stableStringify } from "../../format.js";
7
+ import { checkExtractorPack } from "../../extractor/check.js";
8
+ import {
9
+ EXTRACTOR_MANIFESTS,
10
+ getExtractorManifest,
11
+ loadPackageExtractorManifest,
12
+ packageExtractorInstallCommand
13
+ } from "../../extractor/registry.js";
14
+ import {
15
+ defaultExtractorPolicy,
16
+ effectiveExtractorPolicy,
17
+ EXTRACTOR_POLICY_FILE,
18
+ extractorPackageAllowed,
19
+ extractorPolicyDiagnosticsForPackages,
20
+ loadExtractorPolicy,
21
+ packageScopeFromName,
22
+ parseExtractorPolicyPin,
23
+ writeExtractorPolicy
24
+ } from "../../extractor-policy.js";
25
+
26
+ export function printExtractorHelp() {
27
+ console.log("Usage: topogram extractor list [--json]");
28
+ console.log(" or: topogram extractor show <id-or-package> [--json]");
29
+ console.log(" or: topogram extractor check <path-or-package> [--json]");
30
+ console.log(" or: topogram extractor policy init [path] [--json]");
31
+ console.log(" or: topogram extractor policy status [path] [--json]");
32
+ console.log(" or: topogram extractor policy check [path] [--json]");
33
+ console.log(" or: topogram extractor policy explain [path] [--json]");
34
+ console.log(" or: topogram extractor policy pin [package@version] [path] [--json]");
35
+ console.log("");
36
+ console.log("Inspects extractor manifests and checks extractor pack conformance.");
37
+ console.log("");
38
+ console.log("Notes:");
39
+ console.log(" - extractor packages execute only during `topogram extract` or `topogram extractor check`.");
40
+ console.log(" - extractor packages emit review-only candidates; core owns persistence, reconcile, and adoption.");
41
+ console.log(` - package-backed extractors are governed by ${EXTRACTOR_POLICY_FILE}; bundled topogram/* extractors are allowed.`);
42
+ console.log("");
43
+ console.log("Examples:");
44
+ console.log(" topogram extractor list");
45
+ console.log(" topogram extractor show topogram/api-extractors");
46
+ console.log(" topogram extractor check ./extractor-package");
47
+ console.log(" topogram extractor policy init");
48
+ console.log(" topogram extractor policy pin @topogram/extractor-node-cli@1");
49
+ }
50
+
51
+ /**
52
+ * @param {string} cwd
53
+ * @returns {string[]}
54
+ */
55
+ function declaredExtractorPackages(cwd) {
56
+ const packagePath = path.join(cwd, "package.json");
57
+ if (!fs.existsSync(packagePath)) {
58
+ return [];
59
+ }
60
+ const packageJson = JSON.parse(fs.readFileSync(packagePath, "utf8"));
61
+ const dependencyBuckets = [
62
+ packageJson.dependencies,
63
+ packageJson.devDependencies,
64
+ packageJson.optionalDependencies,
65
+ packageJson.peerDependencies
66
+ ];
67
+ const packages = new Set();
68
+ for (const dependencies of dependencyBuckets) {
69
+ if (!dependencies || typeof dependencies !== "object" || Array.isArray(dependencies)) {
70
+ continue;
71
+ }
72
+ for (const name of Object.keys(dependencies)) {
73
+ if (name.includes("topogram-extractor") || name.startsWith("@topogram/extractor-")) {
74
+ packages.add(name);
75
+ }
76
+ }
77
+ }
78
+ return [...packages].sort();
79
+ }
80
+
81
+ /**
82
+ * @param {any} manifest
83
+ * @param {{ installed?: boolean, manifestPath?: string|null, packageRoot?: string|null, errors?: string[] }} [metadata]
84
+ * @returns {Record<string, any>}
85
+ */
86
+ function extractorManifestSummary(manifest, metadata = {}) {
87
+ const installCommand = manifest.package ? packageExtractorInstallCommand(manifest.package) : null;
88
+ return {
89
+ id: manifest.id,
90
+ version: manifest.version,
91
+ tracks: manifest.tracks || [],
92
+ extractors: manifest.extractors || [],
93
+ stack: manifest.stack || {},
94
+ capabilities: manifest.capabilities || {},
95
+ candidateKinds: manifest.candidateKinds || [],
96
+ evidenceTypes: manifest.evidenceTypes || [],
97
+ source: manifest.source,
98
+ loadsAdapter: false,
99
+ executesPackageCode: false,
100
+ ...(manifest.package ? { package: manifest.package } : {}),
101
+ ...(installCommand ? { installCommand } : {}),
102
+ installed: metadata.installed !== false,
103
+ manifestPath: metadata.manifestPath || null,
104
+ packageRoot: metadata.packageRoot || null,
105
+ errors: metadata.errors || []
106
+ };
107
+ }
108
+
109
+ /**
110
+ * @param {string} cwd
111
+ * @returns {{ ok: boolean, cwd: string, extractors: Record<string, any>[], summary: Record<string, number> }}
112
+ */
113
+ export function buildExtractorListPayload(cwd) {
114
+ const extractors = EXTRACTOR_MANIFESTS
115
+ .map((manifest) => extractorManifestSummary(manifest))
116
+ .sort((left, right) => left.id.localeCompare(right.id));
117
+ for (const packageName of declaredExtractorPackages(cwd)) {
118
+ const loaded = loadPackageExtractorManifest(packageName, cwd);
119
+ if (loaded.manifest) {
120
+ extractors.push(extractorManifestSummary(loaded.manifest, {
121
+ installed: true,
122
+ manifestPath: loaded.manifestPath,
123
+ packageRoot: loaded.packageRoot,
124
+ errors: loaded.errors
125
+ }));
126
+ } else {
127
+ extractors.push({
128
+ id: null,
129
+ version: null,
130
+ tracks: [],
131
+ extractors: [],
132
+ stack: {},
133
+ capabilities: {},
134
+ candidateKinds: [],
135
+ evidenceTypes: [],
136
+ source: "package",
137
+ package: packageName,
138
+ installCommand: packageExtractorInstallCommand(packageName),
139
+ installed: false,
140
+ manifestPath: loaded.manifestPath,
141
+ packageRoot: loaded.packageRoot,
142
+ errors: loaded.errors
143
+ });
144
+ }
145
+ }
146
+ extractors.sort((left, right) => String(left.id || left.package || "").localeCompare(String(right.id || right.package || "")));
147
+ return {
148
+ ok: extractors.every((extractor) => extractor.errors.length === 0),
149
+ cwd,
150
+ extractors,
151
+ summary: {
152
+ total: extractors.length,
153
+ bundled: extractors.filter((extractor) => extractor.source === "bundled").length,
154
+ package: extractors.filter((extractor) => extractor.source === "package").length,
155
+ installed: extractors.filter((extractor) => extractor.installed).length
156
+ }
157
+ };
158
+ }
159
+
160
+ /**
161
+ * @param {string} spec
162
+ * @param {string} cwd
163
+ * @returns {{ ok: boolean, sourceSpec: string, extractor: Record<string, any>|null, errors: string[] }}
164
+ */
165
+ export function buildExtractorShowPayload(spec, cwd) {
166
+ if (!spec || spec.startsWith("-")) {
167
+ return { ok: false, sourceSpec: spec || "", extractor: null, errors: ["Usage: topogram extractor show <id-or-package>"] };
168
+ }
169
+ const bundled = getExtractorManifest(spec);
170
+ if (bundled) {
171
+ return { ok: true, sourceSpec: spec, extractor: extractorManifestSummary(bundled), errors: [] };
172
+ }
173
+ const loaded = loadPackageExtractorManifest(spec, cwd);
174
+ if (loaded.manifest) {
175
+ return {
176
+ ok: true,
177
+ sourceSpec: spec,
178
+ extractor: extractorManifestSummary(loaded.manifest, {
179
+ installed: true,
180
+ manifestPath: loaded.manifestPath,
181
+ packageRoot: loaded.packageRoot,
182
+ errors: loaded.errors
183
+ }),
184
+ errors: []
185
+ };
186
+ }
187
+ return { ok: false, sourceSpec: spec, extractor: null, errors: loaded.errors };
188
+ }
189
+
190
+ /**
191
+ * @param {ReturnType<typeof buildExtractorListPayload>} payload
192
+ * @returns {void}
193
+ */
194
+ export function printExtractorList(payload) {
195
+ console.log("Topogram extractors");
196
+ console.log(`Bundled: ${payload.summary.bundled}; package-backed: ${payload.summary.package}; installed: ${payload.summary.installed}`);
197
+ console.log("");
198
+ for (const extractor of payload.extractors) {
199
+ const id = extractor.id || extractor.package || "unknown";
200
+ const status = extractor.errors.length > 0
201
+ ? "invalid"
202
+ : extractor.source === "package"
203
+ ? (extractor.installed ? "package installed" : "package missing")
204
+ : "bundled";
205
+ console.log(`- ${id}${extractor.version ? `@${extractor.version}` : ""} (${extractor.tracks.join(", ") || "unknown"}, ${status})`);
206
+ console.log(` Source: ${extractor.source}`);
207
+ console.log(" Adapter loaded: no");
208
+ console.log(" Executes package code: no");
209
+ console.log(` Extractors: ${extractor.extractors.join(", ") || "none"}`);
210
+ if (extractor.package) console.log(` Package: ${extractor.package}`);
211
+ if (extractor.installCommand) console.log(` Install: ${extractor.installCommand}`);
212
+ for (const error of extractor.errors || []) console.log(` Error: ${error}`);
213
+ }
214
+ }
215
+
216
+ /**
217
+ * @param {ReturnType<typeof buildExtractorShowPayload>} payload
218
+ * @returns {void}
219
+ */
220
+ export function printExtractorShow(payload) {
221
+ if (!payload.ok || !payload.extractor) {
222
+ console.log("Extractor pack not found.");
223
+ for (const error of payload.errors || []) console.log(`- ${error}`);
224
+ return;
225
+ }
226
+ const extractor = payload.extractor;
227
+ console.log(`Extractor pack: ${extractor.id}@${extractor.version}`);
228
+ console.log(`Tracks: ${extractor.tracks.join(", ") || "none"}`);
229
+ console.log(`Source: ${extractor.source}`);
230
+ console.log("Adapter loaded: no");
231
+ console.log("Executes package code: no");
232
+ if (extractor.package) console.log(`Package: ${extractor.package}`);
233
+ if (extractor.installCommand) console.log(`Install: ${extractor.installCommand}`);
234
+ if (extractor.manifestPath) console.log(`Manifest: ${extractor.manifestPath}`);
235
+ console.log(`Extractors: ${extractor.extractors.join(", ") || "none"}`);
236
+ console.log(`Candidate kinds: ${extractor.candidateKinds.join(", ") || "none"}`);
237
+ console.log(`Evidence types: ${extractor.evidenceTypes.join(", ") || "none"}`);
238
+ }
239
+
240
+ /**
241
+ * @param {ReturnType<typeof checkExtractorPack>} payload
242
+ * @returns {void}
243
+ */
244
+ export function printExtractorCheck(payload) {
245
+ console.log(payload.ok ? "Extractor check passed." : "Extractor check found issues.");
246
+ console.log(`Source: ${payload.sourceSpec}`);
247
+ console.log(`Type: ${payload.source}`);
248
+ if (payload.packageName) console.log(`Package: ${payload.packageName}`);
249
+ if (payload.manifestPath) console.log(`Manifest: ${payload.manifestPath}`);
250
+ if (payload.manifest) {
251
+ console.log(`Extractor pack: ${payload.manifest.id}@${payload.manifest.version}`);
252
+ console.log(`Tracks: ${payload.manifest.tracks.join(", ")}`);
253
+ console.log(`Source mode: ${payload.manifest.source}`);
254
+ }
255
+ console.log("Executes package code: yes (loads adapter and runs smoke extract)");
256
+ console.log("");
257
+ console.log("Checks:");
258
+ for (const check of payload.checks || []) {
259
+ console.log(`- ${check.ok ? "PASS" : "FAIL"} ${check.name}: ${check.message}`);
260
+ }
261
+ if (payload.smoke) {
262
+ console.log("");
263
+ console.log(`Smoke output: ${payload.smoke.extractors} extractor(s), ${payload.smoke.findings} finding(s), ${payload.smoke.candidateKeys} candidate bucket(s), ${payload.smoke.diagnostics} diagnostic(s)`);
264
+ }
265
+ for (const error of payload.errors || []) console.log(`Error: ${error}`);
266
+ }
267
+
268
+ /**
269
+ * @param {string} projectPath
270
+ * @returns {{ ok: boolean, path: string, exists: boolean, policy: any, defaulted: boolean, packages: any[], diagnostics: any[], errors: string[], summary: Record<string, number> }}
271
+ */
272
+ export function buildExtractorPolicyStatusPayload(projectPath) {
273
+ const root = path.resolve(projectPath || ".");
274
+ const policyInfo = loadExtractorPolicy(root);
275
+ const policy = effectiveExtractorPolicy(policyInfo);
276
+ const packages = policy.enabledPackages.map((packageName) => {
277
+ const loaded = loadPackageExtractorManifest(packageName, root);
278
+ const version = loaded.manifest?.version || "unknown";
279
+ const diagnostics = extractorPolicyDiagnosticsForPackages(policyInfo, [{ packageName, version }], "extractor-policy");
280
+ return {
281
+ packageName,
282
+ version,
283
+ allowed: extractorPackageAllowed(policy, packageName),
284
+ installed: Boolean(loaded.manifest),
285
+ manifestPath: loaded.manifestPath,
286
+ packageRoot: loaded.packageRoot,
287
+ errors: [...loaded.errors, ...diagnostics.filter((diagnostic) => diagnostic.severity === "error").map((diagnostic) => diagnostic.message)]
288
+ };
289
+ });
290
+ const diagnostics = [
291
+ ...policyInfo.diagnostics,
292
+ ...packages.flatMap((item) => item.errors.map((message) => ({
293
+ code: "extractor_package_failed",
294
+ severity: "error",
295
+ message,
296
+ path: item.manifestPath,
297
+ suggestedFix: `Review or remove '${item.packageName}' from ${EXTRACTOR_POLICY_FILE}.`,
298
+ step: "extractor-policy",
299
+ packageName: item.packageName,
300
+ version: item.version
301
+ })))
302
+ ];
303
+ const errors = diagnostics.filter((diagnostic) => diagnostic.severity === "error").map((diagnostic) => diagnostic.message);
304
+ return {
305
+ ok: errors.length === 0,
306
+ path: policyInfo.path,
307
+ exists: policyInfo.exists,
308
+ policy,
309
+ defaulted: !policyInfo.exists,
310
+ packages,
311
+ diagnostics,
312
+ errors,
313
+ summary: {
314
+ enabledPackages: policy.enabledPackages.length,
315
+ installed: packages.filter((item) => item.installed).length,
316
+ allowed: packages.filter((item) => item.allowed).length,
317
+ denied: packages.filter((item) => !item.allowed).length
318
+ }
319
+ };
320
+ }
321
+
322
+ /**
323
+ * @param {string} projectPath
324
+ * @returns {{ ok: boolean, path: string, policy: any, diagnostics: any[], errors: string[] }}
325
+ */
326
+ export function buildExtractorPolicyInitPayload(projectPath) {
327
+ const root = path.resolve(projectPath || ".");
328
+ const policy = writeExtractorPolicy(root, defaultExtractorPolicy());
329
+ return { ok: true, path: path.join(root, EXTRACTOR_POLICY_FILE), policy, diagnostics: [], errors: [] };
330
+ }
331
+
332
+ /**
333
+ * @param {string} projectPath
334
+ * @param {string|null|undefined} spec
335
+ * @returns {{ ok: boolean, path: string, policy: any, pinned: Array<{ packageName: string, version: string }>, diagnostics: any[], errors: string[] }}
336
+ */
337
+ export function buildExtractorPolicyPinPayload(projectPath, spec) {
338
+ const root = path.resolve(projectPath || ".");
339
+ const policyInfo = loadExtractorPolicy(root);
340
+ const policy = policyInfo.policy || defaultExtractorPolicy();
341
+ let pin;
342
+ try {
343
+ pin = parseExtractorPolicyPin(spec || "");
344
+ } catch (error) {
345
+ return {
346
+ ok: false,
347
+ path: policyInfo.path,
348
+ policy,
349
+ pinned: [],
350
+ diagnostics: [{ severity: "error", code: "extractor_policy_pin_invalid", message: error instanceof Error ? error.message : String(error), path: policyInfo.path }],
351
+ errors: [error instanceof Error ? error.message : String(error)]
352
+ };
353
+ }
354
+ const nextPolicy = {
355
+ ...policy,
356
+ allowedPackages: policy.allowedPackages.includes(pin.packageName) ? policy.allowedPackages : [...policy.allowedPackages, pin.packageName],
357
+ enabledPackages: policy.enabledPackages.includes(pin.packageName) ? policy.enabledPackages : [...policy.enabledPackages, pin.packageName],
358
+ pinnedVersions: { ...policy.pinnedVersions, [pin.packageName]: pin.version }
359
+ };
360
+ writeExtractorPolicy(root, nextPolicy);
361
+ return { ok: true, path: path.join(root, EXTRACTOR_POLICY_FILE), policy: nextPolicy, pinned: [pin], diagnostics: [], errors: [] };
362
+ }
363
+
364
+ /**
365
+ * @param {ReturnType<typeof buildExtractorPolicyStatusPayload>} payload
366
+ * @returns {void}
367
+ */
368
+ export function printExtractorPolicyStatus(payload) {
369
+ console.log(payload.ok ? "Extractor policy status: allowed" : "Extractor policy status: denied");
370
+ console.log(`Policy file: ${payload.path}`);
371
+ console.log(`Policy file exists: ${payload.exists ? "yes" : "no"}`);
372
+ console.log(`Default policy active: ${payload.defaulted ? "yes" : "no"}`);
373
+ console.log(`Enabled packages: ${payload.summary.enabledPackages}`);
374
+ for (const item of payload.packages) {
375
+ console.log(`- ${item.packageName}@${item.version}: ${item.installed ? "installed" : "missing"}, ${item.allowed ? "allowed" : "denied"}`);
376
+ }
377
+ for (const diagnostic of payload.diagnostics) {
378
+ console.log(`[${diagnostic.severity}] ${diagnostic.code}: ${diagnostic.message}`);
379
+ }
380
+ }
381
+
382
+ /**
383
+ * @param {ReturnType<typeof buildExtractorPolicyInitPayload>} payload
384
+ * @returns {void}
385
+ */
386
+ export function printExtractorPolicyInit(payload) {
387
+ console.log(`Wrote extractor policy: ${payload.path}`);
388
+ console.log(`Allowed package scopes: ${payload.policy.allowedPackageScopes.join(", ") || "(none)"}`);
389
+ console.log(`Allowed packages: ${payload.policy.allowedPackages.join(", ") || "(none)"}`);
390
+ console.log(`Enabled packages: ${payload.policy.enabledPackages.join(", ") || "(none)"}`);
391
+ }
392
+
393
+ /**
394
+ * @param {ReturnType<typeof buildExtractorPolicyPinPayload>} payload
395
+ * @returns {void}
396
+ */
397
+ export function printExtractorPolicyPin(payload) {
398
+ console.log(payload.ok ? "Extractor policy pin updated" : "Extractor policy pin failed");
399
+ console.log(`Policy: ${payload.path}`);
400
+ for (const pin of payload.pinned || []) {
401
+ console.log(`Pinned: ${pin.packageName}@${pin.version}`);
402
+ }
403
+ for (const diagnostic of payload.diagnostics || []) {
404
+ console.log(`[${diagnostic.severity}] ${diagnostic.code}: ${diagnostic.message}`);
405
+ }
406
+ }
407
+
408
+ /**
409
+ * @param {{ commandArgs: Record<string, any>, inputPath: string|null|undefined, json: boolean, cwd: string }} context
410
+ * @returns {number}
411
+ */
412
+ export function runExtractorCommand(context) {
413
+ const { commandArgs, inputPath, json, cwd } = context;
414
+ if (commandArgs.extractorCommand === "check") {
415
+ const payload = checkExtractorPack(inputPath || "", { cwd });
416
+ if (json) console.log(stableStringify(payload));
417
+ else printExtractorCheck(payload);
418
+ return payload.ok ? 0 : 1;
419
+ }
420
+ if (commandArgs.extractorCommand === "list") {
421
+ const payload = buildExtractorListPayload(cwd);
422
+ if (json) console.log(stableStringify(payload));
423
+ else printExtractorList(payload);
424
+ return payload.ok ? 0 : 1;
425
+ }
426
+ if (commandArgs.extractorCommand === "show") {
427
+ const payload = buildExtractorShowPayload(inputPath || "", cwd);
428
+ if (json) console.log(stableStringify(payload));
429
+ else printExtractorShow(payload);
430
+ return payload.ok ? 0 : 1;
431
+ }
432
+ if (commandArgs.extractorPolicyCommand === "init") {
433
+ const payload = buildExtractorPolicyInitPayload(inputPath || ".");
434
+ if (json) console.log(stableStringify(payload));
435
+ else printExtractorPolicyInit(payload);
436
+ return 0;
437
+ }
438
+ if (commandArgs.extractorPolicyCommand === "status" || commandArgs.extractorPolicyCommand === "check" || commandArgs.extractorPolicyCommand === "explain") {
439
+ const payload = buildExtractorPolicyStatusPayload(inputPath || ".");
440
+ if (json) console.log(stableStringify(payload));
441
+ else printExtractorPolicyStatus(payload);
442
+ return payload.ok ? 0 : 1;
443
+ }
444
+ if (commandArgs.extractorPolicyCommand === "pin") {
445
+ const payload = buildExtractorPolicyPinPayload(inputPath || ".", commandArgs.extractorPolicyPinSpec);
446
+ if (json) console.log(stableStringify(payload));
447
+ else printExtractorPolicyPin(payload);
448
+ return payload.ok ? 0 : 1;
449
+ }
450
+ throw new Error(`Unknown extractor command '${commandArgs.extractorCommand || commandArgs.extractorPolicyCommand}'`);
451
+ }
@@ -42,7 +42,7 @@ export function writtenFileHashesForReceipt(outputRoot, writtenFiles) {
42
42
  */
43
43
  export function buildImportAdoptionReceipt({ artifacts, selector, options, importStatus, summary, writtenFiles, outputRoot }) {
44
44
  return {
45
- type: "topogram_import_adoption_receipt",
45
+ type: "topogram_adoption_receipt",
46
46
  version: "0.1",
47
47
  timestamp: new Date().toISOString(),
48
48
  cli: {
@@ -87,18 +87,18 @@ export function buildImportAdoptionReceipt({ artifacts, selector, options, impor
87
87
  */
88
88
  export function buildBrownfieldImportAdoptPayload(selector, inputPath, options = {}) {
89
89
  if (!selector) {
90
- throw new Error("Missing required <selector>. Example: topogram import adopt bundle:task --dry-run");
90
+ throw new Error("Missing required <selector>. Example: topogram adopt bundle:task --dry-run");
91
91
  }
92
92
  if (options.write && options.dryRun) {
93
93
  throw new Error("Use either --dry-run or --write, not both.");
94
94
  }
95
95
  if (options.write && options.force && !options.reason) {
96
- throw new Error("Forced import adoption writes require --reason <text>.");
96
+ throw new Error("Forced adoption writes require --reason <text>.");
97
97
  }
98
98
  const artifacts = readImportAdoptionArtifacts(inputPath);
99
99
  const importStatus = buildTopogramImportStatus(artifacts.projectRoot);
100
100
  if (options.write && !options.force && !importStatus.ok) {
101
- throw new Error(`Refusing to write import adoption because brownfield source provenance is ${importStatus.status}. Run 'topogram import check ${importProjectCommandPath(artifacts.projectRoot)}', review the changed source evidence, rerun import, or pass --force --reason <text> after review.`);
101
+ throw new Error(`Refusing to write adoption because brownfield source provenance is ${importStatus.status}. Run 'topogram extract check ${importProjectCommandPath(artifacts.projectRoot)}', review the changed source evidence, rerun extract, or pass --force --reason <text> after review.`);
102
102
  }
103
103
  const result = runWorkflow("reconcile", artifacts.projectRoot, {
104
104
  adopt: selector,
@@ -129,19 +129,19 @@ export function buildBrownfieldImportAdoptPayload(selector, inputPath, options =
129
129
  receipt,
130
130
  receiptPath,
131
131
  adoption: summary,
132
- import: importStatus,
132
+ extract: importStatus,
133
133
  warnings: options.write && options.force && !importStatus.ok
134
134
  ? [`Brownfield source provenance is ${importStatus.status}; adoption write was forced with reason: ${options.reason}.`]
135
135
  : [],
136
136
  nextCommands: options.write
137
137
  ? [
138
- `topogram import history ${importProjectCommandPath(artifacts.projectRoot)}`,
139
- `topogram import status ${importProjectCommandPath(artifacts.projectRoot)}`,
138
+ `topogram extract history ${importProjectCommandPath(artifacts.projectRoot)}`,
139
+ `topogram extract status ${importProjectCommandPath(artifacts.projectRoot)}`,
140
140
  `topogram check ${importProjectCommandPath(artifacts.projectRoot)}`
141
141
  ]
142
142
  : [
143
143
  importAdoptCommand(artifacts.projectRoot, selector, true),
144
- `topogram import status ${importProjectCommandPath(artifacts.projectRoot)}`
144
+ `topogram extract status ${importProjectCommandPath(artifacts.projectRoot)}`
145
145
  ]
146
146
  };
147
147
  }
@@ -151,7 +151,7 @@ export function buildBrownfieldImportAdoptPayload(selector, inputPath, options =
151
151
  * @returns {void}
152
152
  */
153
153
  export function printBrownfieldImportAdopt(payload) {
154
- console.log(`${payload.dryRun ? "Previewed" : "Applied"} import adoption for ${payload.selector}.`);
154
+ console.log(`${payload.dryRun ? "Previewed" : "Applied"} adoption for ${payload.selector}.`);
155
155
  console.log(`Project: ${payload.projectRoot}`);
156
156
  console.log(`Promoted canonical items: ${payload.promotedCanonicalItemCount}`);
157
157
  console.log(`Written files: ${payload.writtenFiles.length}`);
@@ -40,7 +40,7 @@ export function buildTopogramCheckPayloadForPath(inputPath) {
40
40
 
41
41
  /**
42
42
  * @param {string} projectRoot
43
- * @returns {{ ok: boolean, projectRoot: string, workspaceRoot: string, import: ReturnType<typeof buildTopogramImportStatus>, topogram: ReturnType<typeof buildTopogramCheckPayloadForPath>, errors: any[] }}
43
+ * @returns {{ ok: boolean, projectRoot: string, workspaceRoot: string, extract: ReturnType<typeof buildTopogramImportStatus>, topogram: ReturnType<typeof buildTopogramCheckPayloadForPath>, errors: any[] }}
44
44
  */
45
45
  export function buildBrownfieldImportCheckPayload(projectRoot) {
46
46
  const resolvedRoot = normalizeProjectRoot(projectRoot);
@@ -50,10 +50,10 @@ export function buildBrownfieldImportCheckPayload(projectRoot) {
50
50
  ok: importStatus.ok && topogramCheck.ok,
51
51
  projectRoot: resolvedRoot,
52
52
  workspaceRoot: normalizeTopogramPath(resolvedRoot),
53
- import: importStatus,
53
+ extract: importStatus,
54
54
  topogram: topogramCheck,
55
55
  errors: [
56
- ...(importStatus.errors || []).map((/** @type {string} */ message) => ({ source: "import", message })),
56
+ ...(importStatus.errors || []).map((/** @type {string} */ message) => ({ source: "extract", message })),
57
57
  ...(topogramCheck.errors || [])
58
58
  ]
59
59
  };
@@ -64,23 +64,23 @@ export function buildBrownfieldImportCheckPayload(projectRoot) {
64
64
  * @returns {void}
65
65
  */
66
66
  export function printBrownfieldImportCheck(payload) {
67
- console.log(`Topogram import check: ${payload.import.status}`);
67
+ console.log(`Topogram extract check: ${payload.extract.status}`);
68
68
  console.log(`Project: ${payload.projectRoot}`);
69
- if (payload.import.source?.source?.path) {
70
- console.log(`Imported source: ${payload.import.source.source.path}`);
69
+ if (payload.extract.source?.source?.path) {
70
+ console.log(`Extracted source: ${payload.extract.source.source.path}`);
71
71
  }
72
- console.log(`Provenance: ${payload.import.path}`);
73
- if (payload.import.source?.files) {
74
- console.log(`Trusted source files: ${payload.import.source.files.length}`);
72
+ console.log(`Provenance: ${payload.extract.path}`);
73
+ if (payload.extract.source?.files) {
74
+ console.log(`Trusted source files: ${payload.extract.source.files.length}`);
75
75
  }
76
- if (payload.import.status === "changed") {
77
- console.log(`Changed source files: ${payload.import.content.changed.length}`);
78
- console.log(`Added source files: ${payload.import.content.added.length}`);
79
- console.log(`Removed source files: ${payload.import.content.removed.length}`);
76
+ if (payload.extract.status === "changed") {
77
+ console.log(`Changed source files: ${payload.extract.content.changed.length}`);
78
+ console.log(`Added source files: ${payload.extract.content.added.length}`);
79
+ console.log(`Removed source files: ${payload.extract.content.removed.length}`);
80
80
  }
81
81
  console.log(`Topogram check: ${payload.topogram.ok ? "passed" : "failed"}`);
82
- console.log("Imported Topogram artifacts are project-owned; import check compares only the brownfield source hashes trusted at import time plus normal Topogram validity.");
83
- for (const diagnostic of payload.import.diagnostics || []) {
82
+ console.log("Extracted Topogram artifacts are project-owned; extract check compares only the brownfield source hashes trusted at extraction time plus normal Topogram validity.");
83
+ for (const diagnostic of payload.extract.diagnostics || []) {
84
84
  const label = diagnostic.severity === "warning" ? "Warning" : "Error";
85
85
  console.log(`${label}: ${diagnostic.message}`);
86
86
  if (diagnostic.suggestedFix) {
@@ -21,7 +21,7 @@ export function buildBrownfieldImportDiffPayload(inputPath, options = {}) {
21
21
  topogramRoot: analysis.topogramRoot,
22
22
  sourcePath: analysis.sourcePath,
23
23
  provenancePath: analysis.provenancePath,
24
- importStatus: analysis.previousImportStatus,
24
+ extractStatus: analysis.previousImportStatus,
25
25
  sourceDiff: analysis.sourceDiff,
26
26
  tracks: analysis.tracks,
27
27
  sourceFiles: analysis.sourceFiles,
@@ -31,9 +31,9 @@ export function buildBrownfieldImportDiffPayload(inputPath, options = {}) {
31
31
  receiptVerification: analysis.receiptVerification,
32
32
  plannedFiles: analysis.plannedFiles,
33
33
  nextCommands: [
34
- `topogram import refresh ${importProjectCommandPath(analysis.projectRoot)} --dry-run`,
35
- `topogram import refresh ${importProjectCommandPath(analysis.projectRoot)}`,
36
- `topogram import plan ${importProjectCommandPath(analysis.projectRoot)}`
34
+ `topogram extract refresh ${importProjectCommandPath(analysis.projectRoot)} --dry-run`,
35
+ `topogram extract refresh ${importProjectCommandPath(analysis.projectRoot)}`,
36
+ `topogram extract plan ${importProjectCommandPath(analysis.projectRoot)}`
37
37
  ]
38
38
  };
39
39
  }
@@ -43,9 +43,9 @@ export function buildBrownfieldImportDiffPayload(inputPath, options = {}) {
43
43
  * @returns {void}
44
44
  */
45
45
  export function printBrownfieldImportDiff(payload) {
46
- console.log(`Import diff for ${payload.projectRoot}`);
46
+ console.log(`Extraction diff for ${payload.projectRoot}`);
47
47
  console.log(`Source: ${payload.sourcePath}`);
48
- console.log(`Source status: ${payload.importStatus}`);
48
+ console.log(`Source status: ${payload.extractStatus}`);
49
49
  console.log(`Source diff: changed=${payload.sourceDiff.counts.changed}, added=${payload.sourceDiff.counts.added}, removed=${payload.sourceDiff.counts.removed}`);
50
50
  for (const filePath of [...payload.sourceDiff.changed, ...payload.sourceDiff.added, ...payload.sourceDiff.removed].slice(0, 12)) {
51
51
  const status = payload.sourceDiff.changed.includes(filePath)