@topogram/cli 0.3.78 → 0.3.79
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-parsers/core.js +9 -5
- 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/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 +43 -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 +16 -16
- package/src/cli/commands/import-runner.js +6 -5
- 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 +18 -3
- package/src/cli/help-dispatch.js +22 -8
- package/src/cli/help.js +68 -52
- package/src/cli/migration-guidance.js +9 -0
- 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/import/core/runner/reports.js +4 -4
- package/src/import/provenance.js +16 -16
- 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/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
|
@@ -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
|
|
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
|
-
? ["
|
|
185
|
+
? ["Extracted proposal items require human review before adoption."]
|
|
186
186
|
: [])
|
|
187
187
|
],
|
|
188
|
-
nextCommand: nextBundle ? nextBundle.nextCommand : `topogram
|
|
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
|
|
213
|
-
status: `topogram
|
|
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(`
|
|
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(`
|
|
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
|
|
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
|
|
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
|
|
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
|
|
52
|
-
|
|
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
|
|
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
|
|
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
|
|
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,
|
|
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
|
|
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
|
-
|
|
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 ||
|
|
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
|
-
|
|
413
|
-
|
|
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
|
|
430
|
-
: `topogram
|
|
431
|
-
`topogram
|
|
432
|
-
`topogram
|
|
433
|
-
`topogram
|
|
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
|
|
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(`
|
|
447
|
-
console.log(`Previous source status: ${payload.
|
|
448
|
-
console.log(`Current source status: ${payload.
|
|
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
|
-
|
|
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(`
|
|
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.
|
|
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(`
|
|
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
|
-
"#
|
|
82
|
+
"# Extracted Topogram Workspace",
|
|
83
83
|
"",
|
|
84
|
-
"This workspace was created from a brownfield app
|
|
84
|
+
"This workspace was created from a brownfield app extraction.",
|
|
85
85
|
"",
|
|
86
|
-
`-
|
|
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
|
-
"
|
|
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
|
|
96
|
+
"topogram extract check",
|
|
97
97
|
"topogram check",
|
|
98
|
-
`topogram query
|
|
98
|
+
`topogram query extract-plan ./${DEFAULT_TOPO_FOLDER_NAME}`,
|
|
99
99
|
"```",
|
|
100
100
|
""
|
|
101
101
|
].join("\n");
|
|
@@ -156,10 +156,10 @@ export function buildBrownfieldImportWorkspacePayload(sourcePath, targetPath, op
|
|
|
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
|
|
159
|
+
throw new Error(`Cannot extract missing app directory '${sourcePath}'.`);
|
|
160
160
|
}
|
|
161
161
|
if (sourceRoot === targetRoot) {
|
|
162
|
-
throw new Error("Refusing to
|
|
162
|
+
throw new Error("Refusing to extract into the same directory as the brownfield app.");
|
|
163
163
|
}
|
|
164
164
|
ensureEmptyImportTarget(targetRoot);
|
|
165
165
|
|
|
@@ -206,12 +206,12 @@ export function buildBrownfieldImportWorkspacePayload(sourcePath, targetPath, op
|
|
|
206
206
|
writtenFiles,
|
|
207
207
|
candidateCounts,
|
|
208
208
|
nextCommands: [
|
|
209
|
-
"topogram
|
|
210
|
-
"topogram
|
|
211
|
-
"topogram
|
|
212
|
-
"topogram
|
|
209
|
+
"topogram extract check",
|
|
210
|
+
"topogram extract plan",
|
|
211
|
+
"topogram adopt bundle:task --dry-run",
|
|
212
|
+
"topogram extract status",
|
|
213
213
|
"topogram check",
|
|
214
|
-
`topogram query
|
|
214
|
+
`topogram query extract-plan ./${DEFAULT_TOPO_FOLDER_NAME}`
|
|
215
215
|
]
|
|
216
216
|
};
|
|
217
217
|
}
|
|
@@ -221,15 +221,15 @@ export function buildBrownfieldImportWorkspacePayload(sourcePath, targetPath, op
|
|
|
221
221
|
* @returns {void}
|
|
222
222
|
*/
|
|
223
223
|
export function printBrownfieldImportWorkspace(payload) {
|
|
224
|
-
console.log(`
|
|
224
|
+
console.log(`Extracted brownfield app to ${payload.targetPath}.`);
|
|
225
225
|
console.log(`Source: ${payload.sourcePath}`);
|
|
226
226
|
console.log(`Topogram: ${payload.topogramRoot}`);
|
|
227
227
|
console.log(`Project config: ${payload.projectConfigPath}`);
|
|
228
|
-
console.log(`
|
|
228
|
+
console.log(`Extraction provenance: ${payload.provenancePath}`);
|
|
229
229
|
console.log(`Tracked source files: ${payload.sourceFiles}`);
|
|
230
230
|
console.log(`Raw candidate files: ${payload.rawCandidateFiles}`);
|
|
231
231
|
console.log(`Reconcile proposal files: ${payload.reconcileFiles}`);
|
|
232
|
-
console.log("
|
|
232
|
+
console.log("Extracted Topogram artifacts are project-owned after creation; source hashes record the app evidence trusted at extraction time.");
|
|
233
233
|
console.log("");
|
|
234
234
|
console.log("Next steps:");
|
|
235
235
|
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,7 +21,7 @@ import {
|
|
|
20
21
|
printBrownfieldImportRefresh,
|
|
21
22
|
printBrownfieldImportStatus,
|
|
22
23
|
printBrownfieldImportWorkspace,
|
|
23
|
-
|
|
24
|
+
printExtractHelp
|
|
24
25
|
} from "./import.js";
|
|
25
26
|
|
|
26
27
|
/**
|
|
@@ -45,7 +46,7 @@ export function runImportCommand(context) {
|
|
|
45
46
|
if (command === "workspace") {
|
|
46
47
|
if (!outPath) {
|
|
47
48
|
console.error("Missing required --out <target>.");
|
|
48
|
-
|
|
49
|
+
printExtractHelp();
|
|
49
50
|
return 1;
|
|
50
51
|
}
|
|
51
52
|
const payload = buildBrownfieldImportWorkspacePayload(inputPath || "", outPath, { from: fromValue });
|
|
@@ -110,12 +111,12 @@ export function runImportCommand(context) {
|
|
|
110
111
|
if (command === "adopt") {
|
|
111
112
|
if (!commandArgs.importAdoptSelector || commandArgs.importAdoptSelector.startsWith("-")) {
|
|
112
113
|
console.error("Missing required <selector>.");
|
|
113
|
-
|
|
114
|
+
printAdoptHelp();
|
|
114
115
|
return 1;
|
|
115
116
|
}
|
|
116
117
|
if (write && dryRun) {
|
|
117
118
|
console.error("Use either --dry-run or --write, not both.");
|
|
118
|
-
|
|
119
|
+
printAdoptHelp();
|
|
119
120
|
return 1;
|
|
120
121
|
}
|
|
121
122
|
const payload = buildBrownfieldImportAdoptPayload(commandArgs.importAdoptSelector, inputPath || ".", {
|
|
@@ -153,5 +154,5 @@ export function runImportCommand(context) {
|
|
|
153
154
|
return payload.ok ? 0 : 1;
|
|
154
155
|
}
|
|
155
156
|
|
|
156
|
-
throw new Error(`Unknown
|
|
157
|
+
throw new Error(`Unknown extract/adopt command '${command}'`);
|
|
157
158
|
}
|
|
@@ -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, {}, "
|
|
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, {}, "
|
|
77
|
+
const taskModeResult = buildTaskMode(ast, {}, "extract-adopt");
|
|
78
78
|
if (!resultOk(taskModeResult)) {
|
|
79
79
|
return taskModeResult;
|
|
80
80
|
}
|
|
@@ -14,7 +14,7 @@ import {
|
|
|
14
14
|
} from "../../../../agent-ops/query-builders.js";
|
|
15
15
|
import { parsePath } from "../../../../parser.js";
|
|
16
16
|
import { buildChangePlanContext } from "../change-plan.js";
|
|
17
|
-
import { buildImportPlanForContext } from "../
|
|
17
|
+
import { buildImportPlanForContext } from "../extract-adopt.js";
|
|
18
18
|
import {
|
|
19
19
|
adoptionPlanPath,
|
|
20
20
|
buildTaskMode,
|
|
@@ -57,7 +57,7 @@ export function runChangeQuery(context) {
|
|
|
57
57
|
maintainedRisk: built.importPlan.maintained_risk || null
|
|
58
58
|
});
|
|
59
59
|
return printJson(buildRiskSummaryPayload({
|
|
60
|
-
source: "
|
|
60
|
+
source: "extract-plan",
|
|
61
61
|
risk,
|
|
62
62
|
nextAction: built.importPlan.next_action || null,
|
|
63
63
|
maintainedRisk: built.importPlan.maintained_risk || null
|
|
@@ -10,7 +10,7 @@ import {
|
|
|
10
10
|
buildLaneStatusPayload,
|
|
11
11
|
buildWorkPacketPayload
|
|
12
12
|
} from "../../../../agent-ops/query-builders.js";
|
|
13
|
-
import { buildImportAdoptAgentContext, buildImportPlanForContext } from "../
|
|
13
|
+
import { buildImportAdoptAgentContext, buildImportPlanForContext } from "../extract-adopt.js";
|
|
14
14
|
import { normalizeTopogramPath, printValidationFailure, readJson, resultOk } from "../workspace.js";
|
|
15
15
|
import { printJson } from "./output.js";
|
|
16
16
|
|
|
@@ -25,15 +25,15 @@ import { printJson } from "./output.js";
|
|
|
25
25
|
export function runImportAdoptQuery(context) {
|
|
26
26
|
const queryName = context.commandArgs?.queryName;
|
|
27
27
|
|
|
28
|
-
if (queryName === "
|
|
29
|
-
const built = buildImportPlanForContext(context, "
|
|
28
|
+
if (queryName === "extract-plan") {
|
|
29
|
+
const built = buildImportPlanForContext(context, "extract-plan");
|
|
30
30
|
if (!resultOk(built)) return printValidationFailure(built);
|
|
31
31
|
return printJson(built.importPlan);
|
|
32
32
|
}
|
|
33
33
|
|
|
34
34
|
if (queryName === "multi-agent-plan") {
|
|
35
|
-
if (context.modeId !== "
|
|
36
|
-
throw new Error("query multi-agent-plan currently supports only --mode
|
|
35
|
+
if (context.modeId !== "extract-adopt") {
|
|
36
|
+
throw new Error("query multi-agent-plan currently supports only --mode extract-adopt.");
|
|
37
37
|
}
|
|
38
38
|
const built = buildImportAdoptAgentContext(context, "multi-agent-plan");
|
|
39
39
|
if (!resultOk(built)) return printValidationFailure(built);
|
|
@@ -41,8 +41,8 @@ export function runImportAdoptQuery(context) {
|
|
|
41
41
|
}
|
|
42
42
|
|
|
43
43
|
if (queryName === "work-packet") {
|
|
44
|
-
if (context.modeId !== "
|
|
45
|
-
throw new Error("query work-packet currently supports only --mode
|
|
44
|
+
if (context.modeId !== "extract-adopt") {
|
|
45
|
+
throw new Error("query work-packet currently supports only --mode extract-adopt.");
|
|
46
46
|
}
|
|
47
47
|
if (!context.laneId) {
|
|
48
48
|
throw new Error("query work-packet requires --lane <id>.");
|
|
@@ -57,8 +57,8 @@ export function runImportAdoptQuery(context) {
|
|
|
57
57
|
}
|
|
58
58
|
|
|
59
59
|
if (queryName === "lane-status" || queryName === "handoff-status") {
|
|
60
|
-
if (context.modeId !== "
|
|
61
|
-
throw new Error(`query ${queryName} currently supports only --mode
|
|
60
|
+
if (context.modeId !== "extract-adopt") {
|
|
61
|
+
throw new Error(`query ${queryName} currently supports only --mode extract-adopt.`);
|
|
62
62
|
}
|
|
63
63
|
const built = buildImportAdoptAgentContext(context, queryName);
|
|
64
64
|
if (!resultOk(built)) return printValidationFailure(built);
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
import { runArtifactQuery } from "./artifacts.js";
|
|
4
4
|
import { runBoundaryQuery } from "./boundaries.js";
|
|
5
5
|
import { runChangeQuery } from "./change.js";
|
|
6
|
-
import { runImportAdoptQuery } from "./
|
|
6
|
+
import { runImportAdoptQuery } from "./extract-adopt.js";
|
|
7
7
|
import { runWorkflowQuery } from "./workflow.js";
|
|
8
8
|
|
|
9
9
|
/**
|
|
@@ -46,7 +46,7 @@ export function runWorkflowQuery(context) {
|
|
|
46
46
|
const selectors = selectorOptions(context);
|
|
47
47
|
|
|
48
48
|
if (queryName === "next-action") {
|
|
49
|
-
const result = buildTaskMode(parsePath(context.inputPath), selectors, context.modeId || "
|
|
49
|
+
const result = buildTaskMode(parsePath(context.inputPath), selectors, context.modeId || "extract-adopt", context.fromTopogramPath);
|
|
50
50
|
if (!resultOk(result)) return printValidationFailure(result);
|
|
51
51
|
return printJson({
|
|
52
52
|
type: "next_action_query",
|
|
@@ -72,13 +72,13 @@ export function runWorkflowQuery(context) {
|
|
|
72
72
|
|
|
73
73
|
if (queryName === "workflow-preset-activation") {
|
|
74
74
|
if (!context.modeId) {
|
|
75
|
-
throw new Error("query workflow-preset-activation requires --mode <modeling|maintained-app-edit|
|
|
75
|
+
throw new Error("query workflow-preset-activation requires --mode <modeling|maintained-app-edit|extract-adopt|diff-review|verification>.");
|
|
76
76
|
}
|
|
77
77
|
const topogramRoot = normalizeTopogramPath(context.inputPath);
|
|
78
78
|
const taskModeResult = buildTaskMode(parsePath(context.inputPath), selectors, context.modeId, context.fromTopogramPath);
|
|
79
79
|
if (!resultOk(taskModeResult)) return printValidationFailure(taskModeResult);
|
|
80
80
|
let importPlan = null;
|
|
81
|
-
if (context.modeId === "
|
|
81
|
+
if (context.modeId === "extract-adopt" && fs.existsSync(adoptionPlanPath(topogramRoot))) {
|
|
82
82
|
const workflowPresets = buildWorkflowPresetState({
|
|
83
83
|
workspace: topogramRoot,
|
|
84
84
|
selectors: workflowPresetSelectors(taskModeResult.artifact, context.providerId, context.presetId, "workflow-preset-activation")
|
|
@@ -118,7 +118,7 @@ export function runWorkflowQuery(context) {
|
|
|
118
118
|
*/
|
|
119
119
|
function runSingleAgentPlan(context, selectors) {
|
|
120
120
|
if (!context.modeId) {
|
|
121
|
-
throw new Error("query single-agent-plan requires --mode <modeling|maintained-app-edit|
|
|
121
|
+
throw new Error("query single-agent-plan requires --mode <modeling|maintained-app-edit|extract-adopt|diff-review|verification>.");
|
|
122
122
|
}
|
|
123
123
|
const ast = parsePath(context.inputPath);
|
|
124
124
|
const result = buildTaskMode(ast, selectors, context.modeId, context.fromTopogramPath);
|
|
@@ -134,7 +134,7 @@ function runSingleAgentPlan(context, selectors) {
|
|
|
134
134
|
});
|
|
135
135
|
const topogramRoot = normalizeTopogramPath(context.inputPath);
|
|
136
136
|
let importPlan = null;
|
|
137
|
-
if (context.modeId === "
|
|
137
|
+
if (context.modeId === "extract-adopt" && fs.existsSync(adoptionPlanPath(topogramRoot))) {
|
|
138
138
|
const workflowPresets = buildWorkflowPresetState({
|
|
139
139
|
workspace: topogramRoot,
|
|
140
140
|
selectors: workflowPresetSelectors(result.artifact, context.providerId, context.presetId, "single-agent-plan")
|
|
@@ -163,7 +163,7 @@ function runSingleAgentPlan(context, selectors) {
|
|
|
163
163
|
*/
|
|
164
164
|
function runResolvedWorkflowContext(context, selectors) {
|
|
165
165
|
if (!context.modeId) {
|
|
166
|
-
throw new Error("query resolved-workflow-context requires --mode <modeling|maintained-app-edit|
|
|
166
|
+
throw new Error("query resolved-workflow-context requires --mode <modeling|maintained-app-edit|extract-adopt|diff-review|verification>.");
|
|
167
167
|
}
|
|
168
168
|
const topogramRoot = normalizeTopogramPath(context.inputPath);
|
|
169
169
|
const ast = parsePath(context.inputPath);
|
|
@@ -186,7 +186,7 @@ function runResolvedWorkflowContext(context, selectors) {
|
|
|
186
186
|
maintainedBoundaryArtifact: maintainedBundleResult?.artifact?.maintained_boundary || null
|
|
187
187
|
});
|
|
188
188
|
let importPlan = null;
|
|
189
|
-
if (context.modeId === "
|
|
189
|
+
if (context.modeId === "extract-adopt" && fs.existsSync(adoptionPlanPath(topogramRoot))) {
|
|
190
190
|
const workflowPresets = buildWorkflowPresetState({
|
|
191
191
|
workspace: topogramRoot,
|
|
192
192
|
selectors: workflowPresetSelectors(taskModeResult.artifact, context.providerId, context.presetId, "resolved-workflow-context")
|
|
@@ -29,7 +29,7 @@ export function normalizeTopogramPath(inputPath) {
|
|
|
29
29
|
*/
|
|
30
30
|
export function workflowPresetSelectors(taskModeArtifact, providerId = null, presetId = null, queryFamily = null) {
|
|
31
31
|
const categories = [];
|
|
32
|
-
if (taskModeArtifact?.mode === "
|
|
32
|
+
if (taskModeArtifact?.mode === "extract-adopt") categories.push("provider_adoption");
|
|
33
33
|
if (taskModeArtifact?.mode === "maintained-app-edit") categories.push("maintained_app");
|
|
34
34
|
if ((taskModeArtifact?.verification_targets?.maintained_app_checks || []).length > 0) categories.push("maintained_boundary");
|
|
35
35
|
return {
|
|
@@ -65,7 +65,7 @@ export function generatorTargetsForWorkflowContext(options = {}) {
|
|
|
65
65
|
* @returns {boolean}
|
|
66
66
|
*/
|
|
67
67
|
export function importAdoptOnlyRequested(options = {}) {
|
|
68
|
-
return options.modeId === "
|
|
68
|
+
return options.modeId === "extract-adopt" && !(
|
|
69
69
|
options.capabilityId ||
|
|
70
70
|
options.workflowId ||
|
|
71
71
|
options.projectionId ||
|
|
@@ -267,7 +267,7 @@ export function resolveRecommendedQueryFamily(nextAction, mode) {
|
|
|
267
267
|
case "customize_workflow_preset":
|
|
268
268
|
case "refresh_workflow_preset_customization":
|
|
269
269
|
case "import_declared_workflow_preset":
|
|
270
|
-
return "
|
|
270
|
+
return "extract-plan";
|
|
271
271
|
case "review_diff_impact":
|
|
272
272
|
case "inspect_projection":
|
|
273
273
|
case "inspect_diff":
|
|
@@ -284,7 +284,7 @@ export function resolveRecommendedQueryFamily(nextAction, mode) {
|
|
|
284
284
|
default:
|
|
285
285
|
break;
|
|
286
286
|
}
|
|
287
|
-
if (mode === "
|
|
287
|
+
if (mode === "extract-adopt") return "extract-plan";
|
|
288
288
|
if (mode === "maintained-app-edit") return "maintained-boundary";
|
|
289
289
|
if (mode === "verification") return "verification-targets";
|
|
290
290
|
return "change-plan";
|