@topogram/cli 0.3.71 → 0.3.73

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 (85) hide show
  1. package/README.md +24 -195
  2. package/package.json +1 -1
  3. package/src/adoption/plan/index.js +2 -1
  4. package/src/agent-brief.js +46 -2
  5. package/src/archive/archive.js +1 -1
  6. package/src/archive/jsonl.js +18 -8
  7. package/src/archive/resolver-bridge.js +34 -1
  8. package/src/archive/schema.js +1 -1
  9. package/src/archive/unarchive.js +26 -0
  10. package/src/cli/command-parsers/project.js +0 -3
  11. package/src/cli/command-parsers/sdlc.js +66 -0
  12. package/src/cli/commands/import/help.js +1 -0
  13. package/src/cli/commands/import/plan.js +9 -0
  14. package/src/cli/commands/import/workspace.js +3 -0
  15. package/src/cli/commands/query/definitions.js +11 -10
  16. package/src/cli/commands/query/workspace.js +23 -2
  17. package/src/cli/commands/sdlc.js +213 -5
  18. package/src/cli/dispatcher.js +8 -5
  19. package/src/cli/help.js +14 -3
  20. package/src/cli/migration-guidance.js +3 -0
  21. package/src/cli/options.js +1 -0
  22. package/src/generator/context/shared/domain-sdlc.js +27 -0
  23. package/src/generator/context/shared/relationships.js +2 -1
  24. package/src/generator/context/shared/types.d.ts +1 -0
  25. package/src/generator/context/shared.d.ts +2 -0
  26. package/src/generator/context/shared.js +2 -0
  27. package/src/generator/context/slice/core.js +3 -0
  28. package/src/generator/context/slice/sdlc.js +57 -2
  29. package/src/generator/context/task-mode.js +7 -0
  30. package/src/generator/sdlc/board.js +2 -0
  31. package/src/generator/sdlc/traceability-matrix.js +5 -1
  32. package/src/generator/surfaces/databases/lifecycle-shared.js +2 -2
  33. package/src/import/core/context.js +1 -1
  34. package/src/import/core/contracts.js +3 -3
  35. package/src/import/core/registry.js +3 -0
  36. package/src/import/core/runner/candidates.js +7 -0
  37. package/src/import/core/runner/reports.js +9 -1
  38. package/src/import/core/runner/tracks.js +3 -0
  39. package/src/import/extractors/cli/generic.js +340 -0
  40. package/src/new-project/project-files.js +10 -3
  41. package/src/resolver/enrich/task.js +3 -1
  42. package/src/resolver/index.js +6 -0
  43. package/src/resolver/normalize.js +31 -0
  44. package/src/resolver/projections-cli.js +158 -0
  45. package/src/sdlc/adopt.js +4 -1
  46. package/src/sdlc/check.js +24 -2
  47. package/src/sdlc/complete.js +47 -0
  48. package/src/sdlc/dod/index.js +2 -0
  49. package/src/sdlc/dod/plan.js +15 -0
  50. package/src/sdlc/dod/task.js +7 -3
  51. package/src/sdlc/explain.js +53 -1
  52. package/src/sdlc/gate.js +352 -0
  53. package/src/sdlc/history.d.ts +7 -0
  54. package/src/sdlc/history.js +50 -5
  55. package/src/sdlc/link.js +172 -0
  56. package/src/sdlc/paths.d.ts +4 -0
  57. package/src/sdlc/paths.js +8 -0
  58. package/src/sdlc/plan-steps.js +71 -0
  59. package/src/sdlc/plan.js +245 -0
  60. package/src/sdlc/policy.js +249 -0
  61. package/src/sdlc/prep.js +186 -0
  62. package/src/sdlc/scaffold.js +4 -2
  63. package/src/sdlc/status-filter.js +2 -0
  64. package/src/sdlc/transitions/index.js +3 -0
  65. package/src/sdlc/transitions/plan.js +32 -0
  66. package/src/validator/common.js +25 -4
  67. package/src/validator/index.js +10 -0
  68. package/src/validator/kinds.d.ts +7 -0
  69. package/src/validator/kinds.js +32 -0
  70. package/src/validator/per-kind/plan.js +128 -0
  71. package/src/validator/per-kind/task.js +19 -0
  72. package/src/validator/projections/cli.js +267 -0
  73. package/src/validator.d.ts +1 -0
  74. package/src/workflows/import-app/shared.js +1 -1
  75. package/src/workflows/reconcile/adoption-plan/build.js +3 -1
  76. package/src/workflows/reconcile/adoption-plan/reasons.js +5 -0
  77. package/src/workflows/reconcile/bundle-core/index.js +3 -0
  78. package/src/workflows/reconcile/candidate-model.js +15 -0
  79. package/src/workflows/reconcile/gap-report.js +4 -2
  80. package/src/workflows/reconcile/impacts/adoption-plan.js +13 -0
  81. package/src/workflows/reconcile/renderers.js +82 -0
  82. package/src/workflows/reconcile/summary.js +4 -0
  83. package/src/workflows/reconcile/workflow.js +2 -1
  84. package/src/workspace-paths.js +34 -3
  85. package/src/cli/commands/migrate.js +0 -153
@@ -31,6 +31,7 @@ export function printImportHelp() {
31
31
  console.log("Examples:");
32
32
  console.log(" topogram import ./existing-app --out ./imported-topogram");
33
33
  console.log(" topogram import ./existing-app --out ./imported-topogram --from db,api,ui");
34
+ console.log(" topogram import ./existing-cli --out ./imported-topogram --from cli");
34
35
  console.log(" topogram import diff ./imported-topogram");
35
36
  console.log(" topogram import refresh ./imported-topogram --from ./existing-app --dry-run");
36
37
  console.log(" topogram import refresh ./imported-topogram --from ./existing-app");
@@ -40,6 +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
+ {
44
+ selector: "cli",
45
+ kind: "track",
46
+ label: "CLI surfaces",
47
+ matches: (/** @type {AnyRecord} */ item) =>
48
+ item.bundle === "cli" ||
49
+ item.track === "cli" ||
50
+ item.suggested_action === "promote_cli_surface"
51
+ },
43
52
  {
44
53
  selector: "ui",
45
54
  kind: "track",
@@ -116,6 +116,9 @@ export function importCandidateCounts(summary) {
116
116
  uiRoutes: candidates.ui?.routes?.length || 0,
117
117
  uiWidgets: candidates.ui?.widgets?.length || candidates.ui?.components?.length || 0,
118
118
  uiShapes: candidates.ui?.shapes?.length || 0,
119
+ cliCommands: candidates.cli?.commands?.length || 0,
120
+ cliCapabilities: candidates.cli?.capabilities?.length || 0,
121
+ cliSurfaces: candidates.cli?.surfaces?.length || 0,
119
122
  workflows: candidates.workflows?.workflows?.length || 0,
120
123
  verifications: candidates.verification?.verifications?.length || 0
121
124
  };
@@ -32,21 +32,22 @@
32
32
  * @returns {QueryDefinition[]}
33
33
  */
34
34
  export function queryDefinitions() {
35
+ const contextSelectors = ["mode", "capability", "workflow", "projection", "widget", "entity", "journey", "surface", "domain", "pitch", "requirement", "acceptance", "task", "plan", "bug", "document", "from-topogram"];
35
36
  return [
36
37
  {
37
38
  name: "slice",
38
39
  purpose: "Give an agent the smallest graph slice needed to reason about one selected semantic surface.",
39
40
  description: "Return a focused semantic context slice for one selected surface.",
40
- selectors: ["capability", "workflow", "projection", "widget", "entity", "journey", "domain"],
41
+ selectors: ["capability", "workflow", "projection", "widget", "entity", "journey", "domain", "pitch", "requirement", "acceptance", "task", "plan", "bug", "document"],
41
42
  args: ["[path]", "[selectors]", "[--json]"],
42
43
  output: "context_slice",
43
- example: "topogram query slice ./topo --widget widget_data_grid"
44
+ example: "topogram query slice ./topo --task task_implement_audit_writer"
44
45
  },
45
46
  {
46
47
  name: "verification-targets",
47
48
  purpose: "Map a selected change or mode to the smallest verification set worth running.",
48
49
  description: "Return the smallest verification target set for a mode, selector, or diff.",
49
- selectors: ["mode", "capability", "workflow", "projection", "widget", "entity", "journey", "from-topogram"],
50
+ selectors: contextSelectors.filter((selector) => selector !== "surface" && selector !== "domain" && selector !== "pitch" && selector !== "requirement" && selector !== "acceptance" && selector !== "plan" && selector !== "bug" && selector !== "document"),
50
51
  args: ["[path]", "[selectors]", "[--from-topogram <path>]", "[--json]"],
51
52
  output: "verification_targets",
52
53
  example: "topogram query verification-targets ./topo --widget widget_data_grid"
@@ -64,7 +65,7 @@ export function queryDefinitions() {
64
65
  name: "change-plan",
65
66
  purpose: "Summarize what a selected change affects before code or Topogram edits start.",
66
67
  description: "Return the semantic change plan, generator targets, risk, and alignment recommendations.",
67
- selectors: ["mode", "capability", "workflow", "projection", "widget", "entity", "journey", "surface", "from-topogram"],
68
+ selectors: contextSelectors,
68
69
  args: ["[path]", "[selectors]", "[--from-topogram <path>]", "[--json]"],
69
70
  output: "change_plan_query",
70
71
  example: "topogram query change-plan ./topo --widget widget_data_grid"
@@ -73,7 +74,7 @@ export function queryDefinitions() {
73
74
  name: "review-packet",
74
75
  purpose: "Bundle the context a human or agent needs to review a selected semantic change.",
75
76
  description: "Return the review packet for a selected change or diff.",
76
- selectors: ["mode", "capability", "workflow", "projection", "widget", "entity", "journey", "surface", "from-topogram"],
77
+ selectors: contextSelectors,
77
78
  args: ["[path]", "[selectors]", "[--from-topogram <path>]", "[--json]"],
78
79
  output: "review_packet_query",
79
80
  example: "topogram query review-packet ./topo --widget widget_data_grid"
@@ -82,7 +83,7 @@ export function queryDefinitions() {
82
83
  name: "resolved-workflow-context",
83
84
  purpose: "Resolve workflow guidance and artifact load order for a selected mode or change.",
84
85
  description: "Return resolved workflow guidance, artifact load order, preset policy, and recommended artifact queries.",
85
- selectors: ["mode", "capability", "workflow", "projection", "widget", "entity", "journey", "surface", "provider", "preset", "from-topogram"],
86
+ selectors: [...contextSelectors.filter((selector) => selector !== "document"), "provider", "preset"],
86
87
  args: ["[path]", "[--mode <id>]", "[selectors]", "[--from-topogram <path>]", "[--json]"],
87
88
  output: "resolved_workflow_context_query",
88
89
  example: "topogram query resolved-workflow-context ./topo --mode modeling --widget widget_data_grid --json"
@@ -91,7 +92,7 @@ export function queryDefinitions() {
91
92
  name: "single-agent-plan",
92
93
  purpose: "Give one coding agent a bounded plan, artifact set, and write guidance.",
93
94
  description: "Return a single-agent operating plan for a mode and optional selector.",
94
- selectors: ["mode", "capability", "workflow", "projection", "widget", "entity", "journey", "surface", "from-topogram"],
95
+ selectors: contextSelectors.filter((selector) => selector !== "document"),
95
96
  args: ["[path]", "[--mode <id>]", "[selectors]", "[--from-topogram <path>]", "[--json]"],
96
97
  output: "single_agent_plan_query",
97
98
  example: "topogram query single-agent-plan ./topo --mode modeling --widget widget_data_grid --json"
@@ -100,7 +101,7 @@ export function queryDefinitions() {
100
101
  name: "risk-summary",
101
102
  purpose: "Surface behavioral, ownership, and verification risks for a selected change.",
102
103
  description: "Return the risk summary for a selected change, mode, or diff.",
103
- selectors: ["mode", "capability", "workflow", "projection", "widget", "entity", "journey", "surface", "from-topogram"],
104
+ selectors: contextSelectors,
104
105
  args: ["[path]", "[selectors]", "[--from-topogram <path>]", "[--json]"],
105
106
  output: "risk_summary_query",
106
107
  example: "topogram query risk-summary ./topo --widget widget_data_grid"
@@ -109,7 +110,7 @@ export function queryDefinitions() {
109
110
  name: "proceed-decision",
110
111
  purpose: "Tell a human or agent whether enough context and proof exist to proceed.",
111
112
  description: "Return a proceed/no-go decision for the current selected work.",
112
- selectors: ["mode", "capability", "workflow", "projection", "widget", "entity", "journey", "surface", "from-topogram"],
113
+ selectors: contextSelectors,
113
114
  args: ["[path]", "[--mode <id>]", "[selectors]", "[--from-topogram <path>]", "[--json]"],
114
115
  output: "proceed_decision_query",
115
116
  example: "topogram query proceed-decision ./topo --mode verification"
@@ -118,7 +119,7 @@ export function queryDefinitions() {
118
119
  name: "write-scope",
119
120
  purpose: "Define where an agent may edit for a selected semantic surface.",
120
121
  description: "Return safe edit boundaries for a selected mode or semantic surface.",
121
- selectors: ["mode", "capability", "workflow", "projection", "widget", "entity", "journey", "from-topogram"],
122
+ selectors: contextSelectors.filter((selector) => selector !== "surface" && selector !== "document"),
122
123
  args: ["[path]", "[selectors]", "[--from-topogram <path>]", "[--json]"],
123
124
  output: "write_scope_query",
124
125
  example: "topogram query write-scope ./topo --widget widget_data_grid"
@@ -74,6 +74,13 @@ export function importAdoptOnlyRequested(options = {}) {
74
74
  options.journeyId ||
75
75
  options.surfaceId ||
76
76
  options.domainId ||
77
+ options.pitchId ||
78
+ options.requirementId ||
79
+ options.acceptanceId ||
80
+ options.taskId ||
81
+ options.planId ||
82
+ options.bugId ||
83
+ options.documentId ||
77
84
  options.fromTopogramPath
78
85
  );
79
86
  }
@@ -202,7 +209,14 @@ export function hasSelectors(options) {
202
209
  options.entityId ||
203
210
  options.journeyId ||
204
211
  options.surfaceId ||
205
- options.domainId
212
+ options.domainId ||
213
+ options.pitchId ||
214
+ options.requirementId ||
215
+ options.acceptanceId ||
216
+ options.taskId ||
217
+ options.planId ||
218
+ options.bugId ||
219
+ options.documentId
206
220
  );
207
221
  }
208
222
 
@@ -228,7 +242,14 @@ export function selectorOptions(options) {
228
242
  entityId: options.entityId,
229
243
  journeyId: options.journeyId,
230
244
  surfaceId: options.surfaceId,
231
- domainId: options.domainId
245
+ domainId: options.domainId,
246
+ pitchId: options.pitchId,
247
+ requirementId: options.requirementId,
248
+ acceptanceId: options.acceptanceId,
249
+ taskId: options.taskId,
250
+ planId: options.planId,
251
+ bugId: options.bugId,
252
+ documentId: options.documentId
232
253
  };
233
254
  }
234
255
 
@@ -6,6 +6,7 @@ import { stableStringify } from "../../format.js";
6
6
  import { parsePath } from "../../parser.js";
7
7
  import { resolveWorkspace } from "../../resolver.js";
8
8
  import { formatValidationErrors } from "../../validator.js";
9
+ import { resolveTopoRoot } from "../../workspace-paths.js";
9
10
 
10
11
  /**
11
12
  * @typedef {Record<string, any>} AnyRecord
@@ -21,6 +22,23 @@ function flagValue(args, flag) {
21
22
  return index >= 0 ? args[index + 1] || null : null;
22
23
  }
23
24
 
25
+ /**
26
+ * @param {string[]} args
27
+ * @param {string} flag
28
+ * @returns {string[]}
29
+ */
30
+ function flagValues(args, flag) {
31
+ /** @type {string[]} */
32
+ const values = [];
33
+ for (let index = 0; index < args.length; index += 1) {
34
+ if (args[index] === flag && args[index + 1] && !args[index + 1].startsWith("-")) {
35
+ values.push(args[index + 1]);
36
+ index += 1;
37
+ }
38
+ }
39
+ return values;
40
+ }
41
+
24
42
  /**
25
43
  * @param {string[]} args
26
44
  * @returns {boolean}
@@ -29,6 +47,65 @@ function includeHistory(args) {
29
47
  return args.includes("--history") || args.includes("--include-history");
30
48
  }
31
49
 
50
+ /**
51
+ * @param {Record<string, any>} payload
52
+ * @returns {void}
53
+ */
54
+ function printPolicyExplain(payload) {
55
+ console.log("Topogram SDLC policy");
56
+ console.log(`Status: ${payload.policy?.status || "not_adopted"}`);
57
+ console.log(`Mode: ${payload.policy?.mode || "none"}`);
58
+ console.log(`Policy: ${payload.policy?.path || "missing"}`);
59
+ console.log(payload.enforcement || "Project has not adopted enforced SDLC.");
60
+ if ((payload.policy?.protectedPaths || []).length > 0) {
61
+ console.log("Protected paths:");
62
+ for (const item of payload.policy.protectedPaths) {
63
+ console.log(` - ${item}`);
64
+ }
65
+ }
66
+ if ((payload.nextCommands || []).length > 0) {
67
+ console.log("Next commands:");
68
+ for (const command of payload.nextCommands) {
69
+ console.log(` - ${command}`);
70
+ }
71
+ }
72
+ }
73
+
74
+ /**
75
+ * @param {Record<string, any>} payload
76
+ * @returns {void}
77
+ */
78
+ function printCommitPrep(payload) {
79
+ console.log("Topogram SDLC commit prep");
80
+ console.log(`Status: ${payload.ok ? "ready" : "needs attention"}`);
81
+ console.log(`Changed task files: ${(payload.taskFiles || []).length}`);
82
+ if ((payload.openTasks || []).length > 0) {
83
+ console.log("Open changed tasks:");
84
+ for (const task of payload.openTasks) {
85
+ console.log(` - ${task.id} (${task.status}, ${task.disposition || "unclassified"})`);
86
+ console.log(` file: ${task.file}`);
87
+ }
88
+ }
89
+ if ((payload.errors || []).length > 0) {
90
+ console.log("Errors:");
91
+ for (const error of payload.errors) {
92
+ console.log(` - ${error}`);
93
+ }
94
+ }
95
+ if ((payload.warnings || []).length > 0) {
96
+ console.log("Warnings:");
97
+ for (const warning of payload.warnings) {
98
+ console.log(` - ${warning}`);
99
+ }
100
+ }
101
+ if ((payload.nextCommands || []).length > 0) {
102
+ console.log("Next commands:");
103
+ for (const command of payload.nextCommands) {
104
+ console.log(` - ${command}`);
105
+ }
106
+ }
107
+ }
108
+
32
109
  /**
33
110
  * @param {string} sdlcRoot
34
111
  * @returns {AnyRecord|null}
@@ -56,15 +133,144 @@ function resolveSdlcWorkspace(sdlcRoot) {
56
133
  */
57
134
  export async function runSdlcCommand(context) {
58
135
  const { commandArgs, args } = context;
59
- const sdlcRoot = path.resolve(context.inputPath || ".");
136
+ const sdlcRoot = resolveTopoRoot(context.inputPath || ".");
60
137
  const actor = flagValue(args, "--actor");
61
138
  const note = flagValue(args, "--note");
62
139
  const status = flagValue(args, "--status");
140
+ const id = flagValue(args, "--id");
63
141
  const before = flagValue(args, "--before");
64
142
  const appVersion = flagValue(args, "--app-version");
65
143
  const sinceTag = flagValue(args, "--since-tag");
144
+ const base = flagValue(args, "--base");
145
+ const head = flagValue(args, "--head");
146
+ const exemption = flagValue(args, "--exemption");
147
+ const verification = flagValue(args, "--verification");
66
148
  const dryRun = args.includes("--dry-run");
67
149
  const strict = args.includes("--strict");
150
+ const json = args.includes("--json");
151
+
152
+ if (String(commandArgs.sdlcCommand || "").startsWith("policy:")) {
153
+ const {
154
+ explainSdlcPolicy,
155
+ loadSdlcPolicy,
156
+ policyProjectRoot,
157
+ writeDefaultSdlcPolicy
158
+ } = await import("../../sdlc/policy.js");
159
+ const projectRoot = policyProjectRoot(context.inputPath || ".");
160
+ if (commandArgs.sdlcCommand === "policy:init") {
161
+ const result = writeDefaultSdlcPolicy(projectRoot);
162
+ console.log(stableStringify(result));
163
+ return result.ok ? 0 : 1;
164
+ }
165
+ if (commandArgs.sdlcCommand === "policy:check") {
166
+ const info = loadSdlcPolicy(projectRoot);
167
+ const result = {
168
+ type: "sdlc_policy_check",
169
+ ok: info.exists && info.diagnostics.every((diagnostic) => diagnostic.severity !== "error"),
170
+ exists: info.exists,
171
+ path: info.path,
172
+ status: info.status,
173
+ mode: info.mode,
174
+ diagnostics: info.diagnostics
175
+ };
176
+ if (json) {
177
+ console.log(stableStringify(result));
178
+ } else if (result.ok) {
179
+ console.log(`SDLC policy is valid: ${result.path}`);
180
+ } else {
181
+ console.error(stableStringify(result));
182
+ }
183
+ return result.ok ? 0 : 1;
184
+ }
185
+ if (commandArgs.sdlcCommand === "policy:explain") {
186
+ const result = explainSdlcPolicy(projectRoot);
187
+ if (json) {
188
+ console.log(stableStringify(result));
189
+ } else {
190
+ printPolicyExplain(result);
191
+ }
192
+ return result.ok ? 0 : 1;
193
+ }
194
+ }
195
+
196
+ if (commandArgs.sdlcCommand === "gate") {
197
+ const { runSdlcGate } = await import("../../sdlc/gate.js");
198
+ const result = await runSdlcGate(context.inputPath || ".", {
199
+ base,
200
+ head,
201
+ sdlcIds: flagValues(args, "--sdlc-id"),
202
+ exemption,
203
+ requireAdopted: args.includes("--require-adopted")
204
+ });
205
+ console.log(stableStringify(result));
206
+ return result.ok ? 0 : 1;
207
+ }
208
+
209
+ if (commandArgs.sdlcCommand === "prep:commit") {
210
+ const { runSdlcCommitPrep } = await import("../../sdlc/prep.js");
211
+ const result = runSdlcCommitPrep(context.inputPath || ".", {
212
+ base,
213
+ head
214
+ });
215
+ if (json) {
216
+ console.log(stableStringify(result));
217
+ } else {
218
+ printCommitPrep(result);
219
+ }
220
+ return result.ok ? 0 : 1;
221
+ }
222
+
223
+ if (commandArgs.sdlcCommand === "link") {
224
+ const { linkSdlcRecord } = await import("../../sdlc/link.js");
225
+ const result = linkSdlcRecord(sdlcRoot, commandArgs.sdlcFromId, commandArgs.sdlcToId, {
226
+ write: args.includes("--write")
227
+ });
228
+ console.log(stableStringify(result));
229
+ return result.ok ? 0 : 1;
230
+ }
231
+
232
+ if (commandArgs.sdlcCommand === "complete") {
233
+ const { completeTask } = await import("../../sdlc/complete.js");
234
+ const result = completeTask(sdlcRoot, commandArgs.sdlcId, verification || "", {
235
+ write: args.includes("--write") && !dryRun,
236
+ actor,
237
+ note
238
+ });
239
+ console.log(stableStringify(result));
240
+ return result.ok ? 0 : 1;
241
+ }
242
+
243
+ if (commandArgs.sdlcCommand === "plan:create") {
244
+ const { createPlan } = await import("../../sdlc/plan.js");
245
+ const result = createPlan(sdlcRoot, commandArgs.sdlcId, commandArgs.sdlcSlug, {
246
+ write: args.includes("--write") && !dryRun
247
+ });
248
+ console.log(stableStringify(result));
249
+ return result.ok ? 0 : 1;
250
+ }
251
+
252
+ if (commandArgs.sdlcCommand === "plan:explain") {
253
+ const { explainPlan } = await import("../../sdlc/plan.js");
254
+ const result = explainPlan(sdlcRoot, commandArgs.sdlcId);
255
+ console.log(stableStringify(result));
256
+ return result.ok ? 0 : 1;
257
+ }
258
+
259
+ if (String(commandArgs.sdlcCommand || "").startsWith("plan:step:")) {
260
+ const { transitionPlanStep } = await import("../../sdlc/plan.js");
261
+ if (commandArgs.sdlcCommand === "plan:step:skip" && !note) {
262
+ console.log(stableStringify({ ok: false, error: "sdlc plan step skip requires --note <reason>" }));
263
+ return 1;
264
+ }
265
+ const result = transitionPlanStep(sdlcRoot, commandArgs.sdlcId, commandArgs.sdlcStepId, commandArgs.sdlcTargetStatus, {
266
+ write: args.includes("--write") && !dryRun,
267
+ dryRun,
268
+ actor,
269
+ note
270
+ });
271
+ console.log(stableStringify(result));
272
+ return result.ok ? 0 : 1;
273
+ }
68
274
 
69
275
  if (commandArgs.sdlcCommand === "transition") {
70
276
  const { transitionStatement } = await import("../../sdlc/transition.js");
@@ -115,10 +321,12 @@ export async function runSdlcCommand(context) {
115
321
  if (!resolved) {
116
322
  return 1;
117
323
  }
118
- const ids = archiveEligibleStatements(resolved, {
119
- before,
120
- statuses: status ? status.split(",") : null
121
- });
324
+ const ids = id
325
+ ? [id]
326
+ : archiveEligibleStatements(resolved, {
327
+ before,
328
+ statuses: status ? status.split(",") : null
329
+ });
122
330
  const result = archiveBatch(sdlcRoot, ids, { dryRun, by: actor, reason: note });
123
331
  console.log(stableStringify({ candidates: ids, ...result }));
124
332
  return result.ok ? 0 : 1;
@@ -10,7 +10,6 @@ import { runGenerateAppCommand } from "./commands/generate.js";
10
10
  import { runGeneratorCommand } from "./commands/generator.js";
11
11
  import { runGeneratorPolicyCommand } from "./commands/generator-policy.js";
12
12
  import { runImportCommand } from "./commands/import-runner.js";
13
- import { runMigrateCommand } from "./commands/migrate.js";
14
13
  import { runNewProjectCommand } from "./commands/new.js";
15
14
  import { runPackageCommand } from "./commands/package.js";
16
15
  import { runParseCommand, runResolveCommand } from "./commands/inspect.js";
@@ -110,6 +109,7 @@ export async function runCliDispatch(context) {
110
109
  domainId,
111
110
  seamId,
112
111
  taskId,
112
+ planId,
113
113
  pitchId,
114
114
  requirementId,
115
115
  acceptanceId,
@@ -242,10 +242,6 @@ export async function runCliDispatch(context) {
242
242
  return runPackageCommand({ commandArgs, inputPath, json: emitJson });
243
243
  }
244
244
 
245
- if (commandArgs?.migrateCommand) {
246
- return runMigrateCommand(inputPath, { write: shouldWrite, json: emitJson });
247
- }
248
-
249
245
  if (commandArgs?.importCommand) {
250
246
  return runImportCommand({
251
247
  commandArgs,
@@ -301,6 +297,13 @@ export async function runCliDispatch(context) {
301
297
  journeyId,
302
298
  surfaceId,
303
299
  domainId,
300
+ pitchId,
301
+ requirementId,
302
+ acceptanceId,
303
+ taskId,
304
+ planId,
305
+ bugId,
306
+ documentId,
304
307
  seamId,
305
308
  modeId,
306
309
  providerId,
package/src/cli/help.js CHANGED
@@ -16,6 +16,14 @@ export function printUsage(options = {}) {
16
16
  console.log(" or: topogram widget check [path] [--projection <id>] [--widget <id>] [--json]");
17
17
  console.log(" or: topogram widget behavior [path] [--projection <id>] [--widget <id>] [--json]");
18
18
  console.log(" or: topogram agent brief [path] [--json]");
19
+ console.log(" or: topogram sdlc policy init|check|explain [path] [--json]");
20
+ console.log(" or: topogram sdlc gate [path] --base <ref> --head <ref> [--sdlc-id <id>] [--exemption <text>] [--require-adopted] [--json]");
21
+ console.log(" or: topogram sdlc prep commit [path] [--base <ref> --head <ref>] [--json]");
22
+ console.log(" or: topogram sdlc link <from-id> <to-id> [path] [--write]");
23
+ console.log(" or: topogram sdlc complete <task-id> [path] --verification <verification-id> [--dry-run|--write]");
24
+ console.log(" or: topogram sdlc plan create <task-id> <slug> [path] [--write]");
25
+ console.log(" or: topogram sdlc plan explain <plan-id> [path] [--json]");
26
+ console.log(" or: topogram sdlc plan step complete <plan-id> <step-id> [path] --actor <actor> [--write]");
19
27
  console.log(" or: topogram generate [path] [--out <path>]");
20
28
  console.log(" or: topogram emit <target> [path] [--json|--write --out-dir <path>]");
21
29
  console.log(" or: topogram query list [--json]");
@@ -28,7 +36,6 @@ export function printUsage(options = {}) {
28
36
  console.log(" or: topogram catalog doctor [--json] [--catalog <path-or-source>]");
29
37
  console.log(" or: topogram catalog check <path-or-url> [--json]");
30
38
  console.log(" or: topogram catalog copy <id> <target> [--version <version>] [--json] [--catalog <path-or-source>]");
31
- console.log(" or: topogram migrate workspace-folder [path] [--dry-run|--write] [--json]");
32
39
  console.log(" or: topogram package update-cli <version|--latest> [--json]");
33
40
  console.log(" or: topogram import <app-path> --out <target> [--from <track[,track]>] [--json]");
34
41
  console.log(" or: topogram import refresh [path] [--from <app-path>] [--dry-run] [--json]");
@@ -78,6 +85,10 @@ export function printUsage(options = {}) {
78
85
  console.log(" topogram widget behavior --projection proj_web_surface");
79
86
  console.log(" topogram agent brief");
80
87
  console.log(" topogram agent brief --json");
88
+ console.log(" topogram sdlc policy explain");
89
+ console.log(" topogram sdlc prep commit . --base origin/main --head HEAD");
90
+ console.log(" topogram sdlc gate . --require-adopted");
91
+ console.log(" topogram sdlc plan explain plan_example --json");
81
92
  console.log(" topogram query list");
82
93
  console.log(" topogram query show widget-behavior");
83
94
  console.log(" topogram query widget-behavior ./topo --projection proj_web_surface --json");
@@ -165,7 +176,7 @@ export function printUsage(options = {}) {
165
176
  console.log(" or: node ./src/cli.js query maintained-drift <path> --from-topogram <path>");
166
177
  console.log(" or: node ./src/cli.js query seam-check <path> [--seam <id>] [--from-topogram <path>]");
167
178
  console.log(" or: node ./src/cli.js query diff <path> --from-topogram <path>");
168
- console.log(" or: node ./src/cli.js query slice <path> [--capability <id>] [--workflow <id>] [--projection <id>] [--widget <id>] [--entity <id>] [--journey <id>] [--domain <id>]");
179
+ console.log(" or: node ./src/cli.js query slice <path> [--capability <id>] [--workflow <id>] [--projection <id>] [--widget <id>] [--entity <id>] [--journey <id>] [--domain <id>] [--task <id>] [--plan <id>] [--bug <id>]");
169
180
  console.log(" or: node ./src/cli.js query domain-list <path>");
170
181
  console.log(" or: node ./src/cli.js query domain-coverage <path> --domain <id>");
171
182
  console.log(" or: node ./src/cli.js query review-boundary <path> [--capability <id>] [--workflow <id>] [--projection <id>] [--widget <id>] [--entity <id>] [--journey <id>]");
@@ -179,7 +190,7 @@ export function printUsage(options = {}) {
179
190
  console.log(" or: node ./src/cli.js query proceed-decision <path> [--mode <id>] [--capability <id>] [--workflow <id>] [--projection <id>] [--widget <id>] [--entity <id>] [--journey <id>] [--surface <id>] [--from-topogram <path>]");
180
191
  console.log(" or: node ./src/cli.js query review-packet <path> [--mode <id>] [--capability <id>] [--workflow <id>] [--projection <id>] [--widget <id>] [--entity <id>] [--journey <id>] [--surface <id>] [--from-topogram <path>]");
181
192
  console.log(" or: node ./src/cli.js query next-action <path> [--mode <id>] [--capability <id>] [--workflow <id>] [--projection <id>] [--widget <id>] [--entity <id>] [--journey <id>] [--from-topogram <path>]");
182
- console.log(" or: node ./src/cli.js query single-agent-plan <path> --mode <id> [--capability <id>] [--workflow <id>] [--projection <id>] [--widget <id>] [--entity <id>] [--journey <id>] [--surface <id>] [--from-topogram <path>]");
193
+ console.log(" or: node ./src/cli.js query single-agent-plan <path> --mode <id> [--capability <id>] [--workflow <id>] [--projection <id>] [--widget <id>] [--entity <id>] [--journey <id>] [--surface <id>] [--task <id>] [--plan <id>] [--bug <id>] [--from-topogram <path>]");
183
194
  console.log(" or: node ./src/cli.js query multi-agent-plan <path> --mode import-adopt");
184
195
  console.log(" or: node ./src/cli.js query resolved-workflow-context <path> --mode <id> [--capability <id>] [--workflow <id>] [--projection <id>] [--widget <id>] [--entity <id>] [--journey <id>] [--surface <id>] [--provider <id>] [--preset <id>] [--from-topogram <path>]");
185
196
  console.log(" or: node ./src/cli.js query workflow-preset-activation <path> --mode <id> [--provider <id>] [--preset <id>] [--from-topogram <path>]");
@@ -30,6 +30,9 @@ export function cliMigrationError(args) {
30
30
  if (args[0] === "component") {
31
31
  return "Command 'topogram component' was renamed to 'topogram widget'.";
32
32
  }
33
+ if (args[0] === "migrate") {
34
+ return "Command 'topogram migrate workspace-folder' was removed. Use topo/ workspaces or configure topogram.project.json workspace to a non-legacy relative path.";
35
+ }
33
36
  for (const [oldArg, newArg] of RENAMED_CLI_ARGS) {
34
37
  if (args.includes(oldArg)) {
35
38
  return `CLI flag '${oldArg}' was renamed to '${newArg}'.`;
@@ -81,6 +81,7 @@ export function parseCliOptions(args, commandArgs) {
81
81
  domainId: optionValue(args, "--domain"),
82
82
  seamId: optionValue(args, "--seam"),
83
83
  taskId: optionValue(args, "--task"),
84
+ planId: optionValue(args, "--plan"),
84
85
  pitchId: optionValue(args, "--pitch"),
85
86
  requirementId: optionValue(args, "--requirement"),
86
87
  acceptanceId: optionValue(args, "--acceptance"),
@@ -135,6 +135,14 @@ export function acceptanceCriterionById(graph, id) {
135
135
  export function taskById(graph, id) {
136
136
  return (graph?.byKind?.task || []).find(/** @param {any} s */ (s) => s.id === id) || null;
137
137
  }
138
+ /**
139
+ * @param {import("./types.d.ts").ContextGraph} graph
140
+ * @param {string} id
141
+ * @returns {any}
142
+ */
143
+ export function planById(graph, id) {
144
+ return (graph?.byKind?.plan || []).find(/** @param {any} s */ (s) => s.id === id) || null;
145
+ }
138
146
  /**
139
147
  * @param {import("./types.d.ts").ContextGraph} graph
140
148
  * @param {string} id
@@ -207,10 +215,29 @@ export function summarizeTask(task) {
207
215
  status: task.status,
208
216
  priority: task.priority,
209
217
  work_type: task.workType,
218
+ disposition: task.disposition || null,
210
219
  claimed_by: (task.claimedBy || []).map(/** @param {any} r */ (r) => (typeof r === "string" ? r : r?.id)).filter(Boolean),
211
220
  domain: task.resolvedDomain ? task.resolvedDomain.id : null
212
221
  };
213
222
  }
223
+ /**
224
+ * @param {any} plan
225
+ * @returns {any}
226
+ */
227
+ export function summarizePlan(plan) {
228
+ if (!plan) return null;
229
+ const steps = plan.steps || [];
230
+ return {
231
+ id: plan.id,
232
+ name: plan.name,
233
+ status: plan.status,
234
+ priority: plan.priority,
235
+ task: plan.task?.id || null,
236
+ step_count: steps.length,
237
+ incomplete_steps: steps.filter(/** @param {any} step */ (step) => step.status !== "done" && step.status !== "skipped").map(/** @param {any} step */ (step) => step.id),
238
+ domain: plan.resolvedDomain ? plan.resolvedDomain.id : null
239
+ };
240
+ }
214
241
  /**
215
242
  * @param {any} bug
216
243
  * @returns {any}
@@ -436,13 +436,14 @@ export function ensureContextSelection(options = {}) {
436
436
  options.requirementId ? ["requirement", options.requirementId] : null,
437
437
  options.acceptanceId ? ["acceptance_criterion", options.acceptanceId] : null,
438
438
  options.taskId ? ["task", options.taskId] : null,
439
+ options.planId ? ["plan", options.planId] : null,
439
440
  options.bugId ? ["bug", options.bugId] : null,
440
441
  options.documentId ? ["document", options.documentId] : null
441
442
  ].filter(Boolean));
442
443
 
443
444
  if (selectors.length !== 1) {
444
445
  throw new Error(
445
- "Context selection requires exactly one of --capability, --workflow, --projection, --widget, --entity, --journey, --surface, --domain, --pitch, --requirement, --acceptance, --task, --bug, or --document"
446
+ "Context selection requires exactly one of --capability, --workflow, --projection, --widget, --entity, --journey, --surface, --domain, --pitch, --requirement, --acceptance, --task, --plan, --bug, or --document"
446
447
  );
447
448
  }
448
449
 
@@ -143,6 +143,7 @@ export type ContextSelectionOptions = {
143
143
  requirementId?: string | null;
144
144
  acceptanceId?: string | null;
145
145
  taskId?: string | null;
146
+ planId?: string | null;
146
147
  bugId?: string | null;
147
148
  documentId?: string | null;
148
149
  [key: string]: any;
@@ -9,6 +9,7 @@ export function getJourneyDoc(...args: any[]): any;
9
9
  export function getStatement(...args: any[]): any;
10
10
  export function getWorkflowDoc(...args: any[]): any;
11
11
  export function pitchById(...args: any[]): any;
12
+ export function planById(...args: any[]): any;
12
13
  export function relatedCapabilitiesForEntity(...args: any[]): any;
13
14
  export function relatedCapabilitiesForProjection(...args: any[]): any;
14
15
  export function relatedEntitiesForDomain(...args: any[]): any;
@@ -34,6 +35,7 @@ export function summarizeDocsByIds(...args: any[]): any;
34
35
  export function summarizeDocument(...args: any[]): any;
35
36
  export function summarizeDomain(...args: any[]): any;
36
37
  export function summarizePitch(...args: any[]): any;
38
+ export function summarizePlan(...args: any[]): any;
37
39
  export function summarizeProjection(...args: any[]): any;
38
40
  export function summarizeRequirement(...args: any[]): any;
39
41
  export function summarizeStatementsByIds(...args: any[]): any;
@@ -55,12 +55,14 @@ export {
55
55
  requirementById,
56
56
  acceptanceCriterionById,
57
57
  taskById,
58
+ planById,
58
59
  bugById,
59
60
  documentById,
60
61
  summarizePitch,
61
62
  summarizeRequirement,
62
63
  summarizeAcceptanceCriterion,
63
64
  summarizeTask,
65
+ summarizePlan,
64
66
  summarizeBug,
65
67
  summarizeDocument,
66
68
  getWorkflowDoc,