@topogram/cli 0.3.63 → 0.3.64

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 (121) hide show
  1. package/package.json +1 -1
  2. package/src/adoption/plan.d.ts +6 -0
  3. package/src/adoption/reporting.d.ts +10 -0
  4. package/src/adoption/review-groups.d.ts +6 -0
  5. package/src/agent-brief.d.ts +3 -0
  6. package/src/agent-brief.js +495 -0
  7. package/src/agent-ops/query-builders.d.ts +26 -0
  8. package/src/archive/archive.d.ts +2 -0
  9. package/src/archive/compact.d.ts +1 -0
  10. package/src/archive/unarchive.d.ts +1 -0
  11. package/src/catalog.d.ts +10 -0
  12. package/src/catalog.js +62 -66
  13. package/src/cli/catalog-alias.d.ts +1 -0
  14. package/src/cli/command-parser.js +38 -0
  15. package/src/cli/command-parsers/core.js +102 -0
  16. package/src/cli/command-parsers/generator.js +39 -0
  17. package/src/cli/command-parsers/import.js +44 -0
  18. package/src/cli/command-parsers/legacy-workflow.js +21 -0
  19. package/src/cli/command-parsers/project.js +47 -0
  20. package/src/cli/command-parsers/sdlc.js +47 -0
  21. package/src/cli/command-parsers/shared.js +51 -0
  22. package/src/cli/command-parsers/template.js +48 -0
  23. package/src/cli/commands/agent.js +47 -0
  24. package/src/cli/commands/catalog.js +617 -0
  25. package/src/cli/commands/check.js +268 -0
  26. package/src/cli/commands/doctor.js +268 -0
  27. package/src/cli/commands/emit.js +149 -0
  28. package/src/cli/commands/generate.js +96 -0
  29. package/src/cli/commands/generator-policy.js +785 -0
  30. package/src/cli/commands/generator.js +443 -0
  31. package/src/cli/commands/import-runner.js +157 -0
  32. package/src/cli/commands/import.js +1734 -0
  33. package/src/cli/commands/inspect.js +55 -0
  34. package/src/cli/commands/new.js +94 -0
  35. package/src/cli/commands/package.js +815 -0
  36. package/src/cli/commands/query.js +1302 -0
  37. package/src/cli/commands/release-rollout.js +257 -0
  38. package/src/cli/commands/release-shared.js +528 -0
  39. package/src/cli/commands/release-status.js +429 -0
  40. package/src/cli/commands/release.js +107 -0
  41. package/src/cli/commands/sdlc.js +168 -0
  42. package/src/cli/commands/setup.js +76 -0
  43. package/src/cli/commands/source.js +291 -0
  44. package/src/cli/commands/template-runner.js +198 -0
  45. package/src/cli/commands/template.js +2145 -0
  46. package/src/cli/commands/trust.js +219 -0
  47. package/src/cli/commands/version.js +40 -0
  48. package/src/cli/commands/widget.js +168 -0
  49. package/src/cli/commands/workflow.js +63 -0
  50. package/src/cli/dispatcher.js +392 -0
  51. package/src/cli/help-dispatch.js +188 -0
  52. package/src/cli/help.js +296 -0
  53. package/src/cli/migration-guidance.js +59 -0
  54. package/src/cli/options.js +96 -0
  55. package/src/cli/output-safety.js +107 -0
  56. package/src/cli/path-normalization.js +29 -0
  57. package/src/cli.js +47 -11711
  58. package/src/example-implementation.d.ts +2 -0
  59. package/src/format.d.ts +1 -0
  60. package/src/generator/check.d.ts +1 -0
  61. package/src/generator/context/bundle.d.ts +1 -0
  62. package/src/generator/context/shared.d.ts +2 -0
  63. package/src/generator/native/parity-bundle.js +2 -1
  64. package/src/generator/surfaces/web/html-escape.js +22 -0
  65. package/src/generator/surfaces/web/react.js +10 -8
  66. package/src/generator/surfaces/web/sveltekit.js +7 -5
  67. package/src/generator/surfaces/web/vanilla.js +8 -4
  68. package/src/generator.d.ts +2 -0
  69. package/src/github-client.js +520 -0
  70. package/src/import/core/shared.js +20 -62
  71. package/src/import/extractors/api/flutter-dio.js +4 -8
  72. package/src/import/extractors/api/react-native-repository.js +4 -8
  73. package/src/import/index.d.ts +4 -0
  74. package/src/import/provenance.d.ts +4 -0
  75. package/src/new-project.js +100 -11
  76. package/src/npm-safety.js +79 -0
  77. package/src/parser.d.ts +1 -0
  78. package/src/path-helpers.d.ts +1 -0
  79. package/src/path-helpers.js +20 -0
  80. package/src/project-config.js +1 -0
  81. package/src/reconcile/docs.d.ts +8 -0
  82. package/src/reconcile/journeys.d.ts +1 -0
  83. package/src/resolver.d.ts +1 -0
  84. package/src/runtime-support.js +29 -0
  85. package/src/sdlc/adopt.d.ts +1 -0
  86. package/src/sdlc/check.d.ts +1 -0
  87. package/src/sdlc/explain.d.ts +1 -0
  88. package/src/sdlc/release.d.ts +1 -0
  89. package/src/sdlc/scaffold.d.ts +1 -0
  90. package/src/sdlc/transition.d.ts +1 -0
  91. package/src/text-helpers.d.ts +6 -0
  92. package/src/text-helpers.js +245 -0
  93. package/src/topogram-config.js +306 -0
  94. package/src/validator.d.ts +2 -0
  95. package/src/workflows/adoption/index.js +26 -0
  96. package/src/workflows/docs-generate.js +262 -0
  97. package/src/workflows/docs-scan.js +703 -0
  98. package/src/workflows/docs.js +15 -0
  99. package/src/workflows/import-app/api.js +799 -0
  100. package/src/workflows/import-app/db.js +538 -0
  101. package/src/workflows/import-app/index.js +30 -0
  102. package/src/workflows/import-app/shared.js +218 -0
  103. package/src/workflows/import-app/ui.js +443 -0
  104. package/src/workflows/import-app/workflow.js +159 -0
  105. package/src/workflows/reconcile/adoption-plan.js +742 -0
  106. package/src/workflows/reconcile/auth.js +692 -0
  107. package/src/workflows/reconcile/bundle-core.js +600 -0
  108. package/src/workflows/reconcile/bundle-shared.js +75 -0
  109. package/src/workflows/reconcile/candidate-model.js +477 -0
  110. package/src/workflows/reconcile/canonical-surface.js +264 -0
  111. package/src/workflows/reconcile/gap-report.js +333 -0
  112. package/src/workflows/reconcile/ids.js +6 -0
  113. package/src/workflows/reconcile/impacts.js +625 -0
  114. package/src/workflows/reconcile/index.js +7 -0
  115. package/src/workflows/reconcile/renderers.js +461 -0
  116. package/src/workflows/reconcile/summary.js +90 -0
  117. package/src/workflows/reconcile/workflow.js +309 -0
  118. package/src/workflows/shared.js +189 -0
  119. package/src/workflows/types.d.ts +93 -0
  120. package/src/workflows.d.ts +1 -0
  121. package/src/workflows.js +10 -7652
@@ -0,0 +1,429 @@
1
+ // @ts-check
2
+
3
+ import { loadCatalog } from "../../catalog.js";
4
+ import { catalogRepoSlug } from "../../topogram-config.js";
5
+ import {
6
+ CLI_PACKAGE_NAME,
7
+ latestTopogramCliVersion,
8
+ readInstalledCliPackageVersion
9
+ } from "./package.js";
10
+ import {
11
+ discoverTopogramCliVersionConsumers,
12
+ expectedConsumerWorkflowName,
13
+ inspectConsumerCi,
14
+ inspectReleaseGitTag,
15
+ messageFromError,
16
+ summarizeConsumerCi,
17
+ summarizeConsumerPins
18
+ } from "./release-shared.js";
19
+
20
+ /**
21
+ * @typedef {Record<string, any>} AnyRecord
22
+ */
23
+
24
+ /**
25
+ * @param {{ cwd?: string, strict?: boolean }} [options]
26
+ * @returns {{ ok: boolean, strict: boolean, packageName: string, localVersion: string, latestVersion: string|null, currentPublished: boolean|null, git: ReturnType<typeof inspectReleaseGitTag>, consumerPins: ReturnType<typeof summarizeConsumerPins>, consumerCi: ReturnType<typeof summarizeConsumerCi>, consumers: Array<AnyRecord>, diagnostics: Array<AnyRecord>, errors: string[] }}
27
+ */
28
+ export function buildReleaseStatusPayload(options = {}) {
29
+ const cwd = options.cwd || process.cwd();
30
+ const strict = Boolean(options.strict);
31
+ const localVersion = readInstalledCliPackageVersion();
32
+ /** @type {Array<AnyRecord>} */
33
+ const diagnostics = [];
34
+ let latestVersion = null;
35
+ try {
36
+ latestVersion = latestTopogramCliVersion(cwd);
37
+ } catch (error) {
38
+ diagnostics.push({
39
+ code: "release_latest_unavailable",
40
+ severity: "warning",
41
+ message: messageFromError(error),
42
+ path: CLI_PACKAGE_NAME,
43
+ suggestedFix: "Check npmjs access and rerun `topogram release status`."
44
+ });
45
+ }
46
+ const git = inspectReleaseGitTag(localVersion, cwd);
47
+ diagnostics.push(...git.diagnostics);
48
+ const consumers = discoverTopogramCliVersionConsumers(cwd).map((consumer) => /** @type {AnyRecord} */ ({
49
+ ...consumer,
50
+ matchesLocal: consumer.version ? consumer.version === localVersion : null,
51
+ workflow: expectedConsumerWorkflowName(consumer.name),
52
+ ci: null
53
+ }));
54
+ if (strict) {
55
+ for (const consumer of /** @type {Array<any>} */ (consumers)) {
56
+ if (consumer.matchesLocal === true) {
57
+ consumer.ci = inspectConsumerCi(consumer, { strict: true });
58
+ diagnostics.push(...consumer.ci.diagnostics);
59
+ }
60
+ }
61
+ }
62
+ const consumerPins = summarizeConsumerPins(consumers);
63
+ const consumerCi = summarizeConsumerCi(consumers);
64
+ const currentPublished = latestVersion ? latestVersion === localVersion : null;
65
+ if (strict) {
66
+ diagnostics.push(...releaseStatusStrictDiagnostics({
67
+ localVersion,
68
+ latestVersion,
69
+ currentPublished,
70
+ git,
71
+ consumerPins,
72
+ consumerCi
73
+ }));
74
+ }
75
+ const errors = diagnostics
76
+ .filter((diagnostic) => diagnostic.severity === "error")
77
+ .map((diagnostic) => diagnostic.message);
78
+ return {
79
+ ok: errors.length === 0,
80
+ strict,
81
+ packageName: CLI_PACKAGE_NAME,
82
+ localVersion,
83
+ latestVersion,
84
+ currentPublished,
85
+ git,
86
+ consumerPins,
87
+ consumerCi,
88
+ consumers,
89
+ diagnostics,
90
+ errors
91
+ };
92
+ }
93
+
94
+ /**
95
+ * @param {{
96
+ * localVersion: string,
97
+ * latestVersion: string|null,
98
+ * currentPublished: boolean|null,
99
+ * git: ReturnType<typeof inspectReleaseGitTag>,
100
+ * consumerPins: ReturnType<typeof summarizeConsumerPins>,
101
+ * consumerCi: ReturnType<typeof summarizeConsumerCi>
102
+ * }} release
103
+ * @returns {Array<{ code: string, severity: "error", message: string, path: string, suggestedFix: string }>}
104
+ */
105
+ function releaseStatusStrictDiagnostics(release) {
106
+ /** @type {Array<{ code: string, severity: "error", message: string, path: string, suggestedFix: string }>} */
107
+ const diagnostics = [];
108
+ if (release.currentPublished !== true) {
109
+ diagnostics.push({
110
+ code: "release_latest_not_current",
111
+ severity: "error",
112
+ message: release.latestVersion
113
+ ? `${CLI_PACKAGE_NAME}@${release.localVersion} is not the latest published version (${release.latestVersion}).`
114
+ : `Latest published ${CLI_PACKAGE_NAME} version could not be verified.`,
115
+ path: CLI_PACKAGE_NAME,
116
+ suggestedFix: "Publish the current CLI package version or fix npm package registry auth, then rerun `topogram release status --strict`."
117
+ });
118
+ }
119
+ if (release.git.local !== true && release.git.remote !== true) {
120
+ diagnostics.push({
121
+ code: "release_local_tag_missing",
122
+ severity: "error",
123
+ message: `Release tag ${release.git.tag} is missing locally.`,
124
+ path: release.git.tag,
125
+ suggestedFix: `Fetch, create, or push ${release.git.tag} before treating this release as complete.`
126
+ });
127
+ }
128
+ if (release.git.remote !== true) {
129
+ diagnostics.push({
130
+ code: "release_remote_tag_missing",
131
+ severity: "error",
132
+ message: `Release tag ${release.git.tag} is missing on origin.`,
133
+ path: release.git.tag,
134
+ suggestedFix: `Push or create the remote ${release.git.tag} tag before treating this release as complete.`
135
+ });
136
+ }
137
+ if (release.consumerPins.allKnownPinned !== true) {
138
+ diagnostics.push({
139
+ code: "release_consumer_pins_not_current",
140
+ severity: "error",
141
+ message: `First-party consumers are not all pinned to ${CLI_PACKAGE_NAME}@${release.localVersion}.`,
142
+ path: "topogram-cli.version",
143
+ suggestedFix: "Roll first-party consumer repositories to the current CLI version before treating this release as complete."
144
+ });
145
+ }
146
+ if (release.consumerCi.allCheckedAndPassing !== true) {
147
+ diagnostics.push({
148
+ code: "release_consumer_ci_not_current",
149
+ severity: "error",
150
+ message: "First-party consumer verification workflows are not all passing on the checked-out consumer commits.",
151
+ path: "GitHub Actions",
152
+ suggestedFix: "Wait for or fix the consumer verification workflows, then rerun `topogram release status --strict`."
153
+ });
154
+ }
155
+ return diagnostics;
156
+ }
157
+
158
+ /**
159
+ * @param {ReturnType<typeof buildReleaseStatusPayload>} payload
160
+ * @returns {void}
161
+ */
162
+ export function printReleaseStatus(payload) {
163
+ console.log(payload.ok ? "Topogram release status passed." : "Topogram release status found issues.");
164
+ if (payload.strict) {
165
+ console.log("Strict: enabled");
166
+ }
167
+ console.log(`Package: ${payload.packageName}`);
168
+ console.log(`Local version: ${payload.localVersion}`);
169
+ console.log(`Latest published: ${payload.latestVersion || "unknown"}${payload.currentPublished === true ? " (current)" : payload.currentPublished === false ? " (differs)" : ""}`);
170
+ console.log(`Git tag: ${payload.git.tag} local=${labelBoolean(payload.git.local)} remote=${labelBoolean(payload.git.remote)}`);
171
+ console.log(
172
+ `Consumer pins: ${payload.consumerPins.pinned}/${payload.consumerPins.known} pinned, ` +
173
+ `${payload.consumerPins.matching} matching, ${payload.consumerPins.differing} differing, ${payload.consumerPins.missing} missing`
174
+ );
175
+ if (payload.strict) {
176
+ console.log(
177
+ `Consumer CI: ${payload.consumerCi.passing}/${payload.consumerCi.checked} passing, ` +
178
+ `${payload.consumerCi.failing} failing, ${payload.consumerCi.unavailable} unavailable, ${payload.consumerCi.skipped} skipped`
179
+ );
180
+ }
181
+ for (const consumer of payload.consumers) {
182
+ const status = consumer.matchesLocal === true
183
+ ? "matches"
184
+ : consumer.matchesLocal === false
185
+ ? "differs"
186
+ : "missing";
187
+ const ciStatus = consumer.ci?.run
188
+ ? `; ${consumer.ci.run.workflowName || consumer.workflow}: ${consumer.ci.run.status || "unknown"}/${consumer.ci.run.conclusion || "unknown"}`
189
+ : consumer.ci?.checked
190
+ ? `; ${consumer.workflow || "workflow"} unavailable`
191
+ : "";
192
+ console.log(`- ${consumer.name}: ${consumer.version || "missing"} (${status})${ciStatus}`);
193
+ if (consumer.ci?.run?.url) {
194
+ console.log(` CI: ${consumer.ci.run.url}`);
195
+ }
196
+ }
197
+ if (payload.diagnostics.length > 0) {
198
+ console.log("Diagnostics:");
199
+ for (const diagnostic of payload.diagnostics) {
200
+ const label = diagnostic.severity === "warning"
201
+ ? "Warning"
202
+ : diagnostic.severity === "info"
203
+ ? "Note"
204
+ : "Error";
205
+ console.log(`- ${label}: ${diagnostic.message}`);
206
+ if (diagnostic.suggestedFix) {
207
+ console.log(` Fix: ${diagnostic.suggestedFix}`);
208
+ }
209
+ }
210
+ }
211
+ }
212
+
213
+ /**
214
+ * @param {ReturnType<typeof buildReleaseStatusPayload>} payload
215
+ * @returns {string}
216
+ */
217
+ export function renderReleaseStatusMarkdown(payload) {
218
+ const matrix = buildReleaseMatrixCatalogPayload();
219
+ const catalogConsumer = payload.consumers.find((consumer) => consumer.name === "topograms");
220
+ const demoConsumer = payload.consumers.find((consumer) => consumer.name === "topogram-demo-todo");
221
+ const catalogSlug = catalogRepoSlug();
222
+ const lines = [
223
+ "# Known-Good Release Matrix",
224
+ "",
225
+ "This matrix is generated by `topogram release status --strict --write-report`.",
226
+ `Date checked: ${new Date().toISOString().slice(0, 10)}.`,
227
+ "Treat it as a dated release audit, not a floating compatibility promise.",
228
+ "",
229
+ "## Summary",
230
+ "",
231
+ `- Package: \`${payload.packageName}@${payload.localVersion}\``,
232
+ `- Latest published: \`${payload.latestVersion || "unknown"}\`${payload.currentPublished === true ? " (current)" : payload.currentPublished === false ? " (differs)" : ""}`,
233
+ `- Release tag: \`${payload.git.tag}\` (local=${labelBoolean(payload.git.local)}, remote=${labelBoolean(payload.git.remote)})`,
234
+ `- Consumer pins: ${payload.consumerPins.matching}/${payload.consumerPins.known} matching`,
235
+ `- Consumer CI: ${payload.consumerCi.passing}/${payload.consumerCi.checked} passing`,
236
+ `- Strict status: ${payload.ok ? "passed" : "failed"}`,
237
+ "",
238
+ "## Core",
239
+ "",
240
+ "| Package or Repo | Version or Commit | Verification |",
241
+ "| --- | --- | --- |",
242
+ `| \`${payload.packageName}\` | \`${payload.localVersion}\` | Publish CLI Package, strict release status, fresh npmjs smoke, and installed CLI smoke passed |`,
243
+ `| \`${catalogSlug}\` catalog | \`${releaseMatrixConsumerCommit(catalogConsumer)}\` | ${releaseMatrixConsumerVerification(catalogConsumer, "Catalog Verification", payload.localVersion)} |`,
244
+ `| \`topogram-demo-todo\` | \`${releaseMatrixConsumerCommit(demoConsumer)}\` | ${releaseMatrixConsumerVerification(demoConsumer, "Demo Verification", payload.localVersion)} |`,
245
+ "",
246
+ "## Catalog Entries",
247
+ "",
248
+ "| Catalog ID | Kind | Package | Version | Stack |",
249
+ "| --- | --- | --- | --- | --- |"
250
+ ];
251
+ if (matrix.entries.length > 0) {
252
+ for (const entry of matrix.entries) {
253
+ lines.push(`| \`${entry.id}\` | ${entry.kind} | \`${entry.package}\` | \`${entry.defaultVersion}\` | ${escapeMarkdownTableCell(entry.stack || "not declared")} |`);
254
+ }
255
+ } else {
256
+ lines.push("| unavailable | unavailable | unavailable | unavailable | Catalog could not be loaded for this report |");
257
+ }
258
+ lines.push(
259
+ "",
260
+ "## Generator Packages",
261
+ "",
262
+ "| Generator Package | Surface | Catalog usage |",
263
+ "| --- | --- | --- |"
264
+ );
265
+ if (matrix.generators.length > 0) {
266
+ for (const generator of matrix.generators) {
267
+ lines.push(`| \`${generator.package}\` | ${escapeMarkdownTableCell(generator.surface)} | ${escapeMarkdownTableCell(generator.catalogIds.join(", "))} |`);
268
+ }
269
+ } else {
270
+ lines.push("| unavailable | unavailable | Catalog generator metadata could not be loaded for this report |");
271
+ }
272
+ lines.push(
273
+ "",
274
+ "## Consumers",
275
+ "",
276
+ "| Repo | Pin | Workflow | Status | Run |",
277
+ "| --- | --- | --- | --- | --- |"
278
+ );
279
+ for (const consumer of payload.consumers) {
280
+ const workflow = consumer.workflow || consumer.ci?.expectedWorkflow || "";
281
+ const run = consumer.ci?.run;
282
+ const status = run ? `${run.status || "unknown"}/${run.conclusion || "unknown"}` : consumer.ci?.checked ? "unavailable" : "not checked";
283
+ const url = run?.url ? `[${run.databaseId || "run"}](${run.url})` : "";
284
+ lines.push(`| \`${consumer.name}\` | \`${consumer.version || "missing"}\` | ${escapeMarkdownTableCell(workflow)} | ${escapeMarkdownTableCell(status)} | ${url} |`);
285
+ }
286
+ lines.push(
287
+ "",
288
+ "## Consumer Proofs",
289
+ "",
290
+ "The external Todo demo is the canonical end-to-end consumer proof for the current catalog-backed workflow:",
291
+ "",
292
+ "```bash",
293
+ "topogram new ./todo-demo --template todo",
294
+ "cd ./todo-demo",
295
+ "npm install",
296
+ "npm run check",
297
+ "npm run generate",
298
+ "npm run app:compile",
299
+ "npm run verify",
300
+ "npm run app:runtime",
301
+ "```",
302
+ "",
303
+ "The demo CI also verifies `topogram new` from the default public catalog and from the repo-local catalog fixture. That prevents local fixtures from masking a broken published catalog alias."
304
+ );
305
+ const reportDiagnostics = [...matrix.diagnostics];
306
+ if (reportDiagnostics.length > 0) {
307
+ lines.push("", "## Report Diagnostics", "");
308
+ for (const diagnostic of reportDiagnostics) {
309
+ lines.push(`- **${diagnostic.severity || "warning"}** \`${diagnostic.code || "release_report_catalog_unavailable"}\`: ${diagnostic.message}`);
310
+ }
311
+ }
312
+ if (payload.diagnostics.length > 0) {
313
+ lines.push("", "## Diagnostics", "");
314
+ for (const diagnostic of payload.diagnostics) {
315
+ const label = diagnostic.severity === "warning"
316
+ ? "Warning"
317
+ : diagnostic.severity === "info"
318
+ ? "Note"
319
+ : "Error";
320
+ lines.push(`- **${label}** \`${diagnostic.code}\`: ${diagnostic.message}`);
321
+ if (diagnostic.suggestedFix) {
322
+ lines.push(` Fix: ${diagnostic.suggestedFix}`);
323
+ }
324
+ }
325
+ }
326
+ return `${lines.join("\n")}\n`;
327
+ }
328
+
329
+ /**
330
+ * @returns {{ entries: any[], generators: Array<{ package: string, surface: string, catalogIds: string[] }>, diagnostics: Array<AnyRecord> }}
331
+ */
332
+ function buildReleaseMatrixCatalogPayload() {
333
+ try {
334
+ const loaded = loadCatalog(null);
335
+ const entries = [...loaded.catalog.entries].sort((left, right) => {
336
+ if (left.kind !== right.kind) {
337
+ return left.kind === "template" ? -1 : 1;
338
+ }
339
+ return left.id.localeCompare(right.id);
340
+ });
341
+ const generatorMap = new Map();
342
+ for (const entry of entries) {
343
+ for (const packageName of Array.isArray(entry.generators) ? entry.generators : []) {
344
+ if (!generatorMap.has(packageName)) {
345
+ generatorMap.set(packageName, {
346
+ package: packageName,
347
+ surface: releaseMatrixGeneratorSurface(packageName),
348
+ catalogIds: []
349
+ });
350
+ }
351
+ generatorMap.get(packageName).catalogIds.push(entry.id);
352
+ }
353
+ }
354
+ const generators = [...generatorMap.values()].sort((left, right) => left.package.localeCompare(right.package));
355
+ return {
356
+ entries,
357
+ generators,
358
+ diagnostics: loaded.diagnostics || []
359
+ };
360
+ } catch (error) {
361
+ return {
362
+ entries: [],
363
+ generators: [],
364
+ diagnostics: [{
365
+ code: "release_report_catalog_unavailable",
366
+ severity: "warning",
367
+ message: messageFromError(error)
368
+ }]
369
+ };
370
+ }
371
+ }
372
+
373
+ /**
374
+ * @param {string} packageName
375
+ * @returns {string}
376
+ */
377
+ function releaseMatrixGeneratorSurface(packageName) {
378
+ if (packageName.includes("-web")) return "web";
379
+ if (packageName.includes("-api")) return "api";
380
+ if (packageName.includes("-db")) return "database";
381
+ if (packageName.includes("-native")) return "native";
382
+ return "not declared";
383
+ }
384
+
385
+ /**
386
+ * @param {any} consumer
387
+ * @returns {string}
388
+ */
389
+ function releaseMatrixConsumerCommit(consumer) {
390
+ return shortSha(consumer?.ci?.headSha || consumer?.ci?.run?.headSha || null) || "unknown";
391
+ }
392
+
393
+ /**
394
+ * @param {any} consumer
395
+ * @param {string} workflowName
396
+ * @param {string} version
397
+ * @returns {string}
398
+ */
399
+ function releaseMatrixConsumerVerification(consumer, workflowName, version) {
400
+ const status = consumer?.ci?.run
401
+ ? `${consumer.ci.run.status || "unknown"}/${consumer.ci.run.conclusion || "unknown"}`
402
+ : "not checked";
403
+ return `${workflowName}: ${status}; pinned ${CLI_PACKAGE_NAME}@${version}`;
404
+ }
405
+
406
+ /**
407
+ * @param {string|null|undefined} value
408
+ * @returns {string|null}
409
+ */
410
+ function shortSha(value) {
411
+ const text = String(value || "").trim();
412
+ return text ? text.slice(0, 7) : null;
413
+ }
414
+
415
+ /**
416
+ * @param {string|null|undefined} value
417
+ * @returns {string}
418
+ */
419
+ function escapeMarkdownTableCell(value) {
420
+ return String(value || "").replace(/\|/g, "\\|").replace(/\n/g, "<br>");
421
+ }
422
+
423
+ /**
424
+ * @param {boolean|null} value
425
+ * @returns {string}
426
+ */
427
+ function labelBoolean(value) {
428
+ return value === true ? "yes" : value === false ? "no" : "unknown";
429
+ }
@@ -0,0 +1,107 @@
1
+ // @ts-check
2
+
3
+ import fs from "node:fs";
4
+ import path from "node:path";
5
+
6
+ import { stableStringify } from "../../format.js";
7
+ import {
8
+ buildReleaseRollConsumersPayload,
9
+ printReleaseRollConsumers
10
+ } from "./release-rollout.js";
11
+ import {
12
+ buildReleaseStatusPayload,
13
+ printReleaseStatus,
14
+ renderReleaseStatusMarkdown
15
+ } from "./release-status.js";
16
+
17
+ /**
18
+ * @returns {void}
19
+ */
20
+ export function printReleaseHelp() {
21
+ console.log("Usage: topogram release status [--json] [--strict] [--markdown|--write-report <path>]");
22
+ console.log(" or: topogram release roll-consumers <version|--latest> [--json] [--no-push] [--watch]");
23
+ console.log("");
24
+ console.log("Checks the local CLI version, latest published package version, release tag, first-party consumer pins, and strict consumer CI state.");
25
+ console.log("Rolls first-party consumers to a published CLI version, runs their checks, commits, pushes, and can wait for current workflow runs.");
26
+ console.log("");
27
+ console.log("Examples:");
28
+ console.log(" topogram release status");
29
+ console.log(" topogram release status --json");
30
+ console.log(" topogram release status --strict");
31
+ console.log(" topogram release status --strict --write-report ./docs/release-matrix.md");
32
+ console.log(" topogram release roll-consumers 0.3.46 --watch");
33
+ console.log(" topogram release roll-consumers --latest --watch");
34
+ console.log("");
35
+ console.log("Release preparation and publishing are repo-level tasks in the Topogram source checkout:");
36
+ console.log(" npm run release:prepare -- <version>");
37
+ console.log(" npm run release:check");
38
+ console.log(" GitHub Actions: Publish CLI Package");
39
+ }
40
+
41
+ export {
42
+ buildReleaseRollConsumersPayload,
43
+ printReleaseRollConsumers
44
+ };
45
+ export {
46
+ buildReleaseStatusPayload,
47
+ printReleaseStatus,
48
+ renderReleaseStatusMarkdown
49
+ };
50
+
51
+ /**
52
+ * @param {{ commandArgs: Record<string, any>, args: string[], json?: boolean }} context
53
+ * @returns {number}
54
+ */
55
+ export function runReleaseCommand(context) {
56
+ const { commandArgs, args, json = false } = context;
57
+ const command = commandArgs.releaseCommand;
58
+ const reportIndex = args.indexOf("--write-report");
59
+ const reportPath = reportIndex >= 0 &&
60
+ args[reportIndex + 1] &&
61
+ !args[reportIndex + 1].startsWith("-")
62
+ ? args[reportIndex + 1]
63
+ : null;
64
+ if (command === "status") {
65
+ if (args.includes("--write-report") && !reportPath) {
66
+ console.error("Missing required --write-report <path>.");
67
+ printReleaseHelp();
68
+ return 1;
69
+ }
70
+ const payload = buildReleaseStatusPayload({ strict: args.includes("--strict") });
71
+ if (reportPath) {
72
+ const target = path.resolve(reportPath);
73
+ fs.mkdirSync(path.dirname(target), { recursive: true });
74
+ fs.writeFileSync(target, renderReleaseStatusMarkdown(payload), "utf8");
75
+ }
76
+ if (json) {
77
+ console.log(stableStringify(payload));
78
+ } else if (args.includes("--markdown")) {
79
+ console.log(renderReleaseStatusMarkdown(payload).trimEnd());
80
+ } else {
81
+ printReleaseStatus(payload);
82
+ if (reportPath) {
83
+ console.log(`Report: ${path.resolve(reportPath)}`);
84
+ }
85
+ }
86
+ return payload.ok ? 0 : 1;
87
+ }
88
+
89
+ if (command === "roll-consumers") {
90
+ const push = !args.includes("--no-push");
91
+ const watch = args.includes("--watch");
92
+ if (watch && !push) {
93
+ console.error("Use either --watch or --no-push, not both.");
94
+ printReleaseHelp();
95
+ return 1;
96
+ }
97
+ const payload = buildReleaseRollConsumersPayload(commandArgs.releaseRollVersion, { push, watch });
98
+ if (json) {
99
+ console.log(stableStringify(payload));
100
+ } else {
101
+ printReleaseRollConsumers(payload);
102
+ }
103
+ return payload.ok ? 0 : 1;
104
+ }
105
+
106
+ throw new Error(`Unknown release command '${command}'`);
107
+ }
@@ -0,0 +1,168 @@
1
+ // @ts-check
2
+
3
+ import path from "node:path";
4
+
5
+ import { stableStringify } from "../../format.js";
6
+ import { parsePath } from "../../parser.js";
7
+ import { resolveWorkspace } from "../../resolver.js";
8
+ import { formatValidationErrors } from "../../validator.js";
9
+
10
+ /**
11
+ * @typedef {Record<string, any>} AnyRecord
12
+ */
13
+
14
+ /**
15
+ * @param {string[]} args
16
+ * @param {string} flag
17
+ * @returns {string|null}
18
+ */
19
+ function flagValue(args, flag) {
20
+ const index = args.indexOf(flag);
21
+ return index >= 0 ? args[index + 1] || null : null;
22
+ }
23
+
24
+ /**
25
+ * @param {string[]} args
26
+ * @returns {boolean}
27
+ */
28
+ function includeHistory(args) {
29
+ return args.includes("--history") || args.includes("--include-history");
30
+ }
31
+
32
+ /**
33
+ * @param {string} sdlcRoot
34
+ * @returns {AnyRecord|null}
35
+ */
36
+ function resolveSdlcWorkspace(sdlcRoot) {
37
+ const ast = parsePath(sdlcRoot);
38
+ const resolved = resolveWorkspace(ast);
39
+ if (!resolved.ok) {
40
+ console.error(formatValidationErrors(resolved.validation));
41
+ return null;
42
+ }
43
+ return resolved;
44
+ }
45
+
46
+ /**
47
+ * Runs `topogram sdlc ...` commands and the legacy top-level `topogram release`
48
+ * command.
49
+ *
50
+ * @param {{
51
+ * commandArgs: AnyRecord,
52
+ * args: string[],
53
+ * inputPath: string|null|undefined
54
+ * }} context
55
+ * @returns {Promise<number>}
56
+ */
57
+ export async function runSdlcCommand(context) {
58
+ const { commandArgs, args } = context;
59
+ const sdlcRoot = path.resolve(context.inputPath || ".");
60
+ const actor = flagValue(args, "--actor");
61
+ const note = flagValue(args, "--note");
62
+ const status = flagValue(args, "--status");
63
+ const before = flagValue(args, "--before");
64
+ const appVersion = flagValue(args, "--app-version");
65
+ const sinceTag = flagValue(args, "--since-tag");
66
+ const dryRun = args.includes("--dry-run");
67
+ const strict = args.includes("--strict");
68
+
69
+ if (commandArgs.sdlcCommand === "transition") {
70
+ const { transitionStatement } = await import("../../sdlc/transition.js");
71
+ const result = transitionStatement(sdlcRoot, commandArgs.sdlcId, commandArgs.sdlcTargetStatus, {
72
+ actor,
73
+ note,
74
+ dryRun
75
+ });
76
+ console.log(stableStringify(result));
77
+ return result.ok ? 0 : 1;
78
+ }
79
+
80
+ if (commandArgs.sdlcCommand === "check") {
81
+ const { checkWorkspace } = await import("../../sdlc/check.js");
82
+ const resolved = resolveSdlcWorkspace(sdlcRoot);
83
+ if (!resolved) {
84
+ return 1;
85
+ }
86
+ const result = checkWorkspace(sdlcRoot, resolved);
87
+ console.log(stableStringify(result));
88
+ return strict && (!result.ok || result.warnings.length > 0) ? 1 : 0;
89
+ }
90
+
91
+ if (commandArgs.sdlcCommand === "explain") {
92
+ const { explain } = await import("../../sdlc/explain.js");
93
+ const resolved = resolveSdlcWorkspace(sdlcRoot);
94
+ if (!resolved) {
95
+ return 1;
96
+ }
97
+ const result = explain(sdlcRoot, resolved, commandArgs.sdlcId, {
98
+ includeHistory: includeHistory(args)
99
+ });
100
+ if (args.includes("--brief") && result.ok) {
101
+ console.log(stableStringify({
102
+ id: result.id,
103
+ status: result.status,
104
+ next_action: result.next_action
105
+ }));
106
+ } else {
107
+ console.log(stableStringify(result));
108
+ }
109
+ return result.ok ? 0 : 1;
110
+ }
111
+
112
+ if (commandArgs.sdlcCommand === "archive") {
113
+ const { archiveBatch, archiveEligibleStatements } = await import("../../archive/archive.js");
114
+ const resolved = resolveSdlcWorkspace(sdlcRoot);
115
+ if (!resolved) {
116
+ return 1;
117
+ }
118
+ const ids = archiveEligibleStatements(resolved, {
119
+ before,
120
+ statuses: status ? status.split(",") : null
121
+ });
122
+ const result = archiveBatch(sdlcRoot, ids, { dryRun, by: actor, reason: note });
123
+ console.log(stableStringify({ candidates: ids, ...result }));
124
+ return result.ok ? 0 : 1;
125
+ }
126
+
127
+ if (commandArgs.sdlcCommand === "unarchive") {
128
+ const { unarchive } = await import("../../archive/unarchive.js");
129
+ const result = unarchive(sdlcRoot, commandArgs.sdlcId, {});
130
+ console.log(stableStringify(result));
131
+ return result.ok ? 0 : 1;
132
+ }
133
+
134
+ if (commandArgs.sdlcCommand === "compact") {
135
+ const { compact } = await import("../../archive/compact.js");
136
+ const result = compact(path.resolve(commandArgs.sdlcArchiveFile));
137
+ console.log(stableStringify(result));
138
+ return result.ok ? 0 : 1;
139
+ }
140
+
141
+ if (commandArgs.sdlcCommand === "new") {
142
+ const { scaffoldNew } = await import("../../sdlc/scaffold.js");
143
+ const result = scaffoldNew(sdlcRoot, commandArgs.sdlcNewKind, commandArgs.sdlcNewSlug);
144
+ console.log(stableStringify(result));
145
+ return result.ok ? 0 : 1;
146
+ }
147
+
148
+ if (commandArgs.sdlcCommand === "adopt") {
149
+ const { sdlcAdopt } = await import("../../sdlc/adopt.js");
150
+ const result = sdlcAdopt(sdlcRoot);
151
+ console.log(stableStringify(result));
152
+ return result.ok ? 0 : 1;
153
+ }
154
+
155
+ if (commandArgs.sdlcCommand === "release") {
156
+ const { runRelease } = await import("../../sdlc/release.js");
157
+ const result = runRelease(sdlcRoot, {
158
+ appVersion,
159
+ sinceTag,
160
+ dryRun,
161
+ actor
162
+ });
163
+ console.log(stableStringify(result));
164
+ return result.ok ? 0 : 1;
165
+ }
166
+
167
+ throw new Error(`Unknown sdlc command '${commandArgs.sdlcCommand}'`);
168
+ }