@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.
- package/CHANGELOG.md +20 -0
- package/package.json +2 -2
- package/src/agent-brief.js +29 -23
- package/src/agent-ops/query-builders/change-risk/{import-plan.js → extract-plan.js} +1 -1
- package/src/agent-ops/query-builders/change-risk/review-packets.js +5 -5
- package/src/agent-ops/query-builders/change-risk.js +1 -1
- package/src/agent-ops/query-builders/common.js +2 -2
- package/src/agent-ops/query-builders/multi-agent.js +1 -1
- package/src/agent-ops/query-builders/workflow-context-shared.js +4 -4
- package/src/catalog/provenance.js +1 -1
- package/src/cli/catalog-alias.d.ts +2 -0
- package/src/cli/catalog-alias.js +2 -2
- package/src/cli/command-parser.js +2 -0
- package/src/cli/command-parsers/core.js +9 -5
- package/src/cli/command-parsers/extractor.js +40 -0
- package/src/cli/command-parsers/import.js +11 -17
- package/src/cli/command-parsers/project.js +0 -3
- package/src/cli/commands/catalog/copy.js +3 -3
- package/src/cli/commands/catalog/help.js +1 -2
- package/src/cli/commands/catalog/list.js +7 -4
- package/src/cli/commands/catalog/show.js +4 -4
- package/src/cli/commands/copy.js +356 -0
- package/src/cli/commands/doctor.js +1 -1
- package/src/cli/commands/extractor.js +451 -0
- package/src/cli/commands/import/adopt.js +9 -9
- package/src/cli/commands/import/check.js +15 -15
- package/src/cli/commands/import/diff.js +6 -6
- package/src/cli/commands/import/help.js +45 -34
- package/src/cli/commands/import/paths.js +3 -3
- package/src/cli/commands/import/plan.js +8 -8
- package/src/cli/commands/import/refresh.js +25 -24
- package/src/cli/commands/import/status-history.js +4 -4
- package/src/cli/commands/import/workspace.js +24 -18
- package/src/cli/commands/import-runner.js +10 -7
- package/src/cli/commands/import.js +4 -1
- package/src/cli/commands/init.js +67 -0
- package/src/cli/commands/query/{import-adopt.js → extract-adopt.js} +2 -2
- package/src/cli/commands/query/runner/change.js +2 -2
- package/src/cli/commands/query/runner/{import-adopt.js → extract-adopt.js} +9 -9
- package/src/cli/commands/query/runner/index.js +1 -1
- package/src/cli/commands/query/runner/workflow.js +7 -7
- package/src/cli/commands/query/workspace.js +4 -4
- package/src/cli/commands/release-status.js +2 -2
- package/src/cli/commands/source.js +2 -2
- package/src/cli/commands/template/check.js +2 -2
- package/src/cli/commands/template/list-show.js +4 -4
- package/src/cli/dispatcher.js +32 -3
- package/src/cli/help-dispatch.js +33 -8
- package/src/cli/help.js +79 -52
- package/src/cli/migration-guidance.js +9 -0
- package/src/cli/options.js +17 -0
- package/src/extractor/check.js +155 -0
- package/src/extractor/packages.js +295 -0
- package/src/extractor/registry.js +196 -0
- package/src/extractor-policy.js +249 -0
- package/src/generator/check.js +24 -87
- package/src/generator/context/bundle.js +14 -7
- package/src/generator/context/diff.js +8 -1
- package/src/generator/context/digest.js +10 -1
- package/src/generator/context/shared/domain-sdlc.js +5 -1
- package/src/generator/context/shared/relationships.js +20 -5
- package/src/generator/context/shared/summaries.js +26 -0
- package/src/generator/context/shared.d.ts +1 -0
- package/src/generator/context/shared.js +1 -0
- package/src/generator/context/slice/core.js +9 -5
- package/src/generator/context/slice/sdlc.js +31 -2
- package/src/generator/context/task-mode.js +3 -3
- package/src/generator/registry/index.js +16 -75
- package/src/generator-policy.js +9 -57
- package/src/import/core/registry.d.ts +3 -0
- package/src/import/core/registry.js +82 -8
- package/src/import/core/runner/reports.js +4 -4
- package/src/import/core/runner/run.js +2 -0
- package/src/import/core/runner/tracks.js +66 -4
- package/src/import/provenance.js +18 -17
- package/src/init-project.js +215 -0
- package/src/new-project/constants.js +1 -1
- package/src/new-project/create.js +2 -2
- package/src/new-project/project-files.js +7 -7
- package/src/package-adapters/adapter.js +64 -0
- package/src/package-adapters/file-map.js +30 -0
- package/src/package-adapters/index.js +27 -0
- package/src/package-adapters/manifest.js +108 -0
- package/src/package-adapters/policy.js +81 -0
- package/src/package-adapters/spec.js +51 -0
- package/src/reconcile/journeys.js +8 -3
- package/src/record-blocks.js +125 -0
- package/src/resolver/index.js +3 -0
- package/src/resolver/journeys.js +74 -0
- package/src/resolver/normalize.js +25 -0
- package/src/sdlc/adopt.js +1 -1
- package/src/validator/common.js +34 -1
- package/src/validator/index.js +4 -0
- package/src/validator/kinds.d.ts +2 -0
- package/src/validator/kinds.js +34 -1
- package/src/validator/per-kind/journey.js +233 -0
- package/src/workflows/docs-generate.js +4 -1
- package/src/workflows/reconcile/bundle-core/index.js +4 -2
- package/src/workflows/reconcile/canonical-surface.js +4 -1
- 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: "
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
|
139
|
-
`topogram
|
|
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
|
|
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"}
|
|
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,
|
|
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
|
-
|
|
53
|
+
extract: importStatus,
|
|
54
54
|
topogram: topogramCheck,
|
|
55
55
|
errors: [
|
|
56
|
-
...(importStatus.errors || []).map((/** @type {string} */ message) => ({ source: "
|
|
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
|
|
67
|
+
console.log(`Topogram extract check: ${payload.extract.status}`);
|
|
68
68
|
console.log(`Project: ${payload.projectRoot}`);
|
|
69
|
-
if (payload.
|
|
70
|
-
console.log(`
|
|
69
|
+
if (payload.extract.source?.source?.path) {
|
|
70
|
+
console.log(`Extracted source: ${payload.extract.source.source.path}`);
|
|
71
71
|
}
|
|
72
|
-
console.log(`Provenance: ${payload.
|
|
73
|
-
if (payload.
|
|
74
|
-
console.log(`Trusted source files: ${payload.
|
|
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.
|
|
77
|
-
console.log(`Changed source files: ${payload.
|
|
78
|
-
console.log(`Added source files: ${payload.
|
|
79
|
-
console.log(`Removed source files: ${payload.
|
|
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("
|
|
83
|
-
for (const diagnostic of payload.
|
|
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
|
-
|
|
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
|
|
35
|
-
`topogram
|
|
36
|
-
`topogram
|
|
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(`
|
|
46
|
+
console.log(`Extraction diff for ${payload.projectRoot}`);
|
|
47
47
|
console.log(`Source: ${payload.sourcePath}`);
|
|
48
|
-
console.log(`Source status: ${payload.
|
|
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)
|