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