@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,309 @@
1
+ // @ts-check
2
+ import fs from "node:fs";
3
+ import path from "node:path";
4
+
5
+ import { stableStringify } from "../../format.js";
6
+ import { generateContextBundle } from "../../generator/context/bundle.js";
7
+ import { buildLocalMaintainedBoundaryArtifact } from "../../generator/context/shared.js";
8
+ import { relativeTo } from "../../path-helpers.js";
9
+ import { ensureTrailingNewline } from "../../text-helpers.js";
10
+ import {
11
+ adoptionItemKey,
12
+ applyAdoptionSelector,
13
+ buildAgentAdoptionPlan,
14
+ mergeAdoptionPlanState,
15
+ parseAdoptSelector,
16
+ summarizeAdoptionPlanItems
17
+ } from "../../adoption/plan.js";
18
+ import {
19
+ attachBundleOperatorHints,
20
+ annotateBundlePriorities,
21
+ buildAdoptionStatusFiles as buildAdoptionStatusFilesReport,
22
+ buildAdoptionStatusSummary as buildAdoptionStatusSummaryReport,
23
+ buildPromotedCanonicalItems as buildPromotedCanonicalItemsReport,
24
+ renderBundlePriorityActionsMarkdown,
25
+ renderNextBestActionMarkdown,
26
+ renderPreviewFollowupMarkdown,
27
+ renderPreviewRiskMarkdown,
28
+ renderPromotedCanonicalItemsMarkdown
29
+ } from "../../adoption/reporting.js";
30
+ import {
31
+ buildBundleAdoptionPriorities,
32
+ buildBundleBlockerSummaries,
33
+ buildProjectionReviewGroups,
34
+ buildUiReviewGroups,
35
+ buildWorkflowReviewGroups,
36
+ selectNextBundle
37
+ } from "../../adoption/review-groups.js";
38
+ import { confidenceRank, tryLoadResolvedGraph } from "../docs.js";
39
+ import { listFilesRecursive, normalizeWorkspacePaths, readJsonIfExists } from "../shared.js";
40
+ import {
41
+ annotateBundleAuthHintClosures,
42
+ buildAuthClaimReviewGuidance,
43
+ buildAuthOwnershipReviewGuidance,
44
+ buildAuthPermissionReviewGuidance,
45
+ formatAuthClaimHintInline,
46
+ formatAuthOwnershipHintInline,
47
+ formatAuthPermissionHintInline,
48
+ formatAuthRoleGuidanceInline
49
+ } from "./auth.js";
50
+ import { buildAdoptionPlan, buildCanonicalAdoptionOutputs, buildPromotedCanonicalItems, readAdoptionPlan } from "./adoption-plan.js";
51
+ import { buildCandidateModelFiles } from "./candidate-model.js";
52
+ import { loadImportArtifacts } from "./gap-report.js";
53
+ import { annotateBundleAuthAging, renderCandidateBundleReadme, renderMaintainedSeamCandidatesInline } from "./bundle-core.js";
54
+ import { buildBundleOperatorSummary, summarizeBundleSurface } from "./summary.js";
55
+ import { formatDocDriftSummaryInline, formatDocLinkSuggestionInline, formatDocMetadataPatchInline } from "./adoption-plan.js";
56
+
57
+ /** @param {string} inputPath @param {WorkflowOptions} options @returns {any} */
58
+ export function reconcileWorkflow(inputPath, options = {}) {
59
+ const paths = normalizeWorkspacePaths(inputPath);
60
+ const graph = tryLoadResolvedGraph(paths.topogramRoot);
61
+ const candidatesRoot = path.join(paths.topogramRoot, "candidates", "docs");
62
+ const appImport = loadImportArtifacts(paths, inputPath);
63
+ const adoptSelector = parseAdoptSelector(options.adopt);
64
+ /** @type {WorkflowFiles} */
65
+ /** @type {WorkflowFiles} */
66
+ const files = {};
67
+ /** @type {any[]} */
68
+ const promoted = [];
69
+ /** @type {any[]} */
70
+ const skipped = [];
71
+
72
+ for (const filePath of listFilesRecursive(candidatesRoot, (/** @type {any} */ child) => child.endsWith(".md"))) {
73
+ const relativeCandidate = relativeTo(candidatesRoot, filePath);
74
+ const destination = path.join("docs", relativeCandidate);
75
+ const canonicalPath = path.join(paths.topogramRoot, destination);
76
+ if (fs.existsSync(canonicalPath)) {
77
+ skipped.push(destination.replaceAll(path.sep, "/"));
78
+ continue;
79
+ }
80
+ files[destination.replaceAll(path.sep, "/")] = fs.readFileSync(filePath, "utf8");
81
+ promoted.push(destination.replaceAll(path.sep, "/"));
82
+ }
83
+
84
+ const candidateModel = buildCandidateModelFiles(graph, appImport, paths.topogramRoot);
85
+ const defaultPlanItems = buildAdoptionPlan(candidateModel.bundles);
86
+ const existingPlan = readAdoptionPlan(paths);
87
+ const previousReconcileReport = readJsonIfExists(path.join(paths.topogramRoot, "candidates", "reconcile", "report.json"));
88
+ const mergedPlanItems = mergeAdoptionPlanState(defaultPlanItems, existingPlan, paths.topogramRoot);
89
+ /** @type {WorkflowRecord} */
90
+ const adoptionPlan = {
91
+ type: "reconcile_adoption_plan",
92
+ workspace: paths.topogramRoot,
93
+ approved_review_groups: [...new Set(existingPlan?.approved_review_groups || [])],
94
+ items: mergedPlanItems,
95
+ projection_review_groups: buildProjectionReviewGroups(mergedPlanItems),
96
+ ui_review_groups: buildUiReviewGroups(mergedPlanItems),
97
+ workflow_review_groups: buildWorkflowReviewGroups(mergedPlanItems)
98
+ };
99
+
100
+ const maintainedBoundaryArtifact = graph
101
+ ? generateContextBundle(graph, { taskId: "maintained-app" }).maintained_boundary || null
102
+ : buildLocalMaintainedBoundaryArtifact(paths.workspaceRoot) || null;
103
+ let bundlesWithAuthHintClosures = annotateBundleAuthAging(
104
+ candidateModel.bundles.map((/** @type {any} */ bundle) => annotateBundleAuthHintClosures(bundle, adoptionPlan.items)),
105
+ previousReconcileReport
106
+ );
107
+ let candidateModelFiles = {
108
+ ...candidateModel.files
109
+ };
110
+ /** @type {any[]} */
111
+ let writtenCanonicalFiles = [];
112
+ /** @type {any[]} */
113
+ let reportRefreshedCanonicalFiles = [];
114
+ /** @type {any[]} */
115
+ let appliedItems = [];
116
+ /** @type {any[]} */
117
+ let approvedItems = [];
118
+ /** @type {any[]} */
119
+ let skippedItems = [];
120
+ /** @type {any[]} */
121
+ let blockedItems = [];
122
+ let adoptionRun = null;
123
+
124
+ if (adoptSelector) {
125
+ adoptionRun = applyAdoptionSelector(adoptionPlan, adoptSelector, Boolean(options.write));
126
+ adoptionPlan.items = adoptionRun.plan.items;
127
+ adoptionPlan.approved_review_groups = adoptionRun.plan.approved_review_groups;
128
+ adoptionPlan.projection_review_groups = buildProjectionReviewGroups(adoptionPlan.items);
129
+ adoptionPlan.ui_review_groups = buildUiReviewGroups(adoptionPlan.items);
130
+ adoptionPlan.workflow_review_groups = buildWorkflowReviewGroups(adoptionPlan.items);
131
+ appliedItems = adoptionRun.appliedItems;
132
+ approvedItems = adoptionRun.approvedItems;
133
+ skippedItems = adoptionRun.skippedItems;
134
+ blockedItems = adoptionRun.blockedItems;
135
+ bundlesWithAuthHintClosures = annotateBundleAuthAging(
136
+ candidateModel.bundles.map((/** @type {any} */ bundle) => annotateBundleAuthHintClosures(bundle, adoptionPlan.items)),
137
+ previousReconcileReport
138
+ );
139
+ candidateModelFiles = {};
140
+ const canonicalOutputs = buildCanonicalAdoptionOutputs(
141
+ paths,
142
+ candidateModel.files,
143
+ adoptionPlan.items,
144
+ adoptionRun.selectedItems,
145
+ { refreshAdopted: options.refreshAdopted }
146
+ );
147
+ for (const [relativePath, contents] of Object.entries(canonicalOutputs.files)) {
148
+ files[relativePath.replaceAll(path.sep, "/")] = contents;
149
+ }
150
+ writtenCanonicalFiles = Object.keys(canonicalOutputs.files).sort();
151
+ reportRefreshedCanonicalFiles = canonicalOutputs.refreshedFiles || [];
152
+ } else {
153
+ for (const [relativePath, contents] of Object.entries(candidateModelFiles)) {
154
+ files[relativePath.replaceAll(path.sep, "/")] = contents;
155
+ }
156
+ }
157
+
158
+ const planItemSummary = summarizeAdoptionPlanItems(adoptionPlan.items);
159
+ const agentAdoptionPlan = buildAgentAdoptionPlan(adoptionPlan, maintainedBoundaryArtifact);
160
+ for (const bundle of bundlesWithAuthHintClosures) {
161
+ const readmePath = `candidates/reconcile/model/bundles/${bundle.slug}/README.md`;
162
+ const readme = renderCandidateBundleReadme(bundle, agentAdoptionPlan.imported_proposal_surfaces || []);
163
+ candidateModelFiles[readmePath] = readme;
164
+ files[readmePath] = readme;
165
+ }
166
+ appliedItems = planItemSummary.applied_items;
167
+ approvedItems = planItemSummary.approved_items;
168
+ skippedItems = planItemSummary.skipped_items;
169
+ blockedItems = planItemSummary.blocked_items;
170
+
171
+ files["candidates/reconcile/adoption-plan.json"] = `${stableStringify(adoptionPlan)}\n`;
172
+ files["candidates/reconcile/adoption-plan.agent.json"] = `${stableStringify(agentAdoptionPlan)}\n`;
173
+ /** @type {WorkflowRecord} */
174
+ const report = {
175
+ type: "reconcile_report",
176
+ workspace: paths.topogramRoot,
177
+ bootstrapped_topogram_root: paths.bootstrappedTopogramRoot,
178
+ adoption_plan_path: "candidates/reconcile/adoption-plan.json",
179
+ agent_adoption_plan_path: "candidates/reconcile/adoption-plan.agent.json",
180
+ adopt_selector: adoptSelector,
181
+ adopt_write_mode: Boolean(options.write),
182
+ promoted,
183
+ skipped,
184
+ applied_items: appliedItems,
185
+ approved_items: approvedItems,
186
+ skipped_items: skippedItems,
187
+ blocked_items: blockedItems,
188
+ written_canonical_files: writtenCanonicalFiles,
189
+ promoted_canonical_items: buildPromotedCanonicalItemsReport(
190
+ adoptionPlan.items,
191
+ adoptionRun?.selectedItems || [],
192
+ writtenCanonicalFiles,
193
+ adoptSelector,
194
+ adoptionItemKey,
195
+ reportRefreshedCanonicalFiles
196
+ ),
197
+ refreshed_canonical_files: reportRefreshedCanonicalFiles,
198
+ approved_review_groups: adoptionPlan.approved_review_groups,
199
+ staged_items: agentAdoptionPlan.staged_items,
200
+ adoption_plan_items: adoptionPlan.items.map((/** @type {any} */ item) => ({
201
+ bundle: item.bundle,
202
+ item: item.item,
203
+ kind: item.kind,
204
+ status: item.status,
205
+ confidence: item.confidence || null,
206
+ recommendation: item.recommendation || null,
207
+ related_docs: item.related_docs || [],
208
+ related_capabilities: item.related_capabilities || [],
209
+ related_rules: item.related_rules || [],
210
+ related_workflows: item.related_workflows || []
211
+ })),
212
+ agent_adoption_plan_items: agentAdoptionPlan.imported_proposal_surfaces,
213
+ projection_review_groups: adoptionPlan.projection_review_groups,
214
+ ui_review_groups: adoptionPlan.ui_review_groups,
215
+ workflow_review_groups: adoptionPlan.workflow_review_groups,
216
+ bundle_blockers: buildBundleBlockerSummaries(adoptionPlan.items),
217
+ projection_dependent_items: adoptionPlan.items
218
+ .filter((/** @type {any} */ item) => (item.projection_impacts || []).length > 0)
219
+ .map((/** @type {any} */ item) => ({
220
+ item: item.item,
221
+ kind: item.kind,
222
+ bundle: item.bundle,
223
+ projection_impacts: item.projection_impacts
224
+ })),
225
+ suppressed_noise_bundles: candidateModel.suppressedNoiseBundles || [],
226
+ candidate_model_files: Object.keys(candidateModelFiles).sort(),
227
+ candidate_model_bundles: bundlesWithAuthHintClosures.map((/** @type {any} */ bundle) => ({
228
+ operator_summary: buildBundleOperatorSummary(bundle),
229
+ auth_permission_hints: bundle.authPermissionHints || [],
230
+ auth_claim_hints: bundle.authClaimHints || [],
231
+ auth_ownership_hints: bundle.authOwnershipHints || [],
232
+ auth_role_guidance: bundle.authRoleGuidance || [],
233
+ id: bundle.id,
234
+ slug: bundle.slug,
235
+ label: bundle.label,
236
+ merge_hints: bundle.mergeHints,
237
+ projection_impacts: bundle.projectionImpacts,
238
+ projection_patches: bundle.projectionPatches,
239
+ ui_impacts: bundle.uiImpacts,
240
+ workflow_impacts: bundle.workflowImpacts,
241
+ doc_link_suggestions: bundle.docLinkSuggestions,
242
+ doc_drift_summaries: bundle.docDriftSummaries,
243
+ doc_metadata_patches: bundle.docMetadataPatches,
244
+ adoption_plan: bundle.adoptionPlan,
245
+ actors: bundle.actors.map((/** @type {any} */ entry) => entry.id_hint),
246
+ actor_details: bundle.actors.map((/** @type {any} */ entry) => ({
247
+ id: entry.id_hint,
248
+ related_docs: entry.related_docs || [],
249
+ related_capabilities: entry.related_capabilities || []
250
+ })),
251
+ roles: bundle.roles.map((/** @type {any} */ entry) => entry.id_hint),
252
+ role_details: bundle.roles.map((/** @type {any} */ entry) => ({
253
+ id: entry.id_hint,
254
+ related_docs: entry.related_docs || [],
255
+ related_capabilities: entry.related_capabilities || []
256
+ })),
257
+ entities: bundle.entities.map((/** @type {any} */ entry) => entry.id_hint),
258
+ enums: bundle.enums.map((/** @type {any} */ entry) => entry.id_hint),
259
+ capabilities: bundle.capabilities.map((/** @type {any} */ entry) => entry.id_hint),
260
+ shapes: bundle.shapes.map((/** @type {any} */ entry) => entry.id),
261
+ widgets: bundle.widgets.map((/** @type {any} */ entry) => entry.id_hint),
262
+ screens: bundle.screens.map((/** @type {any} */ entry) => entry.id_hint),
263
+ workflows: bundle.workflows.map((/** @type {any} */ entry) => entry.id_hint),
264
+ docs: bundle.docs.map((/** @type {any} */ entry) => entry.id),
265
+ maintained_seam_candidates: (agentAdoptionPlan.imported_proposal_surfaces || [])
266
+ .filter((/** @type {any} */ surface) => surface.bundle === bundle.slug && (surface.maintained_seam_candidates || []).length > 0)
267
+ .map((/** @type {any} */ surface) => ({
268
+ id: surface.id,
269
+ kind: surface.kind,
270
+ maintained_seam_candidates: surface.maintained_seam_candidates
271
+ }))
272
+ }))
273
+ };
274
+ report.bundle_priorities = annotateBundlePriorities(
275
+ attachBundleOperatorHints(
276
+ buildBundleAdoptionPriorities(report, confidenceRank),
277
+ report.candidate_model_bundles
278
+ )
279
+ );
280
+ const canonicalChangeTitle = report.adopt_selector && !report.adopt_write_mode
281
+ ? "## Preview Canonical Changes"
282
+ : "## Promoted Canonical Items";
283
+ files["candidates/reconcile/report.json"] = `${stableStringify(report)}\n`;
284
+ const candidateModelBundlesMarkdown = report.candidate_model_bundles.length
285
+ ? report.candidate_model_bundles.map((/** @type {any} */ bundle) => `- \`${bundle.slug}\` (${bundle.actors.length} actors, ${bundle.roles.length} roles, ${bundle.entities.length} entities, ${bundle.enums.length} enums, ${bundle.capabilities.length} capabilities, ${bundle.shapes.length} shapes, ${bundle.widgets.length} widgets, ${bundle.screens.length} screens, ${bundle.workflows.length} workflows, ${bundle.docs.length} docs)
286
+ - primary concept \`${bundle.operator_summary.primaryConcept}\`${bundle.operator_summary.primaryEntityId ? `, primary entity \`${bundle.operator_summary.primaryEntityId}\`` : ""}
287
+ - participants ${bundle.operator_summary.participants.label}
288
+ - main capabilities ${summarizeBundleSurface(bundle, bundle.operator_summary.capabilityIds)}
289
+ - main widgets ${summarizeBundleSurface(bundle, bundle.operator_summary.widgetIds)}
290
+ - main routes ${summarizeBundleSurface(bundle, bundle.operator_summary.routePaths)}
291
+ - candidate maintained seam mappings ${renderMaintainedSeamCandidatesInline(bundle)}
292
+ - permission hints ${bundle.auth_permission_hints?.length ? bundle.auth_permission_hints.map((/** @type {any} */ entry) => formatAuthPermissionHintInline(entry)).join(", ") : "_none_"}
293
+ - auth claims ${bundle.auth_claim_hints?.length ? bundle.auth_claim_hints.map((/** @type {any} */ entry) => formatAuthClaimHintInline(entry)).join(", ") : "_none_"}
294
+ - ownership hints ${bundle.auth_ownership_hints?.length ? bundle.auth_ownership_hints.map((/** @type {any} */ entry) => formatAuthOwnershipHintInline(entry)).join(", ") : "_none_"}
295
+ - auth role guidance ${bundle.auth_role_guidance?.length ? bundle.auth_role_guidance.map((/** @type {any} */ entry) => formatAuthRoleGuidanceInline(entry)).join(", ") : "_none_"}
296
+ - auth closure ${bundle.operator_summary.authClosureSummary.label} (adopted=${bundle.operator_summary.authClosureSummary.adopted}, deferred=${bundle.operator_summary.authClosureSummary.deferred}, unresolved=${bundle.operator_summary.authClosureSummary.unresolved})
297
+ ${bundle.operator_summary.authAging && bundle.operator_summary.authAging.escalationLevel !== "none" ? `- auth escalation ${bundle.operator_summary.authAging.escalationLevel === "stale_high_risk" ? "escalated" : "fresh attention"} (high-risk runs=${bundle.operator_summary.authAging.repeatCount})\n` : ""} - why ${bundle.operator_summary.whyThisBundle}${bundle.auth_permission_hints?.length ? `\n${bundle.auth_permission_hints.map((/** @type {any} */ entry) => ` - permission ${formatAuthPermissionHintInline(entry)} <- ${entry.related_capabilities.length ? entry.related_capabilities.map((/** @type {any} */ item) => `\`${item}\``).join(", ") : "_no direct capability match_"}\n - closure ${entry.closure_state || "unresolved"}\n - closure reason ${entry.closure_reason || "No reviewed projection patch has been applied for this inferred auth hint yet."}\n - why inferred ${entry.why_inferred || entry.explanation}\n - review next ${entry.review_guidance || buildAuthPermissionReviewGuidance(entry)}`).join("\n")}` : ""}${bundle.auth_claim_hints?.length ? `\n${bundle.auth_claim_hints.map((/** @type {any} */ entry) => ` - auth ${formatAuthClaimHintInline(entry)} <- ${entry.related_capabilities.length ? entry.related_capabilities.map((/** @type {any} */ item) => `\`${item}\``).join(", ") : "_no direct capability match_"}\n - closure ${entry.closure_state || "unresolved"}\n - closure reason ${entry.closure_reason || "No reviewed projection patch has been applied for this inferred auth hint yet."}\n - why inferred ${entry.why_inferred || entry.explanation}\n - review next ${entry.review_guidance || buildAuthClaimReviewGuidance(entry)}`).join("\n")}` : ""}${bundle.auth_ownership_hints?.length ? `\n${bundle.auth_ownership_hints.map((/** @type {any} */ entry) => ` - ownership ${formatAuthOwnershipHintInline(entry)} <- ${entry.related_capabilities.length ? entry.related_capabilities.map((/** @type {any} */ item) => `\`${item}\``).join(", ") : "_no direct capability match_"}\n - closure ${entry.closure_state || "unresolved"}\n - closure reason ${entry.closure_reason || "No reviewed projection patch has been applied for this inferred auth hint yet."}\n - why inferred ${entry.why_inferred || entry.explanation}\n - review next ${entry.review_guidance || buildAuthOwnershipReviewGuidance(entry)}`).join("\n")}` : ""}${bundle.auth_role_guidance?.length ? `\n${bundle.auth_role_guidance.map((/** @type {any} */ entry) => ` - role ${formatAuthRoleGuidanceInline(entry)} <- ${entry.related_capabilities.length ? entry.related_capabilities.map((/** @type {any} */ item) => `\`${item}\``).join(", ") : "_role naming only_"}\n - why inferred ${entry.why_inferred}\n - suggested follow-up ${entry.followup_label} (${entry.followup_reason})\n - review next ${entry.review_guidance}`).join("\n")}` : ""}${bundle.actor_details.length || bundle.role_details.length ? `\n${bundle.actor_details.map((/** @type {any} */ entry) => ` - actor \`${entry.id}\`${entry.related_docs.length ? ` docs=${entry.related_docs.map((/** @type {any} */ item) => `\`${item}\``).join(", ")}` : ""}${entry.related_capabilities.length ? ` capabilities=${entry.related_capabilities.map((/** @type {any} */ item) => `\`${item}\``).join(", ")}` : ""}`).concat(bundle.role_details.map((/** @type {any} */ entry) => ` - role \`${entry.id}\`${entry.related_docs.length ? ` docs=${entry.related_docs.map((/** @type {any} */ item) => `\`${item}\``).join(", ")}` : ""}${entry.related_capabilities.length ? ` capabilities=${entry.related_capabilities.map((/** @type {any} */ item) => `\`${item}\``).join(", ")}` : ""}`)).join("\n")}` : ""}${bundle.doc_link_suggestions?.length ? `\n${bundle.doc_link_suggestions.map((/** @type {any} */ item) => ` - ${formatDocLinkSuggestionInline(item).replace(/^doc /, "doc-link ")}${item.auth_role_followups?.length ? `\n - auth role follow-up ${item.auth_role_followups.map((/** @type {any} */ entry) => `${entry.followup_label} for \`${entry.role_id}\``).join(", ")}` : ""}`).join("\n")}` : ""}${bundle.doc_drift_summaries?.length ? `\n${bundle.doc_drift_summaries.map((/** @type {any} */ item) => ` - drift ${formatDocDriftSummaryInline(item)}`).join("\n")}` : ""}${bundle.doc_metadata_patches?.length ? `\n${bundle.doc_metadata_patches.map((/** @type {any} */ item) => ` - metadata ${formatDocMetadataPatchInline(item)}`).join("\n")}` : ""}`).join("\n")
298
+ : "- None";
299
+ files["candidates/reconcile/report.md"] = ensureTrailingNewline(
300
+ `# Reconcile Report\n\n## Promoted\n\n${promoted.length ? promoted.map((/** @type {any} */ item) => `- \`${item}\``).join("\n") : "- None"}\n\n## Skipped\n\n${skipped.length ? skipped.map((/** @type {any} */ item) => `- \`${item}\``).join("\n") : "- None"}\n\n## Adoption\n\n- Plan: \`${report.adoption_plan_path}\`\n- Selector: \`${report.adopt_selector || "none"}\`\n- Write mode: ${report.adopt_write_mode ? "yes" : "no"}\n- Approved items: ${report.approved_items.length}\n- Applied items: ${report.applied_items.length}\n- Skipped items: ${report.skipped_items.length}\n- Blocked items: ${report.blocked_items.length}\n- Canonical files: ${report.written_canonical_files.length}\n- Refreshed canonical files: ${report.refreshed_canonical_files.length}\n- Approved review groups: ${report.approved_review_groups.length}\n- Projection-dependent items: ${report.projection_dependent_items.length}\n- Projection review groups: ${report.projection_review_groups.length}\n- UI review groups: ${report.ui_review_groups.length}\n- Workflow review groups: ${report.workflow_review_groups.length}\n\n${renderPromotedCanonicalItemsMarkdown(report.promoted_canonical_items, { title: canonicalChangeTitle })}${renderPreviewRiskMarkdown(report)}${renderPreviewFollowupMarkdown(buildAdoptionStatusSummaryReport(report, selectNextBundle))}${renderNextBestActionMarkdown(selectNextBundle(report.bundle_priorities))}## Approved Review Groups\n\n${report.approved_review_groups.length ? report.approved_review_groups.map((/** @type {any} */ item) => `- \`${item}\``).join("\n") : "- None"}\n\n## Projection Review Groups\n\n${report.projection_review_groups.length ? report.projection_review_groups.map((/** @type {any} */ group) => `- \`${group.projection_id}\` (${group.kind}) <- ${group.items.map((/** @type {any} */ item) => `\`${item.item}\``).join(", ")}`).join("\n") : "- None"}\n\n## UI Review Groups\n\n${report.ui_review_groups.length ? report.ui_review_groups.map((/** @type {any} */ group) => `- \`${group.projection_id}\` (${group.kind}) <- ${group.items.map((/** @type {any} */ item) => `\`${item.item}\``).join(", ")}`).join("\n") : "- None"}\n\n## Workflow Review Groups\n\n${report.workflow_review_groups.length ? report.workflow_review_groups.map((/** @type {any} */ group) => `- \`${group.id}\` <- ${group.items.map((/** @type {any} */ item) => `\`${item.item}\``).join(", ")}`).join("\n") : "- None"}\n\n## Bundle Blockers\n\n${report.bundle_blockers.length ? report.bundle_blockers.map((/** @type {any} */ bundle) => `- \`${bundle.bundle}\`: blocked=${bundle.blocked_items.length}, approved=${bundle.approved_items.length}, applied=${bundle.applied_items.length}, pending=${bundle.pending_items.length}, dependencies=${bundle.blocking_dependencies.length ? bundle.blocking_dependencies.map((/** @type {any} */ item) => `\`${item}\``).join(", ") : "_none_"}`).join("\n") : "- None"}\n\n${renderBundlePriorityActionsMarkdown(report.bundle_priorities)}## Suppressed Noise Bundles\n\n${report.suppressed_noise_bundles.length ? report.suppressed_noise_bundles.map((/** @type {any} */ bundle) => `- \`${bundle.slug}\`: ${bundle.reason}`).join("\n") : "- None"}\n\n## Projection Dependencies\n\n${report.projection_dependent_items.length ? report.projection_dependent_items.map((/** @type {any} */ item) => `- \`${item.item}\` -> ${item.projection_impacts.map((/** @type {any} */ impact) => `\`${impact.projection_id}\``).join(", ")}`).join("\n") : "- None"}\n\n## Blocked Adoption Items\n\n${report.blocked_items.length ? report.blocked_items.map((/** @type {any} */ item) => `- \`${item}\``).join("\n") : "- None"}\n\n## Candidate Model Bundles\n\n${candidateModelBundlesMarkdown}\n\n## Candidate Model Files\n\n${report.candidate_model_files.length ? report.candidate_model_files.map((/** @type {any} */ item) => `- \`${item}\``).join("\n") : "- None"}\n\n## Canonical Outputs\n\n${report.written_canonical_files.length ? report.written_canonical_files.map((/** @type {any} */ item) => `- \`${item}\``).join("\n") : "- None"}\n`
301
+ );
302
+ Object.assign(files, buildAdoptionStatusFilesReport(buildAdoptionStatusSummaryReport(report, selectNextBundle), formatDocLinkSuggestionInline, formatDocDriftSummaryInline, formatDocMetadataPatchInline));
303
+
304
+ return {
305
+ summary: report,
306
+ files,
307
+ defaultOutDir: paths.topogramRoot
308
+ };
309
+ }
@@ -0,0 +1,189 @@
1
+ // @ts-check
2
+ import fs from "node:fs";
3
+ import path from "node:path";
4
+
5
+ import { ensureTrailingNewline, titleCase } from "../text-helpers.js";
6
+
7
+ /** @param {string} startDir @returns {any} */
8
+ export function findNearestGitRoot(startDir) {
9
+ let current = path.resolve(startDir);
10
+ while (true) {
11
+ if (fs.existsSync(path.join(current, ".git"))) {
12
+ return current;
13
+ }
14
+ const parent = path.dirname(current);
15
+ if (parent === current) {
16
+ return startDir;
17
+ }
18
+ current = parent;
19
+ }
20
+ }
21
+
22
+ /** @param {string} inputPath @returns {any} */
23
+ export function normalizeWorkspacePaths(inputPath) {
24
+ const absolute = path.resolve(inputPath);
25
+ const inputExists = fs.existsSync(absolute);
26
+ const hasTopogramChild = fs.existsSync(path.join(absolute, "topogram")) && fs.statSync(path.join(absolute, "topogram")).isDirectory();
27
+ const isTopogramDir = path.basename(absolute) === "topogram" && inputExists;
28
+ const bootstrapWorkspaceRoot = !isTopogramDir && !hasTopogramChild;
29
+ const topogramRoot = isTopogramDir
30
+ ? absolute
31
+ : hasTopogramChild
32
+ ? path.join(absolute, "topogram")
33
+ : path.join(absolute, "topogram");
34
+ const workspaceRoot = isTopogramDir ? path.dirname(topogramRoot) : absolute;
35
+ const repoRoot = findNearestGitRoot(workspaceRoot);
36
+ return {
37
+ inputRoot: absolute,
38
+ topogramRoot,
39
+ workspaceRoot,
40
+ exampleRoot: workspaceRoot,
41
+ repoRoot,
42
+ bootstrappedTopogramRoot: !fs.existsSync(topogramRoot)
43
+ };
44
+ }
45
+
46
+ /** @param {string} markdown @returns {any} */
47
+ export function firstHeading(markdown) {
48
+ const match = markdown.match(/^#\s+(.+)$/m);
49
+ return match ? match[1].trim() : null;
50
+ }
51
+
52
+ /** @param {string} filePath @param {string} markdown @returns {any} */
53
+ export function markdownTitle(filePath, markdown) {
54
+ return firstHeading(markdown) || titleCase(path.basename(filePath, path.extname(filePath)));
55
+ }
56
+
57
+ /** @param {string} filePath @returns {any} */
58
+ export function readTextIfExists(filePath) {
59
+ return fs.existsSync(filePath) ? fs.readFileSync(filePath, "utf8") : null;
60
+ }
61
+
62
+ /** @param {string} filePath @returns {any} */
63
+ export function readJsonIfExists(filePath) {
64
+ if (!fs.existsSync(filePath)) {
65
+ return null;
66
+ }
67
+ return JSON.parse(fs.readFileSync(filePath, "utf8"));
68
+ }
69
+
70
+ export const DEFAULT_IGNORED_DIRS = new Set([
71
+ ".git",
72
+ ".next",
73
+ ".turbo",
74
+ ".yarn",
75
+ "build",
76
+ "coverage",
77
+ "dist",
78
+ "node_modules",
79
+ "tmp"
80
+ ]);
81
+
82
+ /** @param {string} rootDir @param {any} predicate @param {WorkflowOptions} options @returns {any} */
83
+ export function listFilesRecursive(rootDir, predicate = () => true, options = {}) {
84
+ if (!fs.existsSync(rootDir)) {
85
+ return [];
86
+ }
87
+ const ignoredDirs = options.ignoredDirs || DEFAULT_IGNORED_DIRS;
88
+ /** @type {any[]} */
89
+ const files = [];
90
+ const walk = (/** @type {any} */ currentDir) => {
91
+ for (const entry of fs.readdirSync(currentDir, { withFileTypes: true })) {
92
+ const childPath = path.join(currentDir, entry.name);
93
+ if (entry.isDirectory()) {
94
+ if (ignoredDirs.has(entry.name)) {
95
+ continue;
96
+ }
97
+ walk(childPath);
98
+ continue;
99
+ }
100
+ if (entry.isFile() && predicate(childPath)) {
101
+ files.push(childPath);
102
+ }
103
+ }
104
+ };
105
+ walk(rootDir);
106
+ return files.sort();
107
+ }
108
+
109
+ /** @param {WorkflowRecord} metadata @returns {any} */
110
+ export function buildFrontmatter(metadata) {
111
+ const lines = ["---"];
112
+ for (const [key, value] of Object.entries(metadata)) {
113
+ if (value == null || value === "" || (Array.isArray(value) && value.length === 0)) {
114
+ continue;
115
+ }
116
+ if (Array.isArray(value)) {
117
+ lines.push(`${key}:`);
118
+ for (const item of value) {
119
+ lines.push(` - ${item}`);
120
+ }
121
+ continue;
122
+ }
123
+ if (typeof value === "boolean") {
124
+ lines.push(`${key}: ${value ? "true" : "false"}`);
125
+ continue;
126
+ }
127
+ lines.push(`${key}: ${String(value).includes(":") ? JSON.stringify(value) : value}`);
128
+ }
129
+ lines.push("---");
130
+ return lines.join("\n");
131
+ }
132
+
133
+ /** @param {WorkflowRecord} metadata @param {string} body @returns {any} */
134
+ export function renderMarkdownDoc(metadata, body) {
135
+ return ensureTrailingNewline(`${buildFrontmatter(metadata)}\n\n${body.trim()}\n`);
136
+ }
137
+
138
+ /** @param {string} source @returns {any} */
139
+ export function parseMarkdownFrontmatter(source) {
140
+ const normalized = String(source || "").replace(/\r\n/g, "\n");
141
+ const lines = normalized.split("\n");
142
+ if (lines[0]?.trim() !== "---") {
143
+ return null;
144
+ }
145
+ let closingIndex = -1;
146
+ for (let index = 1; index < lines.length; index += 1) {
147
+ if (lines[index].trim() === "---") {
148
+ closingIndex = index;
149
+ break;
150
+ }
151
+ }
152
+ if (closingIndex === -1) {
153
+ return null;
154
+ }
155
+ /** @type {WorkflowRecord} */
156
+ const metadata = {};
157
+ for (let index = 1; index < closingIndex; index += 1) {
158
+ const line = lines[index];
159
+ if (!line.trim()) {
160
+ continue;
161
+ }
162
+ const match = line.match(/^([a-zA-Z_][a-zA-Z0-9_]*)\s*:\s*(.*)$/);
163
+ if (!match) {
164
+ continue;
165
+ }
166
+ const [, key, rawValue] = match;
167
+ if (rawValue.trim() === "") {
168
+ /** @type {any[]} */
169
+ const items = [];
170
+ let cursor = index + 1;
171
+ while (cursor < closingIndex) {
172
+ const itemMatch = lines[cursor].match(/^\s*-\s*(.*)$/);
173
+ if (!itemMatch) {
174
+ break;
175
+ }
176
+ items.push(itemMatch[1]);
177
+ cursor += 1;
178
+ }
179
+ metadata[key] = items;
180
+ index = cursor - 1;
181
+ continue;
182
+ }
183
+ metadata[key] = rawValue === "true" ? true : rawValue === "false" ? false : rawValue;
184
+ }
185
+ return {
186
+ metadata,
187
+ body: lines.slice(closingIndex + 1).join("\n").replace(/^\n+/, "")
188
+ };
189
+ }
@@ -0,0 +1,93 @@
1
+ export type WorkflowRecord = Record<string, any>;
2
+
3
+ export type WorkflowScalar = string | number | boolean | null;
4
+
5
+ export type WorkflowJson =
6
+ | WorkflowScalar
7
+ | WorkflowJson[]
8
+ | { [key: string]: WorkflowJson };
9
+
10
+ export type WorkflowFiles = Record<string, any>;
11
+
12
+ export type WorkflowOptions = Record<string, any>;
13
+
14
+ export interface WorkspacePaths {
15
+ inputRoot: string;
16
+ topogramRoot: string;
17
+ workspaceRoot: string;
18
+ exampleRoot: string;
19
+ repoRoot: string;
20
+ bootstrappedTopogramRoot: boolean;
21
+ }
22
+
23
+ export interface MarkdownFrontmatter {
24
+ metadata: WorkflowRecord;
25
+ body: string;
26
+ }
27
+
28
+ export interface WorkflowResult {
29
+ type?: string;
30
+ version?: number | string;
31
+ summary: WorkflowRecord;
32
+ files?: WorkflowFiles;
33
+ defaultOutDir?: string;
34
+ }
35
+
36
+ export type ResolvedGraph = WorkflowRecord;
37
+
38
+ export type CandidateRecord = WorkflowRecord;
39
+
40
+ export interface CandidateBundle extends WorkflowRecord {
41
+ id: string;
42
+ label: string;
43
+ actors: CandidateRecord[];
44
+ roles: CandidateRecord[];
45
+ enums: CandidateRecord[];
46
+ entities: CandidateRecord[];
47
+ shapes: CandidateRecord[];
48
+ capabilities: CandidateRecord[];
49
+ verifications: CandidateRecord[];
50
+ workflows: CandidateRecord[];
51
+ widgets: CandidateRecord[];
52
+ screens: CandidateRecord[];
53
+ uiRoutes: CandidateRecord[];
54
+ docs: CandidateRecord[];
55
+ docLinkSuggestions: CandidateRecord[];
56
+ docMetadataPatches: CandidateRecord[];
57
+ }
58
+
59
+ export type CandidateBundles = CandidateBundle[];
60
+
61
+ export type ImportArtifacts = WorkflowRecord;
62
+
63
+ export type AdoptionPlanItem = WorkflowRecord;
64
+
65
+ export type ProjectionImpact = WorkflowRecord;
66
+
67
+ export type UiImpact = WorkflowRecord;
68
+
69
+ export type WorkflowImpact = WorkflowRecord;
70
+
71
+ export type AuthHint = WorkflowRecord;
72
+
73
+ export type Diagnostic = WorkflowRecord;
74
+
75
+ declare global {
76
+ type WorkflowRecord = import("./types.js").WorkflowRecord;
77
+ type WorkflowFiles = import("./types.js").WorkflowFiles;
78
+ type WorkflowOptions = import("./types.js").WorkflowOptions;
79
+ type WorkspacePaths = import("./types.js").WorkspacePaths;
80
+ type MarkdownFrontmatter = import("./types.js").MarkdownFrontmatter;
81
+ type WorkflowResult = import("./types.js").WorkflowResult;
82
+ type ResolvedGraph = import("./types.js").ResolvedGraph;
83
+ type CandidateRecord = import("./types.js").CandidateRecord;
84
+ type CandidateBundle = import("./types.js").CandidateBundle;
85
+ type CandidateBundles = import("./types.js").CandidateBundles;
86
+ type ImportArtifacts = import("./types.js").ImportArtifacts;
87
+ type AdoptionPlanItem = import("./types.js").AdoptionPlanItem;
88
+ type ProjectionImpact = import("./types.js").ProjectionImpact;
89
+ type UiImpact = import("./types.js").UiImpact;
90
+ type WorkflowImpact = import("./types.js").WorkflowImpact;
91
+ type AuthHint = import("./types.js").AuthHint;
92
+ type Diagnostic = import("./types.js").Diagnostic;
93
+ }
@@ -0,0 +1 @@
1
+ export function runWorkflow(name: string, inputPath: string, options?: any): any;