@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,404 @@
1
+ // @ts-check
2
+
3
+ import fs from "node:fs";
4
+ import path from "node:path";
5
+
6
+ import { buildTopogramSourceStatus } from "../../../catalog.js";
7
+ import { stableStringify } from "../../../format.js";
8
+ import {
9
+ getTemplateTrustStatus,
10
+ TEMPLATE_TRUST_FILE
11
+ } from "../../../template-trust.js";
12
+ import { TEMPLATE_FILES_MANIFEST, TEMPLATE_POLICY_FILE } from "./constants.js";
13
+ import { buildTemplateOwnedBaselineStatus } from "./baseline.js";
14
+ import { latestTemplateInfo, templateMetadataFromProjectConfig } from "./shared.js";
15
+
16
+ /**
17
+ * @param {{ config: Record<string, any>, configPath: string|null, configDir: string }} projectConfigInfo
18
+ * @param {{ latest?: boolean }} [options]
19
+ * @returns {{ ok: boolean, template: ReturnType<typeof templateMetadataFromProjectConfig>, trust: ReturnType<typeof getTemplateTrustStatus>|null, latest: { checked: boolean, supported?: boolean, packageName?: string|null, version?: string|null, isCurrent?: boolean|null, candidateSpec?: string|null, reason: string|null }, recommendations: string[] }}
20
+ */
21
+ export function buildTemplateStatusPayload(projectConfigInfo, options = {}) {
22
+ const template = templateMetadataFromProjectConfig(projectConfigInfo.config);
23
+ const recommendations = [];
24
+ /** @type {ReturnType<typeof getTemplateTrustStatus>|null} */
25
+ let trust = null;
26
+ if (projectConfigInfo.config.implementation) {
27
+ trust = getTemplateTrustStatus({
28
+ config: projectConfigInfo.config.implementation,
29
+ configPath: projectConfigInfo.configPath,
30
+ configDir: projectConfigInfo.configDir
31
+ }, projectConfigInfo.config);
32
+ if (!trust.ok) {
33
+ recommendations.push("Run `topogram trust diff` to review implementation changes, then `topogram trust template` to trust the current files.");
34
+ }
35
+ }
36
+ if (!template.id) {
37
+ recommendations.push("No template metadata found in topogram.project.json.");
38
+ }
39
+ const latest = options.latest
40
+ ? latestTemplateInfo(template)
41
+ : {
42
+ checked: false,
43
+ supported: false,
44
+ packageName: null,
45
+ version: null,
46
+ isCurrent: null,
47
+ candidateSpec: null,
48
+ reason: "Registry lookups are not performed by default."
49
+ };
50
+ if (latest.checked && latest.supported && latest.candidateSpec && latest.isCurrent === false) {
51
+ recommendations.push(`Run \`topogram template update --recommend --template ${latest.candidateSpec}\` to review the latest template.`);
52
+ }
53
+ return {
54
+ ok: trust ? trust.ok : true,
55
+ template,
56
+ trust,
57
+ latest,
58
+ recommendations
59
+ };
60
+ }
61
+
62
+ /**
63
+ * @param {ReturnType<typeof buildTemplateStatusPayload>} payload
64
+ * @returns {void}
65
+ */
66
+ export function printTemplateStatus(payload) {
67
+ if (!payload.template.id) {
68
+ console.log("Template status: detached");
69
+ } else if (payload.trust?.requiresTrust) {
70
+ console.log(`Template status: attached; implementation trust: ${payload.ok ? "trusted" : "review required"}`);
71
+ } else {
72
+ console.log("Template status: attached; implementation trust: not required");
73
+ }
74
+ if (payload.template.id) {
75
+ console.log(`Template: ${payload.template.id}@${payload.template.version || "unknown"}`);
76
+ }
77
+ if (payload.template.source) {
78
+ console.log(`Source: ${payload.template.source}`);
79
+ }
80
+ if (payload.template.sourceSpec) {
81
+ console.log(`Source spec: ${payload.template.sourceSpec}`);
82
+ }
83
+ if (payload.template.requested) {
84
+ console.log(`Requested: ${payload.template.requested}`);
85
+ }
86
+ if (payload.template.catalog) {
87
+ console.log(`Catalog: ${payload.template.catalog.id || "unknown"} from ${payload.template.catalog.source || "unknown"}`);
88
+ }
89
+ if (payload.template.sourceRoot) {
90
+ console.log(`Source root: ${payload.template.sourceRoot}`);
91
+ }
92
+ if (!payload.latest.checked) {
93
+ console.log("Latest version: not checked");
94
+ } else if (!payload.latest.supported) {
95
+ console.log(`Latest version: not checked (${payload.latest.reason})`);
96
+ } else {
97
+ console.log(`Latest version: ${payload.latest.version}`);
98
+ if (payload.latest.packageName) {
99
+ console.log(`Latest package: ${payload.latest.packageName}`);
100
+ }
101
+ if (payload.latest.candidateSpec) {
102
+ console.log(`Latest candidate: ${payload.latest.candidateSpec}`);
103
+ }
104
+ console.log(`Latest status: ${payload.latest.isCurrent ? "current" : "update available"}`);
105
+ }
106
+ if (payload.trust) {
107
+ if (payload.trust.trustRecord?.trustedAt) {
108
+ console.log(`Trusted at: ${payload.trust.trustRecord.trustedAt}`);
109
+ }
110
+ if (payload.trust.implementation.module) {
111
+ console.log(`Implementation: ${payload.trust.implementation.module}`);
112
+ }
113
+ if (payload.trust.content.trustedDigest) {
114
+ console.log(`Trusted digest: ${payload.trust.content.trustedDigest}`);
115
+ }
116
+ if (payload.trust.content.currentDigest) {
117
+ console.log(`Current digest: ${payload.trust.content.currentDigest}`);
118
+ }
119
+ for (const issue of payload.trust.issues) {
120
+ console.log(`Issue: ${issue}`);
121
+ }
122
+ for (const filePath of payload.trust.content.changed) {
123
+ console.log(`Changed: ${filePath}`);
124
+ }
125
+ for (const filePath of payload.trust.content.added) {
126
+ console.log(`Added: ${filePath}`);
127
+ }
128
+ for (const filePath of payload.trust.content.removed) {
129
+ console.log(`Removed: ${filePath}`);
130
+ }
131
+ }
132
+ for (const recommendation of payload.recommendations) {
133
+ console.log(recommendation);
134
+ }
135
+ }
136
+
137
+ /**
138
+ * @param {{ config: Record<string, any>, configPath: string|null, configDir: string }} projectConfigInfo
139
+ * @returns {{ ok: boolean, projectRoot: string, projectConfigPath: string|null, attached: boolean, ownership: "template-attached"|"project-owned", template: ReturnType<typeof templateMetadataFromProjectConfig>, trust: ReturnType<typeof getTemplateTrustStatus>|null, baseline: ReturnType<typeof buildTemplateOwnedBaselineStatus>, source: ReturnType<typeof buildTopogramSourceStatus>, commands: { status: string, detachDryRun: string|null, detach: string|null, updateCheck: string|null, trustStatus: string|null, trustTemplate: string|null, check: string, generate: string }, summary: string[], diagnostics: any[], errors: string[] }}
140
+ */
141
+ export function buildTemplateExplainPayload(projectConfigInfo) {
142
+ const template = templateMetadataFromProjectConfig(projectConfigInfo.config);
143
+ const attached = Boolean(template.id);
144
+ const projectRoot = projectConfigInfo.configDir;
145
+ const baseline = buildTemplateOwnedBaselineStatus(projectRoot);
146
+ const source = buildTopogramSourceStatus(projectRoot);
147
+ /** @type {ReturnType<typeof getTemplateTrustStatus>|null} */
148
+ let trust = null;
149
+ if (projectConfigInfo.config.implementation) {
150
+ trust = getTemplateTrustStatus({
151
+ config: projectConfigInfo.config.implementation,
152
+ configPath: projectConfigInfo.configPath,
153
+ configDir: projectConfigInfo.configDir
154
+ }, projectConfigInfo.config);
155
+ }
156
+ const summary = [];
157
+ if (attached) {
158
+ summary.push("This project is still attached to its starter template.");
159
+ summary.push("Local edits are allowed; template update checks are opt-in.");
160
+ } else {
161
+ summary.push("This project is detached from starter-template update tracking.");
162
+ summary.push("The project owns its Topogram files and template updates no longer apply.");
163
+ }
164
+ if (baseline.state === "diverged") {
165
+ summary.push("Template-derived files have local changes; those changes are project-owned.");
166
+ } else if (baseline.state === "matches-template") {
167
+ summary.push("Template-derived files still match the recorded template baseline.");
168
+ }
169
+ if (trust?.requiresTrust && trust.ok) {
170
+ summary.push("Executable implementation trust is retained and currently matches reviewed files.");
171
+ } else if (trust?.requiresTrust && !trust.ok) {
172
+ summary.push("Executable implementation changed since it was trusted and needs review.");
173
+ } else {
174
+ summary.push("No executable implementation trust review is required.");
175
+ }
176
+ return {
177
+ ok: trust ? trust.ok : true,
178
+ projectRoot,
179
+ projectConfigPath: projectConfigInfo.configPath,
180
+ attached,
181
+ ownership: attached ? "template-attached" : "project-owned",
182
+ template,
183
+ trust,
184
+ baseline,
185
+ source,
186
+ commands: {
187
+ status: "topogram source status --local",
188
+ detachDryRun: attached ? "topogram template detach --dry-run" : null,
189
+ detach: attached ? "topogram template detach" : null,
190
+ updateCheck: attached ? "topogram template update --check" : null,
191
+ trustStatus: trust?.requiresTrust ? "topogram trust status" : null,
192
+ trustTemplate: trust?.requiresTrust && !trust.ok ? "topogram trust template" : null,
193
+ check: "topogram check",
194
+ generate: "topogram generate"
195
+ },
196
+ summary,
197
+ diagnostics: source.diagnostics,
198
+ errors: trust && !trust.ok ? trust.issues : []
199
+ };
200
+ }
201
+
202
+ /**
203
+ * @param {ReturnType<typeof buildTemplateExplainPayload>} payload
204
+ * @returns {void}
205
+ */
206
+ export function printTemplateExplain(payload) {
207
+ console.log(`Template lifecycle: ${payload.attached ? "attached" : "detached"}`);
208
+ console.log(`Ownership: ${payload.ownership}`);
209
+ console.log(`Project: ${payload.projectRoot}`);
210
+ if (payload.projectConfigPath) {
211
+ console.log(`Project config: ${payload.projectConfigPath}`);
212
+ }
213
+ if (payload.template.id) {
214
+ console.log(`Template: ${payload.template.id}@${payload.template.version || "unknown"}`);
215
+ console.log(`Requested: ${payload.template.requested || "unknown"}`);
216
+ console.log(`Source: ${payload.template.sourceSpec || payload.template.source || "unknown"}`);
217
+ if (payload.template.catalog) {
218
+ console.log(`Catalog: ${payload.template.catalog.id || "unknown"} from ${payload.template.catalog.source || "unknown"}`);
219
+ }
220
+ } else {
221
+ console.log("Template: none");
222
+ }
223
+ console.log(`Template baseline: ${payload.baseline.state}`);
224
+ console.log(`Template baseline meaning: ${payload.baseline.meaning}`);
225
+ if (payload.baseline.content.changed.length > 0) {
226
+ console.log(`Template baseline changed files: ${payload.baseline.content.changed.length}`);
227
+ }
228
+ if (payload.baseline.content.removed.length > 0) {
229
+ console.log(`Template baseline removed files: ${payload.baseline.content.removed.length}`);
230
+ }
231
+ if (payload.trust) {
232
+ console.log(`Implementation trust: ${payload.trust.requiresTrust ? (payload.trust.ok ? "trusted" : "review required") : "not required"}`);
233
+ if (payload.trust.implementation.module) {
234
+ console.log(`Implementation: ${payload.trust.implementation.module}`);
235
+ }
236
+ } else {
237
+ console.log("Implementation trust: not required");
238
+ }
239
+ console.log("");
240
+ console.log("Summary:");
241
+ for (const line of payload.summary) {
242
+ console.log(`- ${line}`);
243
+ }
244
+ console.log("");
245
+ console.log("Useful commands:");
246
+ console.log(` ${payload.commands.status}`);
247
+ if (payload.commands.detachDryRun) {
248
+ console.log(` ${payload.commands.detachDryRun}`);
249
+ }
250
+ if (payload.commands.detach) {
251
+ console.log(` ${payload.commands.detach}`);
252
+ }
253
+ if (payload.commands.updateCheck) {
254
+ console.log(` ${payload.commands.updateCheck}`);
255
+ }
256
+ if (payload.commands.trustStatus) {
257
+ console.log(` ${payload.commands.trustStatus}`);
258
+ }
259
+ if (payload.commands.trustTemplate) {
260
+ console.log(` ${payload.commands.trustTemplate}`);
261
+ }
262
+ console.log(` ${payload.commands.check}`);
263
+ console.log(` ${payload.commands.generate}`);
264
+ for (const diagnostic of payload.diagnostics) {
265
+ const label = diagnostic.severity === "warning" ? "Warning" : "Error";
266
+ console.log(`${label}: ${diagnostic.message}`);
267
+ }
268
+ }
269
+
270
+ /**
271
+ * @param {{ config: Record<string, any>, configPath: string|null, configDir: string }} projectConfigInfo
272
+ * @param {{ dryRun?: boolean, removePolicy?: boolean }} [options]
273
+ * @returns {{ ok: boolean, detached: boolean, dryRun: boolean, projectConfigPath: string, removedTemplate: Record<string, any>|null, implementationTrust: { retained: boolean, removed: boolean, path: string, reason: string }, removedFiles: string[], plannedRemovals: string[], preservedFiles: string[], diagnostics: any[], errors: any[] }}
274
+ */
275
+ export function buildTemplateDetachPayload(projectConfigInfo, options = {}) {
276
+ const dryRun = Boolean(options.dryRun);
277
+ const removePolicy = Boolean(options.removePolicy);
278
+ const projectRoot = projectConfigInfo.configDir;
279
+ const projectConfigPath = projectConfigInfo.configPath || path.join(projectRoot, "topogram.project.json");
280
+ const nextConfig = JSON.parse(JSON.stringify(projectConfigInfo.config || {}));
281
+ const removedTemplate = nextConfig.template && typeof nextConfig.template === "object" && !Array.isArray(nextConfig.template)
282
+ ? nextConfig.template
283
+ : null;
284
+ const removedFiles = [];
285
+ const plannedRemovals = [];
286
+ const preservedFiles = [];
287
+ const diagnostics = [];
288
+
289
+ if (removedTemplate) {
290
+ delete nextConfig.template;
291
+ }
292
+
293
+ const manifestPath = path.join(projectRoot, TEMPLATE_FILES_MANIFEST);
294
+ const policyPath = path.join(projectRoot, TEMPLATE_POLICY_FILE);
295
+ const trustPath = path.join(projectRoot, TEMPLATE_TRUST_FILE);
296
+ const implementationRemains = Boolean(projectConfigInfo.config?.implementation);
297
+
298
+ /** @param {string} filePath */
299
+ const maybeRemove = (filePath) => {
300
+ if (!fs.existsSync(filePath)) {
301
+ return;
302
+ }
303
+ plannedRemovals.push(filePath);
304
+ if (!dryRun) {
305
+ fs.rmSync(filePath);
306
+ removedFiles.push(filePath);
307
+ }
308
+ };
309
+
310
+ maybeRemove(manifestPath);
311
+ if (removePolicy) {
312
+ maybeRemove(policyPath);
313
+ } else if (fs.existsSync(policyPath)) {
314
+ preservedFiles.push(policyPath);
315
+ }
316
+
317
+ const implementationTrust = {
318
+ retained: false,
319
+ removed: false,
320
+ path: trustPath,
321
+ reason: "not-present"
322
+ };
323
+ if (fs.existsSync(trustPath)) {
324
+ if (implementationRemains) {
325
+ implementationTrust.retained = true;
326
+ implementationTrust.reason = "implementation-remains";
327
+ preservedFiles.push(trustPath);
328
+ } else {
329
+ implementationTrust.removed = !dryRun;
330
+ implementationTrust.reason = "no-implementation-config";
331
+ plannedRemovals.push(trustPath);
332
+ if (!dryRun) {
333
+ fs.rmSync(trustPath);
334
+ removedFiles.push(trustPath);
335
+ }
336
+ }
337
+ }
338
+
339
+ if (!removedTemplate) {
340
+ diagnostics.push({
341
+ code: "template_already_detached",
342
+ severity: "warning",
343
+ message: "topogram.project.json has no template metadata.",
344
+ path: projectConfigPath,
345
+ suggestedFix: "No detach action is required."
346
+ });
347
+ }
348
+
349
+ if (!dryRun && removedTemplate) {
350
+ fs.writeFileSync(projectConfigPath, `${stableStringify(nextConfig)}\n`, "utf8");
351
+ }
352
+
353
+ return {
354
+ ok: true,
355
+ detached: Boolean(removedTemplate),
356
+ dryRun,
357
+ projectConfigPath,
358
+ removedTemplate,
359
+ implementationTrust,
360
+ removedFiles,
361
+ plannedRemovals,
362
+ preservedFiles,
363
+ diagnostics,
364
+ errors: []
365
+ };
366
+ }
367
+
368
+ /**
369
+ * @param {ReturnType<typeof buildTemplateDetachPayload>} payload
370
+ * @returns {void}
371
+ */
372
+ export function printTemplateDetachPayload(payload) {
373
+ if (payload.dryRun) {
374
+ console.log(payload.detached ? "Template detach plan ready." : "Template detach plan: already detached.");
375
+ } else {
376
+ console.log(payload.detached ? "Template detached." : "Template already detached.");
377
+ }
378
+ console.log(`Project config: ${payload.projectConfigPath}`);
379
+ if (payload.removedTemplate?.id) {
380
+ console.log(`Removed template metadata: ${payload.removedTemplate.id}@${payload.removedTemplate.version || "unknown"}`);
381
+ }
382
+ if (payload.plannedRemovals.length > 0) {
383
+ console.log(payload.dryRun ? "Would remove:" : "Removed:");
384
+ for (const filePath of (payload.dryRun ? payload.plannedRemovals : payload.removedFiles)) {
385
+ console.log(`- ${filePath}`);
386
+ }
387
+ }
388
+ if (payload.preservedFiles.length > 0) {
389
+ console.log("Preserved:");
390
+ for (const filePath of payload.preservedFiles) {
391
+ console.log(`- ${filePath}`);
392
+ }
393
+ }
394
+ if (payload.implementationTrust.retained) {
395
+ console.log("Implementation trust retained because implementation config remains.");
396
+ } else if (payload.implementationTrust.removed) {
397
+ console.log("Implementation trust removed because no implementation config remains.");
398
+ }
399
+ for (const diagnostic of payload.diagnostics) {
400
+ const label = diagnostic.severity === "warning" ? "Warning" : "Error";
401
+ console.log(`${label}: ${diagnostic.message}`);
402
+ }
403
+ console.log("Next: run `topogram source status --local`, then `topogram check`.");
404
+ }
@@ -0,0 +1,287 @@
1
+ // @ts-check
2
+
3
+ import {
4
+ catalogSourceOrDefault,
5
+ catalogTemplateListItem,
6
+ isCatalogSourceDisabled,
7
+ loadCatalog
8
+ } from "../../../catalog.js";
9
+ import {
10
+ buildCatalogShowPayload,
11
+ catalogShowCommands,
12
+ shellCommandArg
13
+ } from "../catalog.js";
14
+ import { messageFromError, packageNameFromPackageSpec } from "./shared.js";
15
+
16
+ /**
17
+ * @param {{ catalogSource?: string|null }} [options]
18
+ * @returns {{ ok: boolean, catalog: { source: string|null, loaded: boolean }, templates: Array<Record<string, any>>, diagnostics: Array<Record<string, any>>, errors: string[] }}
19
+ */
20
+ export function buildTemplateListPayload(options = {}) {
21
+ const catalogSource = catalogSourceOrDefault(options.catalogSource || null);
22
+ /** @type {Array<Record<string, any>>} */
23
+ const templates = [];
24
+ /** @type {Array<Record<string, any>>} */
25
+ const diagnostics = [];
26
+ let catalogLoaded = false;
27
+ if (!isCatalogSourceDisabled(catalogSource)) {
28
+ try {
29
+ const loaded = loadCatalog(catalogSource);
30
+ catalogLoaded = true;
31
+ const entries = /** @type {any[]} */ (loaded.catalog.entries || []);
32
+ templates.push(
33
+ ...entries
34
+ .filter((entry) => entry.kind === "template")
35
+ .map((entry) => templateListItemFromCatalogEntry(entry, loaded.source))
36
+ );
37
+ } catch (error) {
38
+ diagnostics.push({
39
+ code: "catalog_unavailable",
40
+ severity: "warning",
41
+ message: messageFromError(error),
42
+ path: catalogSource,
43
+ suggestedFix: "Run `topogram catalog list` after authenticating, or pass a local template path/package spec directly."
44
+ });
45
+ }
46
+ }
47
+ return {
48
+ ok: true,
49
+ catalog: {
50
+ source: isCatalogSourceDisabled(catalogSource) ? null : catalogSource,
51
+ loaded: catalogLoaded
52
+ },
53
+ templates,
54
+ diagnostics,
55
+ errors: []
56
+ };
57
+ }
58
+
59
+ /**
60
+ * @param {any} entry
61
+ * @param {string} source
62
+ * @returns {Record<string, any>}
63
+ */
64
+ function templateListItemFromCatalogEntry(entry, source) {
65
+ const item = catalogTemplateListItem(entry);
66
+ const commands = catalogShowCommands(entry, source);
67
+ return {
68
+ ...item,
69
+ surfaces: Array.isArray(item.surfaces) ? item.surfaces : [],
70
+ generators: Array.isArray(item.generators) ? item.generators : [],
71
+ stack: typeof item.stack === "string" ? item.stack : null,
72
+ isDefault: item.id === "hello-web",
73
+ recommendedCommand: commands.primary,
74
+ commands
75
+ };
76
+ }
77
+
78
+ /**
79
+ * @param {ReturnType<typeof buildTemplateListPayload>} payload
80
+ * @returns {void}
81
+ */
82
+ export function printTemplateList(payload) {
83
+ console.log("Template starters:");
84
+ console.log("Catalog aliases resolve to versioned package installs. Local paths and full package specs can also be used with `topogram new`.");
85
+ if (payload.catalog.source) {
86
+ console.log(`Catalog: ${payload.catalog.source} (${payload.catalog.loaded ? "loaded" : "unavailable"})`);
87
+ } else {
88
+ console.log("Catalog: disabled");
89
+ }
90
+ for (const template of payload.templates) {
91
+ const defaultLabel = template.isDefault ? " (default)" : "";
92
+ const stack = template.stack || "not declared";
93
+ const surfaces = Array.isArray(template.surfaces) && template.surfaces.length > 0
94
+ ? template.surfaces.join(", ")
95
+ : "not declared";
96
+ const command = template.recommendedCommand || `topogram new ./my-app --template ${shellCommandArg(template.id)}`;
97
+ console.log(`- ${template.id}@${template.version}${defaultLabel}`);
98
+ console.log(` Source: ${template.source} | Surfaces: ${surfaces} | Stack: ${stack} | Executable implementation: ${template.includesExecutableImplementation ? "yes" : "no"}`);
99
+ console.log(` New: ${command}`);
100
+ }
101
+ for (const diagnostic of payload.diagnostics) {
102
+ console.warn(`Warning: ${diagnostic.message}`);
103
+ }
104
+ }
105
+
106
+ /**
107
+ * @param {Record<string, any>} template
108
+ * @param {"catalog"} sourceKind
109
+ * @param {string|null} packageSpec
110
+ * @param {{ primary: string|null, followUp: string[] }} commands
111
+ * @returns {{ surfaces: string[], generators: string[], stack: string|null, packageSpec: string|null, packageName: string|null, version: string|null, executableImplementation: boolean, policyImpact: string, recommendedCommand: string|null, followUp: string[], notes: string[] }}
112
+ */
113
+ function templateDecisionSummary(template, sourceKind, packageSpec, commands) {
114
+ const trust = template.trust && typeof template.trust === "object" ? template.trust : null;
115
+ const executable = trust
116
+ ? Boolean(trust.includesExecutableImplementation)
117
+ : Boolean(template.includesExecutableImplementation);
118
+ const surfaces = Array.isArray(template.surfaces) ? template.surfaces : [];
119
+ const generators = Array.isArray(template.generators) ? template.generators : [];
120
+ const stack = typeof template.stack === "string" && template.stack ? template.stack : null;
121
+ const notes = [];
122
+ if (sourceKind === "catalog") {
123
+ notes.push("Catalog templates resolve to versioned package installs; the catalog is an index, not the template payload.");
124
+ }
125
+ if (surfaces.length === 0) {
126
+ notes.push("Surface metadata is not declared in this catalog entry.");
127
+ }
128
+ if (generators.length === 0) {
129
+ notes.push("Generator metadata is not declared in this catalog entry.");
130
+ }
131
+ return {
132
+ surfaces,
133
+ generators,
134
+ stack,
135
+ packageSpec,
136
+ packageName: template.package || (packageSpec ? packageNameFromPackageSpec(packageSpec) : null),
137
+ version: template.defaultVersion || template.version || null,
138
+ executableImplementation: executable,
139
+ policyImpact: executable
140
+ ? "Copies implementation/ code into the project; topogram new does not execute it, but topogram generate may load it after local trust is recorded."
141
+ : "No executable implementation trust is required for this template.",
142
+ recommendedCommand: commands.primary,
143
+ followUp: commands.followUp,
144
+ notes
145
+ };
146
+ }
147
+
148
+ /**
149
+ * @param {string} id
150
+ * @param {string|null} source
151
+ * @returns {{ ok: boolean, source: "catalog"|null, catalog: { source: string|null, version: string|null }, template: Record<string, any>|null, packageSpec: string|null, decision: ReturnType<typeof templateDecisionSummary>|null, commands: { primary: string|null, followUp: string[] }, diagnostics: any[], errors: string[] }}
152
+ */
153
+ export function buildTemplateShowPayload(id, source) {
154
+ if (!id || id.startsWith("-")) {
155
+ throw new Error("topogram template show requires <id>.");
156
+ }
157
+ const catalogPayload = buildCatalogShowPayload(id, source);
158
+ if (!catalogPayload.ok || !catalogPayload.entry) {
159
+ return {
160
+ ok: false,
161
+ source: "catalog",
162
+ catalog: {
163
+ source: catalogPayload.source,
164
+ version: catalogPayload.catalog.version
165
+ },
166
+ template: null,
167
+ packageSpec: null,
168
+ decision: null,
169
+ commands: { primary: null, followUp: [] },
170
+ diagnostics: catalogPayload.diagnostics,
171
+ errors: catalogPayload.errors
172
+ };
173
+ }
174
+ if (catalogPayload.entry.kind !== "template") {
175
+ const diagnostic = {
176
+ code: "catalog_entry_not_template",
177
+ severity: "error",
178
+ message: `Catalog entry '${id}' is a ${catalogPayload.entry.kind}, not a template.`,
179
+ path: catalogPayload.source,
180
+ suggestedFix: "Use `topogram catalog show` for non-template catalog entries."
181
+ };
182
+ return {
183
+ ok: false,
184
+ source: "catalog",
185
+ catalog: {
186
+ source: catalogPayload.source,
187
+ version: catalogPayload.catalog.version
188
+ },
189
+ template: catalogPayload.entry,
190
+ packageSpec: catalogPayload.packageSpec,
191
+ decision: null,
192
+ commands: catalogPayload.commands,
193
+ diagnostics: [...catalogPayload.diagnostics, diagnostic],
194
+ errors: [diagnostic.message]
195
+ };
196
+ }
197
+ return {
198
+ ok: true,
199
+ source: "catalog",
200
+ catalog: {
201
+ source: catalogPayload.source,
202
+ version: catalogPayload.catalog.version
203
+ },
204
+ template: catalogPayload.entry,
205
+ packageSpec: catalogPayload.packageSpec,
206
+ decision: templateDecisionSummary(catalogPayload.entry, "catalog", catalogPayload.packageSpec, catalogPayload.commands),
207
+ commands: catalogPayload.commands,
208
+ diagnostics: catalogPayload.diagnostics,
209
+ errors: []
210
+ };
211
+ }
212
+
213
+ /**
214
+ * @param {ReturnType<typeof buildTemplateShowPayload>} payload
215
+ * @returns {void}
216
+ */
217
+ export function printTemplateShow(payload) {
218
+ if (!payload.ok || !payload.template) {
219
+ console.log("Template not found.");
220
+ if (payload.catalog.source) {
221
+ console.log(`Catalog: ${payload.catalog.source}`);
222
+ }
223
+ for (const diagnostic of payload.diagnostics) {
224
+ const label = diagnostic.severity === "warning" ? "Warning" : "Error";
225
+ console.log(`${label}: ${diagnostic.message}`);
226
+ }
227
+ return;
228
+ }
229
+ const template = payload.template;
230
+ console.log(`Template: ${template.id}`);
231
+ console.log(`Source: ${payload.source}`);
232
+ if (template.name) {
233
+ const defaultLabel = template.isDefault ? " (default)" : "";
234
+ console.log(`Name: ${template.name}${defaultLabel}`);
235
+ }
236
+ if (payload.catalog.source) {
237
+ console.log(`Catalog: ${payload.catalog.source}`);
238
+ }
239
+ if (payload.packageSpec) {
240
+ console.log(`Package: ${payload.packageSpec}`);
241
+ }
242
+ if (template.description) {
243
+ console.log(`Description: ${template.description}`);
244
+ }
245
+ if (payload.decision) {
246
+ console.log("");
247
+ console.log("What it creates:");
248
+ console.log(` Surfaces: ${payload.decision.surfaces.join(", ") || "not declared"}`);
249
+ console.log(` Stack: ${payload.decision.stack || "not declared"}`);
250
+ console.log(` Generators: ${payload.decision.generators.join(", ") || "not declared"}`);
251
+ console.log(` Package: ${payload.decision.packageSpec || "not declared"}`);
252
+ console.log(` Executable implementation: ${payload.decision.executableImplementation ? "yes" : "no"}`);
253
+ console.log(` Policy impact: ${payload.decision.policyImpact}`);
254
+ for (const note of payload.decision.notes) {
255
+ console.log(` Note: ${note}`);
256
+ }
257
+ }
258
+ console.log("");
259
+ console.log("Details:");
260
+ if (Array.isArray(template.tags) && template.tags.length > 0) {
261
+ console.log(`Tags: ${template.tags.join(", ")}`);
262
+ }
263
+ if (template.trust?.scope) {
264
+ console.log(`Trust scope: ${template.trust.scope}`);
265
+ }
266
+ const executable = template.trust
267
+ ? template.trust.includesExecutableImplementation
268
+ : template.includesExecutableImplementation;
269
+ console.log(`Executable implementation: ${executable ? "yes" : "no"}`);
270
+ if (template.trust?.notes) {
271
+ console.log(`Trust notes: ${template.trust.notes}`);
272
+ }
273
+ console.log("");
274
+ console.log("Recommended command:");
275
+ console.log(` ${payload.commands.primary}`);
276
+ if (payload.commands.followUp.length > 0) {
277
+ console.log("Follow-up:");
278
+ for (const command of payload.commands.followUp) {
279
+ console.log(` ${command}`);
280
+ }
281
+ }
282
+ for (const diagnostic of payload.diagnostics) {
283
+ if (diagnostic.severity === "warning") {
284
+ console.warn(`Warning: ${diagnostic.message}`);
285
+ }
286
+ }
287
+ }