@topogram/cli 0.3.64 → 0.3.66

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (278) hide show
  1. package/package.json +1 -1
  2. package/src/adoption/plan/index.js +716 -0
  3. package/src/adoption/plan.js +12 -703
  4. package/src/adoption/reporting.js +1 -1
  5. package/src/agent-brief.js +7 -21
  6. package/src/agent-ops/query-builders/auth.js +375 -0
  7. package/src/agent-ops/query-builders/change-risk/change-plan.js +123 -0
  8. package/src/agent-ops/query-builders/change-risk/import-plan.js +49 -0
  9. package/src/agent-ops/query-builders/change-risk/maintained.js +286 -0
  10. package/src/agent-ops/query-builders/change-risk/review-packets.js +123 -0
  11. package/src/agent-ops/query-builders/change-risk/risk.js +189 -0
  12. package/src/agent-ops/query-builders/change-risk.js +25 -0
  13. package/src/agent-ops/query-builders/common.js +149 -0
  14. package/src/agent-ops/query-builders/maintained-risk.js +539 -0
  15. package/src/agent-ops/query-builders/maintained-shared.js +120 -0
  16. package/src/agent-ops/query-builders/multi-agent.js +547 -0
  17. package/src/agent-ops/query-builders/projection-impacts.js +514 -0
  18. package/src/agent-ops/query-builders/work-packets.js +417 -0
  19. package/src/agent-ops/query-builders/workflow-context-shared.js +300 -0
  20. package/src/agent-ops/query-builders/workflow-context.js +398 -0
  21. package/src/agent-ops/query-builders/workflow-presets-core.js +677 -0
  22. package/src/agent-ops/query-builders/workflow-presets.js +341 -0
  23. package/src/agent-ops/query-builders.d.ts +26 -26
  24. package/src/agent-ops/query-builders.js +42 -5021
  25. package/src/archive/jsonl.js +2 -2
  26. package/src/archive/resolver-bridge.js +1 -1
  27. package/src/archive/unarchive.js +2 -1
  28. package/src/catalog/constants.js +10 -0
  29. package/src/catalog/copy.js +65 -0
  30. package/src/catalog/diagnostics.js +15 -0
  31. package/src/catalog/entries.js +42 -0
  32. package/src/catalog/files.js +67 -0
  33. package/src/catalog/provenance.js +123 -0
  34. package/src/catalog/source.js +150 -0
  35. package/src/catalog/validation.js +252 -0
  36. package/src/catalog.d.ts +2 -0
  37. package/src/catalog.js +18 -746
  38. package/src/cli/command-parsers/project.js +3 -0
  39. package/src/cli/command-parsers/shared.js +1 -1
  40. package/src/cli/commands/agent.js +2 -2
  41. package/src/cli/commands/catalog/check.js +31 -0
  42. package/src/cli/commands/catalog/copy.js +59 -0
  43. package/src/cli/commands/catalog/doctor.js +248 -0
  44. package/src/cli/commands/catalog/help.js +21 -0
  45. package/src/cli/commands/catalog/list.js +52 -0
  46. package/src/cli/commands/catalog/runner.js +92 -0
  47. package/src/cli/commands/catalog/shared.js +17 -0
  48. package/src/cli/commands/catalog/show.js +134 -0
  49. package/src/cli/commands/catalog.js +30 -615
  50. package/src/cli/commands/check.js +3 -3
  51. package/src/cli/commands/doctor.js +2 -9
  52. package/src/cli/commands/generator-policy/package-info.js +162 -0
  53. package/src/cli/commands/generator-policy/payloads.js +372 -0
  54. package/src/cli/commands/generator-policy/printers.js +159 -0
  55. package/src/cli/commands/generator-policy/runner.js +81 -0
  56. package/src/cli/commands/generator-policy/shared.js +39 -0
  57. package/src/cli/commands/generator-policy.js +15 -783
  58. package/src/cli/commands/import/adopt.js +170 -0
  59. package/src/cli/commands/import/check.js +91 -0
  60. package/src/cli/commands/import/diff.js +84 -0
  61. package/src/cli/commands/import/help.js +47 -0
  62. package/src/cli/commands/import/paths.js +269 -0
  63. package/src/cli/commands/import/plan.js +292 -0
  64. package/src/cli/commands/import/refresh.js +471 -0
  65. package/src/cli/commands/import/status-history.js +196 -0
  66. package/src/cli/commands/import/workspace.js +233 -0
  67. package/src/cli/commands/import.js +33 -1732
  68. package/src/cli/commands/migrate.js +153 -0
  69. package/src/cli/commands/package/constants.js +17 -0
  70. package/src/cli/commands/package/doctor.js +240 -0
  71. package/src/cli/commands/package/help.js +27 -0
  72. package/src/cli/commands/package/lockfile.js +135 -0
  73. package/src/cli/commands/package/npm.js +97 -0
  74. package/src/cli/commands/package/reporting.js +35 -0
  75. package/src/cli/commands/package/runner.js +33 -0
  76. package/src/cli/commands/package/shared.js +9 -0
  77. package/src/cli/commands/package/update-cli.js +252 -0
  78. package/src/cli/commands/package/versions.js +35 -0
  79. package/src/cli/commands/package.js +29 -813
  80. package/src/cli/commands/query/change-plan.js +68 -0
  81. package/src/cli/commands/query/definitions.js +202 -0
  82. package/src/cli/commands/query/import-adopt.js +121 -0
  83. package/src/cli/commands/query/runner/artifacts.js +102 -0
  84. package/src/cli/commands/query/runner/boundaries.js +211 -0
  85. package/src/cli/commands/query/runner/change.js +182 -0
  86. package/src/cli/commands/query/runner/import-adopt.js +111 -0
  87. package/src/cli/commands/query/runner/index.js +31 -0
  88. package/src/cli/commands/query/runner/output.js +12 -0
  89. package/src/cli/commands/query/runner/workflow.js +241 -0
  90. package/src/cli/commands/query/runner.js +3 -0
  91. package/src/cli/commands/query/workflow-context.js +5 -0
  92. package/src/cli/commands/query/workspace.js +270 -0
  93. package/src/cli/commands/query.js +9 -1300
  94. package/src/cli/commands/source.js +3 -12
  95. package/src/cli/commands/template/baseline.js +100 -0
  96. package/src/cli/commands/template/check.js +467 -0
  97. package/src/cli/commands/template/constants.js +8 -0
  98. package/src/cli/commands/template/diagnostics.js +26 -0
  99. package/src/cli/commands/template/help.js +28 -0
  100. package/src/cli/commands/template/lifecycle.js +404 -0
  101. package/src/cli/commands/template/list-show.js +287 -0
  102. package/src/cli/commands/template/policy.js +422 -0
  103. package/src/cli/commands/template/shared.js +127 -0
  104. package/src/cli/commands/template/updates.js +352 -0
  105. package/src/cli/commands/template-runner.js +6 -6
  106. package/src/cli/commands/template.js +41 -2143
  107. package/src/cli/commands/trust.js +1 -1
  108. package/src/cli/commands/workflow.js +6 -1
  109. package/src/cli/dispatcher.js +6 -1
  110. package/src/cli/help.js +15 -14
  111. package/src/cli/migration-guidance.js +1 -1
  112. package/src/cli/output-safety.js +2 -1
  113. package/src/cli/path-normalization.js +3 -13
  114. package/src/generator/api/contracts.js +497 -0
  115. package/src/generator/api/metadata.js +221 -0
  116. package/src/generator/api/openapi.js +559 -0
  117. package/src/generator/api/schema.js +124 -0
  118. package/src/generator/api/types.d.ts +98 -0
  119. package/src/generator/api.js +3 -1195
  120. package/src/generator/context/domain-page.js +1 -1
  121. package/src/generator/context/shared/domain-sdlc.js +282 -0
  122. package/src/generator/context/shared/maintained-boundary.js +665 -0
  123. package/src/generator/context/shared/metrics.js +85 -0
  124. package/src/generator/context/shared/primitives.js +64 -0
  125. package/src/generator/context/shared/relationships.js +453 -0
  126. package/src/generator/context/shared/summaries.js +263 -0
  127. package/src/generator/context/shared/types.d.ts +207 -0
  128. package/src/generator/context/shared.d.ts +42 -0
  129. package/src/generator/context/shared.js +80 -1390
  130. package/src/generator/context/slice/core.js +397 -0
  131. package/src/generator/context/slice/sdlc.js +417 -0
  132. package/src/generator/context/slice/ui-packets.js +183 -0
  133. package/src/generator/context/slice.js +2 -859
  134. package/src/generator/context/task-mode.js +2 -2
  135. package/src/generator/registry/index.js +507 -0
  136. package/src/generator/registry.js +18 -504
  137. package/src/generator/runtime/environment/index.js +666 -0
  138. package/src/generator/runtime/environment.js +4 -666
  139. package/src/generator/runtime/runtime-check/index.js +554 -0
  140. package/src/generator/runtime/runtime-check.js +4 -554
  141. package/src/generator/runtime/shared/index.js +572 -0
  142. package/src/generator/runtime/shared.js +19 -570
  143. package/src/generator/sdlc/doc-page.js +1 -1
  144. package/src/generator/shared.d.ts +2 -0
  145. package/src/generator/surfaces/databases/lifecycle-shared.js +1 -1
  146. package/src/generator/surfaces/native/swiftui-templates/README.generated.md +1 -1
  147. package/src/generator/surfaces/shared.d.ts +3 -0
  148. package/src/generator/widget-conformance/behavior-report.js +258 -0
  149. package/src/generator/widget-conformance/checks.js +371 -0
  150. package/src/generator/widget-conformance/projection-context.js +200 -0
  151. package/src/generator/widget-conformance/report.js +166 -0
  152. package/src/generator/widget-conformance/types.d.ts +121 -0
  153. package/src/generator/widget-conformance.js +3 -824
  154. package/src/import/core/context.d.ts +3 -0
  155. package/src/import/core/context.js +5 -7
  156. package/src/import/core/contracts.d.ts +1 -0
  157. package/src/import/core/registry.d.ts +4 -0
  158. package/src/import/core/runner/candidates.js +337 -0
  159. package/src/import/core/runner/options.js +22 -0
  160. package/src/import/core/runner/reports.js +51 -0
  161. package/src/import/core/runner/run.js +79 -0
  162. package/src/import/core/runner/tracks.js +150 -0
  163. package/src/import/core/runner/ui-drafts.js +393 -0
  164. package/src/import/core/runner.js +3 -698
  165. package/src/import/core/shared/api-routes.js +221 -0
  166. package/src/import/core/shared/candidates.js +97 -0
  167. package/src/import/core/shared/files.js +177 -0
  168. package/src/import/core/shared/next-app.js +389 -0
  169. package/src/import/core/shared/types.d.ts +51 -0
  170. package/src/import/core/shared/ui-routes.js +230 -0
  171. package/src/import/core/shared.js +60 -861
  172. package/src/new-project/constants.js +128 -0
  173. package/src/new-project/create.js +90 -0
  174. package/src/new-project/json.js +28 -0
  175. package/src/new-project/metadata.js +96 -0
  176. package/src/new-project/package-spec.js +161 -0
  177. package/src/new-project/project-files.js +351 -0
  178. package/src/new-project/template-policy.js +269 -0
  179. package/src/new-project/template-resolution.js +370 -0
  180. package/src/new-project/template-snapshots.js +442 -0
  181. package/src/new-project/template-updates.js +512 -0
  182. package/src/new-project/types.d.ts +83 -0
  183. package/src/new-project.js +6 -2277
  184. package/src/parser.d.ts +87 -1
  185. package/src/parser.js +118 -0
  186. package/src/policy/review-boundaries.d.ts +15 -0
  187. package/src/project-config/index.js +591 -0
  188. package/src/project-config.js +19 -561
  189. package/src/resolver/enrich/acceptance-criterion.js +2 -0
  190. package/src/resolver/enrich/bug.js +2 -0
  191. package/src/resolver/enrich/pitch.js +2 -0
  192. package/src/resolver/enrich/requirement.js +2 -0
  193. package/src/resolver/enrich/task.js +2 -0
  194. package/src/resolver/index.js +19 -2089
  195. package/src/resolver/normalize.js +384 -1
  196. package/src/resolver/plans.js +168 -0
  197. package/src/resolver/projections-api.js +494 -0
  198. package/src/resolver/projections-db.js +133 -0
  199. package/src/resolver/projections-ui.js +317 -0
  200. package/src/resolver/shapes.js +251 -0
  201. package/src/resolver/shared.js +278 -0
  202. package/src/resolver/widgets.js +132 -0
  203. package/src/sdlc/adopt.js +6 -5
  204. package/src/sdlc/paths.js +3 -5
  205. package/src/sdlc/scaffold.js +2 -1
  206. package/src/template-trust/constants.js +62 -0
  207. package/src/template-trust/content.js +258 -0
  208. package/src/template-trust/diff.js +92 -0
  209. package/src/template-trust/policy.js +61 -0
  210. package/src/template-trust/record.js +90 -0
  211. package/src/template-trust/status.js +182 -0
  212. package/src/template-trust.js +24 -687
  213. package/src/text-helpers.d.ts +1 -0
  214. package/src/topogram-types.d.ts +69 -0
  215. package/src/validator/common.js +488 -0
  216. package/src/validator/data-model.js +237 -0
  217. package/src/validator/docs.js +167 -0
  218. package/src/validator/expressions.js +146 -1
  219. package/src/validator/index.d.ts +23 -0
  220. package/src/validator/index.js +32 -3585
  221. package/src/validator/kinds.d.ts +41 -0
  222. package/src/validator/kinds.js +2 -0
  223. package/src/validator/model-helpers.js +46 -0
  224. package/src/validator/per-kind/acceptance-criterion.js +5 -0
  225. package/src/validator/per-kind/bug.js +6 -0
  226. package/src/validator/per-kind/domain.js +15 -2
  227. package/src/validator/per-kind/pitch.js +7 -0
  228. package/src/validator/per-kind/requirement.js +5 -0
  229. package/src/validator/per-kind/task.js +7 -0
  230. package/src/validator/per-kind/widget.js +14 -0
  231. package/src/validator/projections/api-http-async.js +410 -0
  232. package/src/validator/projections/api-http-authz.js +88 -0
  233. package/src/validator/projections/api-http-core.js +205 -0
  234. package/src/validator/projections/api-http-policies.js +339 -0
  235. package/src/validator/projections/api-http-responses.js +233 -0
  236. package/src/validator/projections/api-http.js +44 -0
  237. package/src/validator/projections/db.js +353 -0
  238. package/src/validator/projections/generator-defaults.js +45 -0
  239. package/src/validator/projections/helpers.js +87 -0
  240. package/src/validator/projections/ui-helpers.js +214 -0
  241. package/src/validator/projections/ui-navigation.js +344 -0
  242. package/src/validator/projections/ui-structure.js +364 -0
  243. package/src/validator/projections/ui-widgets.js +493 -0
  244. package/src/validator/projections/ui.js +46 -0
  245. package/src/validator/registry.js +48 -1
  246. package/src/validator/utils.d.ts +20 -0
  247. package/src/validator/utils.js +115 -12
  248. package/src/widget-behavior.d.ts +1 -0
  249. package/src/workflows/import-app/api/collect.js +221 -0
  250. package/src/workflows/import-app/api/openapi.js +257 -0
  251. package/src/workflows/import-app/api/routes.js +327 -0
  252. package/src/workflows/import-app/api/sources.js +22 -0
  253. package/src/workflows/import-app/api.js +2 -797
  254. package/src/workflows/reconcile/adoption-plan/build.js +212 -0
  255. package/src/workflows/reconcile/adoption-plan/dependencies.js +75 -0
  256. package/src/workflows/reconcile/adoption-plan/outputs.js +153 -0
  257. package/src/workflows/reconcile/adoption-plan/paths.js +58 -0
  258. package/src/workflows/reconcile/adoption-plan/projection-patches.js +177 -0
  259. package/src/workflows/reconcile/adoption-plan/reasons.js +107 -0
  260. package/src/workflows/reconcile/adoption-plan.js +30 -740
  261. package/src/workflows/reconcile/auth/closures.js +115 -0
  262. package/src/workflows/reconcile/auth/formatters.js +142 -0
  263. package/src/workflows/reconcile/auth/inference.js +330 -0
  264. package/src/workflows/reconcile/auth/roles.js +122 -0
  265. package/src/workflows/reconcile/auth.js +35 -690
  266. package/src/workflows/reconcile/bundle-core/index.js +600 -0
  267. package/src/workflows/reconcile/bundle-core.js +12 -598
  268. package/src/workflows/reconcile/candidate-model.js +18 -2
  269. package/src/workflows/reconcile/canonical-surface.js +1 -1
  270. package/src/workflows/reconcile/impacts/adoption-plan.js +196 -0
  271. package/src/workflows/reconcile/impacts/indexes.js +105 -0
  272. package/src/workflows/reconcile/impacts/patches.js +252 -0
  273. package/src/workflows/reconcile/impacts/reports.js +80 -0
  274. package/src/workflows/reconcile/impacts.js +14 -623
  275. package/src/workflows/reconcile/renderers.js +41 -6
  276. package/src/workflows/shared.js +5 -11
  277. package/src/workspace-docs.d.ts +29 -0
  278. package/src/workspace-paths.js +328 -0
@@ -0,0 +1,170 @@
1
+ // @ts-check
2
+
3
+ import fs from "node:fs";
4
+ import path from "node:path";
5
+
6
+ import { buildTopogramImportStatus } from "../../../import/provenance.js";
7
+ import { runWorkflow } from "../../../workflows.js";
8
+ import { CLI_PACKAGE_NAME, readInstalledCliPackageVersion } from "../package.js";
9
+ import {
10
+ appendImportAdoptionReceipt,
11
+ importAdoptCommand,
12
+ importProjectCommandPath,
13
+ projectFileHash
14
+ } from "./paths.js";
15
+ import { readImportAdoptionArtifacts } from "./plan.js";
16
+ import { writeRelativeFiles } from "./workspace.js";
17
+
18
+ /**
19
+ * @typedef {Record<string, any>} AnyRecord
20
+ */
21
+
22
+ /**
23
+ * @param {string} outputRoot
24
+ * @param {string[]} writtenFiles
25
+ * @returns {AnyRecord[]}
26
+ */
27
+ export function writtenFileHashesForReceipt(outputRoot, writtenFiles) {
28
+ return (writtenFiles || []).map((relativePath) => {
29
+ const filePath = path.join(outputRoot, relativePath);
30
+ const hash = fs.existsSync(filePath) ? projectFileHash(filePath) : null;
31
+ return {
32
+ path: relativePath,
33
+ sha256: hash?.sha256 || null,
34
+ size: hash?.size || null
35
+ };
36
+ });
37
+ }
38
+
39
+ /**
40
+ * @param {{ artifacts: AnyRecord, selector: string, options: AnyRecord, importStatus: AnyRecord, summary: AnyRecord, writtenFiles: string[], outputRoot: string }} input
41
+ * @returns {AnyRecord}
42
+ */
43
+ export function buildImportAdoptionReceipt({ artifacts, selector, options, importStatus, summary, writtenFiles, outputRoot }) {
44
+ return {
45
+ type: "topogram_import_adoption_receipt",
46
+ version: "0.1",
47
+ timestamp: new Date().toISOString(),
48
+ cli: {
49
+ packageName: CLI_PACKAGE_NAME,
50
+ version: readInstalledCliPackageVersion()
51
+ },
52
+ projectRoot: artifacts.projectRoot,
53
+ topogramRoot: artifacts.topogramRoot,
54
+ selector,
55
+ mode: "write",
56
+ dryRun: false,
57
+ forced: Boolean(options.force),
58
+ reason: options.reason || null,
59
+ sourceProvenance: {
60
+ ok: importStatus.ok,
61
+ status: importStatus.status,
62
+ path: importStatus.path || null,
63
+ changed: importStatus.content?.changed || [],
64
+ added: importStatus.content?.added || [],
65
+ removed: importStatus.content?.removed || []
66
+ },
67
+ promotedCanonicalItems: (summary.promoted_canonical_items || []).map((/** @type {AnyRecord} */ item) => ({
68
+ bundle: item.bundle || null,
69
+ kind: item.kind || null,
70
+ item: item.item || null,
71
+ canonicalRelPath: item.canonical_rel_path || null,
72
+ sourcePath: item.source_path || null,
73
+ changeType: item.change_type || null
74
+ })),
75
+ writtenFiles,
76
+ writtenFileHashes: writtenFileHashesForReceipt(outputRoot, writtenFiles),
77
+ outputRoot
78
+ };
79
+ }
80
+
81
+ /**
82
+ * @param {string} selector
83
+ * @param {string} inputPath
84
+ * @param {{ write?: boolean, dryRun?: boolean, force?: boolean, reason?: string|null, refreshAdopted?: boolean }} [options]
85
+ * @returns {AnyRecord}
86
+ */
87
+ export function buildBrownfieldImportAdoptPayload(selector, inputPath, options = {}) {
88
+ if (!selector) {
89
+ throw new Error("Missing required <selector>. Example: topogram import adopt bundle:task --dry-run");
90
+ }
91
+ if (options.write && options.dryRun) {
92
+ throw new Error("Use either --dry-run or --write, not both.");
93
+ }
94
+ if (options.write && options.force && !options.reason) {
95
+ throw new Error("Forced import adoption writes require --reason <text>.");
96
+ }
97
+ const artifacts = readImportAdoptionArtifacts(inputPath);
98
+ const importStatus = buildTopogramImportStatus(artifacts.projectRoot);
99
+ if (options.write && !options.force && !importStatus.ok) {
100
+ throw new Error(`Refusing to write import adoption because brownfield source provenance is ${importStatus.status}. Run 'topogram import check ${importProjectCommandPath(artifacts.projectRoot)}', review the changed source evidence, rerun import, or pass --force --reason <text> after review.`);
101
+ }
102
+ const result = runWorkflow("reconcile", artifacts.projectRoot, {
103
+ adopt: selector,
104
+ write: Boolean(options.write),
105
+ refreshAdopted: Boolean(options.refreshAdopted)
106
+ });
107
+ const outputRoot = path.resolve(result.defaultOutDir || artifacts.topogramRoot);
108
+ const writtenFiles = options.write ? writeRelativeFiles(outputRoot, result.files || {}) : [];
109
+ const summary = result.summary || {};
110
+ const receipt = options.write
111
+ ? buildImportAdoptionReceipt({ artifacts, selector, options, importStatus, summary, writtenFiles, outputRoot })
112
+ : null;
113
+ const receiptPath = receipt ? appendImportAdoptionReceipt(artifacts.projectRoot, receipt) : null;
114
+ return {
115
+ ok: true,
116
+ projectRoot: artifacts.projectRoot,
117
+ topogramRoot: artifacts.topogramRoot,
118
+ selector,
119
+ dryRun: !options.write,
120
+ write: Boolean(options.write),
121
+ forced: Boolean(options.force),
122
+ reason: options.reason || null,
123
+ outputRoot,
124
+ promotedCanonicalItemCount: (summary.promoted_canonical_items || []).length,
125
+ promotedCanonicalItems: summary.promoted_canonical_items || [],
126
+ writtenFiles,
127
+ receipt,
128
+ receiptPath,
129
+ adoption: summary,
130
+ import: importStatus,
131
+ warnings: options.write && options.force && !importStatus.ok
132
+ ? [`Brownfield source provenance is ${importStatus.status}; adoption write was forced with reason: ${options.reason}.`]
133
+ : [],
134
+ nextCommands: options.write
135
+ ? [
136
+ `topogram import history ${importProjectCommandPath(artifacts.projectRoot)}`,
137
+ `topogram import status ${importProjectCommandPath(artifacts.projectRoot)}`,
138
+ `topogram check ${importProjectCommandPath(artifacts.projectRoot)}`
139
+ ]
140
+ : [
141
+ importAdoptCommand(artifacts.projectRoot, selector, true),
142
+ `topogram import status ${importProjectCommandPath(artifacts.projectRoot)}`
143
+ ]
144
+ };
145
+ }
146
+
147
+ /**
148
+ * @param {AnyRecord} payload
149
+ * @returns {void}
150
+ */
151
+ export function printBrownfieldImportAdopt(payload) {
152
+ console.log(`${payload.dryRun ? "Previewed" : "Applied"} import adoption for ${payload.selector}.`);
153
+ console.log(`Project: ${payload.projectRoot}`);
154
+ console.log(`Promoted canonical items: ${payload.promotedCanonicalItemCount}`);
155
+ console.log(`Written files: ${payload.writtenFiles.length}`);
156
+ if (payload.receiptPath) {
157
+ console.log(`Receipt: ${payload.receiptPath}`);
158
+ }
159
+ if (payload.dryRun) {
160
+ console.log("No files were written. Re-run with --write to promote these candidates.");
161
+ }
162
+ for (const warning of payload.warnings || []) {
163
+ console.log(`Warning: ${warning}`);
164
+ }
165
+ console.log("");
166
+ console.log("Next steps:");
167
+ for (const command of payload.nextCommands) {
168
+ console.log(` ${command}`);
169
+ }
170
+ }
@@ -0,0 +1,91 @@
1
+ // @ts-check
2
+
3
+ import { parsePath } from "../../../parser.js";
4
+ import {
5
+ loadProjectConfig,
6
+ validateProjectConfig,
7
+ validateProjectOutputOwnership
8
+ } from "../../../project-config.js";
9
+ import { resolveWorkspace } from "../../../resolver.js";
10
+ import { validateProjectImplementationTrust } from "../../../template-trust.js";
11
+ import { buildTopogramImportStatus } from "../../../import/provenance.js";
12
+ import {
13
+ checkSummaryPayload,
14
+ combineProjectValidationResults,
15
+ normalizeProjectRoot
16
+ } from "./paths.js";
17
+
18
+ /**
19
+ * @typedef {Record<string, any>} AnyRecord
20
+ */
21
+
22
+ /**
23
+ * @param {string} inputPath
24
+ * @returns {ReturnType<typeof checkSummaryPayload>}
25
+ */
26
+ export function buildTopogramCheckPayloadForPath(inputPath) {
27
+ const ast = parsePath(inputPath);
28
+ const resolved = resolveWorkspace(ast);
29
+ const explicitProjectConfig = loadProjectConfig(inputPath);
30
+ const projectValidation = explicitProjectConfig
31
+ ? combineProjectValidationResults(
32
+ validateProjectConfig(explicitProjectConfig.config, resolved.ok ? resolved.graph : null, { configDir: explicitProjectConfig.configDir }),
33
+ validateProjectOutputOwnership(explicitProjectConfig),
34
+ validateProjectImplementationTrust(explicitProjectConfig)
35
+ )
36
+ : { ok: false, errors: [{ message: "Missing topogram.project.json or compatible topogram.implementation.json", loc: null }] };
37
+ return checkSummaryPayload({ inputPath, ast, resolved, projectConfigInfo: explicitProjectConfig, projectValidation });
38
+ }
39
+
40
+ /**
41
+ * @param {string} projectRoot
42
+ * @returns {{ ok: boolean, projectRoot: string, import: ReturnType<typeof buildTopogramImportStatus>, topogram: ReturnType<typeof buildTopogramCheckPayloadForPath>, errors: any[] }}
43
+ */
44
+ export function buildBrownfieldImportCheckPayload(projectRoot) {
45
+ const resolvedRoot = normalizeProjectRoot(projectRoot);
46
+ const importStatus = buildTopogramImportStatus(resolvedRoot);
47
+ const topogramCheck = buildTopogramCheckPayloadForPath(resolvedRoot);
48
+ return {
49
+ ok: importStatus.ok && topogramCheck.ok,
50
+ projectRoot: resolvedRoot,
51
+ import: importStatus,
52
+ topogram: topogramCheck,
53
+ errors: [
54
+ ...(importStatus.errors || []).map((/** @type {string} */ message) => ({ source: "import", message })),
55
+ ...(topogramCheck.errors || [])
56
+ ]
57
+ };
58
+ }
59
+
60
+ /**
61
+ * @param {ReturnType<typeof buildBrownfieldImportCheckPayload>} payload
62
+ * @returns {void}
63
+ */
64
+ export function printBrownfieldImportCheck(payload) {
65
+ console.log(`Topogram import check: ${payload.import.status}`);
66
+ console.log(`Project: ${payload.projectRoot}`);
67
+ if (payload.import.source?.source?.path) {
68
+ console.log(`Imported source: ${payload.import.source.source.path}`);
69
+ }
70
+ console.log(`Provenance: ${payload.import.path}`);
71
+ if (payload.import.source?.files) {
72
+ console.log(`Trusted source files: ${payload.import.source.files.length}`);
73
+ }
74
+ if (payload.import.status === "changed") {
75
+ console.log(`Changed source files: ${payload.import.content.changed.length}`);
76
+ console.log(`Added source files: ${payload.import.content.added.length}`);
77
+ console.log(`Removed source files: ${payload.import.content.removed.length}`);
78
+ }
79
+ console.log(`Topogram check: ${payload.topogram.ok ? "passed" : "failed"}`);
80
+ console.log("Imported Topogram artifacts are project-owned; import check compares only the brownfield source hashes trusted at import time plus normal Topogram validity.");
81
+ for (const diagnostic of payload.import.diagnostics || []) {
82
+ const label = diagnostic.severity === "warning" ? "Warning" : "Error";
83
+ console.log(`${label}: ${diagnostic.message}`);
84
+ if (diagnostic.suggestedFix) {
85
+ console.log(`Fix: ${diagnostic.suggestedFix}`);
86
+ }
87
+ }
88
+ for (const error of payload.topogram.errors || []) {
89
+ console.log(`Error: ${error.message}`);
90
+ }
91
+ }
@@ -0,0 +1,84 @@
1
+ // @ts-check
2
+
3
+ import { importProjectCommandPath } from "./paths.js";
4
+ import { buildBrownfieldImportRefreshAnalysis } from "./refresh.js";
5
+
6
+ /**
7
+ * @typedef {Record<string, any>} AnyRecord
8
+ */
9
+
10
+ /**
11
+ * @param {string} inputPath
12
+ * @param {{ sourcePath?: string|null }} [options]
13
+ * @returns {AnyRecord}
14
+ */
15
+ export function buildBrownfieldImportDiffPayload(inputPath, options = {}) {
16
+ const analysis = buildBrownfieldImportRefreshAnalysis(inputPath, options);
17
+ return {
18
+ ok: true,
19
+ projectRoot: analysis.projectRoot,
20
+ topogramRoot: analysis.topogramRoot,
21
+ sourcePath: analysis.sourcePath,
22
+ provenancePath: analysis.provenancePath,
23
+ importStatus: analysis.previousImportStatus,
24
+ sourceDiff: analysis.sourceDiff,
25
+ tracks: analysis.tracks,
26
+ sourceFiles: analysis.sourceFiles,
27
+ candidateCounts: analysis.candidateCounts,
28
+ candidateCountDeltas: analysis.candidateCountDeltas,
29
+ adoptionPlanDeltas: analysis.adoptionPlanDeltas,
30
+ receiptVerification: analysis.receiptVerification,
31
+ plannedFiles: analysis.plannedFiles,
32
+ nextCommands: [
33
+ `topogram import refresh ${importProjectCommandPath(analysis.projectRoot)} --dry-run`,
34
+ `topogram import refresh ${importProjectCommandPath(analysis.projectRoot)}`,
35
+ `topogram import plan ${importProjectCommandPath(analysis.projectRoot)}`
36
+ ]
37
+ };
38
+ }
39
+
40
+ /**
41
+ * @param {ReturnType<typeof buildBrownfieldImportDiffPayload>} payload
42
+ * @returns {void}
43
+ */
44
+ export function printBrownfieldImportDiff(payload) {
45
+ console.log(`Import diff for ${payload.projectRoot}`);
46
+ console.log(`Source: ${payload.sourcePath}`);
47
+ console.log(`Source status: ${payload.importStatus}`);
48
+ console.log(`Source diff: changed=${payload.sourceDiff.counts.changed}, added=${payload.sourceDiff.counts.added}, removed=${payload.sourceDiff.counts.removed}`);
49
+ for (const filePath of [...payload.sourceDiff.changed, ...payload.sourceDiff.added, ...payload.sourceDiff.removed].slice(0, 12)) {
50
+ const status = payload.sourceDiff.changed.includes(filePath)
51
+ ? "changed"
52
+ : payload.sourceDiff.added.includes(filePath)
53
+ ? "added"
54
+ : "removed";
55
+ console.log(`- ${filePath}: ${status}`);
56
+ }
57
+ console.log("");
58
+ console.log("Candidate count changes:");
59
+ const candidateChanges = payload.candidateCountDeltas.changed || [];
60
+ if (candidateChanges.length === 0) {
61
+ console.log("- None");
62
+ } else {
63
+ for (const item of candidateChanges) {
64
+ const sign = item.delta > 0 ? "+" : "";
65
+ console.log(`- ${item.key}: ${item.previous} -> ${item.next} (${sign}${item.delta})`);
66
+ }
67
+ }
68
+ console.log("");
69
+ console.log(`Adoption plan changes: added=${payload.adoptionPlanDeltas.added.length}, removed=${payload.adoptionPlanDeltas.removed.length}, changed=${payload.adoptionPlanDeltas.changed.length}`);
70
+ for (const item of payload.adoptionPlanDeltas.added.slice(0, 8)) {
71
+ console.log(`- added ${item.bundle}/${item.kind}/${item.item}`);
72
+ }
73
+ for (const item of payload.adoptionPlanDeltas.removed.slice(0, 8)) {
74
+ console.log(`- removed ${item.bundle}/${item.kind}/${item.item}`);
75
+ }
76
+ console.log(`Receipt verification: ${payload.receiptVerification.status}`);
77
+ const receiptSummary = payload.receiptVerification.summary;
78
+ console.log(`Adopted file audit: changed=${receiptSummary.changedFileCount}, removed=${receiptSummary.removedFileCount}, unverifiable=${receiptSummary.unverifiableFileCount}`);
79
+ console.log("");
80
+ console.log("Next steps:");
81
+ for (const command of payload.nextCommands) {
82
+ console.log(` ${command}`);
83
+ }
84
+ }
@@ -0,0 +1,47 @@
1
+ // @ts-check
2
+
3
+ import { TOPOGRAM_IMPORT_FILE } from "../../../import/provenance.js";
4
+ import { TOPOGRAM_IMPORT_ADOPTIONS_FILE } from "./paths.js";
5
+
6
+ export function printImportHelp() {
7
+ console.log("Usage: topogram import <app-path> --out <target> [--from <track[,track]>] [--json]");
8
+ console.log(" or: topogram import refresh [path] [--from <app-path>] [--dry-run] [--json]");
9
+ console.log(" or: topogram import diff [path] [--json]");
10
+ console.log(" or: topogram import check [path] [--json]");
11
+ console.log(" or: topogram import plan [path] [--json]");
12
+ console.log(" or: topogram import adopt --list [path] [--json]");
13
+ console.log(" or: topogram import adopt <selector> [path] [--dry-run|--write] [--force --reason <text>] [--json]");
14
+ console.log(" or: topogram import status [path] [--json]");
15
+ console.log(" or: topogram import history [path] [--verify] [--json]");
16
+ console.log("");
17
+ console.log("Creates an editable Topogram workspace from a brownfield app without modifying the app.");
18
+ console.log("");
19
+ console.log("Behavior:");
20
+ console.log(" - writes raw import candidates under topo/candidates/app");
21
+ console.log(" - writes reconcile proposal bundles under topo/candidates/reconcile");
22
+ console.log(" - writes topogram.project.json with maintained ownership and no generated stack binding");
23
+ console.log(` - writes ${TOPOGRAM_IMPORT_FILE} with source file hashes from import time`);
24
+ console.log(" - imported Topogram artifacts are project-owned after creation");
25
+ console.log(" - refresh rewrites only candidate/reconcile artifacts and source provenance");
26
+ console.log(" - adoption previews never write canonical Topogram files unless --write is passed");
27
+ console.log(" - adoption writes refuse dirty brownfield source provenance unless --force is passed");
28
+ console.log(` - adoption writes append audit receipts to ${TOPOGRAM_IMPORT_ADOPTIONS_FILE}`);
29
+ console.log(" - forced adoption writes require --reason <text>");
30
+ console.log("");
31
+ console.log("Examples:");
32
+ console.log(" topogram import ./existing-app --out ./imported-topogram");
33
+ console.log(" topogram import ./existing-app --out ./imported-topogram --from db,api,ui");
34
+ console.log(" topogram import diff ./imported-topogram");
35
+ console.log(" topogram import refresh ./imported-topogram --from ./existing-app --dry-run");
36
+ console.log(" topogram import refresh ./imported-topogram --from ./existing-app");
37
+ console.log(" topogram import check ./imported-topogram");
38
+ console.log(" topogram import plan ./imported-topogram");
39
+ console.log(" topogram import adopt --list ./imported-topogram");
40
+ console.log(" topogram import adopt bundle:task ./imported-topogram --dry-run");
41
+ console.log(" topogram import adopt bundle:task ./imported-topogram --write");
42
+ console.log(" topogram import adopt bundle:task ./imported-topogram --write --force --reason \"Reviewed source drift\"");
43
+ console.log(" topogram import status ./imported-topogram");
44
+ console.log(" topogram import history ./imported-topogram");
45
+ console.log(" topogram import history ./imported-topogram --verify");
46
+ console.log(" topogram import check --json");
47
+ }
@@ -0,0 +1,269 @@
1
+ // @ts-check
2
+
3
+ import crypto from "node:crypto";
4
+ import fs from "node:fs";
5
+ import path from "node:path";
6
+
7
+ import { stableStringify } from "../../../format.js";
8
+ import { TOPOGRAM_IMPORT_FILE } from "../../../import/provenance.js";
9
+ import { shellCommandArg } from "../catalog.js";
10
+ import { resolveTopoRoot, resolveWorkspaceContext } from "../../../workspace-paths.js";
11
+
12
+ export const TOPOGRAM_IMPORT_ADOPTIONS_FILE = ".topogram-import-adoptions.jsonl";
13
+
14
+ /**
15
+ * @typedef {Record<string, any>} AnyRecord
16
+ */
17
+
18
+ /**
19
+ * @param {string} inputPath
20
+ * @returns {string}
21
+ */
22
+ export function normalizeTopogramPath(inputPath) {
23
+ return resolveTopoRoot(inputPath);
24
+ }
25
+
26
+ /**
27
+ * @param {string} inputPath
28
+ * @returns {string}
29
+ */
30
+ export function normalizeProjectRoot(inputPath) {
31
+ return resolveWorkspaceContext(inputPath).projectRoot;
32
+ }
33
+
34
+ /**
35
+ * @param {...{ ok: boolean, errors?: any[] }|null|undefined} results
36
+ * @returns {{ ok: boolean, errors: any[] }}
37
+ */
38
+ export function combineProjectValidationResults(...results) {
39
+ const errors = [];
40
+ for (const result of results) {
41
+ errors.push(...(result?.errors || []));
42
+ }
43
+ return {
44
+ ok: errors.length === 0,
45
+ errors
46
+ };
47
+ }
48
+
49
+ /**
50
+ * @param {AnyRecord} component
51
+ * @returns {{ uses_api: string|null, uses_database: string|null }}
52
+ */
53
+ export function topologyComponentReferences(component) {
54
+ return {
55
+ uses_api: component.uses_api || null,
56
+ uses_database: component.uses_database || null
57
+ };
58
+ }
59
+
60
+ /**
61
+ * @param {AnyRecord} component
62
+ * @returns {any}
63
+ */
64
+ export function topologyComponentPort(component) {
65
+ return Object.prototype.hasOwnProperty.call(component, "port") ? component.port : null;
66
+ }
67
+
68
+ /**
69
+ * @param {AnyRecord|null|undefined} config
70
+ * @returns {{ outputs: any[], runtimes: any[], edges: any[] }}
71
+ */
72
+ export function summarizeProjectTopology(config) {
73
+ const outputs = Object.entries(config?.outputs || {})
74
+ .map(([name, output]) => ({
75
+ name,
76
+ path: output?.path || null,
77
+ ownership: output?.ownership || null
78
+ }))
79
+ .sort((left, right) => left.name.localeCompare(right.name));
80
+ const runtimes = (config?.topology?.runtimes || [])
81
+ .map((/** @type {AnyRecord} */ component) => ({
82
+ id: component.id,
83
+ kind: component.kind,
84
+ projection: component.projection,
85
+ generator: {
86
+ id: component.generator?.id || null,
87
+ version: component.generator?.version || null
88
+ },
89
+ port: topologyComponentPort(component),
90
+ references: topologyComponentReferences(component)
91
+ }))
92
+ .sort((/** @type {AnyRecord} */ left, /** @type {AnyRecord} */ right) => left.id.localeCompare(right.id));
93
+ const edges = runtimes.flatMap((/** @type {AnyRecord} */ component) => {
94
+ const references = [];
95
+ if (component.references.uses_api) {
96
+ references.push({
97
+ from: component.id,
98
+ to: component.references.uses_api,
99
+ type: "calls_api"
100
+ });
101
+ }
102
+ if (component.references.uses_database) {
103
+ references.push({
104
+ from: component.id,
105
+ to: component.references.uses_database,
106
+ type: "uses_database"
107
+ });
108
+ }
109
+ return references;
110
+ }).sort((/** @type {AnyRecord} */ left, /** @type {AnyRecord} */ right) => `${left.from}:${left.type}:${left.to}`.localeCompare(`${right.from}:${right.type}:${right.to}`));
111
+ return {
112
+ outputs,
113
+ runtimes,
114
+ edges
115
+ };
116
+ }
117
+
118
+ /**
119
+ * @param {AnyRecord|null|undefined} topology
120
+ * @returns {AnyRecord|null}
121
+ */
122
+ export function publicProjectTopology(topology) {
123
+ if (!topology || typeof topology !== "object") {
124
+ return topology || null;
125
+ }
126
+ return {
127
+ ...Object.fromEntries(Object.entries(topology).filter(([key]) => key !== "components")),
128
+ runtimes: topology.runtimes || []
129
+ };
130
+ }
131
+
132
+ /**
133
+ * @param {{ inputPath: string, ast: AnyRecord, resolved: AnyRecord, projectConfigInfo: AnyRecord|null, projectValidation: { ok: boolean, errors: any[] } }} input
134
+ * @returns {AnyRecord}
135
+ */
136
+ export function checkSummaryPayload({ inputPath, ast, resolved, projectConfigInfo, projectValidation }) {
137
+ const statementCount = ast.files.flatMap((/** @type {{ statements: any[] }} */ file) => file.statements).length;
138
+ const projectInfo = projectConfigInfo || {
139
+ configPath: null,
140
+ compatibility: false,
141
+ config: { topology: null }
142
+ };
143
+ const resolvedTopology = summarizeProjectTopology(projectInfo.config);
144
+ return {
145
+ ok: resolved.ok && projectValidation.ok,
146
+ inputPath,
147
+ topogram: {
148
+ files: ast.files.length,
149
+ statements: statementCount,
150
+ valid: resolved.ok
151
+ },
152
+ project: {
153
+ configPath: projectInfo.configPath,
154
+ compatibility: Boolean(projectInfo.compatibility),
155
+ valid: projectValidation.ok,
156
+ topology: publicProjectTopology(projectInfo.config.topology),
157
+ resolvedTopology
158
+ },
159
+ errors: [
160
+ ...(resolved.ok ? [] : resolved.validation.errors.map((/** @type {AnyRecord} */ error) => ({
161
+ source: "topogram",
162
+ message: error.message,
163
+ loc: error.loc
164
+ }))),
165
+ ...projectValidation.errors.map((error) => ({
166
+ source: "project",
167
+ message: error.message,
168
+ loc: error.loc
169
+ }))
170
+ ]
171
+ };
172
+ }
173
+
174
+ /**
175
+ * @param {string} filePath
176
+ * @returns {{ sha256: string, size: number }}
177
+ */
178
+ export function projectFileHash(filePath) {
179
+ const bytes = fs.readFileSync(filePath);
180
+ return {
181
+ sha256: crypto.createHash("sha256").update(bytes).digest("hex"),
182
+ size: bytes.length
183
+ };
184
+ }
185
+
186
+ /**
187
+ * @param {string} filePath
188
+ * @returns {AnyRecord|null}
189
+ */
190
+ export function readJsonIfExists(filePath) {
191
+ if (!fs.existsSync(filePath)) {
192
+ return null;
193
+ }
194
+ return JSON.parse(fs.readFileSync(filePath, "utf8"));
195
+ }
196
+
197
+ /**
198
+ * @param {string} projectRoot
199
+ * @returns {string}
200
+ */
201
+ export function importAdoptionsPath(projectRoot) {
202
+ return path.join(normalizeProjectRoot(projectRoot), TOPOGRAM_IMPORT_ADOPTIONS_FILE);
203
+ }
204
+
205
+ /**
206
+ * @param {string} projectRoot
207
+ * @returns {AnyRecord[]}
208
+ */
209
+ export function readImportAdoptionReceipts(projectRoot) {
210
+ const historyPath = importAdoptionsPath(projectRoot);
211
+ if (!fs.existsSync(historyPath)) {
212
+ return [];
213
+ }
214
+ return fs.readFileSync(historyPath, "utf8")
215
+ .split(/\r?\n/)
216
+ .map((/** @type {string} */ line) => line.trim())
217
+ .filter(Boolean)
218
+ .map((/** @type {string} */ line, /** @type {number} */ index) => {
219
+ try {
220
+ return JSON.parse(line);
221
+ } catch (error) {
222
+ throw new Error(`Invalid import adoption receipt JSON at ${historyPath}:${index + 1}.`);
223
+ }
224
+ });
225
+ }
226
+
227
+ /**
228
+ * @param {string} projectRoot
229
+ * @param {AnyRecord} receipt
230
+ * @returns {string}
231
+ */
232
+ export function appendImportAdoptionReceipt(projectRoot, receipt) {
233
+ const historyPath = importAdoptionsPath(projectRoot);
234
+ fs.appendFileSync(historyPath, `${JSON.stringify(receipt)}\n`, "utf8");
235
+ return historyPath;
236
+ }
237
+
238
+ /**
239
+ * @param {AnyRecord[]} items
240
+ * @param {string} fieldName
241
+ * @returns {Record<string, number>}
242
+ */
243
+ export function countByField(items, fieldName) {
244
+ /** @type {Record<string, number>} */
245
+ const counts = {};
246
+ for (const item of items || []) {
247
+ const key = item?.[fieldName] || "unknown";
248
+ counts[key] = (counts[key] || 0) + 1;
249
+ }
250
+ return Object.fromEntries(Object.entries(counts).sort(([left], [right]) => left.localeCompare(right)));
251
+ }
252
+
253
+ /**
254
+ * @param {string} projectRoot
255
+ * @returns {string}
256
+ */
257
+ export function importProjectCommandPath(projectRoot) {
258
+ return shellCommandArg(path.relative(process.cwd(), projectRoot) || ".");
259
+ }
260
+
261
+ /**
262
+ * @param {string} projectRoot
263
+ * @param {string} selector
264
+ * @param {boolean} [write]
265
+ * @returns {string}
266
+ */
267
+ export function importAdoptCommand(projectRoot, selector, write = false) {
268
+ return `topogram import adopt ${selector} ${importProjectCommandPath(projectRoot)} ${write ? "--write" : "--dry-run"}`;
269
+ }