@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
|
@@ -0,0 +1,356 @@
|
|
|
1
|
+
// @ts-check
|
|
2
|
+
|
|
3
|
+
import fs from "node:fs";
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
|
|
6
|
+
import {
|
|
7
|
+
catalogEntryPackageSpec,
|
|
8
|
+
copyCatalogTopogramEntry,
|
|
9
|
+
findCatalogEntry,
|
|
10
|
+
loadCatalog,
|
|
11
|
+
TOPOGRAM_SOURCE_FILE
|
|
12
|
+
} from "../../catalog.js";
|
|
13
|
+
import { GENERATOR_POLICY_FILE } from "../../generator-policy.js";
|
|
14
|
+
import { createNewProject } from "../../new-project.js";
|
|
15
|
+
import { copyPath, ensureEmptyDirectory } from "../../catalog/files.js";
|
|
16
|
+
import { writeTopogramSourceRecord } from "../../catalog/provenance.js";
|
|
17
|
+
import { DEFAULT_TOPO_FOLDER_NAME, DEFAULT_WORKSPACE_PATH, resolvePackageWorkspace } from "../../workspace-paths.js";
|
|
18
|
+
import { formatCatalogTemplateAliasError, suggestCatalogTemplateIds } from "../catalog-alias.js";
|
|
19
|
+
import {
|
|
20
|
+
buildCatalogListPayload,
|
|
21
|
+
printCatalogList
|
|
22
|
+
} from "./catalog/list.js";
|
|
23
|
+
import { shellCommandArg } from "./catalog/shared.js";
|
|
24
|
+
import { stableStringify } from "../../format.js";
|
|
25
|
+
|
|
26
|
+
const ENGINE_ROOT = path.resolve(decodeURIComponent(new URL("../../../", import.meta.url).pathname));
|
|
27
|
+
const TEMPLATES_ROOT = path.join(ENGINE_ROOT, "templates");
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* @param {string} source
|
|
31
|
+
* @returns {boolean}
|
|
32
|
+
*/
|
|
33
|
+
function isCatalogIdCandidate(source) {
|
|
34
|
+
return Boolean(source) &&
|
|
35
|
+
!source.startsWith("@") &&
|
|
36
|
+
!source.startsWith("./") &&
|
|
37
|
+
!source.startsWith("../") &&
|
|
38
|
+
!path.isAbsolute(source) &&
|
|
39
|
+
!source.includes("/") &&
|
|
40
|
+
!source.endsWith(".tgz");
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* @param {ReturnType<typeof createNewProject>} result
|
|
45
|
+
* @param {string} cwd
|
|
46
|
+
* @returns {string}
|
|
47
|
+
*/
|
|
48
|
+
function displayProjectRootForCopy(result, cwd) {
|
|
49
|
+
const relativeProjectRoot = path.relative(cwd, result.projectRoot);
|
|
50
|
+
return !relativeProjectRoot || relativeProjectRoot.startsWith("..")
|
|
51
|
+
? result.projectRoot
|
|
52
|
+
: relativeProjectRoot;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* @param {string} targetPath
|
|
57
|
+
* @returns {void}
|
|
58
|
+
*/
|
|
59
|
+
function assertProjectTargetOutsideEngine(targetPath) {
|
|
60
|
+
const projectRoot = path.resolve(targetPath);
|
|
61
|
+
const relativeToEngine = path.relative(ENGINE_ROOT, projectRoot);
|
|
62
|
+
if (relativeToEngine === "" || (!relativeToEngine.startsWith("..") && !path.isAbsolute(relativeToEngine))) {
|
|
63
|
+
throw new Error(
|
|
64
|
+
`Refusing to copy a project inside the engine directory. Use a path outside engine, for example '../${path.basename(projectRoot)}'.`
|
|
65
|
+
);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* @param {string} source
|
|
71
|
+
* @param {string} targetPath
|
|
72
|
+
* @param {{ catalogSource?: string|null, version?: string|null }} options
|
|
73
|
+
* @returns {{ ok: boolean, action: "copy_topogram", source: string, id: string, kind: "topogram", packageSpec: string, targetPath: string, provenancePath: string, files: string[], diagnostics: any[], errors: string[] }|null}
|
|
74
|
+
*/
|
|
75
|
+
function tryCopyCatalogTopogram(source, targetPath, options) {
|
|
76
|
+
if (!isCatalogIdCandidate(source)) {
|
|
77
|
+
return null;
|
|
78
|
+
}
|
|
79
|
+
const loaded = loadCatalog(options.catalogSource || null);
|
|
80
|
+
const entry = findCatalogEntry(loaded.catalog, source, "topogram");
|
|
81
|
+
if (!entry) {
|
|
82
|
+
return null;
|
|
83
|
+
}
|
|
84
|
+
const copied = copyCatalogTopogramEntry(entry, targetPath, {
|
|
85
|
+
catalogSource: loaded.source,
|
|
86
|
+
version: options.version || null
|
|
87
|
+
});
|
|
88
|
+
return {
|
|
89
|
+
action: "copy_topogram",
|
|
90
|
+
source: loaded.source,
|
|
91
|
+
...copied,
|
|
92
|
+
diagnostics: [],
|
|
93
|
+
errors: []
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* @param {string} source
|
|
99
|
+
* @param {string} targetPath
|
|
100
|
+
* @param {{ catalogSource?: string|null, version?: string|null }} options
|
|
101
|
+
* @returns {{ templateName: string, provenance: any|null }}
|
|
102
|
+
*/
|
|
103
|
+
function resolveCopyTemplateSource(source, targetPath, options) {
|
|
104
|
+
void targetPath;
|
|
105
|
+
if (!isCatalogIdCandidate(source)) {
|
|
106
|
+
return { templateName: source, provenance: null };
|
|
107
|
+
}
|
|
108
|
+
const loaded = loadCatalog(options.catalogSource || null);
|
|
109
|
+
const entry = findCatalogEntry(loaded.catalog, source, "template");
|
|
110
|
+
if (!entry) {
|
|
111
|
+
throw new Error(formatCatalogTemplateAliasError(source, loaded.source, null, {
|
|
112
|
+
suggestions: suggestCatalogTemplateIds(loaded.catalog, source)
|
|
113
|
+
}));
|
|
114
|
+
}
|
|
115
|
+
const packageSpec = catalogEntryPackageSpec(entry, options.version || null);
|
|
116
|
+
return {
|
|
117
|
+
templateName: packageSpec,
|
|
118
|
+
provenance: {
|
|
119
|
+
id: entry.id,
|
|
120
|
+
source: loaded.source,
|
|
121
|
+
package: entry.package,
|
|
122
|
+
version: options.version || entry.defaultVersion,
|
|
123
|
+
packageSpec,
|
|
124
|
+
includesExecutableImplementation: Boolean(entry.trust?.includesExecutableImplementation)
|
|
125
|
+
}
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* @param {string} sourcePath
|
|
131
|
+
* @param {string} targetPath
|
|
132
|
+
* @returns {{ ok: boolean, action: "copy_topogram", source: string, id: string, kind: "topogram", packageSpec: string, targetPath: string, provenancePath: string, files: string[], diagnostics: any[], errors: string[] }}
|
|
133
|
+
*/
|
|
134
|
+
function copyLocalTopogramSource(sourcePath, targetPath) {
|
|
135
|
+
const packageRoot = path.resolve(sourcePath);
|
|
136
|
+
if (!fs.existsSync(packageRoot) || !fs.statSync(packageRoot).isDirectory()) {
|
|
137
|
+
throw new Error(`Copy source '${sourcePath}' was not found.`);
|
|
138
|
+
}
|
|
139
|
+
if (fs.existsSync(path.join(packageRoot, "implementation"))) {
|
|
140
|
+
throw new Error(`Topogram source '${sourcePath}' contains implementation/, which is not allowed for pure topogram copy.`);
|
|
141
|
+
}
|
|
142
|
+
const packageWorkspace = resolvePackageWorkspace(packageRoot);
|
|
143
|
+
const resolvedTarget = path.resolve(targetPath);
|
|
144
|
+
ensureEmptyDirectory(resolvedTarget);
|
|
145
|
+
/** @type {string[]} */
|
|
146
|
+
const files = [];
|
|
147
|
+
copyPath(packageWorkspace.root, path.join(resolvedTarget, DEFAULT_TOPO_FOLDER_NAME), DEFAULT_TOPO_FOLDER_NAME, files);
|
|
148
|
+
for (const fileName of ["topogram.project.json", "README.md"]) {
|
|
149
|
+
const sourceFile = path.join(packageRoot, fileName);
|
|
150
|
+
if (!fs.existsSync(sourceFile) || !fs.statSync(sourceFile).isFile()) {
|
|
151
|
+
continue;
|
|
152
|
+
}
|
|
153
|
+
if (fileName === "topogram.project.json") {
|
|
154
|
+
const projectConfig = JSON.parse(fs.readFileSync(sourceFile, "utf8"));
|
|
155
|
+
projectConfig.workspace = DEFAULT_WORKSPACE_PATH;
|
|
156
|
+
fs.writeFileSync(path.join(resolvedTarget, fileName), `${JSON.stringify(projectConfig, null, 2)}\n`, "utf8");
|
|
157
|
+
files.push(fileName);
|
|
158
|
+
} else {
|
|
159
|
+
copyPath(sourceFile, path.join(resolvedTarget, fileName), fileName, files);
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
const sourceId = path.basename(packageRoot);
|
|
163
|
+
const provenance = writeTopogramSourceRecord(resolvedTarget, {
|
|
164
|
+
catalogSource: null,
|
|
165
|
+
entry: {
|
|
166
|
+
id: sourceId,
|
|
167
|
+
package: sourcePath,
|
|
168
|
+
defaultVersion: "local"
|
|
169
|
+
},
|
|
170
|
+
packageSpec: sourcePath,
|
|
171
|
+
version: "local"
|
|
172
|
+
});
|
|
173
|
+
return {
|
|
174
|
+
ok: true,
|
|
175
|
+
action: "copy_topogram",
|
|
176
|
+
source: sourcePath,
|
|
177
|
+
id: sourceId,
|
|
178
|
+
kind: "topogram",
|
|
179
|
+
packageSpec: sourcePath,
|
|
180
|
+
targetPath: resolvedTarget,
|
|
181
|
+
provenancePath: provenance.path,
|
|
182
|
+
files: files.sort((a, b) => a.localeCompare(b)),
|
|
183
|
+
diagnostics: [],
|
|
184
|
+
errors: []
|
|
185
|
+
};
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* @param {ReturnType<typeof createNewProject>} result
|
|
190
|
+
* @returns {Record<string, any>}
|
|
191
|
+
*/
|
|
192
|
+
function templateCopyPayload(result) {
|
|
193
|
+
return {
|
|
194
|
+
ok: true,
|
|
195
|
+
action: "copy_template",
|
|
196
|
+
kind: "template",
|
|
197
|
+
projectRoot: result.projectRoot,
|
|
198
|
+
templateName: result.templateName,
|
|
199
|
+
template: result.template,
|
|
200
|
+
topogramPath: result.topogramPath,
|
|
201
|
+
appPath: result.appPath,
|
|
202
|
+
warnings: result.warnings,
|
|
203
|
+
diagnostics: [],
|
|
204
|
+
errors: []
|
|
205
|
+
};
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
/**
|
|
209
|
+
* @param {ReturnType<typeof createNewProject>} result
|
|
210
|
+
* @param {string} cwd
|
|
211
|
+
* @returns {void}
|
|
212
|
+
*/
|
|
213
|
+
export function printTemplateCopyResult(result, cwd) {
|
|
214
|
+
const template = result.template || {};
|
|
215
|
+
console.log(`Copied Topogram template to ${result.projectRoot}.`);
|
|
216
|
+
console.log(`Template: ${result.templateName}`);
|
|
217
|
+
console.log(`Source: ${template.source || "unknown"}`);
|
|
218
|
+
if (template.sourceSpec) {
|
|
219
|
+
console.log(`Source spec: ${template.sourceSpec}`);
|
|
220
|
+
}
|
|
221
|
+
if (template.catalog) {
|
|
222
|
+
console.log(`Catalog: ${template.catalog.id} from ${template.catalog.source}`);
|
|
223
|
+
console.log(`Package: ${template.catalog.packageSpec}`);
|
|
224
|
+
}
|
|
225
|
+
console.log(`Executable implementation: ${template.includesExecutableImplementation ? "yes" : "no"}`);
|
|
226
|
+
console.log("Policy: topogram.template-policy.json");
|
|
227
|
+
console.log(`Generator policy: ${GENERATOR_POLICY_FILE}`);
|
|
228
|
+
console.log("Template files: .topogram-template-files.json");
|
|
229
|
+
if (template.includesExecutableImplementation) {
|
|
230
|
+
console.log("Trust: .topogram-template-trust.json");
|
|
231
|
+
}
|
|
232
|
+
for (const warning of result.warnings) {
|
|
233
|
+
console.warn(`Warning: ${warning}`);
|
|
234
|
+
}
|
|
235
|
+
console.log("");
|
|
236
|
+
console.log("Next steps:");
|
|
237
|
+
console.log(` cd ${displayProjectRootForCopy(result, cwd)}`);
|
|
238
|
+
console.log(" npm install");
|
|
239
|
+
console.log(" npm run agent:brief");
|
|
240
|
+
console.log(" npm run doctor");
|
|
241
|
+
console.log(" npm run source:status");
|
|
242
|
+
console.log(" npm run template:explain");
|
|
243
|
+
console.log(" npm run check");
|
|
244
|
+
console.log(" npm run generator:policy:status");
|
|
245
|
+
console.log(" npm run generator:policy:check");
|
|
246
|
+
if (template.includesExecutableImplementation) {
|
|
247
|
+
console.log(" npm run template:policy:explain");
|
|
248
|
+
console.log(" npm run trust:status");
|
|
249
|
+
}
|
|
250
|
+
console.log(" npm run generate");
|
|
251
|
+
console.log(" npm run verify");
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
/**
|
|
255
|
+
* @param {any} payload
|
|
256
|
+
* @returns {void}
|
|
257
|
+
*/
|
|
258
|
+
export function printTopogramCopy(payload) {
|
|
259
|
+
console.log(`Copied topogram '${payload.id}' to ${payload.targetPath}.`);
|
|
260
|
+
console.log(`Package: ${payload.packageSpec}`);
|
|
261
|
+
console.log(`Source provenance: ${payload.provenancePath}`);
|
|
262
|
+
console.log(`Files: ${payload.files.length}`);
|
|
263
|
+
console.log(`${TOPOGRAM_SOURCE_FILE} records copy provenance only. Local edits are allowed.`);
|
|
264
|
+
console.log("");
|
|
265
|
+
console.log("Next steps:");
|
|
266
|
+
console.log(` cd ${shellCommandArg(path.relative(process.cwd(), payload.targetPath) || ".")}`);
|
|
267
|
+
console.log(" topogram source status --local");
|
|
268
|
+
console.log(" topogram check");
|
|
269
|
+
console.log(" topogram generate");
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
/**
|
|
273
|
+
* @param {{ commandArgs: Record<string, any>, catalogSource?: string|null, requestedVersion?: string|null, json?: boolean, cwd?: string }} context
|
|
274
|
+
* @returns {number}
|
|
275
|
+
*/
|
|
276
|
+
export function runCopyCommand(context) {
|
|
277
|
+
const {
|
|
278
|
+
commandArgs,
|
|
279
|
+
catalogSource = null,
|
|
280
|
+
requestedVersion = null,
|
|
281
|
+
json = false,
|
|
282
|
+
cwd = process.cwd()
|
|
283
|
+
} = context;
|
|
284
|
+
|
|
285
|
+
if (commandArgs.copyCommand === "list") {
|
|
286
|
+
const payload = buildCatalogListPayload(catalogSource || null);
|
|
287
|
+
if (json) {
|
|
288
|
+
console.log(stableStringify(payload));
|
|
289
|
+
} else {
|
|
290
|
+
printCatalogList(payload);
|
|
291
|
+
}
|
|
292
|
+
return 0;
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
const source = commandArgs.copySource;
|
|
296
|
+
const targetPath = commandArgs.inputPath;
|
|
297
|
+
if (!source || String(source).startsWith("-")) {
|
|
298
|
+
throw new Error("topogram copy requires <source>.");
|
|
299
|
+
}
|
|
300
|
+
if (!targetPath || String(targetPath).startsWith("-")) {
|
|
301
|
+
throw new Error("topogram copy requires <target>.");
|
|
302
|
+
}
|
|
303
|
+
assertProjectTargetOutsideEngine(targetPath);
|
|
304
|
+
|
|
305
|
+
let catalogTopogram;
|
|
306
|
+
try {
|
|
307
|
+
catalogTopogram = tryCopyCatalogTopogram(source, targetPath, {
|
|
308
|
+
catalogSource,
|
|
309
|
+
version: requestedVersion
|
|
310
|
+
});
|
|
311
|
+
} catch (error) {
|
|
312
|
+
if (isCatalogIdCandidate(source)) {
|
|
313
|
+
throw new Error(formatCatalogTemplateAliasError(source, catalogSource || null, error));
|
|
314
|
+
}
|
|
315
|
+
throw error;
|
|
316
|
+
}
|
|
317
|
+
if (catalogTopogram) {
|
|
318
|
+
if (json) {
|
|
319
|
+
console.log(stableStringify(catalogTopogram));
|
|
320
|
+
} else {
|
|
321
|
+
printTopogramCopy(catalogTopogram);
|
|
322
|
+
}
|
|
323
|
+
return catalogTopogram.ok ? 0 : 1;
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
if (source === "." || source.startsWith("./") || source.startsWith("../") || path.isAbsolute(source)) {
|
|
327
|
+
const localRoot = path.resolve(source);
|
|
328
|
+
if (fs.existsSync(path.join(localRoot, DEFAULT_TOPO_FOLDER_NAME)) && !fs.existsSync(path.join(localRoot, "topogram-template.json"))) {
|
|
329
|
+
const payload = copyLocalTopogramSource(source, targetPath);
|
|
330
|
+
if (json) {
|
|
331
|
+
console.log(stableStringify(payload));
|
|
332
|
+
} else {
|
|
333
|
+
printTopogramCopy(payload);
|
|
334
|
+
}
|
|
335
|
+
return payload.ok ? 0 : 1;
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
const resolvedTemplate = resolveCopyTemplateSource(source, targetPath, {
|
|
340
|
+
catalogSource,
|
|
341
|
+
version: requestedVersion
|
|
342
|
+
});
|
|
343
|
+
const result = createNewProject({
|
|
344
|
+
targetPath,
|
|
345
|
+
templateName: resolvedTemplate.templateName,
|
|
346
|
+
templateProvenance: resolvedTemplate.provenance,
|
|
347
|
+
engineRoot: ENGINE_ROOT,
|
|
348
|
+
templatesRoot: TEMPLATES_ROOT
|
|
349
|
+
});
|
|
350
|
+
if (json) {
|
|
351
|
+
console.log(stableStringify(templateCopyPayload(result)));
|
|
352
|
+
} else {
|
|
353
|
+
printTemplateCopyResult(result, cwd);
|
|
354
|
+
}
|
|
355
|
+
return 0;
|
|
356
|
+
}
|
|
@@ -37,7 +37,7 @@ export function printDoctorHelp() {
|
|
|
37
37
|
console.log(" npm install --save-dev @topogram/cli");
|
|
38
38
|
console.log(" npx topogram doctor");
|
|
39
39
|
console.log(" npx topogram template list");
|
|
40
|
-
console.log(" npx topogram
|
|
40
|
+
console.log(" npx topogram copy hello-web ./my-app");
|
|
41
41
|
console.log("");
|
|
42
42
|
console.log("Related setup commands:");
|
|
43
43
|
console.log(" topogram setup package-auth");
|
|
@@ -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)
|
|
@@ -3,46 +3,55 @@
|
|
|
3
3
|
import { TOPOGRAM_IMPORT_FILE } from "../../../import/provenance.js";
|
|
4
4
|
import { TOPOGRAM_IMPORT_ADOPTIONS_FILE } from "./paths.js";
|
|
5
5
|
|
|
6
|
-
export function
|
|
7
|
-
console.log("Usage: topogram
|
|
8
|
-
console.log(" or: topogram
|
|
9
|
-
console.log(" or: topogram
|
|
10
|
-
console.log(" or: topogram
|
|
11
|
-
console.log(" or: topogram
|
|
12
|
-
console.log(" or: topogram
|
|
13
|
-
console.log(" or: topogram
|
|
14
|
-
console.log(" or: topogram import status [path] [--json]");
|
|
15
|
-
console.log(" or: topogram import history [path] [--verify] [--json]");
|
|
6
|
+
export function printExtractHelp() {
|
|
7
|
+
console.log("Usage: topogram extract <app-path> --out <target> [--from <track[,track]>] [--json]");
|
|
8
|
+
console.log(" or: topogram extract refresh [path] [--from <app-path>] [--dry-run] [--json]");
|
|
9
|
+
console.log(" or: topogram extract diff [path] [--json]");
|
|
10
|
+
console.log(" or: topogram extract check [path] [--json]");
|
|
11
|
+
console.log(" or: topogram extract plan [path] [--json]");
|
|
12
|
+
console.log(" or: topogram extract status [path] [--json]");
|
|
13
|
+
console.log(" or: topogram extract history [path] [--verify] [--json]");
|
|
16
14
|
console.log("");
|
|
17
|
-
console.log("
|
|
15
|
+
console.log("Extracts reviewable Topogram candidates from a brownfield app without modifying the app.");
|
|
18
16
|
console.log("");
|
|
19
17
|
console.log("Behavior:");
|
|
20
|
-
console.log(" - writes raw
|
|
18
|
+
console.log(" - writes raw extraction candidates under topo/candidates/app");
|
|
21
19
|
console.log(" - writes reconcile proposal bundles under topo/candidates/reconcile");
|
|
22
20
|
console.log(" - writes topogram.project.json with maintained ownership and no generated stack binding");
|
|
23
|
-
console.log(` - writes ${TOPOGRAM_IMPORT_FILE} with source file hashes from
|
|
24
|
-
console.log(" -
|
|
21
|
+
console.log(` - writes ${TOPOGRAM_IMPORT_FILE} with source file hashes from extraction time`);
|
|
22
|
+
console.log(" - extracted Topogram artifacts are project-owned after creation");
|
|
25
23
|
console.log(" - refresh rewrites only candidate/reconcile artifacts and source provenance");
|
|
26
|
-
console.log(" - adoption previews never write canonical Topogram files unless --write is passed");
|
|
27
|
-
console.log(" - adoption writes refuse dirty brownfield source provenance unless --force is passed");
|
|
28
|
-
console.log(` - adoption writes append audit receipts to ${TOPOGRAM_IMPORT_ADOPTIONS_FILE}`);
|
|
29
|
-
console.log(" - forced adoption writes require --reason <text>");
|
|
30
24
|
console.log("");
|
|
31
25
|
console.log("Examples:");
|
|
32
|
-
console.log(" topogram
|
|
33
|
-
console.log(" topogram
|
|
34
|
-
console.log(" topogram
|
|
35
|
-
console.log(" topogram
|
|
36
|
-
console.log(" topogram
|
|
37
|
-
console.log(" topogram
|
|
38
|
-
console.log(" topogram
|
|
39
|
-
console.log(" topogram
|
|
40
|
-
console.log(" topogram
|
|
41
|
-
console.log(" topogram
|
|
42
|
-
console.log(" topogram
|
|
43
|
-
console.log(" topogram
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
console.log("
|
|
26
|
+
console.log(" topogram extract ./existing-app --out ./extracted-topogram");
|
|
27
|
+
console.log(" topogram extract ./existing-app --out ./extracted-topogram --from db,api,ui");
|
|
28
|
+
console.log(" topogram extract ./existing-cli --out ./extracted-topogram --from cli");
|
|
29
|
+
console.log(" topogram extract diff ./extracted-topogram");
|
|
30
|
+
console.log(" topogram extract refresh ./extracted-topogram --from ./existing-app --dry-run");
|
|
31
|
+
console.log(" topogram extract refresh ./extracted-topogram --from ./existing-app");
|
|
32
|
+
console.log(" topogram extract check ./extracted-topogram");
|
|
33
|
+
console.log(" topogram extract plan ./extracted-topogram");
|
|
34
|
+
console.log(" topogram extract status ./extracted-topogram");
|
|
35
|
+
console.log(" topogram extract history ./extracted-topogram");
|
|
36
|
+
console.log(" topogram extract history ./extracted-topogram --verify");
|
|
37
|
+
console.log(" topogram extract check --json");
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export function printAdoptHelp() {
|
|
41
|
+
console.log("Usage: topogram adopt --list [path] [--json]");
|
|
42
|
+
console.log(" or: topogram adopt <selector> [path] [--dry-run|--write] [--force --reason <text>] [--json]");
|
|
43
|
+
console.log("");
|
|
44
|
+
console.log("Promotes reviewed extraction candidates into canonical topo/ files.");
|
|
45
|
+
console.log("");
|
|
46
|
+
console.log("Behavior:");
|
|
47
|
+
console.log(" - previews never write canonical Topogram files unless --write is passed");
|
|
48
|
+
console.log(" - writes refuse dirty brownfield source provenance unless --force is passed");
|
|
49
|
+
console.log(` - writes append audit receipts to ${TOPOGRAM_IMPORT_ADOPTIONS_FILE}`);
|
|
50
|
+
console.log(" - forced writes require --reason <text>");
|
|
51
|
+
console.log("");
|
|
52
|
+
console.log("Examples:");
|
|
53
|
+
console.log(" topogram adopt --list ./extracted-topogram");
|
|
54
|
+
console.log(" topogram adopt bundle:task ./extracted-topogram --dry-run");
|
|
55
|
+
console.log(" topogram adopt bundle:task ./extracted-topogram --write");
|
|
56
|
+
console.log(" topogram adopt bundle:task ./extracted-topogram --write --force --reason \"Reviewed source drift\"");
|
|
48
57
|
}
|
|
@@ -9,7 +9,7 @@ import { TOPOGRAM_IMPORT_FILE } from "../../../import/provenance.js";
|
|
|
9
9
|
import { shellCommandArg } from "../catalog.js";
|
|
10
10
|
import { resolveTopoRoot, resolveWorkspaceContext } from "../../../workspace-paths.js";
|
|
11
11
|
|
|
12
|
-
export const TOPOGRAM_IMPORT_ADOPTIONS_FILE = ".topogram-
|
|
12
|
+
export const TOPOGRAM_IMPORT_ADOPTIONS_FILE = ".topogram-adoptions.jsonl";
|
|
13
13
|
|
|
14
14
|
/**
|
|
15
15
|
* @typedef {Record<string, any>} AnyRecord
|
|
@@ -219,7 +219,7 @@ export function readImportAdoptionReceipts(projectRoot) {
|
|
|
219
219
|
try {
|
|
220
220
|
return JSON.parse(line);
|
|
221
221
|
} catch (error) {
|
|
222
|
-
throw new Error(`Invalid
|
|
222
|
+
throw new Error(`Invalid adoption receipt JSON at ${historyPath}:${index + 1}.`);
|
|
223
223
|
}
|
|
224
224
|
});
|
|
225
225
|
}
|
|
@@ -265,5 +265,5 @@ export function importProjectCommandPath(projectRoot) {
|
|
|
265
265
|
* @returns {string}
|
|
266
266
|
*/
|
|
267
267
|
export function importAdoptCommand(projectRoot, selector, write = false) {
|
|
268
|
-
return `topogram
|
|
268
|
+
return `topogram adopt ${selector} ${importProjectCommandPath(projectRoot)} ${write ? "--write" : "--dry-run"}`;
|
|
269
269
|
}
|