@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
@@ -1,6 +1,5 @@
1
1
  // @ts-check
2
2
 
3
- import fs from "node:fs";
4
3
  import path from "node:path";
5
4
 
6
5
  import { stableStringify } from "../../format.js";
@@ -18,6 +17,7 @@ import {
18
17
  localTemplatePackageStatus
19
18
  } from "./package.js";
20
19
  import { buildTemplateOwnedBaselineStatus } from "./template.js";
20
+ import { resolveTopoRoot, resolveWorkspaceContext } from "../../workspace-paths.js";
21
21
 
22
22
  /**
23
23
  * @typedef {Record<string, any>} AnyRecord
@@ -43,12 +43,7 @@ export function printSourceHelp() {
43
43
  * @returns {string}
44
44
  */
45
45
  function normalizeTopogramPath(inputPath) {
46
- const absolute = path.resolve(inputPath);
47
- if (path.basename(absolute) === "topogram") {
48
- return absolute;
49
- }
50
- const candidate = path.join(absolute, "topogram");
51
- return fs.existsSync(candidate) ? candidate : absolute;
46
+ return resolveTopoRoot(inputPath);
52
47
  }
53
48
 
54
49
  /**
@@ -56,11 +51,7 @@ function normalizeTopogramPath(inputPath) {
56
51
  * @returns {string}
57
52
  */
58
53
  export function normalizeProjectRoot(inputPath) {
59
- const absolute = path.resolve(inputPath);
60
- if (path.basename(absolute) === "topogram") {
61
- return path.dirname(absolute);
62
- }
63
- return absolute;
54
+ return resolveWorkspaceContext(inputPath).projectRoot;
64
55
  }
65
56
 
66
57
  /**
@@ -0,0 +1,100 @@
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 { TEMPLATE_FILES_MANIFEST } from "./constants.js";
9
+
10
+ /**
11
+ * @param {string} filePath
12
+ * @returns {{ sha256: string, size: number }}
13
+ */
14
+ function projectFileHash(filePath) {
15
+ const bytes = fs.readFileSync(filePath);
16
+ return {
17
+ sha256: crypto.createHash("sha256").update(bytes).digest("hex"),
18
+ size: bytes.length
19
+ };
20
+ }
21
+
22
+ /**
23
+ * @param {string} projectRoot
24
+ * @param {string} relativePath
25
+ * @returns {{ sha256: string, size: number }}
26
+ */
27
+ function templateBaselineFileHash(projectRoot, relativePath) {
28
+ const filePath = path.join(projectRoot, relativePath);
29
+ if (relativePath === "topogram.project.json") {
30
+ const content = `${stableStringify(JSON.parse(fs.readFileSync(filePath, "utf8")))}\n`;
31
+ return {
32
+ sha256: crypto.createHash("sha256").update(content).digest("hex"),
33
+ size: Buffer.byteLength(content)
34
+ };
35
+ }
36
+ return projectFileHash(filePath);
37
+ }
38
+
39
+ /**
40
+ * @param {string} projectRoot
41
+ * @returns {{ exists: boolean, path: string, status: "missing"|"clean"|"changed", state: "missing"|"matches-template"|"diverged", meaning: "no-template-baseline"|"matches-template-baseline"|"local-project-owns-changes", changedAllowed: boolean, localOwnership: boolean, blocksCheck: boolean, blocksGenerate: boolean, nextCommand: string|null, content: { changed: string[], added: string[], removed: string[] }, trustedFiles: number }}
42
+ */
43
+ export function buildTemplateOwnedBaselineStatus(projectRoot) {
44
+ const manifestPath = path.join(projectRoot, TEMPLATE_FILES_MANIFEST);
45
+ if (!fs.existsSync(manifestPath)) {
46
+ return {
47
+ exists: false,
48
+ path: manifestPath,
49
+ status: "missing",
50
+ state: "missing",
51
+ meaning: "no-template-baseline",
52
+ changedAllowed: true,
53
+ localOwnership: false,
54
+ blocksCheck: false,
55
+ blocksGenerate: false,
56
+ nextCommand: null,
57
+ content: { changed: [], added: [], removed: [] },
58
+ trustedFiles: 0
59
+ };
60
+ }
61
+ const manifest = JSON.parse(fs.readFileSync(manifestPath, "utf8"));
62
+ const trustedFiles = Array.isArray(manifest.files) ? manifest.files : [];
63
+ const changed = [];
64
+ const removed = [];
65
+ for (const file of trustedFiles) {
66
+ const relativePath = String(file.path || "");
67
+ if (!relativePath) {
68
+ continue;
69
+ }
70
+ const absolutePath = path.join(projectRoot, relativePath);
71
+ if (!fs.existsSync(absolutePath)) {
72
+ removed.push(relativePath);
73
+ continue;
74
+ }
75
+ const current = templateBaselineFileHash(projectRoot, relativePath);
76
+ if (current.sha256 !== file.sha256 || current.size !== file.size) {
77
+ changed.push(relativePath);
78
+ }
79
+ }
80
+ const status = changed.length || removed.length ? "changed" : "clean";
81
+ const diverged = status === "changed";
82
+ return {
83
+ exists: true,
84
+ path: manifestPath,
85
+ status,
86
+ state: diverged ? "diverged" : "matches-template",
87
+ meaning: diverged ? "local-project-owns-changes" : "matches-template-baseline",
88
+ changedAllowed: true,
89
+ localOwnership: diverged,
90
+ blocksCheck: false,
91
+ blocksGenerate: false,
92
+ nextCommand: diverged ? "topogram template update --check" : null,
93
+ content: {
94
+ changed: changed.sort((a, b) => a.localeCompare(b)),
95
+ added: [],
96
+ removed: removed.sort((a, b) => a.localeCompare(b))
97
+ },
98
+ trustedFiles: trustedFiles.length
99
+ };
100
+ }
@@ -0,0 +1,467 @@
1
+ // @ts-check
2
+
3
+ import fs from "node:fs";
4
+ import os from "node:os";
5
+ import path from "node:path";
6
+
7
+ import { parsePath } from "../../../parser.js";
8
+ import { resolveWorkspace } from "../../../resolver.js";
9
+ import {
10
+ loadProjectConfig,
11
+ validateProjectConfig,
12
+ validateProjectOutputOwnership
13
+ } from "../../../project-config.js";
14
+ import { resolveTopoRoot } from "../../../workspace-paths.js";
15
+ import {
16
+ buildTemplateUpdatePlan,
17
+ createNewProject,
18
+ loadTemplatePolicy,
19
+ resolveTemplate,
20
+ templatePolicyDiagnosticsForTemplate
21
+ } from "../../../new-project.js";
22
+ import {
23
+ getTemplateTrustStatus,
24
+ implementationRequiresTrust,
25
+ TEMPLATE_TRUST_FILE,
26
+ templateTrustRecoveryGuidance,
27
+ validateProjectImplementationTrust
28
+ } from "../../../template-trust.js";
29
+ import { runNpmForPackageUpdate } from "../package.js";
30
+ import { ENGINE_ROOT, TEMPLATES_ROOT } from "./constants.js";
31
+ import { combineProjectValidationResults, messageFromError } from "./shared.js";
32
+ import { templateCheckDiagnostic } from "./diagnostics.js";
33
+
34
+ /**
35
+ * @typedef {Object} TemplateCheckDiagnostic
36
+ * @property {string} code
37
+ * @property {"error"|"warning"} severity
38
+ * @property {string} message
39
+ * @property {string|null} path
40
+ * @property {string|null} suggestedFix
41
+ * @property {string|null} step
42
+ */
43
+
44
+ /**
45
+ * @param {string} templateSpec
46
+ * @param {string} relativePath
47
+ * @returns {string|null}
48
+ */
49
+ function localTemplatePath(templateSpec, relativePath) {
50
+ if (
51
+ templateSpec === "." ||
52
+ templateSpec.startsWith("./") ||
53
+ templateSpec.startsWith("../") ||
54
+ path.isAbsolute(templateSpec)
55
+ ) {
56
+ return path.join(path.resolve(templateSpec), relativePath);
57
+ }
58
+ return null;
59
+ }
60
+
61
+ /**
62
+ * @param {string} message
63
+ * @param {string} templateSpec
64
+ * @param {string} step
65
+ * @returns {TemplateCheckDiagnostic}
66
+ */
67
+ function diagnosticForTemplateCreateFailure(message, templateSpec, step) {
68
+ if (message.includes("is missing topogram-template.json")) {
69
+ return templateCheckDiagnostic({
70
+ code: "template_manifest_missing",
71
+ message,
72
+ path: localTemplatePath(templateSpec, "topogram-template.json"),
73
+ suggestedFix: "Add topogram-template.json with id, version, kind, and topogramVersion.",
74
+ step
75
+ });
76
+ }
77
+ if (message.includes("contains implementation/") && message.includes("includesExecutableImplementation: true")) {
78
+ return templateCheckDiagnostic({
79
+ code: "template_implementation_undeclared",
80
+ message,
81
+ path: localTemplatePath(templateSpec, "topogram-template.json"),
82
+ suggestedFix: "Set includesExecutableImplementation to true after reviewing implementation/, or remove implementation/.",
83
+ step
84
+ });
85
+ }
86
+ if (message.includes("is missing required string field") || message.includes("topogram-template.json")) {
87
+ return templateCheckDiagnostic({
88
+ code: "template_manifest_invalid",
89
+ message,
90
+ path: localTemplatePath(templateSpec, "topogram-template.json"),
91
+ suggestedFix: "Fix topogram-template.json so it matches the template manifest schema.",
92
+ step
93
+ });
94
+ }
95
+ if (message.includes("is missing topo/") || message.includes("is missing topogram/")) {
96
+ return templateCheckDiagnostic({
97
+ code: "template_topogram_missing",
98
+ message,
99
+ path: localTemplatePath(templateSpec, "topo"),
100
+ suggestedFix: "Add a topo/ directory with the reusable Topogram source files.",
101
+ step
102
+ });
103
+ }
104
+ if (message.includes("is missing topogram.project.json")) {
105
+ return templateCheckDiagnostic({
106
+ code: "template_project_config_missing",
107
+ message,
108
+ path: localTemplatePath(templateSpec, "topogram.project.json"),
109
+ suggestedFix: "Add topogram.project.json beside topo/ with outputs and topology.runtimes.",
110
+ step
111
+ });
112
+ }
113
+ if (message.includes("is missing implementation/")) {
114
+ return templateCheckDiagnostic({
115
+ code: "template_implementation_missing",
116
+ message,
117
+ path: localTemplatePath(templateSpec, "implementation"),
118
+ suggestedFix: "Add implementation/ or set includesExecutableImplementation to false.",
119
+ step
120
+ });
121
+ }
122
+ if (message.includes("unsupported symlink")) {
123
+ return templateCheckDiagnostic({
124
+ code: "template_symlink_unsupported",
125
+ message,
126
+ path: path.isAbsolute(templateSpec) ? templateSpec : null,
127
+ suggestedFix: "Replace template symlinks with real files or directories, then rerun `topogram new` or `topogram template check`.",
128
+ step
129
+ });
130
+ }
131
+ return templateCheckDiagnostic({
132
+ code: "template_create_failed",
133
+ message,
134
+ path: path.isAbsolute(templateSpec) ? templateSpec : null,
135
+ suggestedFix: "Fix the template pack so topogram new can create a starter from it.",
136
+ step
137
+ });
138
+ }
139
+
140
+ /**
141
+ * @param {{ message: string, loc?: any }} error
142
+ * @param {string} step
143
+ * @param {string|null} configPath
144
+ * @returns {TemplateCheckDiagnostic}
145
+ */
146
+ function diagnosticForStarterCheckFailure(error, step, configPath) {
147
+ const locFile = typeof error?.loc?.file === "string" ? error.loc.file : null;
148
+ const isTrust = error.message.includes(TEMPLATE_TRUST_FILE) ||
149
+ error.message.includes("unsupported symlink") ||
150
+ error.message.includes("must be under implementation/");
151
+ return templateCheckDiagnostic({
152
+ code: isTrust ? "template_trust_invalid" : "starter_check_failed",
153
+ message: error.message,
154
+ path: locFile || configPath,
155
+ suggestedFix: isTrust
156
+ ? templateTrustRecoveryGuidance(error.message)
157
+ : "Fix the generated Topogram source or topogram.project.json so topogram check passes.",
158
+ step
159
+ });
160
+ }
161
+
162
+ /**
163
+ * @param {string} name
164
+ * @param {boolean} ok
165
+ * @param {Record<string, any>} [details]
166
+ * @param {TemplateCheckDiagnostic[]} [diagnostics]
167
+ * @returns {{ name: string, ok: boolean, details: Record<string, any>, diagnostics: TemplateCheckDiagnostic[] }}
168
+ */
169
+ function templateCheckStep(name, ok, details = {}, diagnostics = []) {
170
+ return { name, ok, details, diagnostics };
171
+ }
172
+
173
+ /**
174
+ * @param {string} projectRoot
175
+ * @returns {string[]}
176
+ */
177
+ function templateCheckGeneratorDependencies(projectRoot) {
178
+ const packagePath = path.join(projectRoot, "package.json");
179
+ if (!fs.existsSync(packagePath)) {
180
+ return [];
181
+ }
182
+ const pkg = JSON.parse(fs.readFileSync(packagePath, "utf8"));
183
+ const dependencies = {
184
+ ...(pkg.dependencies || {}),
185
+ ...(pkg.devDependencies || {})
186
+ };
187
+ return Object.keys(dependencies).filter((name) =>
188
+ name.includes("topogram-generator") || name.startsWith("@topogram/generator-")
189
+ ).sort();
190
+ }
191
+
192
+ /**
193
+ * @param {string} projectRoot
194
+ * @param {string[]} dependencies
195
+ * @returns {TemplateCheckDiagnostic|null}
196
+ */
197
+ function installTemplateCheckGeneratorDependencies(projectRoot, dependencies) {
198
+ if (dependencies.length === 0) {
199
+ return null;
200
+ }
201
+ const result = runNpmForPackageUpdate(["install", "--ignore-scripts"], projectRoot);
202
+ if (result.status === 0) {
203
+ return null;
204
+ }
205
+ const output = `${result.stdout || ""}\n${result.stderr || ""}`.trim();
206
+ return templateCheckDiagnostic({
207
+ code: "template_generator_dependencies_install_failed",
208
+ message: `Failed to install package-backed generator dependencies: ${dependencies.join(", ")}.`,
209
+ path: path.join(projectRoot, "package.json"),
210
+ suggestedFix: `Run npm install before checking this package-backed generator template.${output ? ` ${output.split(/\r?\n/).slice(-3).join(" ")}` : ""}`,
211
+ step: "generator-dependencies"
212
+ });
213
+ }
214
+
215
+ /**
216
+ * @param {string} templateSpec
217
+ * @returns {{ ok: boolean, templateSpec: string, projectRoot: string|null, steps: Array<{ name: string, ok: boolean, details: Record<string, any>, diagnostics: TemplateCheckDiagnostic[] }>, diagnostics: TemplateCheckDiagnostic[], errors: string[] }}
218
+ */
219
+ export function buildTemplateCheckPayload(templateSpec) {
220
+ if (!templateSpec) {
221
+ throw new Error("topogram template check requires <template-spec-or-path>.");
222
+ }
223
+ const runRoot = fs.mkdtempSync(path.join(os.tmpdir(), "topogram-template-check-"));
224
+ const projectRoot = path.join(runRoot, "starter");
225
+ /** @type {Array<{ name: string, ok: boolean, details: Record<string, any>, diagnostics: TemplateCheckDiagnostic[] }>} */
226
+ const steps = [];
227
+ /** @type {TemplateCheckDiagnostic[]} */
228
+ const diagnostics = [];
229
+ try {
230
+ const callerPolicyInfo = loadTemplatePolicy(process.cwd());
231
+ if (callerPolicyInfo.exists) {
232
+ const resolvedTemplate = resolveTemplate(templateSpec, TEMPLATES_ROOT);
233
+ const policyDiagnostics = templatePolicyDiagnosticsForTemplate(callerPolicyInfo, resolvedTemplate, "template-check-policy");
234
+ if (policyDiagnostics.some((diagnostic) => diagnostic.severity === "error")) {
235
+ const stepDiagnostics = policyDiagnostics.map((diagnostic) => templateCheckDiagnostic(diagnostic));
236
+ diagnostics.push(...stepDiagnostics);
237
+ steps.push(templateCheckStep("template-policy", false, {
238
+ path: callerPolicyInfo.path
239
+ }, stepDiagnostics));
240
+ return {
241
+ ok: false,
242
+ templateSpec,
243
+ projectRoot: null,
244
+ steps,
245
+ diagnostics,
246
+ errors: diagnostics.map((diagnostic) => diagnostic.message)
247
+ };
248
+ }
249
+ }
250
+ const created = createNewProject({
251
+ targetPath: projectRoot,
252
+ templateName: templateSpec,
253
+ engineRoot: ENGINE_ROOT,
254
+ templatesRoot: TEMPLATES_ROOT
255
+ });
256
+ steps.push(templateCheckStep("create-starter", true, {
257
+ template: created.templateName,
258
+ warnings: created.warnings.length
259
+ }));
260
+ const generatorDependencies = templateCheckGeneratorDependencies(projectRoot);
261
+ const installDiagnostic = installTemplateCheckGeneratorDependencies(projectRoot, generatorDependencies);
262
+ if (installDiagnostic) {
263
+ diagnostics.push(installDiagnostic);
264
+ steps.push(templateCheckStep("generator-dependencies", false, {
265
+ dependencies: generatorDependencies
266
+ }, [installDiagnostic]));
267
+ return {
268
+ ok: false,
269
+ templateSpec,
270
+ projectRoot,
271
+ steps,
272
+ diagnostics,
273
+ errors: diagnostics.map((diagnostic) => diagnostic.message)
274
+ };
275
+ }
276
+ if (generatorDependencies.length > 0) {
277
+ steps.push(templateCheckStep("generator-dependencies", true, {
278
+ dependencies: generatorDependencies
279
+ }));
280
+ }
281
+ } catch (error) {
282
+ const stepDiagnostics = [
283
+ diagnosticForTemplateCreateFailure(messageFromError(error), templateSpec, "create-starter")
284
+ ];
285
+ diagnostics.push(...stepDiagnostics);
286
+ steps.push(templateCheckStep("create-starter", false, {}, stepDiagnostics));
287
+ return {
288
+ ok: false,
289
+ templateSpec,
290
+ projectRoot: null,
291
+ steps,
292
+ diagnostics,
293
+ errors: diagnostics.map((diagnostic) => diagnostic.message)
294
+ };
295
+ }
296
+
297
+ const projectConfigInfo = loadProjectConfig(projectRoot);
298
+ if (!projectConfigInfo) {
299
+ const stepDiagnostics = [
300
+ templateCheckDiagnostic({
301
+ code: "starter_project_config_missing",
302
+ message: "Generated starter is missing topogram.project.json.",
303
+ path: path.join(projectRoot, "topogram.project.json"),
304
+ suggestedFix: "Ensure the template includes topogram.project.json at its root.",
305
+ step: "project-config"
306
+ })
307
+ ];
308
+ diagnostics.push(...stepDiagnostics);
309
+ steps.push(templateCheckStep("project-config", false, {}, stepDiagnostics));
310
+ return {
311
+ ok: false,
312
+ templateSpec,
313
+ projectRoot,
314
+ steps,
315
+ diagnostics,
316
+ errors: diagnostics.map((diagnostic) => diagnostic.message)
317
+ };
318
+ }
319
+ steps.push(templateCheckStep("project-config", true, {
320
+ path: projectConfigInfo.configPath,
321
+ template: projectConfigInfo.config.template?.id || null
322
+ }));
323
+
324
+ const ast = parsePath(resolveTopoRoot(projectRoot));
325
+ const resolved = resolveWorkspace(ast);
326
+ const projectValidation = combineProjectValidationResults(
327
+ validateProjectConfig(projectConfigInfo.config, resolved.ok ? resolved.graph : null, { configDir: projectConfigInfo.configDir }),
328
+ validateProjectOutputOwnership(projectConfigInfo),
329
+ validateProjectImplementationTrust(projectConfigInfo)
330
+ );
331
+ const starterCheckOk = resolved.ok && projectValidation.ok;
332
+ const starterDiagnostics = [
333
+ ...(resolved.ok ? [] : resolved.validation.errors),
334
+ ...projectValidation.errors
335
+ ].map((error) => diagnosticForStarterCheckFailure(error, "starter-check", projectConfigInfo.configPath));
336
+ steps.push(templateCheckStep("starter-check", starterCheckOk, {
337
+ files: ast.files.length,
338
+ statements: ast.files.flatMap((/** @type {{ statements: any[] }} */ file) => file.statements).length
339
+ }, starterDiagnostics));
340
+ if (!starterCheckOk) {
341
+ diagnostics.push(...starterDiagnostics);
342
+ }
343
+
344
+ const implementationInfo = projectConfigInfo.config.implementation
345
+ ? {
346
+ config: projectConfigInfo.config.implementation,
347
+ configPath: projectConfigInfo.configPath,
348
+ configDir: projectConfigInfo.configDir
349
+ }
350
+ : null;
351
+ if (implementationInfo && implementationRequiresTrust(implementationInfo, projectConfigInfo.config)) {
352
+ const trustStatus = getTemplateTrustStatus(implementationInfo, projectConfigInfo.config);
353
+ const trustDiagnostics = trustStatus.issues.map((issue) => templateCheckDiagnostic({
354
+ code: "template_trust_invalid",
355
+ message: issue,
356
+ path: trustStatus.trustPath,
357
+ suggestedFix: templateTrustRecoveryGuidance(issue),
358
+ step: "executable-implementation-trust"
359
+ }));
360
+ steps.push(templateCheckStep("executable-implementation-trust", trustStatus.ok, {
361
+ requiresTrust: true,
362
+ trustPath: trustStatus.trustPath,
363
+ trustedFiles: trustStatus.trustRecord?.content?.files?.length || 0
364
+ }, trustDiagnostics));
365
+ if (!trustStatus.ok) {
366
+ diagnostics.push(...trustDiagnostics);
367
+ }
368
+ } else {
369
+ steps.push(templateCheckStep("executable-implementation-trust", true, {
370
+ requiresTrust: false
371
+ }));
372
+ }
373
+
374
+ try {
375
+ const updatePlan = buildTemplateUpdatePlan({
376
+ projectRoot,
377
+ projectConfig: projectConfigInfo.config,
378
+ templateName: null,
379
+ templatesRoot: TEMPLATES_ROOT
380
+ });
381
+ steps.push(templateCheckStep("template-update-plan", updatePlan.ok, {
382
+ writes: updatePlan.writes,
383
+ added: updatePlan.summary.added,
384
+ changed: updatePlan.summary.changed,
385
+ currentOnly: updatePlan.summary.currentOnly
386
+ }));
387
+ if (!updatePlan.ok) {
388
+ const stepDiagnostics = updatePlan.issues.map((issue) => templateCheckDiagnostic({
389
+ code: "template_update_plan_failed",
390
+ message: issue,
391
+ path: projectConfigInfo.configPath,
392
+ suggestedFix: "Fix template metadata so a no-write update plan can be produced.",
393
+ step: "template-update-plan"
394
+ }));
395
+ steps[steps.length - 1].diagnostics.push(...stepDiagnostics);
396
+ diagnostics.push(...stepDiagnostics);
397
+ }
398
+ } catch (error) {
399
+ const stepDiagnostics = [
400
+ templateCheckDiagnostic({
401
+ code: "template_update_plan_failed",
402
+ message: messageFromError(error),
403
+ path: projectConfigInfo.configPath,
404
+ suggestedFix: "Fix template metadata so a no-write update plan can be produced.",
405
+ step: "template-update-plan"
406
+ })
407
+ ];
408
+ diagnostics.push(...stepDiagnostics);
409
+ steps.push(templateCheckStep("template-update-plan", false, {}, stepDiagnostics));
410
+ }
411
+
412
+ return {
413
+ ok: steps.every((step) => step.ok),
414
+ templateSpec,
415
+ projectRoot,
416
+ steps,
417
+ diagnostics,
418
+ errors: diagnostics.map((diagnostic) => diagnostic.message)
419
+ };
420
+ }
421
+
422
+ /**
423
+ * @param {Record<string, any>} details
424
+ * @returns {string[]}
425
+ */
426
+ function formatTemplateCheckDetails(details) {
427
+ return Object.entries(details)
428
+ .filter(([, value]) => value !== undefined && value !== null)
429
+ .map(([key, value]) => ` ${key}: ${typeof value === "object" ? JSON.stringify(value) : String(value)}`);
430
+ }
431
+
432
+ /**
433
+ * @param {ReturnType<typeof buildTemplateCheckPayload>} payload
434
+ * @returns {void}
435
+ */
436
+ export function printTemplateCheckPayload(payload) {
437
+ console.log(payload.ok ? "Template check passed" : "Template check failed");
438
+ console.log(`Template spec: ${payload.templateSpec}`);
439
+ if (payload.projectRoot) {
440
+ console.log(`Temp starter: ${payload.projectRoot}`);
441
+ }
442
+ for (const step of payload.steps) {
443
+ console.log(`${step.ok ? "PASS" : "FAIL"} ${step.name}`);
444
+ for (const detail of formatTemplateCheckDetails(step.details)) {
445
+ console.log(detail);
446
+ }
447
+ for (const diagnostic of step.diagnostics) {
448
+ console.log(` [${diagnostic.severity}] ${diagnostic.code}: ${diagnostic.message}`);
449
+ if (diagnostic.path) {
450
+ console.log(` path: ${diagnostic.path}`);
451
+ }
452
+ if (diagnostic.suggestedFix) {
453
+ console.log(` fix: ${diagnostic.suggestedFix}`);
454
+ }
455
+ }
456
+ }
457
+ const stepDiagnostics = new Set(payload.steps.flatMap((step) => step.diagnostics));
458
+ for (const diagnostic of payload.diagnostics.filter((item) => !stepDiagnostics.has(item))) {
459
+ console.log(`[${diagnostic.severity}] ${diagnostic.code}: ${diagnostic.message}`);
460
+ if (diagnostic.path) {
461
+ console.log(` path: ${diagnostic.path}`);
462
+ }
463
+ if (diagnostic.suggestedFix) {
464
+ console.log(` fix: ${diagnostic.suggestedFix}`);
465
+ }
466
+ }
467
+ }
@@ -0,0 +1,8 @@
1
+ // @ts-check
2
+
3
+ import path from "node:path";
4
+
5
+ export const TEMPLATE_FILES_MANIFEST = ".topogram-template-files.json";
6
+ export const TEMPLATE_POLICY_FILE = "topogram.template-policy.json";
7
+ export const ENGINE_ROOT = decodeURIComponent(new URL("../../../../", import.meta.url).pathname);
8
+ export const TEMPLATES_ROOT = path.join(ENGINE_ROOT, "templates");
@@ -0,0 +1,26 @@
1
+ // @ts-check
2
+
3
+ /**
4
+ * @typedef {Object} TemplateCheckDiagnostic
5
+ * @property {string} code
6
+ * @property {"error"|"warning"} severity
7
+ * @property {string} message
8
+ * @property {string|null} path
9
+ * @property {string|null} suggestedFix
10
+ * @property {string|null} step
11
+ */
12
+
13
+ /**
14
+ * @param {Record<string, any>} input
15
+ * @returns {TemplateCheckDiagnostic}
16
+ */
17
+ export function templateCheckDiagnostic(input) {
18
+ return {
19
+ code: String(input.code || "template_check_failed"),
20
+ severity: input.severity === "warning" ? "warning" : "error",
21
+ message: String(input.message || "Template check failed."),
22
+ path: typeof input.path === "string" ? input.path : null,
23
+ suggestedFix: typeof input.suggestedFix === "string" ? input.suggestedFix : null,
24
+ step: typeof input.step === "string" ? input.step : null
25
+ };
26
+ }
@@ -0,0 +1,28 @@
1
+ // @ts-check
2
+
3
+ /**
4
+ * @returns {void}
5
+ */
6
+ export function printTemplateHelp() {
7
+ console.log("Usage: topogram template list [--json] [--catalog <path-or-source>]");
8
+ console.log(" or: topogram template explain [path] [--json]");
9
+ console.log(" or: topogram template status [path] [--latest] [--json]");
10
+ console.log(" or: topogram template detach [path] [--dry-run] [--remove-policy] [--json]");
11
+ console.log(" or: topogram template check <template-spec-or-path> [--json]");
12
+ console.log(" or: topogram template policy init [path] [--json]");
13
+ console.log(" or: topogram template policy check [path] [--json]");
14
+ console.log(" or: topogram template policy explain [path] [--json]");
15
+ console.log(" or: topogram template policy pin <template-id@version> [path] [--json]");
16
+ console.log(" or: topogram template update [path] --status|--recommend|--plan|--check|--apply [--template <spec>|--latest] [--json] [--out <path>]");
17
+ console.log("");
18
+ console.log("Template commands inspect catalog-backed starters, project provenance, trust policy, and update plans.");
19
+ console.log("");
20
+ console.log("Examples:");
21
+ console.log(" topogram template list");
22
+ console.log(" topogram template explain");
23
+ console.log(" topogram template status");
24
+ console.log(" topogram template status --latest");
25
+ console.log(" topogram template policy check");
26
+ console.log(" topogram template check ./local-template");
27
+ console.log(" topogram template update --recommend");
28
+ }