@topogram/cli 0.3.65 → 0.3.67
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/package.json +1 -1
- package/src/adoption/plan/index.js +21 -8
- package/src/adoption/reporting.js +1 -1
- package/src/agent-brief.js +7 -21
- package/src/agent-ops/query-builders/change-risk/review-packets.js +2 -2
- 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-presets-core.js +3 -2
- package/src/archive/jsonl.js +2 -2
- package/src/archive/resolver-bridge.js +1 -1
- package/src/archive/unarchive.js +2 -1
- package/src/catalog/copy.js +11 -6
- package/src/catalog/provenance.js +2 -1
- package/src/cli/command-parsers/project.js +3 -0
- package/src/cli/command-parsers/shared.js +1 -1
- package/src/cli/commands/agent.js +2 -2
- package/src/cli/commands/check.js +3 -3
- package/src/cli/commands/doctor.js +2 -9
- package/src/cli/commands/generator-policy/runner.js +1 -1
- package/src/cli/commands/import/help.js +2 -2
- package/src/cli/commands/import/paths.js +3 -11
- package/src/cli/commands/import/plan.js +9 -1
- package/src/cli/commands/import/refresh.js +7 -6
- package/src/cli/commands/import/workspace.js +8 -5
- package/src/cli/commands/migrate.js +153 -0
- package/src/cli/commands/query/definitions.js +10 -10
- package/src/cli/commands/query/workspace.js +2 -6
- package/src/cli/commands/source.js +3 -12
- package/src/cli/commands/template/check.js +6 -5
- package/src/cli/commands/template-runner.js +6 -6
- package/src/cli/commands/trust.js +1 -1
- package/src/cli/commands/workflow.js +6 -1
- package/src/cli/dispatcher.js +6 -1
- package/src/cli/help.js +15 -14
- package/src/cli/migration-guidance.js +1 -1
- package/src/cli/output-safety.js +2 -1
- package/src/cli/path-normalization.js +3 -13
- package/src/generator/context/domain-page.js +1 -1
- package/src/generator/context/shared/maintained-boundary.js +2 -2
- package/src/generator/context/shared/metrics.js +2 -2
- package/src/generator/context/task-mode.js +2 -2
- package/src/generator/sdlc/doc-page.js +1 -1
- package/src/generator/surfaces/databases/lifecycle-shared.js +1 -1
- package/src/generator/surfaces/native/swiftui-templates/README.generated.md +1 -1
- package/src/import/core/context.js +5 -7
- package/src/import/core/runner/candidates.js +123 -3
- package/src/import/core/runner/reports.js +4 -3
- package/src/import/core/runner/ui-drafts.js +58 -2
- package/src/new-project/constants.js +1 -1
- package/src/new-project/create.js +9 -2
- package/src/new-project/project-files.js +16 -13
- package/src/new-project/template-resolution.js +6 -4
- package/src/new-project/template-snapshots.js +38 -8
- package/src/new-project/template-updates.js +1 -1
- package/src/project-config/index.js +27 -0
- package/src/sdlc/adopt.js +6 -5
- package/src/sdlc/paths.js +3 -5
- package/src/sdlc/scaffold.js +2 -1
- package/src/workflows/reconcile/adoption-plan/build.js +7 -3
- package/src/workflows/reconcile/adoption-plan/outputs.js +12 -2
- package/src/workflows/reconcile/adoption-plan/paths.js +1 -1
- package/src/workflows/reconcile/candidate-model.js +18 -2
- package/src/workflows/reconcile/impacts/adoption-plan.js +6 -2
- package/src/workflows/reconcile/impacts/indexes.js +5 -1
- package/src/workflows/reconcile/renderers.js +41 -6
- package/src/workflows/shared.js +5 -11
- package/src/workspace-paths.js +328 -0
package/package.json
CHANGED
|
@@ -360,7 +360,7 @@ export function selectorMatchesItem(selector, item) {
|
|
|
360
360
|
if (selector === "journeys") return item.track === "docs" && String(item.canonical_rel_path || "").startsWith("docs/journeys/");
|
|
361
361
|
if (selector === "workflows") return item.track === "workflows" || item.kind === "decision";
|
|
362
362
|
if (selector === "verification") return item.kind === "verification";
|
|
363
|
-
if (selector === "ui") return item.track === "ui";
|
|
363
|
+
if (selector === "ui") return item.track === "ui" || item.kind === "widget" || item.source_kind === "ui_widget_event";
|
|
364
364
|
if (selector.startsWith("bundle:")) return item.bundle === selector.slice("bundle:".length);
|
|
365
365
|
return false;
|
|
366
366
|
}
|
|
@@ -467,14 +467,21 @@ export function applyAdoptionSelector(adoptionPlan, selector, writeMode) {
|
|
|
467
467
|
|
|
468
468
|
const expandSelectedItemsForDependentShapes = (keys) => {
|
|
469
469
|
const expanded = new Set(keys);
|
|
470
|
-
const
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
470
|
+
const selectedItemsForExpansion = [...expanded]
|
|
471
|
+
.map((key) => updatedItems.find((item) => adoptionItemKey(item) === key))
|
|
472
|
+
.filter(Boolean);
|
|
473
|
+
const selectedCapabilityBundles = new Set(selectedItemsForExpansion
|
|
474
|
+
.filter((item) => item?.kind === "capability")
|
|
475
|
+
.map((item) => item.bundle));
|
|
476
|
+
const selectedWidgetShapeIds = new Set(selectedItemsForExpansion
|
|
477
|
+
.filter((item) => item?.kind === "widget")
|
|
478
|
+
.flatMap((item) => item.related_shapes || []));
|
|
476
479
|
for (const item of updatedItems) {
|
|
477
|
-
if (
|
|
480
|
+
if (
|
|
481
|
+
item.kind === "shape" &&
|
|
482
|
+
item.status !== "skipped" &&
|
|
483
|
+
(selectedCapabilityBundles.has(item.bundle) || selectedWidgetShapeIds.has(item.item))
|
|
484
|
+
) {
|
|
478
485
|
expanded.add(adoptionItemKey(item));
|
|
479
486
|
}
|
|
480
487
|
}
|
|
@@ -640,6 +647,7 @@ export function buildAgentAdoptionPlan(adoptionPlan, maintainedBoundaryArtifact
|
|
|
640
647
|
requirements: {
|
|
641
648
|
related_docs: [...new Set(item.related_docs || [])].sort(),
|
|
642
649
|
related_capabilities: [...new Set(item.related_capabilities || [])].sort(),
|
|
650
|
+
related_shapes: [...new Set(item.related_shapes || [])].sort(),
|
|
643
651
|
related_rules: [...new Set(item.related_rules || [])].sort(),
|
|
644
652
|
related_workflows: [...new Set(item.related_workflows || [])].sort()
|
|
645
653
|
},
|
|
@@ -653,8 +661,12 @@ export function buildAgentAdoptionPlan(adoptionPlan, maintainedBoundaryArtifact
|
|
|
653
661
|
item: item.item,
|
|
654
662
|
kind: item.kind,
|
|
655
663
|
track: item.track || null,
|
|
664
|
+
source_kind: item.source_kind || null,
|
|
665
|
+
widget_id: item.widget_id || null,
|
|
666
|
+
event_name: item.event_name || null,
|
|
656
667
|
source_path: item.source_path || null,
|
|
657
668
|
canonical_rel_path: item.canonical_rel_path || null,
|
|
669
|
+
related_shapes: [...new Set(item.related_shapes || [])].sort(),
|
|
658
670
|
review_boundary: reviewBoundaryForImportProposal(item),
|
|
659
671
|
current_state: currentState,
|
|
660
672
|
recommended_state: inferRecommendedAdoptionState(item),
|
|
@@ -670,6 +682,7 @@ export function buildAgentAdoptionPlan(adoptionPlan, maintainedBoundaryArtifact
|
|
|
670
682
|
requirements: {
|
|
671
683
|
related_docs: [...new Set(item.related_docs || [])].sort(),
|
|
672
684
|
related_capabilities: [...new Set(item.related_capabilities || [])].sort(),
|
|
685
|
+
related_shapes: [...new Set(item.related_shapes || [])].sort(),
|
|
673
686
|
related_rules: [...new Set(item.related_rules || [])].sort(),
|
|
674
687
|
related_workflows: [...new Set(item.related_workflows || [])].sort(),
|
|
675
688
|
blocking_dependencies: [...(item.blocking_dependencies || [])]
|
|
@@ -347,7 +347,7 @@ export function buildPromotedCanonicalItems(planItems, selectedItems, writtenCan
|
|
|
347
347
|
track: item.track || null,
|
|
348
348
|
source_path: item.source_path || null,
|
|
349
349
|
canonical_rel_path: normalizeReportPath(item.canonical_rel_path),
|
|
350
|
-
canonical_path: item.canonical_path || `
|
|
350
|
+
canonical_path: item.canonical_path || `topo/${normalizeReportPath(item.canonical_rel_path)}`,
|
|
351
351
|
suggested_action: item.suggested_action || null,
|
|
352
352
|
change_type: updatedSet.has(normalizeReportPath(item.canonical_rel_path)) ? "update" : "create"
|
|
353
353
|
}))
|
package/src/agent-brief.js
CHANGED
|
@@ -20,6 +20,7 @@ import {
|
|
|
20
20
|
getTemplateTrustStatus,
|
|
21
21
|
TEMPLATE_TRUST_FILE
|
|
22
22
|
} from "./template-trust.js";
|
|
23
|
+
import { DEFAULT_TOPO_FOLDER_NAME, resolveTopoRoot, resolveWorkspaceContext } from "./workspace-paths.js";
|
|
23
24
|
|
|
24
25
|
/**
|
|
25
26
|
* @typedef {{ path: string, reason: string, required: boolean, exists: boolean }} AgentBriefReadItem
|
|
@@ -36,12 +37,7 @@ import {
|
|
|
36
37
|
* @returns {string}
|
|
37
38
|
*/
|
|
38
39
|
export function normalizeAgentTopogramPath(inputPath) {
|
|
39
|
-
|
|
40
|
-
if (path.basename(resolved) === "topogram") {
|
|
41
|
-
return resolved;
|
|
42
|
-
}
|
|
43
|
-
const nested = path.join(resolved, "topogram");
|
|
44
|
-
return fs.existsSync(nested) && fs.statSync(nested).isDirectory() ? nested : resolved;
|
|
40
|
+
return resolveTopoRoot(inputPath || ".");
|
|
45
41
|
}
|
|
46
42
|
|
|
47
43
|
/**
|
|
@@ -49,17 +45,7 @@ export function normalizeAgentTopogramPath(inputPath) {
|
|
|
49
45
|
* @returns {string}
|
|
50
46
|
*/
|
|
51
47
|
function normalizeProjectRoot(inputPath) {
|
|
52
|
-
|
|
53
|
-
if (path.basename(resolved) === "topogram") {
|
|
54
|
-
return path.dirname(resolved);
|
|
55
|
-
}
|
|
56
|
-
if (fs.existsSync(path.join(resolved, "topogram.project.json"))) {
|
|
57
|
-
return resolved;
|
|
58
|
-
}
|
|
59
|
-
if (fs.existsSync(path.join(resolved, "topogram"))) {
|
|
60
|
-
return resolved;
|
|
61
|
-
}
|
|
62
|
-
return path.dirname(normalizeAgentTopogramPath(inputPath));
|
|
48
|
+
return resolveWorkspaceContext(inputPath || ".").projectRoot;
|
|
63
49
|
}
|
|
64
50
|
|
|
65
51
|
/**
|
|
@@ -325,7 +311,7 @@ export function buildAgentBrief(inputPath, workspaceAst) {
|
|
|
325
311
|
const generatorDiagnostics = generatorPolicyDiagnosticsForBindings(generatorPolicyInfo, generatorBindings, "agent-brief");
|
|
326
312
|
const importSummary = readImportSummary(configDir);
|
|
327
313
|
|
|
328
|
-
const topogramReadPath = path.resolve(topogramRoot) === path.resolve(projectRoot) ? "." :
|
|
314
|
+
const topogramReadPath = path.resolve(topogramRoot) === path.resolve(projectRoot) ? "." : `${DEFAULT_TOPO_FOLDER_NAME}/`;
|
|
329
315
|
const readOrder = [
|
|
330
316
|
readItem(projectRoot, "AGENTS.md", "Human-readable first-run guidance generated with this project.", false),
|
|
331
317
|
readItem(projectRoot, "README.md", "Project workflow and template provenance summary.", true),
|
|
@@ -394,7 +380,7 @@ export function buildAgentBrief(inputPath, workspaceAst) {
|
|
|
394
380
|
first_commands: firstCommands,
|
|
395
381
|
edit_boundaries: {
|
|
396
382
|
safe_paths: [
|
|
397
|
-
|
|
383
|
+
`${DEFAULT_TOPO_FOLDER_NAME}/**`,
|
|
398
384
|
"topogram.project.json",
|
|
399
385
|
"topogram.template-policy.json",
|
|
400
386
|
GENERATOR_POLICY_FILE,
|
|
@@ -405,8 +391,8 @@ export function buildAgentBrief(inputPath, workspaceAst) {
|
|
|
405
391
|
},
|
|
406
392
|
workflows: buildWorkflows(config, Boolean(importSummary)),
|
|
407
393
|
file_organization: {
|
|
408
|
-
small: [
|
|
409
|
-
large: [
|
|
394
|
+
small: [`${DEFAULT_TOPO_FOLDER_NAME}/actors`, `${DEFAULT_TOPO_FOLDER_NAME}/entities`, `${DEFAULT_TOPO_FOLDER_NAME}/shapes`, `${DEFAULT_TOPO_FOLDER_NAME}/capabilities`, `${DEFAULT_TOPO_FOLDER_NAME}/widgets`, `${DEFAULT_TOPO_FOLDER_NAME}/projections`, `${DEFAULT_TOPO_FOLDER_NAME}/verifications`],
|
|
395
|
+
large: [`${DEFAULT_TOPO_FOLDER_NAME}/domains/<domain>`, `${DEFAULT_TOPO_FOLDER_NAME}/shared`, `${DEFAULT_TOPO_FOLDER_NAME}/domains/<domain>/widgets`, `${DEFAULT_TOPO_FOLDER_NAME}/domains/<domain>/projections`],
|
|
410
396
|
parserRule: "Folder layout is for humans and agents; Topogram flattens statements into one graph."
|
|
411
397
|
},
|
|
412
398
|
topology: {
|
|
@@ -21,7 +21,7 @@ export function buildCanonicalWritesPayloadForImportPlan(proposalSurfaces = [])
|
|
|
21
21
|
current_state: surface.current_state,
|
|
22
22
|
recommended_state: surface.recommended_state,
|
|
23
23
|
canonical_rel_path: surface.canonical_rel_path,
|
|
24
|
-
canonical_path: `
|
|
24
|
+
canonical_path: `topo/${surface.canonical_rel_path}`
|
|
25
25
|
}))
|
|
26
26
|
};
|
|
27
27
|
}
|
|
@@ -55,7 +55,7 @@ export function buildReviewPacketPayloadForImportPlan({ importPlan, risk }) {
|
|
|
55
55
|
.map((surface) => ({
|
|
56
56
|
id: surface.id,
|
|
57
57
|
canonical_rel_path: surface.canonical_rel_path,
|
|
58
|
-
canonical_path: `
|
|
58
|
+
canonical_path: `topo/${surface.canonical_rel_path}`
|
|
59
59
|
})),
|
|
60
60
|
review_groups: importPlan.review_groups || [],
|
|
61
61
|
write_scope: importPlan.write_scope || null,
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export function canonicalWriteCandidatesFromWriteScope(writeScope) {
|
|
2
|
-
return (writeScope?.safe_to_edit || []).filter((entry) => entry === "
|
|
2
|
+
return (writeScope?.safe_to_edit || []).filter((entry) => entry === "topo/**" || String(entry).startsWith("topo/"));
|
|
3
3
|
}
|
|
4
4
|
|
|
5
5
|
export function summarizeDiffArtifact(diffArtifact) {
|
|
@@ -91,7 +91,7 @@ export function componentBehaviorQueryCommand(target = {}) {
|
|
|
91
91
|
if (target.target !== "widget-behavior-report") {
|
|
92
92
|
return null;
|
|
93
93
|
}
|
|
94
|
-
const parts = ["topogram", "query", "widget-behavior", "./
|
|
94
|
+
const parts = ["topogram", "query", "widget-behavior", "./topo"];
|
|
95
95
|
if (target.projection_id) {
|
|
96
96
|
parts.push("--projection", target.projection_id);
|
|
97
97
|
}
|
|
@@ -28,7 +28,7 @@ export function canonicalTargetsForProposalSurfaces(proposalSurfaces = []) {
|
|
|
28
28
|
return stableSortedStrings(
|
|
29
29
|
proposalSurfaces
|
|
30
30
|
.filter((surface) => surface.canonical_rel_path)
|
|
31
|
-
.map((surface) => `
|
|
31
|
+
.map((surface) => `topo/${surface.canonical_rel_path}`)
|
|
32
32
|
);
|
|
33
33
|
}
|
|
34
34
|
|
|
@@ -8,6 +8,7 @@ import {
|
|
|
8
8
|
stableOrderedUnion,
|
|
9
9
|
stableSortedStrings
|
|
10
10
|
} from "./common.js";
|
|
11
|
+
import { DEFAULT_TOPO_FOLDER_NAME, resolveTopoRoot } from "../../workspace-paths.js";
|
|
11
12
|
export function workflowPresetReviewClass(preset) {
|
|
12
13
|
const categories = stableSortedStrings([
|
|
13
14
|
...(preset?.review_policy?.escalate_categories || []),
|
|
@@ -178,12 +179,12 @@ export function loadWorkflowPresetArtifacts(workspaceRoot) {
|
|
|
178
179
|
if (!workspaceRoot) {
|
|
179
180
|
return { provider_presets: [], team_presets: [], provider_manifests: [] };
|
|
180
181
|
}
|
|
181
|
-
const topogramRoot =
|
|
182
|
+
const topogramRoot = resolveTopoRoot(workspaceRoot);
|
|
182
183
|
const providerDir = path.join(topogramRoot, "candidates", "providers", "workflow-presets");
|
|
183
184
|
const providerManifestDir = path.join(topogramRoot, "candidates", "providers", "manifests");
|
|
184
185
|
const teamDirs = [
|
|
185
186
|
path.join(topogramRoot, "workflow-presets"),
|
|
186
|
-
path.join(topogramRoot,
|
|
187
|
+
path.join(topogramRoot, DEFAULT_TOPO_FOLDER_NAME, "workflow-presets")
|
|
187
188
|
];
|
|
188
189
|
|
|
189
190
|
const providerPresets = readJsonArtifactsFromDir(providerDir)
|
package/src/archive/jsonl.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// Year-bucketed JSONL archive I/O.
|
|
2
2
|
//
|
|
3
|
-
// File layout: `<project-
|
|
4
|
-
// or `<
|
|
3
|
+
// File layout: `<project-root>/topo/_archive/{kind}s-{year}.jsonl`
|
|
4
|
+
// or `<workspace-root>/_archive/{kind}s-{year}.jsonl`
|
|
5
5
|
// (e.g. `tasks-2026.jsonl`, `bugs-2026.jsonl`).
|
|
6
6
|
//
|
|
7
7
|
// Each line is a self-contained archived statement. The format is JSONL so
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// Bridge between archived JSONL entries and the live resolver graph.
|
|
2
2
|
//
|
|
3
3
|
// At workspace load time the resolver bridge:
|
|
4
|
-
// 1. Walks `
|
|
4
|
+
// 1. Walks the workspace `_archive/*.jsonl`
|
|
5
5
|
// 2. Builds a flat list of frozen entries (each with `archived: true`)
|
|
6
6
|
// 3. Returns `{ entries, byId }` so the caller can merge them into the
|
|
7
7
|
// registry / graph
|
package/src/archive/unarchive.js
CHANGED
|
@@ -19,6 +19,7 @@ import {
|
|
|
19
19
|
parseArchiveFile,
|
|
20
20
|
rewriteArchiveFile
|
|
21
21
|
} from "./jsonl.js";
|
|
22
|
+
import { resolveTopoRoot } from "../workspace-paths.js";
|
|
22
23
|
|
|
23
24
|
const REOPEN_STATUSES = {
|
|
24
25
|
bug: "open",
|
|
@@ -83,7 +84,7 @@ export function unarchive(workspaceRoot, id, options = {}) {
|
|
|
83
84
|
|
|
84
85
|
const { file, entries, entry } = found;
|
|
85
86
|
const reopenStatus = options.status || REOPEN_STATUSES[entry.kind] || "draft";
|
|
86
|
-
const targetDir = options.targetDir || path.join(workspaceRoot,
|
|
87
|
+
const targetDir = options.targetDir || path.join(resolveTopoRoot(workspaceRoot), `${entry.kind}s`);
|
|
87
88
|
if (!existsSync(targetDir)) mkdirSync(targetDir, { recursive: true });
|
|
88
89
|
const targetFile = path.join(targetDir, `${entry.id}.tg`);
|
|
89
90
|
|
package/src/catalog/copy.js
CHANGED
|
@@ -4,6 +4,7 @@ import fs from "node:fs";
|
|
|
4
4
|
import path from "node:path";
|
|
5
5
|
|
|
6
6
|
import { installPackageSpec } from "../new-project.js";
|
|
7
|
+
import { DEFAULT_TOPO_FOLDER_NAME, DEFAULT_WORKSPACE_PATH, resolvePackageWorkspace } from "../workspace-paths.js";
|
|
7
8
|
import { catalogEntryPackageSpec } from "./entries.js";
|
|
8
9
|
import { copyPath, ensureEmptyDirectory } from "./files.js";
|
|
9
10
|
import { writeTopogramSourceRecord } from "./provenance.js";
|
|
@@ -26,20 +27,24 @@ export function copyCatalogTopogramEntry(entry, targetPath, options = {}) {
|
|
|
26
27
|
`Catalog topogram entry '${entry.id}' package '${packageSpec}' contains implementation/, which is not allowed for v1 topogram entries.`
|
|
27
28
|
);
|
|
28
29
|
}
|
|
29
|
-
const
|
|
30
|
-
if (!fs.existsSync(topogramRoot) || !fs.statSync(topogramRoot).isDirectory()) {
|
|
31
|
-
throw new Error(`Catalog topogram entry '${entry.id}' package '${packageSpec}' is missing topogram/.`);
|
|
32
|
-
}
|
|
30
|
+
const packageWorkspace = resolvePackageWorkspace(packageRoot);
|
|
33
31
|
|
|
34
32
|
const resolvedTarget = path.resolve(targetPath);
|
|
35
33
|
ensureEmptyDirectory(resolvedTarget);
|
|
36
34
|
/** @type {string[]} */
|
|
37
35
|
const files = [];
|
|
38
|
-
copyPath(
|
|
36
|
+
copyPath(packageWorkspace.root, path.join(resolvedTarget, DEFAULT_TOPO_FOLDER_NAME), DEFAULT_TOPO_FOLDER_NAME, files);
|
|
39
37
|
for (const fileName of ["topogram.project.json", "README.md"]) {
|
|
40
38
|
const sourcePath = path.join(packageRoot, fileName);
|
|
41
39
|
if (fs.existsSync(sourcePath) && fs.statSync(sourcePath).isFile()) {
|
|
42
|
-
|
|
40
|
+
if (fileName === "topogram.project.json") {
|
|
41
|
+
const projectConfig = JSON.parse(fs.readFileSync(sourcePath, "utf8"));
|
|
42
|
+
projectConfig.workspace = DEFAULT_WORKSPACE_PATH;
|
|
43
|
+
fs.writeFileSync(path.join(resolvedTarget, fileName), `${JSON.stringify(projectConfig, null, 2)}\n`, "utf8");
|
|
44
|
+
files.push(fileName);
|
|
45
|
+
} else {
|
|
46
|
+
copyPath(sourcePath, path.join(resolvedTarget, fileName), fileName, files);
|
|
47
|
+
}
|
|
43
48
|
}
|
|
44
49
|
}
|
|
45
50
|
const provenance = writeTopogramSourceRecord(resolvedTarget, {
|
|
@@ -5,6 +5,7 @@ import path from "node:path";
|
|
|
5
5
|
|
|
6
6
|
import { TOPOGRAM_SOURCE_FILE } from "./constants.js";
|
|
7
7
|
import { collectFiles, fileHash } from "./files.js";
|
|
8
|
+
import { DEFAULT_TOPO_FOLDER_NAME } from "../workspace-paths.js";
|
|
8
9
|
|
|
9
10
|
/**
|
|
10
11
|
* @param {string} projectRoot
|
|
@@ -107,7 +108,7 @@ export function buildTopogramSourceStatus(projectRoot) {
|
|
|
107
108
|
function collectSourceFileRecords(projectRoot) {
|
|
108
109
|
/** @type {string[]} */
|
|
109
110
|
const files = [];
|
|
110
|
-
for (const sourceRoot of [
|
|
111
|
+
for (const sourceRoot of [DEFAULT_TOPO_FOLDER_NAME, "topogram.project.json", "README.md"]) {
|
|
111
112
|
const sourcePath = path.join(projectRoot, sourceRoot);
|
|
112
113
|
if (fs.existsSync(sourcePath)) {
|
|
113
114
|
collectFiles(sourcePath, sourceRoot, files);
|
|
@@ -43,5 +43,8 @@ export function parseProjectCommandArgs(args) {
|
|
|
43
43
|
if (args[0] === "package" && args[1] === "update-cli") {
|
|
44
44
|
return { packageCommand: "update-cli", inputPath: args.includes("--latest") ? "latest" : args[2] };
|
|
45
45
|
}
|
|
46
|
+
if (args[0] === "migrate" && args[1] === "workspace-folder") {
|
|
47
|
+
return { migrateCommand: "workspace-folder", inputPath: commandPath(args, 2, ".") };
|
|
48
|
+
}
|
|
46
49
|
return null;
|
|
47
50
|
}
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
* @param {string} [fallback]
|
|
13
13
|
* @returns {string}
|
|
14
14
|
*/
|
|
15
|
-
export function commandPath(args, index, fallback = "./
|
|
15
|
+
export function commandPath(args, index, fallback = "./topo") {
|
|
16
16
|
const value = args[index];
|
|
17
17
|
return value && !value.startsWith("-") ? value : fallback;
|
|
18
18
|
}
|
|
@@ -14,12 +14,12 @@ export function printAgentHelp() {
|
|
|
14
14
|
console.log("");
|
|
15
15
|
console.log("Prints read-only first-run guidance for humans and agents working in a Topogram project.");
|
|
16
16
|
console.log("");
|
|
17
|
-
console.log("Defaults: path is ./
|
|
17
|
+
console.log("Defaults: path is ./topo. The command validates the Topogram and project config, but does not write files, generate apps, load generator adapters, or execute template implementation.");
|
|
18
18
|
console.log("");
|
|
19
19
|
console.log("Examples:");
|
|
20
20
|
console.log(" topogram agent brief");
|
|
21
21
|
console.log(" topogram agent brief --json");
|
|
22
|
-
console.log(" topogram agent brief ./
|
|
22
|
+
console.log(" topogram agent brief ./topo --json");
|
|
23
23
|
}
|
|
24
24
|
|
|
25
25
|
/**
|
|
@@ -30,12 +30,12 @@ export function printCheckHelp() {
|
|
|
30
30
|
console.log("");
|
|
31
31
|
console.log("Validates Topogram files, project configuration, topology, generator compatibility, generator policy, output ownership, and template policy.");
|
|
32
32
|
console.log("");
|
|
33
|
-
console.log("Defaults: path is ./
|
|
33
|
+
console.log("Defaults: path is ./topo.");
|
|
34
34
|
console.log("");
|
|
35
35
|
console.log("Examples:");
|
|
36
36
|
console.log(" topogram check");
|
|
37
37
|
console.log(" topogram check --json");
|
|
38
|
-
console.log(" topogram check ./
|
|
38
|
+
console.log(" topogram check ./topo");
|
|
39
39
|
}
|
|
40
40
|
|
|
41
41
|
/**
|
|
@@ -234,7 +234,7 @@ export function combineProjectValidationResults(...results) {
|
|
|
234
234
|
* @returns {Promise<number>}
|
|
235
235
|
*/
|
|
236
236
|
export async function runCheckCommand(inputPath, options = {}) {
|
|
237
|
-
const topogramPath = inputPath || "./
|
|
237
|
+
const topogramPath = inputPath || "./topo";
|
|
238
238
|
const ast = parsePath(topogramPath);
|
|
239
239
|
const resolved = resolveWorkspace(ast);
|
|
240
240
|
const implementation = await loadImplementationProvider(topogramPath).catch(() => null);
|
|
@@ -1,8 +1,5 @@
|
|
|
1
1
|
// @ts-check
|
|
2
2
|
|
|
3
|
-
import fs from "node:fs";
|
|
4
|
-
import path from "node:path";
|
|
5
|
-
|
|
6
3
|
import {
|
|
7
4
|
catalogSourceOrDefault,
|
|
8
5
|
isCatalogSourceDisabled
|
|
@@ -26,6 +23,7 @@ import {
|
|
|
26
23
|
readInstalledCliPackageVersion,
|
|
27
24
|
readProjectCliDependencySpec
|
|
28
25
|
} from "./package.js";
|
|
26
|
+
import { resolveTopoRoot } from "../../workspace-paths.js";
|
|
29
27
|
|
|
30
28
|
/**
|
|
31
29
|
* @returns {void}
|
|
@@ -67,12 +65,7 @@ function messageFromError(error) {
|
|
|
67
65
|
* @returns {string}
|
|
68
66
|
*/
|
|
69
67
|
function normalizeTopogramPath(inputPath) {
|
|
70
|
-
|
|
71
|
-
if (path.basename(absolute) === "topogram") {
|
|
72
|
-
return absolute;
|
|
73
|
-
}
|
|
74
|
-
const candidate = path.join(absolute, "topogram");
|
|
75
|
-
return fs.existsSync(candidate) ? candidate : absolute;
|
|
68
|
+
return resolveTopoRoot(inputPath);
|
|
76
69
|
}
|
|
77
70
|
|
|
78
71
|
/**
|
|
@@ -26,7 +26,7 @@ import {
|
|
|
26
26
|
*/
|
|
27
27
|
export function runGeneratorPolicyCommand(context) {
|
|
28
28
|
const { commandArgs, inputPath, json } = context;
|
|
29
|
-
const projectPath = inputPath || "./
|
|
29
|
+
const projectPath = inputPath || "./topo";
|
|
30
30
|
if (commandArgs.generatorPolicyCommand === "init") {
|
|
31
31
|
const payload = buildGeneratorPolicyInitPayload(projectPath);
|
|
32
32
|
if (json) {
|
|
@@ -17,8 +17,8 @@ export function printImportHelp() {
|
|
|
17
17
|
console.log("Creates an editable Topogram workspace from a brownfield app without modifying the app.");
|
|
18
18
|
console.log("");
|
|
19
19
|
console.log("Behavior:");
|
|
20
|
-
console.log(" - writes raw import candidates under
|
|
21
|
-
console.log(" - writes reconcile proposal bundles under
|
|
20
|
+
console.log(" - writes raw import candidates under topo/candidates/app");
|
|
21
|
+
console.log(" - writes reconcile proposal bundles under topo/candidates/reconcile");
|
|
22
22
|
console.log(" - writes topogram.project.json with maintained ownership and no generated stack binding");
|
|
23
23
|
console.log(` - writes ${TOPOGRAM_IMPORT_FILE} with source file hashes from import time`);
|
|
24
24
|
console.log(" - imported Topogram artifacts are project-owned after creation");
|
|
@@ -7,6 +7,7 @@ import path from "node:path";
|
|
|
7
7
|
import { stableStringify } from "../../../format.js";
|
|
8
8
|
import { TOPOGRAM_IMPORT_FILE } from "../../../import/provenance.js";
|
|
9
9
|
import { shellCommandArg } from "../catalog.js";
|
|
10
|
+
import { resolveTopoRoot, resolveWorkspaceContext } from "../../../workspace-paths.js";
|
|
10
11
|
|
|
11
12
|
export const TOPOGRAM_IMPORT_ADOPTIONS_FILE = ".topogram-import-adoptions.jsonl";
|
|
12
13
|
|
|
@@ -19,12 +20,7 @@ export const TOPOGRAM_IMPORT_ADOPTIONS_FILE = ".topogram-import-adoptions.jsonl"
|
|
|
19
20
|
* @returns {string}
|
|
20
21
|
*/
|
|
21
22
|
export function normalizeTopogramPath(inputPath) {
|
|
22
|
-
|
|
23
|
-
if (path.basename(absolute) === "topogram") {
|
|
24
|
-
return absolute;
|
|
25
|
-
}
|
|
26
|
-
const candidate = path.join(absolute, "topogram");
|
|
27
|
-
return fs.existsSync(candidate) ? candidate : absolute;
|
|
23
|
+
return resolveTopoRoot(inputPath);
|
|
28
24
|
}
|
|
29
25
|
|
|
30
26
|
/**
|
|
@@ -32,11 +28,7 @@ export function normalizeTopogramPath(inputPath) {
|
|
|
32
28
|
* @returns {string}
|
|
33
29
|
*/
|
|
34
30
|
export function normalizeProjectRoot(inputPath) {
|
|
35
|
-
|
|
36
|
-
if (path.basename(absolute) === "topogram") {
|
|
37
|
-
return path.dirname(absolute);
|
|
38
|
-
}
|
|
39
|
-
return absolute;
|
|
31
|
+
return resolveWorkspaceContext(inputPath).projectRoot;
|
|
40
32
|
}
|
|
41
33
|
|
|
42
34
|
/**
|
|
@@ -40,7 +40,15 @@ export const BROWNFIELD_BROAD_ADOPT_SELECTORS = [
|
|
|
40
40
|
},
|
|
41
41
|
{ selector: "workflows", kind: "track", label: "workflows", matches: (/** @type {AnyRecord} */ item) => item.track === "workflows" || item.kind === "decision" },
|
|
42
42
|
{ selector: "verification", kind: "kind", label: "verification", matches: (/** @type {AnyRecord} */ item) => item.kind === "verification" },
|
|
43
|
-
{
|
|
43
|
+
{
|
|
44
|
+
selector: "ui",
|
|
45
|
+
kind: "track",
|
|
46
|
+
label: "UI reports, widgets, and event shapes",
|
|
47
|
+
matches: (/** @type {AnyRecord} */ item) =>
|
|
48
|
+
item.track === "ui" ||
|
|
49
|
+
item.kind === "widget" ||
|
|
50
|
+
item.source_kind === "ui_widget_event"
|
|
51
|
+
}
|
|
44
52
|
];
|
|
45
53
|
|
|
46
54
|
/**
|
|
@@ -12,6 +12,7 @@ import {
|
|
|
12
12
|
writeTopogramImportRecord
|
|
13
13
|
} from "../../../import/provenance.js";
|
|
14
14
|
import { runWorkflow } from "../../../workflows.js";
|
|
15
|
+
import { DEFAULT_TOPO_FOLDER_NAME } from "../../../workspace-paths.js";
|
|
15
16
|
import {
|
|
16
17
|
countByField,
|
|
17
18
|
importProjectCommandPath,
|
|
@@ -240,7 +241,7 @@ export function buildRefreshPreviewReconcile(projectRoot, topogramRoot, importFi
|
|
|
240
241
|
const tempRoot = fs.mkdtempSync(path.join(os.tmpdir(), "topogram-import-refresh-preview."));
|
|
241
242
|
try {
|
|
242
243
|
const tempProjectRoot = path.join(tempRoot, "workspace");
|
|
243
|
-
const tempTopogramRoot = path.join(tempProjectRoot,
|
|
244
|
+
const tempTopogramRoot = path.join(tempProjectRoot, DEFAULT_TOPO_FOLDER_NAME);
|
|
244
245
|
fs.mkdirSync(tempProjectRoot, { recursive: true });
|
|
245
246
|
fs.cpSync(topogramRoot, tempTopogramRoot, { recursive: true });
|
|
246
247
|
const projectConfigPath = path.join(projectRoot, "topogram.project.json");
|
|
@@ -282,7 +283,7 @@ export function buildBrownfieldImportRefreshAnalysis(inputPath, options = {}) {
|
|
|
282
283
|
const projectRoot = normalizeProjectRoot(inputPath);
|
|
283
284
|
const topogramRoot = normalizeTopogramPath(projectRoot);
|
|
284
285
|
if (!fs.existsSync(topogramRoot) || !fs.statSync(topogramRoot).isDirectory()) {
|
|
285
|
-
throw new Error(`No
|
|
286
|
+
throw new Error(`No workspace folder found for imported workspace '${inputPath}'.`);
|
|
286
287
|
}
|
|
287
288
|
|
|
288
289
|
const { record: importRecord } = readTopogramImportRecord(projectRoot);
|
|
@@ -315,8 +316,8 @@ export function buildBrownfieldImportRefreshAnalysis(inputPath, options = {}) {
|
|
|
315
316
|
const receiptVerification = verifyImportAdoptionReceipts(projectRoot, readImportAdoptionReceipts(projectRoot));
|
|
316
317
|
const plannedFiles = [
|
|
317
318
|
TOPOGRAM_IMPORT_FILE,
|
|
318
|
-
...Object.keys(importResult.files || {}).map((filePath) =>
|
|
319
|
-
...previewReconcile.reconcileFilePaths.map((/** @type {string} */ filePath) =>
|
|
319
|
+
...Object.keys(importResult.files || {}).map((filePath) => `${DEFAULT_TOPO_FOLDER_NAME}/${filePath}`),
|
|
320
|
+
...previewReconcile.reconcileFilePaths.map((/** @type {string} */ filePath) => `${DEFAULT_TOPO_FOLDER_NAME}/${filePath}`)
|
|
320
321
|
].sort((a, b) => a.localeCompare(b));
|
|
321
322
|
const analysis = /** @type {AnyRecord} */ ({
|
|
322
323
|
projectRoot,
|
|
@@ -393,8 +394,8 @@ export function buildBrownfieldImportRefreshPayload(inputPath, options = {}) {
|
|
|
393
394
|
currentImportStatus = buildTopogramImportStatus(analysis.projectRoot).status;
|
|
394
395
|
writtenFiles = [
|
|
395
396
|
TOPOGRAM_IMPORT_FILE,
|
|
396
|
-
...rawCandidateFiles.map((filePath) =>
|
|
397
|
-
...reconcileFiles.map((filePath) =>
|
|
397
|
+
...rawCandidateFiles.map((filePath) => `${DEFAULT_TOPO_FOLDER_NAME}/${filePath}`),
|
|
398
|
+
...reconcileFiles.map((filePath) => `${DEFAULT_TOPO_FOLDER_NAME}/${filePath}`)
|
|
398
399
|
].sort((a, b) => a.localeCompare(b));
|
|
399
400
|
analysis.removedCandidateFiles = removedCandidateFiles;
|
|
400
401
|
analysis.rawCandidateFiles = rawCandidateFiles.length;
|
|
@@ -10,6 +10,7 @@ import {
|
|
|
10
10
|
writeTopogramImportRecord
|
|
11
11
|
} from "../../../import/provenance.js";
|
|
12
12
|
import { runWorkflow } from "../../../workflows.js";
|
|
13
|
+
import { DEFAULT_TOPO_FOLDER_NAME, DEFAULT_WORKSPACE_PATH } from "../../../workspace-paths.js";
|
|
13
14
|
import { shellCommandArg } from "../catalog.js";
|
|
14
15
|
|
|
15
16
|
/**
|
|
@@ -57,6 +58,7 @@ export function writeRelativeFiles(outDir, files) {
|
|
|
57
58
|
function importedProjectConfig() {
|
|
58
59
|
return {
|
|
59
60
|
version: "0.1",
|
|
61
|
+
workspace: DEFAULT_WORKSPACE_PATH,
|
|
60
62
|
outputs: {
|
|
61
63
|
maintained_app: {
|
|
62
64
|
path: "./app",
|
|
@@ -93,7 +95,7 @@ function importedWorkspaceReadme(sourceRoot, targetRoot, importSummary) {
|
|
|
93
95
|
"```sh",
|
|
94
96
|
"topogram import check",
|
|
95
97
|
"topogram check",
|
|
96
|
-
|
|
98
|
+
`topogram query import-plan ./${DEFAULT_TOPO_FOLDER_NAME}`,
|
|
97
99
|
"```",
|
|
98
100
|
""
|
|
99
101
|
].join("\n");
|
|
@@ -113,6 +115,7 @@ export function importCandidateCounts(summary) {
|
|
|
113
115
|
uiScreens: candidates.ui?.screens?.length || 0,
|
|
114
116
|
uiRoutes: candidates.ui?.routes?.length || 0,
|
|
115
117
|
uiWidgets: candidates.ui?.widgets?.length || candidates.ui?.components?.length || 0,
|
|
118
|
+
uiShapes: candidates.ui?.shapes?.length || 0,
|
|
116
119
|
workflows: candidates.workflows?.workflows?.length || 0,
|
|
117
120
|
verifications: candidates.verification?.verifications?.length || 0
|
|
118
121
|
};
|
|
@@ -155,7 +158,7 @@ export function buildBrownfieldImportWorkspacePayload(sourcePath, targetPath, op
|
|
|
155
158
|
}
|
|
156
159
|
ensureEmptyImportTarget(targetRoot);
|
|
157
160
|
|
|
158
|
-
const topogramRoot = path.join(targetRoot,
|
|
161
|
+
const topogramRoot = path.join(targetRoot, DEFAULT_TOPO_FOLDER_NAME);
|
|
159
162
|
fs.mkdirSync(topogramRoot, { recursive: true });
|
|
160
163
|
const sourceFiles = collectImportSourceFileRecords(sourceRoot, { excludeRoots: [targetRoot] });
|
|
161
164
|
const importResult = runWorkflow("import-app", sourceRoot, { from: options.from || null });
|
|
@@ -180,8 +183,8 @@ export function buildBrownfieldImportWorkspacePayload(sourcePath, targetPath, op
|
|
|
180
183
|
"README.md",
|
|
181
184
|
"topogram.project.json",
|
|
182
185
|
TOPOGRAM_IMPORT_FILE,
|
|
183
|
-
...rawCandidateFiles.map((filePath) =>
|
|
184
|
-
...reconcileFiles.map((filePath) =>
|
|
186
|
+
...rawCandidateFiles.map((filePath) => `${DEFAULT_TOPO_FOLDER_NAME}/${filePath}`),
|
|
187
|
+
...reconcileFiles.map((filePath) => `${DEFAULT_TOPO_FOLDER_NAME}/${filePath}`)
|
|
185
188
|
].sort((a, b) => a.localeCompare(b));
|
|
186
189
|
return {
|
|
187
190
|
ok: true,
|
|
@@ -202,7 +205,7 @@ export function buildBrownfieldImportWorkspacePayload(sourcePath, targetPath, op
|
|
|
202
205
|
"topogram import adopt bundle:task --dry-run",
|
|
203
206
|
"topogram import status",
|
|
204
207
|
"topogram check",
|
|
205
|
-
|
|
208
|
+
`topogram query import-plan ./${DEFAULT_TOPO_FOLDER_NAME}`
|
|
206
209
|
]
|
|
207
210
|
};
|
|
208
211
|
}
|