@topogram/cli 0.3.64 → 0.3.65

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 (245) hide show
  1. package/package.json +1 -1
  2. package/src/adoption/plan/index.js +703 -0
  3. package/src/adoption/plan.js +12 -703
  4. package/src/agent-ops/query-builders/auth.js +375 -0
  5. package/src/agent-ops/query-builders/change-risk/change-plan.js +123 -0
  6. package/src/agent-ops/query-builders/change-risk/import-plan.js +49 -0
  7. package/src/agent-ops/query-builders/change-risk/maintained.js +286 -0
  8. package/src/agent-ops/query-builders/change-risk/review-packets.js +123 -0
  9. package/src/agent-ops/query-builders/change-risk/risk.js +189 -0
  10. package/src/agent-ops/query-builders/change-risk.js +25 -0
  11. package/src/agent-ops/query-builders/common.js +149 -0
  12. package/src/agent-ops/query-builders/maintained-risk.js +539 -0
  13. package/src/agent-ops/query-builders/maintained-shared.js +120 -0
  14. package/src/agent-ops/query-builders/multi-agent.js +547 -0
  15. package/src/agent-ops/query-builders/projection-impacts.js +514 -0
  16. package/src/agent-ops/query-builders/work-packets.js +417 -0
  17. package/src/agent-ops/query-builders/workflow-context-shared.js +300 -0
  18. package/src/agent-ops/query-builders/workflow-context.js +398 -0
  19. package/src/agent-ops/query-builders/workflow-presets-core.js +676 -0
  20. package/src/agent-ops/query-builders/workflow-presets.js +341 -0
  21. package/src/agent-ops/query-builders.d.ts +26 -26
  22. package/src/agent-ops/query-builders.js +42 -5021
  23. package/src/catalog/constants.js +10 -0
  24. package/src/catalog/copy.js +60 -0
  25. package/src/catalog/diagnostics.js +15 -0
  26. package/src/catalog/entries.js +42 -0
  27. package/src/catalog/files.js +67 -0
  28. package/src/catalog/provenance.js +122 -0
  29. package/src/catalog/source.js +150 -0
  30. package/src/catalog/validation.js +252 -0
  31. package/src/catalog.d.ts +2 -0
  32. package/src/catalog.js +18 -746
  33. package/src/cli/commands/catalog/check.js +31 -0
  34. package/src/cli/commands/catalog/copy.js +59 -0
  35. package/src/cli/commands/catalog/doctor.js +248 -0
  36. package/src/cli/commands/catalog/help.js +21 -0
  37. package/src/cli/commands/catalog/list.js +52 -0
  38. package/src/cli/commands/catalog/runner.js +92 -0
  39. package/src/cli/commands/catalog/shared.js +17 -0
  40. package/src/cli/commands/catalog/show.js +134 -0
  41. package/src/cli/commands/catalog.js +30 -615
  42. package/src/cli/commands/generator-policy/package-info.js +162 -0
  43. package/src/cli/commands/generator-policy/payloads.js +372 -0
  44. package/src/cli/commands/generator-policy/printers.js +159 -0
  45. package/src/cli/commands/generator-policy/runner.js +81 -0
  46. package/src/cli/commands/generator-policy/shared.js +39 -0
  47. package/src/cli/commands/generator-policy.js +15 -783
  48. package/src/cli/commands/import/adopt.js +170 -0
  49. package/src/cli/commands/import/check.js +91 -0
  50. package/src/cli/commands/import/diff.js +84 -0
  51. package/src/cli/commands/import/help.js +47 -0
  52. package/src/cli/commands/import/paths.js +277 -0
  53. package/src/cli/commands/import/plan.js +284 -0
  54. package/src/cli/commands/import/refresh.js +470 -0
  55. package/src/cli/commands/import/status-history.js +196 -0
  56. package/src/cli/commands/import/workspace.js +230 -0
  57. package/src/cli/commands/import.js +33 -1732
  58. package/src/cli/commands/package/constants.js +17 -0
  59. package/src/cli/commands/package/doctor.js +240 -0
  60. package/src/cli/commands/package/help.js +27 -0
  61. package/src/cli/commands/package/lockfile.js +135 -0
  62. package/src/cli/commands/package/npm.js +97 -0
  63. package/src/cli/commands/package/reporting.js +35 -0
  64. package/src/cli/commands/package/runner.js +33 -0
  65. package/src/cli/commands/package/shared.js +9 -0
  66. package/src/cli/commands/package/update-cli.js +252 -0
  67. package/src/cli/commands/package/versions.js +35 -0
  68. package/src/cli/commands/package.js +29 -813
  69. package/src/cli/commands/query/change-plan.js +68 -0
  70. package/src/cli/commands/query/definitions.js +202 -0
  71. package/src/cli/commands/query/import-adopt.js +121 -0
  72. package/src/cli/commands/query/runner/artifacts.js +102 -0
  73. package/src/cli/commands/query/runner/boundaries.js +211 -0
  74. package/src/cli/commands/query/runner/change.js +182 -0
  75. package/src/cli/commands/query/runner/import-adopt.js +111 -0
  76. package/src/cli/commands/query/runner/index.js +31 -0
  77. package/src/cli/commands/query/runner/output.js +12 -0
  78. package/src/cli/commands/query/runner/workflow.js +241 -0
  79. package/src/cli/commands/query/runner.js +3 -0
  80. package/src/cli/commands/query/workflow-context.js +5 -0
  81. package/src/cli/commands/query/workspace.js +274 -0
  82. package/src/cli/commands/query.js +9 -1300
  83. package/src/cli/commands/template/baseline.js +100 -0
  84. package/src/cli/commands/template/check.js +466 -0
  85. package/src/cli/commands/template/constants.js +8 -0
  86. package/src/cli/commands/template/diagnostics.js +26 -0
  87. package/src/cli/commands/template/help.js +28 -0
  88. package/src/cli/commands/template/lifecycle.js +404 -0
  89. package/src/cli/commands/template/list-show.js +287 -0
  90. package/src/cli/commands/template/policy.js +422 -0
  91. package/src/cli/commands/template/shared.js +127 -0
  92. package/src/cli/commands/template/updates.js +352 -0
  93. package/src/cli/commands/template.js +41 -2143
  94. package/src/generator/api/contracts.js +497 -0
  95. package/src/generator/api/metadata.js +221 -0
  96. package/src/generator/api/openapi.js +559 -0
  97. package/src/generator/api/schema.js +124 -0
  98. package/src/generator/api/types.d.ts +98 -0
  99. package/src/generator/api.js +3 -1195
  100. package/src/generator/context/shared/domain-sdlc.js +282 -0
  101. package/src/generator/context/shared/maintained-boundary.js +665 -0
  102. package/src/generator/context/shared/metrics.js +85 -0
  103. package/src/generator/context/shared/primitives.js +64 -0
  104. package/src/generator/context/shared/relationships.js +453 -0
  105. package/src/generator/context/shared/summaries.js +263 -0
  106. package/src/generator/context/shared/types.d.ts +207 -0
  107. package/src/generator/context/shared.d.ts +42 -0
  108. package/src/generator/context/shared.js +80 -1390
  109. package/src/generator/context/slice/core.js +397 -0
  110. package/src/generator/context/slice/sdlc.js +417 -0
  111. package/src/generator/context/slice/ui-packets.js +183 -0
  112. package/src/generator/context/slice.js +2 -859
  113. package/src/generator/registry/index.js +507 -0
  114. package/src/generator/registry.js +18 -504
  115. package/src/generator/runtime/environment/index.js +666 -0
  116. package/src/generator/runtime/environment.js +4 -666
  117. package/src/generator/runtime/runtime-check/index.js +554 -0
  118. package/src/generator/runtime/runtime-check.js +4 -554
  119. package/src/generator/runtime/shared/index.js +572 -0
  120. package/src/generator/runtime/shared.js +19 -570
  121. package/src/generator/shared.d.ts +2 -0
  122. package/src/generator/surfaces/shared.d.ts +3 -0
  123. package/src/generator/widget-conformance/behavior-report.js +258 -0
  124. package/src/generator/widget-conformance/checks.js +371 -0
  125. package/src/generator/widget-conformance/projection-context.js +200 -0
  126. package/src/generator/widget-conformance/report.js +166 -0
  127. package/src/generator/widget-conformance/types.d.ts +121 -0
  128. package/src/generator/widget-conformance.js +3 -824
  129. package/src/import/core/context.d.ts +3 -0
  130. package/src/import/core/contracts.d.ts +1 -0
  131. package/src/import/core/registry.d.ts +4 -0
  132. package/src/import/core/runner/candidates.js +217 -0
  133. package/src/import/core/runner/options.js +22 -0
  134. package/src/import/core/runner/reports.js +50 -0
  135. package/src/import/core/runner/run.js +79 -0
  136. package/src/import/core/runner/tracks.js +150 -0
  137. package/src/import/core/runner/ui-drafts.js +337 -0
  138. package/src/import/core/runner.js +3 -698
  139. package/src/import/core/shared/api-routes.js +221 -0
  140. package/src/import/core/shared/candidates.js +97 -0
  141. package/src/import/core/shared/files.js +177 -0
  142. package/src/import/core/shared/next-app.js +389 -0
  143. package/src/import/core/shared/types.d.ts +51 -0
  144. package/src/import/core/shared/ui-routes.js +230 -0
  145. package/src/import/core/shared.js +60 -861
  146. package/src/new-project/constants.js +128 -0
  147. package/src/new-project/create.js +83 -0
  148. package/src/new-project/json.js +28 -0
  149. package/src/new-project/metadata.js +96 -0
  150. package/src/new-project/package-spec.js +161 -0
  151. package/src/new-project/project-files.js +348 -0
  152. package/src/new-project/template-policy.js +269 -0
  153. package/src/new-project/template-resolution.js +368 -0
  154. package/src/new-project/template-snapshots.js +430 -0
  155. package/src/new-project/template-updates.js +512 -0
  156. package/src/new-project/types.d.ts +83 -0
  157. package/src/new-project.js +6 -2277
  158. package/src/parser.d.ts +87 -1
  159. package/src/parser.js +118 -0
  160. package/src/policy/review-boundaries.d.ts +15 -0
  161. package/src/project-config/index.js +564 -0
  162. package/src/project-config.js +19 -561
  163. package/src/resolver/enrich/acceptance-criterion.js +2 -0
  164. package/src/resolver/enrich/bug.js +2 -0
  165. package/src/resolver/enrich/pitch.js +2 -0
  166. package/src/resolver/enrich/requirement.js +2 -0
  167. package/src/resolver/enrich/task.js +2 -0
  168. package/src/resolver/index.js +19 -2089
  169. package/src/resolver/normalize.js +384 -1
  170. package/src/resolver/plans.js +168 -0
  171. package/src/resolver/projections-api.js +494 -0
  172. package/src/resolver/projections-db.js +133 -0
  173. package/src/resolver/projections-ui.js +317 -0
  174. package/src/resolver/shapes.js +251 -0
  175. package/src/resolver/shared.js +278 -0
  176. package/src/resolver/widgets.js +132 -0
  177. package/src/template-trust/constants.js +62 -0
  178. package/src/template-trust/content.js +258 -0
  179. package/src/template-trust/diff.js +92 -0
  180. package/src/template-trust/policy.js +61 -0
  181. package/src/template-trust/record.js +90 -0
  182. package/src/template-trust/status.js +182 -0
  183. package/src/template-trust.js +24 -687
  184. package/src/text-helpers.d.ts +1 -0
  185. package/src/topogram-types.d.ts +69 -0
  186. package/src/validator/common.js +488 -0
  187. package/src/validator/data-model.js +237 -0
  188. package/src/validator/docs.js +167 -0
  189. package/src/validator/expressions.js +146 -1
  190. package/src/validator/index.d.ts +23 -0
  191. package/src/validator/index.js +32 -3585
  192. package/src/validator/kinds.d.ts +41 -0
  193. package/src/validator/kinds.js +2 -0
  194. package/src/validator/model-helpers.js +46 -0
  195. package/src/validator/per-kind/acceptance-criterion.js +5 -0
  196. package/src/validator/per-kind/bug.js +6 -0
  197. package/src/validator/per-kind/domain.js +15 -2
  198. package/src/validator/per-kind/pitch.js +7 -0
  199. package/src/validator/per-kind/requirement.js +5 -0
  200. package/src/validator/per-kind/task.js +7 -0
  201. package/src/validator/per-kind/widget.js +14 -0
  202. package/src/validator/projections/api-http-async.js +410 -0
  203. package/src/validator/projections/api-http-authz.js +88 -0
  204. package/src/validator/projections/api-http-core.js +205 -0
  205. package/src/validator/projections/api-http-policies.js +339 -0
  206. package/src/validator/projections/api-http-responses.js +233 -0
  207. package/src/validator/projections/api-http.js +44 -0
  208. package/src/validator/projections/db.js +353 -0
  209. package/src/validator/projections/generator-defaults.js +45 -0
  210. package/src/validator/projections/helpers.js +87 -0
  211. package/src/validator/projections/ui-helpers.js +214 -0
  212. package/src/validator/projections/ui-navigation.js +344 -0
  213. package/src/validator/projections/ui-structure.js +364 -0
  214. package/src/validator/projections/ui-widgets.js +493 -0
  215. package/src/validator/projections/ui.js +46 -0
  216. package/src/validator/registry.js +48 -1
  217. package/src/validator/utils.d.ts +20 -0
  218. package/src/validator/utils.js +115 -12
  219. package/src/widget-behavior.d.ts +1 -0
  220. package/src/workflows/import-app/api/collect.js +221 -0
  221. package/src/workflows/import-app/api/openapi.js +257 -0
  222. package/src/workflows/import-app/api/routes.js +327 -0
  223. package/src/workflows/import-app/api/sources.js +22 -0
  224. package/src/workflows/import-app/api.js +2 -797
  225. package/src/workflows/reconcile/adoption-plan/build.js +208 -0
  226. package/src/workflows/reconcile/adoption-plan/dependencies.js +75 -0
  227. package/src/workflows/reconcile/adoption-plan/outputs.js +143 -0
  228. package/src/workflows/reconcile/adoption-plan/paths.js +58 -0
  229. package/src/workflows/reconcile/adoption-plan/projection-patches.js +177 -0
  230. package/src/workflows/reconcile/adoption-plan/reasons.js +107 -0
  231. package/src/workflows/reconcile/adoption-plan.js +30 -740
  232. package/src/workflows/reconcile/auth/closures.js +115 -0
  233. package/src/workflows/reconcile/auth/formatters.js +142 -0
  234. package/src/workflows/reconcile/auth/inference.js +330 -0
  235. package/src/workflows/reconcile/auth/roles.js +122 -0
  236. package/src/workflows/reconcile/auth.js +35 -690
  237. package/src/workflows/reconcile/bundle-core/index.js +600 -0
  238. package/src/workflows/reconcile/bundle-core.js +12 -598
  239. package/src/workflows/reconcile/canonical-surface.js +1 -1
  240. package/src/workflows/reconcile/impacts/adoption-plan.js +192 -0
  241. package/src/workflows/reconcile/impacts/indexes.js +101 -0
  242. package/src/workflows/reconcile/impacts/patches.js +252 -0
  243. package/src/workflows/reconcile/impacts/reports.js +80 -0
  244. package/src/workflows/reconcile/impacts.js +14 -623
  245. package/src/workspace-docs.d.ts +29 -0
@@ -0,0 +1,352 @@
1
+ // @ts-check
2
+
3
+ import fs from "node:fs";
4
+ import path from "node:path";
5
+
6
+ import { stableStringify } from "../../../format.js";
7
+ import { loadProjectConfig } from "../../../project-config.js";
8
+ import {
9
+ applyTemplateUpdate,
10
+ applyTemplateUpdateFileAction,
11
+ buildTemplateUpdateCheck,
12
+ buildTemplateUpdatePlan,
13
+ buildTemplateUpdateStatus
14
+ } from "../../../new-project.js";
15
+ import { TEMPLATES_ROOT } from "./constants.js";
16
+ import { templateCheckDiagnostic } from "./diagnostics.js";
17
+ import { latestTemplateInfo, messageFromError, templateMetadataFromProjectConfig } from "./shared.js";
18
+
19
+ /**
20
+ * @param {any} plan
21
+ * @returns {void}
22
+ */
23
+ export function printTemplateUpdatePlan(plan) {
24
+ const isApply = plan.mode === "apply";
25
+ const isCheck = plan.mode === "check";
26
+ const isStatus = plan.mode === "status";
27
+ const isFileAction = ["accept-current", "accept-candidate", "delete-current"].includes(plan.mode);
28
+ if (isApply) {
29
+ console.log(plan.ok ? "Template update apply: complete" : "Template update apply: refused");
30
+ } else if (isStatus) {
31
+ console.log(plan.ok ? "Template update status: aligned" : "Template update status: action needed");
32
+ } else if (isCheck) {
33
+ console.log(plan.ok ? "Template update check: aligned" : "Template update check: out of date");
34
+ } else if (isFileAction) {
35
+ console.log(plan.ok ? `Template update ${plan.mode}: complete` : `Template update ${plan.mode}: refused`);
36
+ } else {
37
+ console.log(plan.ok ? "Template update plan: ready for review" : "Template update plan: incompatible");
38
+ }
39
+ console.log(`Current: ${plan.current?.id || "unknown"}@${plan.current?.version || "unknown"}`);
40
+ console.log(`Candidate: ${plan.candidate?.id || "unknown"}@${plan.candidate?.version || "unknown"}`);
41
+ console.log(`Writes: ${plan.writes ? "applied" : "none"}`);
42
+ if (plan.reportPath) {
43
+ console.log(`Report: ${plan.reportPath}`);
44
+ }
45
+ console.log(`Added: ${plan.summary.added}`);
46
+ console.log(`Changed: ${plan.summary.changed}`);
47
+ console.log(`Current-only: ${plan.summary.currentOnly}`);
48
+ console.log(`Unchanged: ${plan.summary.unchanged}`);
49
+ if (isApply || isStatus || isFileAction) {
50
+ const appliedCount = (plan.applied || []).length;
51
+ const acceptedCount = (plan.accepted || []).length;
52
+ const deletedCount = (plan.deleted || []).length;
53
+ const skippedCount = (plan.skipped || []).length;
54
+ const conflictCount = (plan.conflicts || []).length;
55
+ if (isApply && appliedCount === 0 && skippedCount === 0 && conflictCount === 0 && plan.files.length === 0) {
56
+ console.log("No changes to apply.");
57
+ }
58
+ if (isStatus && plan.files.length === 0 && conflictCount === 0 && skippedCount === 0 && (plan.diagnostics || []).length === 0) {
59
+ console.log("No template update action needed.");
60
+ }
61
+ if (isApply && appliedCount > 0) {
62
+ console.log(`Applied ${appliedCount} file(s).`);
63
+ }
64
+ if (isFileAction && appliedCount > 0) {
65
+ console.log(`Accepted candidate for ${appliedCount} file(s).`);
66
+ }
67
+ if (acceptedCount > 0) {
68
+ console.log(`Accepted current baseline for ${acceptedCount} file(s).`);
69
+ }
70
+ if (deletedCount > 0) {
71
+ console.log(`Deleted ${deletedCount} current-only file(s).`);
72
+ }
73
+ if (skippedCount > 0) {
74
+ console.log(`Skipped ${skippedCount} current-only file(s).`);
75
+ }
76
+ if (conflictCount > 0) {
77
+ console.log(`Refused due to ${conflictCount} conflict(s).`);
78
+ }
79
+ }
80
+ const diagnostics = Array.isArray(plan.diagnostics) ? plan.diagnostics : [];
81
+ for (const diagnostic of diagnostics) {
82
+ console.log(`[${diagnostic.severity}] ${diagnostic.code}: ${diagnostic.message}`);
83
+ if (diagnostic.path) {
84
+ console.log(` path: ${diagnostic.path}`);
85
+ }
86
+ if (diagnostic.suggestedFix) {
87
+ console.log(` fix: ${diagnostic.suggestedFix}`);
88
+ }
89
+ if (diagnostic.step) {
90
+ console.log(` step: ${diagnostic.step}`);
91
+ }
92
+ }
93
+ for (const conflict of plan.conflicts || []) {
94
+ console.log(`Conflict: ${conflict.path}`);
95
+ console.log(` reason: ${conflict.reason}`);
96
+ }
97
+ for (const applied of plan.applied || []) {
98
+ console.log(`Applied: ${applied.path}`);
99
+ }
100
+ for (const skipped of plan.skipped || []) {
101
+ console.log(`Skipped: ${skipped.path}`);
102
+ console.log(` reason: ${skipped.reason}`);
103
+ }
104
+ for (const accepted of plan.accepted || []) {
105
+ console.log(`Accepted current: ${accepted.path}`);
106
+ }
107
+ for (const deleted of plan.deleted || []) {
108
+ console.log(`Deleted: ${deleted.path}`);
109
+ }
110
+ for (const file of plan.files) {
111
+ console.log("");
112
+ console.log(`${file.kind.toUpperCase()}: ${file.path}`);
113
+ if (file.current) {
114
+ console.log(` current sha256: ${file.current.sha256}`);
115
+ console.log(` current size: ${file.current.size}`);
116
+ }
117
+ if (file.candidate) {
118
+ console.log(` candidate sha256: ${file.candidate.sha256}`);
119
+ console.log(` candidate size: ${file.candidate.size}`);
120
+ }
121
+ if (file.binary) {
122
+ console.log(" diff: binary file");
123
+ } else if (file.diffOmitted && !file.unifiedDiff) {
124
+ console.log(" diff: hash-only");
125
+ }
126
+ if (file.unifiedDiff) {
127
+ console.log(file.unifiedDiff.trimEnd());
128
+ }
129
+ }
130
+ if (plan.files.length === 0) {
131
+ console.log("No template-owned file changes found.");
132
+ }
133
+ if (!isApply && !isCheck && !isStatus && !isFileAction) {
134
+ console.log("");
135
+ console.log("This command did not write files. Review the plan before applying template updates.");
136
+ } else if (isCheck || isStatus) {
137
+ console.log("");
138
+ console.log("This command did not write files.");
139
+ }
140
+ }
141
+
142
+ /**
143
+ * @param {any} status
144
+ * @returns {any}
145
+ */
146
+ export function buildTemplateUpdateRecommendationPayload(status) {
147
+ /** @type {Array<{ action: string, command: string|null, reason: string, path: string|null }>} */
148
+ const recommendations = [];
149
+ /** @type {any[]} */
150
+ const diagnostics = Array.isArray(status.diagnostics)
151
+ ? status.diagnostics.map((/** @type {any} */ diagnostic) => diagnostic.code === "template_update_available"
152
+ ? { ...diagnostic, severity: "warning" }
153
+ : diagnostic)
154
+ : [];
155
+ const errorDiagnostics = diagnostics.filter((/** @type {any} */ diagnostic) => diagnostic.severity === "error");
156
+ const conflicts = Array.isArray(status.conflicts) ? status.conflicts : [];
157
+ const skipped = Array.isArray(status.skipped) ? status.skipped : [];
158
+ const files = Array.isArray(status.files) ? status.files : [];
159
+ const addedChanged = files.filter((/** @type {any} */ file) => file.kind === "added" || file.kind === "changed");
160
+
161
+ if (errorDiagnostics.length > 0) {
162
+ recommendations.push({
163
+ action: "resolve-errors",
164
+ command: "topogram template update --status",
165
+ reason: "Template policy, compatibility, baseline, or conflict errors must be resolved before applying candidate files.",
166
+ path: null
167
+ });
168
+ }
169
+ for (const conflict of conflicts) {
170
+ recommendations.push({
171
+ action: "review-conflict",
172
+ command: `topogram template update --accept-current ${conflict.path}`,
173
+ reason: "Local edits differ from the last trusted template-owned baseline. Accept current after review, or apply the candidate manually.",
174
+ path: conflict.path
175
+ });
176
+ }
177
+ if (addedChanged.length > 0 && conflicts.length === 0 && errorDiagnostics.length === 0) {
178
+ recommendations.push({
179
+ action: "apply-candidate",
180
+ command: "topogram template update --apply",
181
+ reason: `${addedChanged.length} added or changed candidate file(s) can be applied without local conflicts.`,
182
+ path: null
183
+ });
184
+ }
185
+ for (const item of skipped) {
186
+ recommendations.push({
187
+ action: "review-delete",
188
+ command: `topogram template update --delete-current ${item.path}`,
189
+ reason: "The candidate no longer owns this current file. Delete it only after review.",
190
+ path: item.path
191
+ });
192
+ }
193
+ if (files.length === 0 && errorDiagnostics.length === 0) {
194
+ recommendations.push({
195
+ action: "none",
196
+ command: null,
197
+ reason: "Current project files already match the candidate template.",
198
+ path: null
199
+ });
200
+ }
201
+ if (status.candidate?.id && status.candidate?.version && errorDiagnostics.length === 0) {
202
+ recommendations.push({
203
+ action: "pin-reviewed-version",
204
+ command: `topogram template policy pin ${status.candidate.id}@${status.candidate.version}`,
205
+ reason: "After reviewing or applying this candidate, pin the template version in project policy.",
206
+ path: null
207
+ });
208
+ }
209
+ return {
210
+ ...status,
211
+ ok: errorDiagnostics.length === 0,
212
+ mode: "recommend",
213
+ writes: false,
214
+ issues: errorDiagnostics.map((/** @type {any} */ diagnostic) => diagnostic.message),
215
+ diagnostics,
216
+ recommendations
217
+ };
218
+ }
219
+
220
+ /**
221
+ * @param {ReturnType<typeof buildTemplateUpdateRecommendationPayload>} payload
222
+ * @returns {void}
223
+ */
224
+ export function printTemplateUpdateRecommendation(payload) {
225
+ console.log(payload.ok ? "Template update recommendation: ready" : "Template update recommendation: blocked");
226
+ console.log(`Current: ${payload.current?.id || "unknown"}@${payload.current?.version || "unknown"}`);
227
+ console.log(`Candidate: ${payload.candidate?.id || "unknown"}@${payload.candidate?.version || "unknown"}`);
228
+ console.log(`Added: ${payload.summary.added}`);
229
+ console.log(`Changed: ${payload.summary.changed}`);
230
+ console.log(`Current-only: ${payload.summary.currentOnly}`);
231
+ console.log(`Conflicts: ${payload.conflicts.length}`);
232
+ if (payload.reportPath) {
233
+ console.log(`Report: ${payload.reportPath}`);
234
+ }
235
+ for (const diagnostic of payload.diagnostics || []) {
236
+ console.log(`[${diagnostic.severity}] ${diagnostic.code}: ${diagnostic.message}`);
237
+ if (diagnostic.path) {
238
+ console.log(` path: ${diagnostic.path}`);
239
+ }
240
+ if (diagnostic.suggestedFix) {
241
+ console.log(` fix: ${diagnostic.suggestedFix}`);
242
+ }
243
+ }
244
+ console.log("");
245
+ console.log("Recommended next steps:");
246
+ for (const recommendation of payload.recommendations) {
247
+ console.log(`- ${recommendation.reason}`);
248
+ if (recommendation.command) {
249
+ console.log(` ${recommendation.command}`);
250
+ }
251
+ }
252
+ }
253
+
254
+ /**
255
+ * @param {{ args: string[], inputPath: string, templateIndex: number, templateName: string|null|undefined, useLatestTemplate: boolean, outPath?: string|null }} options
256
+ * @returns {any}
257
+ */
258
+ export function buildTemplateUpdateCliPayload(options) {
259
+ const { args, inputPath, templateIndex, templateName, useLatestTemplate, outPath = null } = options;
260
+ const applyUpdate = args.includes("--apply");
261
+ const checkUpdate = args.includes("--check");
262
+ const planUpdate = args.includes("--plan");
263
+ const statusUpdate = args.includes("--status");
264
+ const recommendUpdate = args.includes("--recommend");
265
+ const acceptCurrentIndex = args.indexOf("--accept-current");
266
+ const acceptCandidateIndex = args.indexOf("--accept-candidate");
267
+ const deleteCurrentIndex = args.indexOf("--delete-current");
268
+ const acceptCurrentUpdate = acceptCurrentIndex >= 0;
269
+ const acceptCandidateUpdate = acceptCandidateIndex >= 0;
270
+ const deleteCurrentUpdate = deleteCurrentIndex >= 0;
271
+ const fileAction = acceptCurrentUpdate ? "accept-current" : acceptCandidateUpdate ? "accept-candidate" : deleteCurrentUpdate ? "delete-current" : null;
272
+ const fileActionIndex = acceptCurrentUpdate ? acceptCurrentIndex : acceptCandidateUpdate ? acceptCandidateIndex : deleteCurrentUpdate ? deleteCurrentIndex : -1;
273
+ const fileActionPath = fileActionIndex >= 0 ? args[fileActionIndex + 1] : null;
274
+ const updateModeCount = [applyUpdate, checkUpdate, planUpdate, statusUpdate, recommendUpdate, acceptCurrentUpdate, acceptCandidateUpdate, deleteCurrentUpdate].filter(Boolean).length;
275
+ if (updateModeCount > 1) {
276
+ throw new Error("Choose one template update mode or file adoption action.");
277
+ }
278
+ if (updateModeCount === 0) {
279
+ throw new Error("Template update requires `--status`, `--recommend`, `--plan`, `--check`, `--apply`, `--accept-current <file>`, `--accept-candidate <file>`, or `--delete-current <file>`.");
280
+ }
281
+ if (fileAction && (!fileActionPath || fileActionPath.startsWith("-"))) {
282
+ throw new Error(`Template update ${fileAction} requires a relative file path.`);
283
+ }
284
+ const projectConfigInfo = loadProjectConfig(inputPath);
285
+ if (!projectConfigInfo) {
286
+ throw new Error("Cannot update template without topogram.project.json.");
287
+ }
288
+ if (!projectConfigInfo.config.template?.id && !projectConfigInfo.config.template?.sourceSpec) {
289
+ throw new Error("Cannot update template because this project is detached from template metadata.");
290
+ }
291
+ const requestedTemplateName = templateIndex >= 0
292
+ ? templateName
293
+ : useLatestTemplate
294
+ ? latestTemplateInfo(templateMetadataFromProjectConfig(projectConfigInfo.config)).candidateSpec
295
+ : null;
296
+ if (useLatestTemplate && !requestedTemplateName) {
297
+ throw new Error("Cannot use --latest because the current template is not package-backed.");
298
+ }
299
+ let update;
300
+ try {
301
+ const updateOptions = {
302
+ projectRoot: projectConfigInfo.configDir,
303
+ projectConfig: projectConfigInfo.config,
304
+ templateName: requestedTemplateName,
305
+ templatesRoot: TEMPLATES_ROOT
306
+ };
307
+ update = fileAction
308
+ ? applyTemplateUpdateFileAction({ ...updateOptions, action: fileAction, filePath: fileActionPath || "" })
309
+ : recommendUpdate
310
+ ? buildTemplateUpdateRecommendationPayload(buildTemplateUpdateStatus(updateOptions))
311
+ : (applyUpdate ? applyTemplateUpdate : checkUpdate ? buildTemplateUpdateCheck : statusUpdate ? buildTemplateUpdateStatus : buildTemplateUpdatePlan)(updateOptions);
312
+ } catch (error) {
313
+ const message = messageFromError(error);
314
+ update = {
315
+ ok: false,
316
+ mode: fileAction || (applyUpdate ? "apply" : checkUpdate ? "check" : statusUpdate ? "status" : recommendUpdate ? "recommend" : "plan"),
317
+ writes: false,
318
+ current: {
319
+ id: typeof projectConfigInfo.config.template?.id === "string" ? projectConfigInfo.config.template.id : null,
320
+ version: typeof projectConfigInfo.config.template?.version === "string" ? projectConfigInfo.config.template.version : null
321
+ },
322
+ candidate: null,
323
+ compatible: false,
324
+ issues: [message],
325
+ diagnostics: [templateCheckDiagnostic({
326
+ code: "template_resolve_failed",
327
+ message,
328
+ path: templateIndex >= 0 && typeof templateName === "string" && path.isAbsolute(templateName) ? templateName : null,
329
+ suggestedFix: "Check the template path or package spec, and verify private registry authentication if this is a package template.",
330
+ step: "resolve-candidate"
331
+ })],
332
+ summary: { added: 0, changed: 0, currentOnly: 0, unchanged: 0 },
333
+ files: [],
334
+ applied: [],
335
+ skipped: [],
336
+ conflicts: [],
337
+ recommendations: recommendUpdate ? [{
338
+ action: "resolve-errors",
339
+ command: "topogram template update --status",
340
+ reason: "Resolve the candidate template before choosing an update action.",
341
+ path: null
342
+ }] : undefined
343
+ };
344
+ }
345
+ if (outPath) {
346
+ const reportPath = path.resolve(outPath);
347
+ fs.mkdirSync(path.dirname(reportPath), { recursive: true });
348
+ fs.writeFileSync(reportPath, `${stableStringify(update)}\n`, "utf8");
349
+ update.reportPath = reportPath;
350
+ }
351
+ return update;
352
+ }