@topogram/cli 0.3.65 → 0.3.66

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (67) hide show
  1. package/package.json +1 -1
  2. package/src/adoption/plan/index.js +21 -8
  3. package/src/adoption/reporting.js +1 -1
  4. package/src/agent-brief.js +7 -21
  5. package/src/agent-ops/query-builders/change-risk/review-packets.js +2 -2
  6. package/src/agent-ops/query-builders/common.js +2 -2
  7. package/src/agent-ops/query-builders/multi-agent.js +1 -1
  8. package/src/agent-ops/query-builders/workflow-presets-core.js +3 -2
  9. package/src/archive/jsonl.js +2 -2
  10. package/src/archive/resolver-bridge.js +1 -1
  11. package/src/archive/unarchive.js +2 -1
  12. package/src/catalog/copy.js +11 -6
  13. package/src/catalog/provenance.js +2 -1
  14. package/src/cli/command-parsers/project.js +3 -0
  15. package/src/cli/command-parsers/shared.js +1 -1
  16. package/src/cli/commands/agent.js +2 -2
  17. package/src/cli/commands/check.js +3 -3
  18. package/src/cli/commands/doctor.js +2 -9
  19. package/src/cli/commands/generator-policy/runner.js +1 -1
  20. package/src/cli/commands/import/help.js +2 -2
  21. package/src/cli/commands/import/paths.js +3 -11
  22. package/src/cli/commands/import/plan.js +9 -1
  23. package/src/cli/commands/import/refresh.js +7 -6
  24. package/src/cli/commands/import/workspace.js +8 -5
  25. package/src/cli/commands/migrate.js +153 -0
  26. package/src/cli/commands/query/definitions.js +10 -10
  27. package/src/cli/commands/query/workspace.js +2 -6
  28. package/src/cli/commands/source.js +3 -12
  29. package/src/cli/commands/template/check.js +6 -5
  30. package/src/cli/commands/template-runner.js +6 -6
  31. package/src/cli/commands/trust.js +1 -1
  32. package/src/cli/commands/workflow.js +6 -1
  33. package/src/cli/dispatcher.js +6 -1
  34. package/src/cli/help.js +15 -14
  35. package/src/cli/migration-guidance.js +1 -1
  36. package/src/cli/output-safety.js +2 -1
  37. package/src/cli/path-normalization.js +3 -13
  38. package/src/generator/context/domain-page.js +1 -1
  39. package/src/generator/context/shared/maintained-boundary.js +2 -2
  40. package/src/generator/context/shared/metrics.js +2 -2
  41. package/src/generator/context/task-mode.js +2 -2
  42. package/src/generator/sdlc/doc-page.js +1 -1
  43. package/src/generator/surfaces/databases/lifecycle-shared.js +1 -1
  44. package/src/generator/surfaces/native/swiftui-templates/README.generated.md +1 -1
  45. package/src/import/core/context.js +5 -7
  46. package/src/import/core/runner/candidates.js +123 -3
  47. package/src/import/core/runner/reports.js +4 -3
  48. package/src/import/core/runner/ui-drafts.js +58 -2
  49. package/src/new-project/constants.js +1 -1
  50. package/src/new-project/create.js +9 -2
  51. package/src/new-project/project-files.js +16 -13
  52. package/src/new-project/template-resolution.js +6 -4
  53. package/src/new-project/template-snapshots.js +19 -7
  54. package/src/new-project/template-updates.js +1 -1
  55. package/src/project-config/index.js +27 -0
  56. package/src/sdlc/adopt.js +6 -5
  57. package/src/sdlc/paths.js +3 -5
  58. package/src/sdlc/scaffold.js +2 -1
  59. package/src/workflows/reconcile/adoption-plan/build.js +7 -3
  60. package/src/workflows/reconcile/adoption-plan/outputs.js +12 -2
  61. package/src/workflows/reconcile/adoption-plan/paths.js +1 -1
  62. package/src/workflows/reconcile/candidate-model.js +18 -2
  63. package/src/workflows/reconcile/impacts/adoption-plan.js +6 -2
  64. package/src/workflows/reconcile/impacts/indexes.js +5 -1
  65. package/src/workflows/reconcile/renderers.js +41 -6
  66. package/src/workflows/shared.js +5 -11
  67. package/src/workspace-paths.js +328 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@topogram/cli",
3
- "version": "0.3.65",
3
+ "version": "0.3.66",
4
4
  "description": "Topogram CLI for checking Topogram workspaces and generating app bundles.",
5
5
  "license": "Apache-2.0",
6
6
  "repository": {
@@ -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 selectedCapabilityBundles = new Set(
471
- [...expanded]
472
- .map((key) => updatedItems.find((item) => adoptionItemKey(item) === key))
473
- .filter((item) => item?.kind === "capability")
474
- .map((item) => item.bundle)
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 (item.kind === "shape" && item.status !== "skipped" && selectedCapabilityBundles.has(item.bundle)) {
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 || `topogram/${normalizeReportPath(item.canonical_rel_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
  }))
@@ -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
- const resolved = path.resolve(inputPath || "./topogram");
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
- const resolved = path.resolve(inputPath || ".");
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) ? "." : "topogram/";
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
- "topogram/**",
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: ["topogram/actors", "topogram/entities", "topogram/shapes", "topogram/capabilities", "topogram/widgets", "topogram/projections", "topogram/verifications"],
409
- large: ["topogram/domains/<domain>", "topogram/shared", "topogram/domains/<domain>/widgets", "topogram/domains/<domain>/projections"],
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: `topogram/${surface.canonical_rel_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: `topogram/${surface.canonical_rel_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 === "topogram/**" || String(entry).startsWith("topogram/"));
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", "./topogram"];
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) => `topogram/${surface.canonical_rel_path}`)
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 = path.basename(workspaceRoot) === "topogram" ? workspaceRoot : path.join(workspaceRoot, "topogram");
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, "topogram", "workflow-presets")
187
+ path.join(topogramRoot, DEFAULT_TOPO_FOLDER_NAME, "workflow-presets")
187
188
  ];
188
189
 
189
190
  const providerPresets = readJsonArtifactsFromDir(providerDir)
@@ -1,7 +1,7 @@
1
1
  // Year-bucketed JSONL archive I/O.
2
2
  //
3
- // File layout: `<project-or-topogram-root>/topogram/_archive/{kind}s-{year}.jsonl`
4
- // or `<topogram-root>/_archive/{kind}s-{year}.jsonl`
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 `topogram/_archive/*.jsonl`
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
@@ -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, "topogram", `${entry.kind}s`);
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
 
@@ -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 topogramRoot = path.join(packageRoot, "topogram");
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(topogramRoot, path.join(resolvedTarget, "topogram"), "topogram", files);
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
- copyPath(sourcePath, path.join(resolvedTarget, fileName), fileName, files);
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 ["topogram", "topogram.project.json", "README.md"]) {
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 = "./topogram") {
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 ./topogram. The command validates the Topogram and project config, but does not write files, generate apps, load generator adapters, or execute template implementation.");
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 ./topogram --json");
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 ./topogram.");
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 ./topogram");
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 || "./topogram";
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
- const absolute = path.resolve(inputPath);
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 || "./topogram";
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 topogram/candidates/app");
21
- console.log(" - writes reconcile proposal bundles under topogram/candidates/reconcile");
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
- const absolute = path.resolve(inputPath);
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
- const absolute = path.resolve(inputPath);
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
- { selector: "ui", kind: "track", label: "UI reports and widgets", matches: (/** @type {AnyRecord} */ item) => item.track === "ui" }
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, "topogram");
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 topogram directory found for imported workspace '${inputPath}'.`);
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) => `topogram/${filePath}`),
319
- ...previewReconcile.reconcileFilePaths.map((/** @type {string} */ filePath) => `topogram/${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) => `topogram/${filePath}`),
397
- ...reconcileFiles.map((filePath) => `topogram/${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
- "topogram query import-plan ./topogram",
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, "topogram");
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) => `topogram/${filePath}`),
184
- ...reconcileFiles.map((filePath) => `topogram/${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
- "topogram query import-plan ./topogram"
208
+ `topogram query import-plan ./${DEFAULT_TOPO_FOLDER_NAME}`
206
209
  ]
207
210
  };
208
211
  }