@topogram/cli 0.3.62 → 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,306 @@
1
+ // @ts-check
2
+
3
+ import fs from "node:fs";
4
+ import path from "node:path";
5
+
6
+ export const TOPOGRAM_CONFIG_FILE = "topogram.config.json";
7
+
8
+ export const DEFAULT_FIRST_PARTY_GENERATOR_REPOS = [
9
+ "topogram-generator-express-api",
10
+ "topogram-generator-hono-api",
11
+ "topogram-generator-postgres-db",
12
+ "topogram-generator-react-web",
13
+ "topogram-generator-sqlite-db",
14
+ "topogram-generator-sveltekit-web",
15
+ "topogram-generator-swiftui-native",
16
+ "topogram-generator-vanilla-web"
17
+ ];
18
+
19
+ export const DEFAULT_RELEASE_CONSUMER_REPOS = [
20
+ ...DEFAULT_FIRST_PARTY_GENERATOR_REPOS,
21
+ "topogram-starters",
22
+ "topogram-template-todo",
23
+ "topogram-demo-todo",
24
+ "topogram-hello",
25
+ "topograms"
26
+ ];
27
+
28
+ export const DEFAULT_RELEASE_CONSUMER_WORKFLOWS = {
29
+ "topogram-generator-express-api": "Generator Verification",
30
+ "topogram-generator-hono-api": "Generator Verification",
31
+ "topogram-generator-postgres-db": "Generator Verification",
32
+ "topogram-generator-react-web": "Generator Verification",
33
+ "topogram-generator-sqlite-db": "Generator Verification",
34
+ "topogram-generator-sveltekit-web": "Generator Verification",
35
+ "topogram-generator-swiftui-native": "Generator Verification",
36
+ "topogram-generator-vanilla-web": "Generator Verification",
37
+ "topogram-starters": "Starter Verification",
38
+ "topogram-template-todo": "Template Verification",
39
+ "topogram-demo-todo": "Demo Verification",
40
+ "topogram-hello": "Topogram Package Verification",
41
+ "topograms": "Catalog Verification"
42
+ };
43
+
44
+ export const DEFAULT_RELEASE_CONSUMER_WORKFLOW_JOBS = {
45
+ topograms: [
46
+ "Validate catalog",
47
+ "Smoke native starter",
48
+ "Smoke starter alias (hello-web)",
49
+ "Smoke starter alias (hello-api)",
50
+ "Smoke starter alias (hello-db)",
51
+ "Smoke starter alias (web-api)",
52
+ "Smoke starter alias (web-api-db)"
53
+ ]
54
+ };
55
+
56
+ export const DEFAULT_TOPOGRAM_CONFIG = {
57
+ github: {
58
+ owner: "attebury",
59
+ repo: "topogram"
60
+ },
61
+ catalog: {
62
+ owner: "attebury",
63
+ repo: "topograms",
64
+ ref: "main",
65
+ path: "topograms.catalog.json",
66
+ source: null
67
+ },
68
+ release: {
69
+ consumers: DEFAULT_RELEASE_CONSUMER_REPOS,
70
+ workflows: DEFAULT_RELEASE_CONSUMER_WORKFLOWS,
71
+ workflowJobs: DEFAULT_RELEASE_CONSUMER_WORKFLOW_JOBS
72
+ }
73
+ };
74
+
75
+ export const DEFAULT_CATALOG_SOURCE = `https://raw.githubusercontent.com/${DEFAULT_TOPOGRAM_CONFIG.catalog.owner}/${DEFAULT_TOPOGRAM_CONFIG.catalog.repo}/${DEFAULT_TOPOGRAM_CONFIG.catalog.ref}/${DEFAULT_TOPOGRAM_CONFIG.catalog.path}`;
76
+
77
+ /**
78
+ * @typedef {Object} TopogramRuntimeConfig
79
+ * @property {{ owner: string, repo: string }} github
80
+ * @property {{ owner: string, repo: string, ref: string, path: string, source: string|null }} catalog
81
+ * @property {{ consumers: string[], workflows: Record<string, string>, workflowJobs: Record<string, string[]> }} release
82
+ */
83
+
84
+ /**
85
+ * @param {string} cwd
86
+ * @returns {string|null}
87
+ */
88
+ export function findTopogramConfigFile(cwd = process.cwd()) {
89
+ if (process.env.TOPOGRAM_CONFIG_PATH) {
90
+ return path.resolve(process.env.TOPOGRAM_CONFIG_PATH);
91
+ }
92
+ let current = path.resolve(cwd);
93
+ while (true) {
94
+ const candidate = path.join(current, TOPOGRAM_CONFIG_FILE);
95
+ if (fs.existsSync(candidate)) {
96
+ return candidate;
97
+ }
98
+ const parent = path.dirname(current);
99
+ if (parent === current) {
100
+ return null;
101
+ }
102
+ current = parent;
103
+ }
104
+ }
105
+
106
+ /**
107
+ * @param {string} cwd
108
+ * @returns {Record<string, any>}
109
+ */
110
+ export function readTopogramConfigFile(cwd = process.cwd()) {
111
+ const filePath = findTopogramConfigFile(cwd);
112
+ if (!filePath) {
113
+ return {};
114
+ }
115
+ return JSON.parse(fs.readFileSync(filePath, "utf8"));
116
+ }
117
+
118
+ /**
119
+ * @param {string|null|undefined} value
120
+ * @returns {string[]|null}
121
+ */
122
+ function parseListEnv(value) {
123
+ if (!value) {
124
+ return null;
125
+ }
126
+ return String(value)
127
+ .split(",")
128
+ .map((item) => item.trim())
129
+ .filter(Boolean);
130
+ }
131
+
132
+ /**
133
+ * @param {string|null|undefined} value
134
+ * @returns {Record<string, any>|null}
135
+ */
136
+ function parseJsonEnv(value) {
137
+ if (!value) {
138
+ return null;
139
+ }
140
+ return JSON.parse(value);
141
+ }
142
+
143
+ /**
144
+ * @param {Record<string, any>} fileConfig
145
+ * @returns {Record<string, any>}
146
+ */
147
+ function envConfig(fileConfig = {}) {
148
+ const consumers = parseListEnv(process.env.TOPOGRAM_RELEASE_CONSUMERS);
149
+ const workflows = parseJsonEnv(process.env.TOPOGRAM_RELEASE_WORKFLOWS || process.env.TOPOGRAM_RELEASE_CONSUMER_WORKFLOWS_JSON);
150
+ const workflowJobs = parseJsonEnv(process.env.TOPOGRAM_RELEASE_WORKFLOW_JOBS || process.env.TOPOGRAM_RELEASE_CONSUMER_WORKFLOW_JOBS_JSON);
151
+ return {
152
+ github: {
153
+ owner: process.env.TOPOGRAM_GITHUB_OWNER || fileConfig.github?.owner,
154
+ repo: process.env.TOPOGRAM_GITHUB_REPO || process.env.TOPOGRAM_REPO_NAME || fileConfig.github?.repo
155
+ },
156
+ catalog: {
157
+ owner: process.env.TOPOGRAM_CATALOG_OWNER || fileConfig.catalog?.owner,
158
+ repo: process.env.TOPOGRAM_CATALOG_REPO || fileConfig.catalog?.repo,
159
+ ref: process.env.TOPOGRAM_CATALOG_REF || fileConfig.catalog?.ref,
160
+ path: process.env.TOPOGRAM_CATALOG_PATH || fileConfig.catalog?.path,
161
+ source: fileConfig.catalog?.source
162
+ },
163
+ release: {
164
+ consumers: consumers || fileConfig.release?.consumers,
165
+ workflows: workflows || fileConfig.release?.workflows,
166
+ workflowJobs: workflowJobs || fileConfig.release?.workflowJobs
167
+ }
168
+ };
169
+ }
170
+
171
+ /**
172
+ * @param {unknown} value
173
+ * @param {string[]} fallback
174
+ * @returns {string[]}
175
+ */
176
+ function normalizeStringList(value, fallback) {
177
+ if (!Array.isArray(value)) {
178
+ return [...fallback];
179
+ }
180
+ const items = value.map((item) => String(item || "").trim()).filter(Boolean);
181
+ return [...new Set(items)];
182
+ }
183
+
184
+ /**
185
+ * @param {unknown} value
186
+ * @param {Record<string, string>} fallback
187
+ * @returns {Record<string, string>}
188
+ */
189
+ function normalizeStringMap(value, fallback) {
190
+ const output = { ...fallback };
191
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
192
+ return output;
193
+ }
194
+ for (const [key, item] of Object.entries(value)) {
195
+ const name = String(key || "").trim();
196
+ const text = String(item || "").trim();
197
+ if (name && text) {
198
+ output[name] = text;
199
+ }
200
+ }
201
+ return output;
202
+ }
203
+
204
+ /**
205
+ * @param {unknown} value
206
+ * @param {Record<string, string[]>} fallback
207
+ * @returns {Record<string, string[]>}
208
+ */
209
+ function normalizeStringListMap(value, fallback) {
210
+ const output = Object.fromEntries(Object.entries(fallback).map(([key, items]) => [key, [...items]]));
211
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
212
+ return output;
213
+ }
214
+ for (const [key, item] of Object.entries(value)) {
215
+ const name = String(key || "").trim();
216
+ if (!name) {
217
+ continue;
218
+ }
219
+ output[name] = normalizeStringList(item, []);
220
+ }
221
+ return output;
222
+ }
223
+
224
+ /**
225
+ * @param {string} cwd
226
+ * @returns {TopogramRuntimeConfig}
227
+ */
228
+ export function topogramRuntimeConfig(cwd = process.cwd()) {
229
+ const fileConfig = readTopogramConfigFile(cwd);
230
+ const overrides = envConfig(fileConfig);
231
+ return {
232
+ github: {
233
+ owner: overrides.github.owner || DEFAULT_TOPOGRAM_CONFIG.github.owner,
234
+ repo: overrides.github.repo || DEFAULT_TOPOGRAM_CONFIG.github.repo
235
+ },
236
+ catalog: {
237
+ owner: overrides.catalog.owner || DEFAULT_TOPOGRAM_CONFIG.catalog.owner,
238
+ repo: overrides.catalog.repo || DEFAULT_TOPOGRAM_CONFIG.catalog.repo,
239
+ ref: overrides.catalog.ref || DEFAULT_TOPOGRAM_CONFIG.catalog.ref,
240
+ path: overrides.catalog.path || DEFAULT_TOPOGRAM_CONFIG.catalog.path,
241
+ source: overrides.catalog.source || DEFAULT_TOPOGRAM_CONFIG.catalog.source
242
+ },
243
+ release: {
244
+ consumers: normalizeStringList(overrides.release.consumers, DEFAULT_TOPOGRAM_CONFIG.release.consumers),
245
+ workflows: normalizeStringMap(overrides.release.workflows, DEFAULT_TOPOGRAM_CONFIG.release.workflows),
246
+ workflowJobs: normalizeStringListMap(overrides.release.workflowJobs, DEFAULT_TOPOGRAM_CONFIG.release.workflowJobs)
247
+ }
248
+ };
249
+ }
250
+
251
+ /**
252
+ * @param {TopogramRuntimeConfig} [config]
253
+ * @returns {string}
254
+ */
255
+ export function defaultCatalogSource(config = topogramRuntimeConfig()) {
256
+ if (config.catalog.source) {
257
+ return config.catalog.source;
258
+ }
259
+ return `https://raw.githubusercontent.com/${config.catalog.owner}/${config.catalog.repo}/${config.catalog.ref}/${config.catalog.path}`;
260
+ }
261
+
262
+ /**
263
+ * @param {string|null|undefined} repo
264
+ * @param {string} [cwd]
265
+ * @param {string|null|undefined} owner
266
+ * @returns {string}
267
+ */
268
+ export function githubRepoSlug(repo, cwd = process.cwd(), owner = null) {
269
+ const config = topogramRuntimeConfig(cwd);
270
+ return `${owner || config.github.owner}/${repo || config.github.repo}`;
271
+ }
272
+
273
+ /**
274
+ * @param {string} [cwd]
275
+ * @returns {string}
276
+ */
277
+ export function catalogRepoSlug(cwd = process.cwd()) {
278
+ const config = topogramRuntimeConfig(cwd);
279
+ return githubRepoSlug(config.catalog.repo, cwd, config.catalog.owner);
280
+ }
281
+
282
+ /**
283
+ * @param {string} [cwd]
284
+ * @returns {string[]}
285
+ */
286
+ export function releaseConsumerRepos(cwd = process.cwd()) {
287
+ return [...topogramRuntimeConfig(cwd).release.consumers];
288
+ }
289
+
290
+ /**
291
+ * @param {string} name
292
+ * @param {string} [cwd]
293
+ * @returns {string|null}
294
+ */
295
+ export function releaseConsumerWorkflowName(name, cwd = process.cwd()) {
296
+ return topogramRuntimeConfig(cwd).release.workflows[name] || null;
297
+ }
298
+
299
+ /**
300
+ * @param {string} name
301
+ * @param {string} [cwd]
302
+ * @returns {string[]}
303
+ */
304
+ export function releaseConsumerWorkflowJobs(name, cwd = process.cwd()) {
305
+ return [...(topogramRuntimeConfig(cwd).release.workflowJobs[name] || [])];
306
+ }
@@ -0,0 +1,2 @@
1
+ export function formatValidationErrors(validation: any, configPath?: string): string;
2
+ export function validateWorkspace(ast: any): any;
@@ -0,0 +1,26 @@
1
+ // @ts-check
2
+ import {
3
+ buildAdoptionStatusFiles as buildAdoptionStatusFilesReport,
4
+ buildAdoptionStatusSummary as buildAdoptionStatusSummaryReport
5
+ } from "../../adoption/reporting.js";
6
+ import { selectNextBundle } from "../../adoption/review-groups.js";
7
+ import {
8
+ formatDocDriftSummaryInline,
9
+ formatDocLinkSuggestionInline,
10
+ formatDocMetadataPatchInline,
11
+ reconcileWorkflow
12
+ } from "../reconcile/index.js";
13
+ import { normalizeWorkspacePaths } from "../shared.js";
14
+
15
+ /** @param {string} inputPath @returns {any} */
16
+ export function adoptionStatusWorkflow(inputPath) {
17
+ const reconcile = reconcileWorkflow(inputPath);
18
+ const report = reconcile.summary;
19
+ const summary = buildAdoptionStatusSummaryReport(report, selectNextBundle);
20
+ const files = buildAdoptionStatusFilesReport(summary, formatDocLinkSuggestionInline, formatDocDriftSummaryInline, formatDocMetadataPatchInline);
21
+ return {
22
+ summary,
23
+ files,
24
+ defaultOutDir: normalizeWorkspacePaths(inputPath).topogramRoot
25
+ };
26
+ }
@@ -0,0 +1,262 @@
1
+ // @ts-check
2
+ import fs from "node:fs";
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 { buildJourneyDrafts as buildJourneyDraftsReconcile } from "../reconcile/journeys.js";
9
+ import { relativeTo } from "../path-helpers.js";
10
+ import { ensureTrailingNewline, titleCase } from "../text-helpers.js";
11
+ import { listFilesRecursive, markdownTitle, normalizeWorkspacePaths, renderMarkdownDoc } from "./shared.js";
12
+
13
+ /** @param {string} kind @returns {any} */
14
+ export function docDirForKind(kind) {
15
+ if (kind === "glossary") {
16
+ return "glossary";
17
+ }
18
+ if (kind === "workflow") {
19
+ return "workflows";
20
+ }
21
+ if (kind === "journey") {
22
+ return "journeys";
23
+ }
24
+ return "reports";
25
+ }
26
+
27
+ /** @param {ResolvedGraph} graph @returns {any} */
28
+ export function generateDocsBundleFromGraph(graph) {
29
+ /** @type {WorkflowFiles} */
30
+ /** @type {WorkflowFiles} */
31
+ const files = {};
32
+ for (const entity of graph.byKind.entity || []) {
33
+ const id = entity.id.replace(/^entity_/, "");
34
+ /** @type {WorkflowRecord} */
35
+ const metadata = {
36
+ id,
37
+ kind: "glossary",
38
+ title: entity.name || titleCase(id),
39
+ status: "canonical",
40
+ summary: entity.description || `Generated glossary entry for ${entity.id}.`,
41
+ related_entities: [entity.id],
42
+ source_of_truth: "canonical",
43
+ confidence: "high",
44
+ review_required: false,
45
+ tags: ["generated", "glossary"]
46
+ };
47
+ const body = [
48
+ entity.description || `Canonical entity \`${entity.id}\`.`,
49
+ "",
50
+ "Fields:",
51
+ ...(entity.fields || []).map((/** @type {any} */ field) => `- \`${field.name}\` (${field.fieldType}) ${field.required ? "required" : "optional"}`)
52
+ ].join("\n");
53
+ files[`glossary/${id}.md`] = renderMarkdownDoc(metadata, body);
54
+ }
55
+
56
+ for (const capability of graph.byKind.capability || []) {
57
+ const writes = [...capability.creates, ...capability.updates, ...capability.deletes];
58
+ if (writes.length === 0) {
59
+ continue;
60
+ }
61
+ const id = capability.id.replace(/^cap_/, "");
62
+ const relatedEntities = [...new Set(writes.map((/** @type {any} */ entry) => entry.id).filter(Boolean))];
63
+ const metadata = {
64
+ id,
65
+ kind: "workflow",
66
+ title: capability.name || titleCase(id),
67
+ status: "canonical",
68
+ summary: capability.description || `Generated workflow entry for ${capability.id}.`,
69
+ related_capabilities: [capability.id],
70
+ related_entities: relatedEntities,
71
+ source_of_truth: "canonical",
72
+ confidence: "high",
73
+ review_required: false,
74
+ tags: ["generated", "workflow"]
75
+ };
76
+ const body = [
77
+ capability.description || `Canonical workflow surface for \`${capability.id}\`.`,
78
+ "",
79
+ `Actors: ${(capability.actors || []).map((/** @type {any} */ actor) => `\`${actor.id}\``).join(", ") || "_none_"}`,
80
+ `Creates: ${(capability.creates || []).map((/** @type {any} */ ref) => `\`${ref.id}\``).join(", ") || "_none_"}`,
81
+ `Updates: ${(capability.updates || []).map((/** @type {any} */ ref) => `\`${ref.id}\``).join(", ") || "_none_"}`,
82
+ `Deletes: ${(capability.deletes || []).map((/** @type {any} */ ref) => `\`${ref.id}\``).join(", ") || "_none_"}`,
83
+ `Input: ${capability.input?.id ? `\`${capability.input.id}\`` : "_none_"}`,
84
+ `Output: ${capability.output?.id ? `\`${capability.output.id}\`` : "_none_"}`
85
+ ].join("\n\n");
86
+ files[`workflows/${id}.md`] = renderMarkdownDoc(metadata, body);
87
+ }
88
+
89
+ const reportMetadata = {
90
+ id: "model_overview",
91
+ kind: "report",
92
+ title: "Model Overview",
93
+ status: "canonical",
94
+ summary: "Generated overview of the current Topogram model surface.",
95
+ source_of_truth: "canonical",
96
+ confidence: "high",
97
+ review_required: false,
98
+ tags: ["generated", "report"]
99
+ };
100
+ files["reports/model-overview.md"] = renderMarkdownDoc(
101
+ reportMetadata,
102
+ [
103
+ `Entities: ${(graph.byKind.entity || []).length}`,
104
+ `Capabilities: ${(graph.byKind.capability || []).length}`,
105
+ `Shapes: ${(graph.byKind.shape || []).length}`,
106
+ `Projections: ${(graph.byKind.projection || []).length}`,
107
+ `Companion docs: ${(graph.docs || []).length}`
108
+ ].join("\n\n")
109
+ );
110
+
111
+ const index = Object.entries(files).map(([filePath, contents]) => ({
112
+ path: filePath,
113
+ title: markdownTitle(filePath, contents)
114
+ }));
115
+ files["docs-index.json"] = `${stableStringify(index)}\n`;
116
+ return files;
117
+ }
118
+
119
+ /** @param {string} inputPath @returns {any} */
120
+ export function loadResolvedGraph(inputPath) {
121
+ const ast = parsePath(inputPath);
122
+ const resolved = resolveWorkspace(ast);
123
+ if (!resolved.ok) {
124
+ const error = /** @type {Error & { validation?: any }} */ (new Error("Workspace validation failed"));
125
+ error.validation = resolved.validation;
126
+ throw error;
127
+ }
128
+ return resolved.graph;
129
+ }
130
+
131
+ /** @param {string} inputPath @returns {any} */
132
+ export function tryLoadResolvedGraph(inputPath) {
133
+ try {
134
+ return loadResolvedGraph(inputPath);
135
+ } catch {
136
+ return null;
137
+ }
138
+ }
139
+
140
+ /** @param {string} inputPath @returns {any} */
141
+ export function refreshDocsWorkflow(inputPath) {
142
+ const paths = normalizeWorkspacePaths(inputPath);
143
+ const graph = loadResolvedGraph(paths.topogramRoot);
144
+ const generated = generateDocsBundleFromGraph(graph);
145
+ const canonicalRoot = path.join(paths.topogramRoot, "docs");
146
+ const generatedRoot = path.join(paths.topogramRoot, "candidates", "docs", "refreshed");
147
+ /** @type {WorkflowRecord} */
148
+ const report = {
149
+ type: "refresh_docs",
150
+ workspace: paths.topogramRoot,
151
+ missing: [],
152
+ stale: [],
153
+ orphaned: []
154
+ };
155
+
156
+ for (const [relativePath, contents] of Object.entries(generated)) {
157
+ if (relativePath === "docs-index.json") {
158
+ continue;
159
+ }
160
+ const canonicalPath = path.join(canonicalRoot, relativePath);
161
+ if (!fs.existsSync(canonicalPath)) {
162
+ report.missing.push(relativePath);
163
+ continue;
164
+ }
165
+ if ((fs.readFileSync(canonicalPath, "utf8")) !== contents) {
166
+ report.stale.push(relativePath);
167
+ }
168
+ }
169
+
170
+ for (const filePath of listFilesRecursive(canonicalRoot, (/** @type {any} */ child) => child.endsWith(".md"))) {
171
+ const relativePath = relativeTo(canonicalRoot, filePath);
172
+ if (!generated[relativePath]) {
173
+ report.orphaned.push(relativePath);
174
+ }
175
+ }
176
+
177
+ /** @type {WorkflowFiles} */
178
+
179
+ const files = {
180
+ "candidates/docs/refreshed/report.json": `${stableStringify(report)}\n`,
181
+ "candidates/docs/refreshed/report.md": ensureTrailingNewline(
182
+ `# Docs Refresh Report\n\n## Missing\n\n${report.missing.length ? report.missing.map((/** @type {any} */ item) => `- \`${item}\``).join("\n") : "- None"}\n\n## Stale\n\n${report.stale.length ? report.stale.map((/** @type {any} */ item) => `- \`${item}\``).join("\n") : "- None"}\n\n## Orphaned\n\n${report.orphaned.length ? report.orphaned.map((/** @type {any} */ item) => `- \`${item}\``).join("\n") : "- None"}\n`
183
+ )
184
+ };
185
+
186
+ for (const [relativePath, contents] of Object.entries(generated)) {
187
+ files[path.join("candidates/docs/refreshed/generated", relativePath).replaceAll(path.sep, "/")] = contents;
188
+ }
189
+
190
+ return {
191
+ summary: report,
192
+ files,
193
+ defaultOutDir: paths.topogramRoot
194
+ };
195
+ }
196
+
197
+ /** @param {string} inputPath @returns {any} */
198
+ export function generateDocsWorkflow(inputPath) {
199
+ const paths = normalizeWorkspacePaths(inputPath);
200
+ const graph = loadResolvedGraph(paths.topogramRoot);
201
+ const files = generateDocsBundleFromGraph(graph);
202
+ return {
203
+ summary: {
204
+ type: "generate_docs",
205
+ workspace: paths.topogramRoot,
206
+ bootstrapped_topogram_root: paths.bootstrappedTopogramRoot,
207
+ file_count: Object.keys(files).length
208
+ },
209
+ files,
210
+ defaultOutDir: path.join(paths.topogramRoot, "docs-generated")
211
+ };
212
+ }
213
+
214
+ /** @param {string} inputPath @returns {any} */
215
+ export function generateJourneyDraftsWorkflow(inputPath) {
216
+ const paths = normalizeWorkspacePaths(inputPath);
217
+ const graph = loadResolvedGraph(paths.topogramRoot);
218
+ const canonicalJourneys = (graph.docs || []).filter((/** @type {any} */ doc) => doc.kind === "journey");
219
+ const { drafts, skippedEntities } = buildJourneyDraftsReconcile(graph);
220
+ /** @type {WorkflowFiles} */
221
+ /** @type {WorkflowFiles} */
222
+ const files = {};
223
+
224
+ for (const draft of drafts) {
225
+ files[draft.path] = renderMarkdownDoc(draft.metadata, draft.body);
226
+ }
227
+
228
+ /** @type {WorkflowRecord} */
229
+ const summary = {
230
+ type: "generate_journeys",
231
+ workspace: paths.topogramRoot,
232
+ bootstrapped_topogram_root: paths.bootstrappedTopogramRoot,
233
+ canonical_journey_count: canonicalJourneys.length,
234
+ generated_draft_count: drafts.length,
235
+ draft_journeys: drafts.map((/** @type {any} */ draft) => ({
236
+ id: draft.id,
237
+ title: draft.title,
238
+ entity_id: draft.entity_id,
239
+ path: draft.path,
240
+ type: draft.type,
241
+ related_capabilities: draft.related_capabilities
242
+ })),
243
+ skipped_entities: skippedEntities
244
+ };
245
+
246
+ files["candidates/docs/journeys/import-report.json"] = `${stableStringify(summary)}\n`;
247
+ files["candidates/docs/journeys/import-report.md"] = ensureTrailingNewline(
248
+ `# Journey Draft Report\n\n` +
249
+ `Canonical journeys: ${canonicalJourneys.length}\n\n` +
250
+ `Generated drafts: ${drafts.length}\n\n` +
251
+ `## Draft Journeys\n\n` +
252
+ `${drafts.length === 0 ? "- None" : drafts.map((/** @type {any} */ draft) => `- \`${draft.id}\` -> \`${draft.path}\``).join("\n")}\n\n` +
253
+ `## Skipped Entities\n\n` +
254
+ `${skippedEntities.length === 0 ? "- None" : skippedEntities.map((/** @type {any} */ entry) => `- \`${entry.entity_id}\` (${entry.reason})`).join("\n")}\n`
255
+ );
256
+
257
+ return {
258
+ summary,
259
+ files,
260
+ defaultOutDir: paths.topogramRoot
261
+ };
262
+ }