@topogram/cli 0.3.64 → 0.3.66

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (278) hide show
  1. package/package.json +1 -1
  2. package/src/adoption/plan/index.js +716 -0
  3. package/src/adoption/plan.js +12 -703
  4. package/src/adoption/reporting.js +1 -1
  5. package/src/agent-brief.js +7 -21
  6. package/src/agent-ops/query-builders/auth.js +375 -0
  7. package/src/agent-ops/query-builders/change-risk/change-plan.js +123 -0
  8. package/src/agent-ops/query-builders/change-risk/import-plan.js +49 -0
  9. package/src/agent-ops/query-builders/change-risk/maintained.js +286 -0
  10. package/src/agent-ops/query-builders/change-risk/review-packets.js +123 -0
  11. package/src/agent-ops/query-builders/change-risk/risk.js +189 -0
  12. package/src/agent-ops/query-builders/change-risk.js +25 -0
  13. package/src/agent-ops/query-builders/common.js +149 -0
  14. package/src/agent-ops/query-builders/maintained-risk.js +539 -0
  15. package/src/agent-ops/query-builders/maintained-shared.js +120 -0
  16. package/src/agent-ops/query-builders/multi-agent.js +547 -0
  17. package/src/agent-ops/query-builders/projection-impacts.js +514 -0
  18. package/src/agent-ops/query-builders/work-packets.js +417 -0
  19. package/src/agent-ops/query-builders/workflow-context-shared.js +300 -0
  20. package/src/agent-ops/query-builders/workflow-context.js +398 -0
  21. package/src/agent-ops/query-builders/workflow-presets-core.js +677 -0
  22. package/src/agent-ops/query-builders/workflow-presets.js +341 -0
  23. package/src/agent-ops/query-builders.d.ts +26 -26
  24. package/src/agent-ops/query-builders.js +42 -5021
  25. package/src/archive/jsonl.js +2 -2
  26. package/src/archive/resolver-bridge.js +1 -1
  27. package/src/archive/unarchive.js +2 -1
  28. package/src/catalog/constants.js +10 -0
  29. package/src/catalog/copy.js +65 -0
  30. package/src/catalog/diagnostics.js +15 -0
  31. package/src/catalog/entries.js +42 -0
  32. package/src/catalog/files.js +67 -0
  33. package/src/catalog/provenance.js +123 -0
  34. package/src/catalog/source.js +150 -0
  35. package/src/catalog/validation.js +252 -0
  36. package/src/catalog.d.ts +2 -0
  37. package/src/catalog.js +18 -746
  38. package/src/cli/command-parsers/project.js +3 -0
  39. package/src/cli/command-parsers/shared.js +1 -1
  40. package/src/cli/commands/agent.js +2 -2
  41. package/src/cli/commands/catalog/check.js +31 -0
  42. package/src/cli/commands/catalog/copy.js +59 -0
  43. package/src/cli/commands/catalog/doctor.js +248 -0
  44. package/src/cli/commands/catalog/help.js +21 -0
  45. package/src/cli/commands/catalog/list.js +52 -0
  46. package/src/cli/commands/catalog/runner.js +92 -0
  47. package/src/cli/commands/catalog/shared.js +17 -0
  48. package/src/cli/commands/catalog/show.js +134 -0
  49. package/src/cli/commands/catalog.js +30 -615
  50. package/src/cli/commands/check.js +3 -3
  51. package/src/cli/commands/doctor.js +2 -9
  52. package/src/cli/commands/generator-policy/package-info.js +162 -0
  53. package/src/cli/commands/generator-policy/payloads.js +372 -0
  54. package/src/cli/commands/generator-policy/printers.js +159 -0
  55. package/src/cli/commands/generator-policy/runner.js +81 -0
  56. package/src/cli/commands/generator-policy/shared.js +39 -0
  57. package/src/cli/commands/generator-policy.js +15 -783
  58. package/src/cli/commands/import/adopt.js +170 -0
  59. package/src/cli/commands/import/check.js +91 -0
  60. package/src/cli/commands/import/diff.js +84 -0
  61. package/src/cli/commands/import/help.js +47 -0
  62. package/src/cli/commands/import/paths.js +269 -0
  63. package/src/cli/commands/import/plan.js +292 -0
  64. package/src/cli/commands/import/refresh.js +471 -0
  65. package/src/cli/commands/import/status-history.js +196 -0
  66. package/src/cli/commands/import/workspace.js +233 -0
  67. package/src/cli/commands/import.js +33 -1732
  68. package/src/cli/commands/migrate.js +153 -0
  69. package/src/cli/commands/package/constants.js +17 -0
  70. package/src/cli/commands/package/doctor.js +240 -0
  71. package/src/cli/commands/package/help.js +27 -0
  72. package/src/cli/commands/package/lockfile.js +135 -0
  73. package/src/cli/commands/package/npm.js +97 -0
  74. package/src/cli/commands/package/reporting.js +35 -0
  75. package/src/cli/commands/package/runner.js +33 -0
  76. package/src/cli/commands/package/shared.js +9 -0
  77. package/src/cli/commands/package/update-cli.js +252 -0
  78. package/src/cli/commands/package/versions.js +35 -0
  79. package/src/cli/commands/package.js +29 -813
  80. package/src/cli/commands/query/change-plan.js +68 -0
  81. package/src/cli/commands/query/definitions.js +202 -0
  82. package/src/cli/commands/query/import-adopt.js +121 -0
  83. package/src/cli/commands/query/runner/artifacts.js +102 -0
  84. package/src/cli/commands/query/runner/boundaries.js +211 -0
  85. package/src/cli/commands/query/runner/change.js +182 -0
  86. package/src/cli/commands/query/runner/import-adopt.js +111 -0
  87. package/src/cli/commands/query/runner/index.js +31 -0
  88. package/src/cli/commands/query/runner/output.js +12 -0
  89. package/src/cli/commands/query/runner/workflow.js +241 -0
  90. package/src/cli/commands/query/runner.js +3 -0
  91. package/src/cli/commands/query/workflow-context.js +5 -0
  92. package/src/cli/commands/query/workspace.js +270 -0
  93. package/src/cli/commands/query.js +9 -1300
  94. package/src/cli/commands/source.js +3 -12
  95. package/src/cli/commands/template/baseline.js +100 -0
  96. package/src/cli/commands/template/check.js +467 -0
  97. package/src/cli/commands/template/constants.js +8 -0
  98. package/src/cli/commands/template/diagnostics.js +26 -0
  99. package/src/cli/commands/template/help.js +28 -0
  100. package/src/cli/commands/template/lifecycle.js +404 -0
  101. package/src/cli/commands/template/list-show.js +287 -0
  102. package/src/cli/commands/template/policy.js +422 -0
  103. package/src/cli/commands/template/shared.js +127 -0
  104. package/src/cli/commands/template/updates.js +352 -0
  105. package/src/cli/commands/template-runner.js +6 -6
  106. package/src/cli/commands/template.js +41 -2143
  107. package/src/cli/commands/trust.js +1 -1
  108. package/src/cli/commands/workflow.js +6 -1
  109. package/src/cli/dispatcher.js +6 -1
  110. package/src/cli/help.js +15 -14
  111. package/src/cli/migration-guidance.js +1 -1
  112. package/src/cli/output-safety.js +2 -1
  113. package/src/cli/path-normalization.js +3 -13
  114. package/src/generator/api/contracts.js +497 -0
  115. package/src/generator/api/metadata.js +221 -0
  116. package/src/generator/api/openapi.js +559 -0
  117. package/src/generator/api/schema.js +124 -0
  118. package/src/generator/api/types.d.ts +98 -0
  119. package/src/generator/api.js +3 -1195
  120. package/src/generator/context/domain-page.js +1 -1
  121. package/src/generator/context/shared/domain-sdlc.js +282 -0
  122. package/src/generator/context/shared/maintained-boundary.js +665 -0
  123. package/src/generator/context/shared/metrics.js +85 -0
  124. package/src/generator/context/shared/primitives.js +64 -0
  125. package/src/generator/context/shared/relationships.js +453 -0
  126. package/src/generator/context/shared/summaries.js +263 -0
  127. package/src/generator/context/shared/types.d.ts +207 -0
  128. package/src/generator/context/shared.d.ts +42 -0
  129. package/src/generator/context/shared.js +80 -1390
  130. package/src/generator/context/slice/core.js +397 -0
  131. package/src/generator/context/slice/sdlc.js +417 -0
  132. package/src/generator/context/slice/ui-packets.js +183 -0
  133. package/src/generator/context/slice.js +2 -859
  134. package/src/generator/context/task-mode.js +2 -2
  135. package/src/generator/registry/index.js +507 -0
  136. package/src/generator/registry.js +18 -504
  137. package/src/generator/runtime/environment/index.js +666 -0
  138. package/src/generator/runtime/environment.js +4 -666
  139. package/src/generator/runtime/runtime-check/index.js +554 -0
  140. package/src/generator/runtime/runtime-check.js +4 -554
  141. package/src/generator/runtime/shared/index.js +572 -0
  142. package/src/generator/runtime/shared.js +19 -570
  143. package/src/generator/sdlc/doc-page.js +1 -1
  144. package/src/generator/shared.d.ts +2 -0
  145. package/src/generator/surfaces/databases/lifecycle-shared.js +1 -1
  146. package/src/generator/surfaces/native/swiftui-templates/README.generated.md +1 -1
  147. package/src/generator/surfaces/shared.d.ts +3 -0
  148. package/src/generator/widget-conformance/behavior-report.js +258 -0
  149. package/src/generator/widget-conformance/checks.js +371 -0
  150. package/src/generator/widget-conformance/projection-context.js +200 -0
  151. package/src/generator/widget-conformance/report.js +166 -0
  152. package/src/generator/widget-conformance/types.d.ts +121 -0
  153. package/src/generator/widget-conformance.js +3 -824
  154. package/src/import/core/context.d.ts +3 -0
  155. package/src/import/core/context.js +5 -7
  156. package/src/import/core/contracts.d.ts +1 -0
  157. package/src/import/core/registry.d.ts +4 -0
  158. package/src/import/core/runner/candidates.js +337 -0
  159. package/src/import/core/runner/options.js +22 -0
  160. package/src/import/core/runner/reports.js +51 -0
  161. package/src/import/core/runner/run.js +79 -0
  162. package/src/import/core/runner/tracks.js +150 -0
  163. package/src/import/core/runner/ui-drafts.js +393 -0
  164. package/src/import/core/runner.js +3 -698
  165. package/src/import/core/shared/api-routes.js +221 -0
  166. package/src/import/core/shared/candidates.js +97 -0
  167. package/src/import/core/shared/files.js +177 -0
  168. package/src/import/core/shared/next-app.js +389 -0
  169. package/src/import/core/shared/types.d.ts +51 -0
  170. package/src/import/core/shared/ui-routes.js +230 -0
  171. package/src/import/core/shared.js +60 -861
  172. package/src/new-project/constants.js +128 -0
  173. package/src/new-project/create.js +90 -0
  174. package/src/new-project/json.js +28 -0
  175. package/src/new-project/metadata.js +96 -0
  176. package/src/new-project/package-spec.js +161 -0
  177. package/src/new-project/project-files.js +351 -0
  178. package/src/new-project/template-policy.js +269 -0
  179. package/src/new-project/template-resolution.js +370 -0
  180. package/src/new-project/template-snapshots.js +442 -0
  181. package/src/new-project/template-updates.js +512 -0
  182. package/src/new-project/types.d.ts +83 -0
  183. package/src/new-project.js +6 -2277
  184. package/src/parser.d.ts +87 -1
  185. package/src/parser.js +118 -0
  186. package/src/policy/review-boundaries.d.ts +15 -0
  187. package/src/project-config/index.js +591 -0
  188. package/src/project-config.js +19 -561
  189. package/src/resolver/enrich/acceptance-criterion.js +2 -0
  190. package/src/resolver/enrich/bug.js +2 -0
  191. package/src/resolver/enrich/pitch.js +2 -0
  192. package/src/resolver/enrich/requirement.js +2 -0
  193. package/src/resolver/enrich/task.js +2 -0
  194. package/src/resolver/index.js +19 -2089
  195. package/src/resolver/normalize.js +384 -1
  196. package/src/resolver/plans.js +168 -0
  197. package/src/resolver/projections-api.js +494 -0
  198. package/src/resolver/projections-db.js +133 -0
  199. package/src/resolver/projections-ui.js +317 -0
  200. package/src/resolver/shapes.js +251 -0
  201. package/src/resolver/shared.js +278 -0
  202. package/src/resolver/widgets.js +132 -0
  203. package/src/sdlc/adopt.js +6 -5
  204. package/src/sdlc/paths.js +3 -5
  205. package/src/sdlc/scaffold.js +2 -1
  206. package/src/template-trust/constants.js +62 -0
  207. package/src/template-trust/content.js +258 -0
  208. package/src/template-trust/diff.js +92 -0
  209. package/src/template-trust/policy.js +61 -0
  210. package/src/template-trust/record.js +90 -0
  211. package/src/template-trust/status.js +182 -0
  212. package/src/template-trust.js +24 -687
  213. package/src/text-helpers.d.ts +1 -0
  214. package/src/topogram-types.d.ts +69 -0
  215. package/src/validator/common.js +488 -0
  216. package/src/validator/data-model.js +237 -0
  217. package/src/validator/docs.js +167 -0
  218. package/src/validator/expressions.js +146 -1
  219. package/src/validator/index.d.ts +23 -0
  220. package/src/validator/index.js +32 -3585
  221. package/src/validator/kinds.d.ts +41 -0
  222. package/src/validator/kinds.js +2 -0
  223. package/src/validator/model-helpers.js +46 -0
  224. package/src/validator/per-kind/acceptance-criterion.js +5 -0
  225. package/src/validator/per-kind/bug.js +6 -0
  226. package/src/validator/per-kind/domain.js +15 -2
  227. package/src/validator/per-kind/pitch.js +7 -0
  228. package/src/validator/per-kind/requirement.js +5 -0
  229. package/src/validator/per-kind/task.js +7 -0
  230. package/src/validator/per-kind/widget.js +14 -0
  231. package/src/validator/projections/api-http-async.js +410 -0
  232. package/src/validator/projections/api-http-authz.js +88 -0
  233. package/src/validator/projections/api-http-core.js +205 -0
  234. package/src/validator/projections/api-http-policies.js +339 -0
  235. package/src/validator/projections/api-http-responses.js +233 -0
  236. package/src/validator/projections/api-http.js +44 -0
  237. package/src/validator/projections/db.js +353 -0
  238. package/src/validator/projections/generator-defaults.js +45 -0
  239. package/src/validator/projections/helpers.js +87 -0
  240. package/src/validator/projections/ui-helpers.js +214 -0
  241. package/src/validator/projections/ui-navigation.js +344 -0
  242. package/src/validator/projections/ui-structure.js +364 -0
  243. package/src/validator/projections/ui-widgets.js +493 -0
  244. package/src/validator/projections/ui.js +46 -0
  245. package/src/validator/registry.js +48 -1
  246. package/src/validator/utils.d.ts +20 -0
  247. package/src/validator/utils.js +115 -12
  248. package/src/widget-behavior.d.ts +1 -0
  249. package/src/workflows/import-app/api/collect.js +221 -0
  250. package/src/workflows/import-app/api/openapi.js +257 -0
  251. package/src/workflows/import-app/api/routes.js +327 -0
  252. package/src/workflows/import-app/api/sources.js +22 -0
  253. package/src/workflows/import-app/api.js +2 -797
  254. package/src/workflows/reconcile/adoption-plan/build.js +212 -0
  255. package/src/workflows/reconcile/adoption-plan/dependencies.js +75 -0
  256. package/src/workflows/reconcile/adoption-plan/outputs.js +153 -0
  257. package/src/workflows/reconcile/adoption-plan/paths.js +58 -0
  258. package/src/workflows/reconcile/adoption-plan/projection-patches.js +177 -0
  259. package/src/workflows/reconcile/adoption-plan/reasons.js +107 -0
  260. package/src/workflows/reconcile/adoption-plan.js +30 -740
  261. package/src/workflows/reconcile/auth/closures.js +115 -0
  262. package/src/workflows/reconcile/auth/formatters.js +142 -0
  263. package/src/workflows/reconcile/auth/inference.js +330 -0
  264. package/src/workflows/reconcile/auth/roles.js +122 -0
  265. package/src/workflows/reconcile/auth.js +35 -690
  266. package/src/workflows/reconcile/bundle-core/index.js +600 -0
  267. package/src/workflows/reconcile/bundle-core.js +12 -598
  268. package/src/workflows/reconcile/candidate-model.js +18 -2
  269. package/src/workflows/reconcile/canonical-surface.js +1 -1
  270. package/src/workflows/reconcile/impacts/adoption-plan.js +196 -0
  271. package/src/workflows/reconcile/impacts/indexes.js +105 -0
  272. package/src/workflows/reconcile/impacts/patches.js +252 -0
  273. package/src/workflows/reconcile/impacts/reports.js +80 -0
  274. package/src/workflows/reconcile/impacts.js +14 -623
  275. package/src/workflows/reconcile/renderers.js +41 -6
  276. package/src/workflows/shared.js +5 -11
  277. package/src/workspace-docs.d.ts +29 -0
  278. package/src/workspace-paths.js +328 -0
@@ -0,0 +1,351 @@
1
+ // @ts-check
2
+
3
+ import fs from "node:fs";
4
+ import path from "node:path";
5
+
6
+ import { githubRepoSlug } from "../topogram-config.js";
7
+ import { DEFAULT_TOPO_FOLDER_NAME, DEFAULT_WORKSPACE_PATH, resolvePackageWorkspace } from "../workspace-paths.js";
8
+ import { cliDependencyForProject, generatorDependenciesForTemplate, isSameOrInside, packageNameFromPath, writeProjectNpmConfig } from "./package-spec.js";
9
+
10
+ /** @typedef {import("./types.js").CreateNewProjectOptions} CreateNewProjectOptions */
11
+ /** @typedef {import("./types.js").TemplateUpdatePlanOptions} TemplateUpdatePlanOptions */
12
+ /** @typedef {import("./types.js").TemplateUpdateFileActionOptions} TemplateUpdateFileActionOptions */
13
+ /** @typedef {import("./types.js").TemplateOwnedFileRecord} TemplateOwnedFileRecord */
14
+ /** @typedef {import("./types.js").TemplateManifest} TemplateManifest */
15
+ /** @typedef {import("./types.js").TemplateTopologySummary} TemplateTopologySummary */
16
+ /** @typedef {import("./types.js").TemplatePolicy} TemplatePolicy */
17
+ /** @typedef {import("./types.js").TemplatePolicyInfo} TemplatePolicyInfo */
18
+ /** @typedef {import("./types.js").TemplateUpdateDiagnostic} TemplateUpdateDiagnostic */
19
+ /** @typedef {import("./types.js").ResolvedTemplate} ResolvedTemplate */
20
+ /** @typedef {import("./types.js").CatalogTemplateProvenance} CatalogTemplateProvenance */
21
+
22
+ /**
23
+ * @param {string} projectRoot
24
+ * @param {string} engineRoot
25
+ * @returns {void}
26
+ */
27
+ export function assertProjectOutsideEngine(projectRoot, engineRoot) {
28
+ if (isSameOrInside(path.resolve(engineRoot), path.resolve(projectRoot))) {
29
+ throw new Error(
30
+ `Refusing to create a generated project inside the engine directory. Use a path outside engine, for example '../${path.basename(projectRoot)}'.`
31
+ );
32
+ }
33
+ }
34
+
35
+ /**
36
+ * @param {string} projectRoot
37
+ * @returns {void}
38
+ */
39
+ export function ensureCreatableProjectRoot(projectRoot) {
40
+ if (!fs.existsSync(projectRoot)) {
41
+ fs.mkdirSync(projectRoot, { recursive: true });
42
+ return;
43
+ }
44
+ if (!fs.statSync(projectRoot).isDirectory()) {
45
+ throw new Error(`Cannot create project at '${projectRoot}' because it is not a directory.`);
46
+ }
47
+ /** @type {string[]} */
48
+ const dirEntries = fs.readdirSync(projectRoot);
49
+ const entries = dirEntries.filter((entry) => entry !== ".DS_Store");
50
+ if (entries.length > 0) {
51
+ throw new Error(`Refusing to create a Topogram project in non-empty directory '${projectRoot}'.`);
52
+ }
53
+ }
54
+
55
+ /**
56
+ * @param {string} templateRoot
57
+ * @param {string} projectRoot
58
+ * @returns {{ legacyWorkspace: boolean }}
59
+ */
60
+ export function copyTopogramWorkspace(templateRoot, projectRoot) {
61
+ const templateWorkspace = resolvePackageWorkspace(templateRoot);
62
+ const topoRoot = path.join(projectRoot, DEFAULT_TOPO_FOLDER_NAME);
63
+ fs.cpSync(templateWorkspace.root, topoRoot, { recursive: true });
64
+
65
+ const projectConfigPath = path.join(templateRoot, "topogram.project.json");
66
+ const projectConfig = JSON.parse(fs.readFileSync(projectConfigPath, "utf8"));
67
+ projectConfig.workspace = DEFAULT_WORKSPACE_PATH;
68
+ fs.writeFileSync(path.join(projectRoot, "topogram.project.json"), `${JSON.stringify(projectConfig, null, 2)}\n`, "utf8");
69
+ const implementationRoot = path.join(templateRoot, "implementation");
70
+ if (fs.existsSync(implementationRoot)) {
71
+ fs.cpSync(
72
+ implementationRoot,
73
+ path.join(projectRoot, "implementation"),
74
+ { recursive: true }
75
+ );
76
+ }
77
+ return { legacyWorkspace: templateWorkspace.legacy };
78
+ }
79
+
80
+ /**
81
+ * @param {string} projectRoot
82
+ * @param {string} engineRoot
83
+ * @param {ResolvedTemplate} template
84
+ * @returns {void}
85
+ */
86
+ export function writeProjectPackage(projectRoot, engineRoot, template) {
87
+ const cliDependency = cliDependencyForProject(projectRoot, engineRoot);
88
+ const generatorDependencies = generatorDependenciesForTemplate(template.root);
89
+ const starterScripts = template.manifest.starterScripts || {};
90
+ const pkg = {
91
+ name: packageNameFromPath(projectRoot),
92
+ private: true,
93
+ type: "module",
94
+ scripts: {
95
+ explain: "node ./scripts/explain.mjs",
96
+ doctor: "topogram doctor",
97
+ "agent:brief": "topogram agent brief --json",
98
+ "source:status": "topogram source status --local",
99
+ "source:status:remote": "topogram source status --remote",
100
+ check: "topogram check",
101
+ "check:json": "topogram check --json",
102
+ "query:list": "topogram query list --json",
103
+ "query:show": "topogram query show",
104
+ generate: "topogram generate",
105
+ "template:explain": "topogram template explain",
106
+ "template:status": "topogram template status",
107
+ "template:detach": "topogram template detach",
108
+ "template:detach:dry-run": "topogram template detach --dry-run",
109
+ "template:policy:check": "topogram template policy check",
110
+ "template:policy:explain": "topogram template policy explain",
111
+ "generator:policy:status": "topogram generator policy status",
112
+ "generator:policy:check": "topogram generator policy check",
113
+ "generator:policy:explain": "topogram generator policy explain",
114
+ "template:update:status": "topogram template update --status",
115
+ "template:update:recommend": "topogram template update --recommend",
116
+ "template:update:plan": "topogram template update --plan",
117
+ "template:update:check": "topogram template update --check",
118
+ "template:update:apply": "topogram template update --apply",
119
+ "trust:status": "topogram trust status",
120
+ "trust:diff": "topogram trust diff",
121
+ verify: "npm run app:compile",
122
+ bootstrap: "npm run app:bootstrap",
123
+ dev: "npm run app:dev",
124
+ "app:bootstrap": "npm --prefix ./app run bootstrap",
125
+ "app:dev": "npm --prefix ./app run dev",
126
+ "app:compile": "npm --prefix ./app run compile",
127
+ "app:smoke": "npm --prefix ./app run smoke",
128
+ "app:runtime-check": "npm --prefix ./app run runtime-check",
129
+ "app:check": "npm run app:compile",
130
+ "app:probe": "npm run app:smoke && npm run app:runtime-check",
131
+ "app:runtime": "npm --prefix ./app run runtime",
132
+ ...starterScripts
133
+ },
134
+ devDependencies: {
135
+ [cliDependency.name]: cliDependency.spec,
136
+ ...generatorDependencies
137
+ }
138
+ };
139
+ fs.writeFileSync(path.join(projectRoot, "package.json"), `${JSON.stringify(pkg, null, 2)}\n`, "utf8");
140
+ writeProjectNpmConfig(projectRoot, cliDependency);
141
+ }
142
+
143
+ /**
144
+ * @param {string} projectRoot
145
+ * @returns {void}
146
+ */
147
+ export function writeExplainScript(projectRoot) {
148
+ const scriptDir = path.join(projectRoot, "scripts");
149
+ fs.mkdirSync(scriptDir, { recursive: true });
150
+ const script = `const message = \`
151
+ Topogram app workflow
152
+
153
+ 1. Edit:
154
+ topo/
155
+ topogram.project.json
156
+
157
+ 2. Start with project guidance:
158
+ npm run agent:brief
159
+
160
+ 3. Validate:
161
+ npm run doctor
162
+ npm run source:status
163
+ npm run template:explain
164
+ npm run check
165
+
166
+ 4. Regenerate:
167
+ npm run generate
168
+
169
+ 5. Verify generated app:
170
+ npm run verify
171
+
172
+ 6. Run locally:
173
+ npm run bootstrap
174
+ npm run dev
175
+
176
+ 7. Probe the running app from another terminal:
177
+ npm run app:probe
178
+
179
+ Or run self-contained local runtime verification:
180
+ npm run app:runtime
181
+
182
+ Useful inspection:
183
+ npm run agent:brief
184
+ npm run check:json
185
+ topogram emit ui-widget-contract ./topo --json
186
+ topogram emit widget-conformance-report ./topo --json
187
+ npm run doctor
188
+ npm run source:status
189
+ npm run source:status:remote
190
+ npm run template:explain
191
+ npm run template:status
192
+ npm run template:detach:dry-run
193
+ npm run template:policy:check
194
+ npm run template:policy:explain
195
+ npm run generator:policy:status
196
+ npm run generator:policy:check
197
+ npm run generator:policy:explain
198
+ npm run template:update:status
199
+ npm run template:update:recommend
200
+ npm run template:update:plan
201
+ npm run template:update:check
202
+ npm run template:update:apply
203
+ npm run trust:status
204
+ npm run trust:diff
205
+ \`;
206
+
207
+ console.log(message.trimEnd());
208
+ `;
209
+ fs.writeFileSync(path.join(scriptDir, "explain.mjs"), script, "utf8");
210
+ }
211
+
212
+ /**
213
+ * @param {string} projectRoot
214
+ * @param {Record<string, any>} projectConfig
215
+ * @returns {void}
216
+ */
217
+ export function writeProjectReadme(projectRoot, projectConfig) {
218
+ const template = projectConfig.template || {};
219
+ const templateName = template.id || "unknown";
220
+ const workflowCommands = [
221
+ "npm install",
222
+ "npm run explain",
223
+ "npm run agent:brief",
224
+ "npm run doctor",
225
+ "npm run source:status",
226
+ "npm run template:explain",
227
+ "npm run check",
228
+ "npm run template:policy:check",
229
+ "npm run generator:policy:status",
230
+ "npm run generator:policy:check",
231
+ ...(template.includesExecutableImplementation ? [
232
+ "npm run template:policy:explain",
233
+ "npm run trust:status"
234
+ ] : []),
235
+ "npm run generate",
236
+ "npm run verify"
237
+ ];
238
+ const provenanceLines = [];
239
+ provenanceLines.push(`- Template: \`${templateName}@${template.version || "unknown"}\``);
240
+ provenanceLines.push(`- Source: \`${template.source || "unknown"}\``);
241
+ if (template.sourceSpec) {
242
+ provenanceLines.push(`- Source spec: \`${template.sourceSpec}\``);
243
+ }
244
+ if (template.catalog) {
245
+ provenanceLines.push(`- Catalog: \`${template.catalog.id}\` from \`${template.catalog.source}\``);
246
+ provenanceLines.push(`- Package: \`${template.catalog.packageSpec}\``);
247
+ }
248
+ provenanceLines.push(`- Executable implementation: \`${template.includesExecutableImplementation ? "yes" : "no"}\``);
249
+ const readme = `# ${packageNameFromPath(projectRoot)}
250
+
251
+ Generated by \`topogram new\`.
252
+
253
+ ## Template
254
+
255
+ ${provenanceLines.join("\n")}
256
+
257
+ ## Workflow
258
+
259
+ \`\`\`bash
260
+ ${workflowCommands.join("\n")}
261
+ \`\`\`
262
+
263
+ Edit the workspace folder \`${DEFAULT_TOPO_FOLDER_NAME}/\` and \`topogram.project.json\`, then regenerate with \`npm run generate\`.
264
+ Generated app code is written to \`app/\`.
265
+ Use \`topogram emit <target>\` to inspect contracts, reports, snapshots, and other artifacts without regenerating the app.
266
+ Agents should start with \`AGENTS.md\` and \`npm run agent:brief\`. The direct \`topogram agent brief --json\` command is the canonical machine-readable first-run guidance.
267
+ ${template.includesExecutableImplementation ? "\nThis template copied `implementation/` code. `topogram new` did not execute it; review `implementation/`, `topogram.template-policy.json`, and `.topogram-template-trust.json` before regenerating after edits.\n" : ""}
268
+ `;
269
+ fs.writeFileSync(path.join(projectRoot, "README.md"), readme, "utf8");
270
+ }
271
+
272
+ /**
273
+ * @param {string} projectRoot
274
+ * @param {Record<string, any>} projectConfig
275
+ * @returns {void}
276
+ */
277
+ export function writeAgentsGuide(projectRoot, projectConfig) {
278
+ const template = projectConfig.template || {};
279
+ const hasImplementation = Boolean(projectConfig.implementation || template.includesExecutableImplementation);
280
+ const guide = `# Agent Guide
281
+
282
+ Start here before editing this Topogram project.
283
+
284
+ ## First Read
285
+
286
+ 1. \`AGENTS.md\`
287
+ 2. \`README.md\`
288
+ 3. \`topogram.project.json\`
289
+ 4. \`topogram.template-policy.json\`
290
+ 5. \`topogram.generator-policy.json\`
291
+ ${hasImplementation ? "6. `.topogram-template-trust.json`\n7. `implementation/`\n8. Focused `topogram query ...` output\n" : "6. Focused `topogram query ...` output\n"}
292
+ Machine-readable source:
293
+
294
+ \`\`\`bash
295
+ topogram agent brief --json
296
+ \`\`\`
297
+
298
+ Local shortcut:
299
+
300
+ \`\`\`bash
301
+ npm run agent:brief
302
+ \`\`\`
303
+
304
+ Reference: https://github.com/${githubRepoSlug(null)}/blob/main/docs/agent-first-run.md
305
+
306
+ ## First Commands
307
+
308
+ \`\`\`bash
309
+ npm run agent:brief
310
+ npm run doctor
311
+ npm run source:status
312
+ npm run template:explain
313
+ npm run generator:policy:check
314
+ ${hasImplementation ? "npm run trust:status\n" : ""}npm run check
315
+ npm run query:list
316
+ npm run query:show -- widget-behavior
317
+ \`\`\`
318
+
319
+ ## Edit Rules
320
+
321
+ - Edit \`topo/**\` and \`topogram.project.json\` first.
322
+ - Review policy files before editing \`topogram.template-policy.json\` or \`topogram.generator-policy.json\`.
323
+ - Do not make lasting edits under generated-owned \`app/**\`; use \`npm run generate\` to replace generated output.
324
+ - If an output is changed to maintained ownership, agents may edit that app code directly after reading focused query packets.
325
+
326
+ ## UI And Widgets
327
+
328
+ - \`ui_contract\` owns screens, regions, widget bindings, behavior, visibility, and semantic design tokens.
329
+ - Web/iOS/Android surfaces realize the shared UI contract; they do not own widget placement.
330
+ - Use \`topogram widget check --json\`, \`topogram widget behavior --json\`, and focused \`topogram query ...\` packets after UI edits.
331
+
332
+ ## Template And Trust
333
+
334
+ - Local edits to template-derived Topogram files are project-owned.
335
+ - Use \`npm run source:status\` and \`npm run template:update:recommend\` before applying template updates.
336
+ ${hasImplementation ? "- This project has executable `implementation/` code. `topogram new` did not execute it. Do not refresh trust until the implementation has been reviewed.\n" : "- This template does not declare executable implementation code.\n"}
337
+ ## Import And Adoption
338
+
339
+ - If \`.topogram-import.json\` exists, run \`topogram import check .\`, \`topogram import plan .\`, \`topogram import adopt --list .\`, and \`topogram import history . --verify\`.
340
+ - Imported Topogram files are project-owned after adoption; source hashes record trusted import evidence at the time of import.
341
+
342
+ ## Verification Gates
343
+
344
+ \`\`\`bash
345
+ npm run check
346
+ npm run generate
347
+ npm run verify
348
+ \`\`\`
349
+ `;
350
+ fs.writeFileSync(path.join(projectRoot, "AGENTS.md"), guide, "utf8");
351
+ }
@@ -0,0 +1,269 @@
1
+ // @ts-check
2
+
3
+ import fs from "node:fs";
4
+ import path from "node:path";
5
+
6
+ import { TEMPLATE_POLICY_FILE } from "./constants.js";
7
+ import { stableJsonStringify } from "./json.js";
8
+ import { currentTemplateMetadata } from "./metadata.js";
9
+ import { packageScopeFromSpec } from "./package-spec.js";
10
+
11
+ /** @typedef {import("./types.js").CreateNewProjectOptions} CreateNewProjectOptions */
12
+ /** @typedef {import("./types.js").TemplateUpdatePlanOptions} TemplateUpdatePlanOptions */
13
+ /** @typedef {import("./types.js").TemplateUpdateFileActionOptions} TemplateUpdateFileActionOptions */
14
+ /** @typedef {import("./types.js").TemplateOwnedFileRecord} TemplateOwnedFileRecord */
15
+ /** @typedef {import("./types.js").TemplateManifest} TemplateManifest */
16
+ /** @typedef {import("./types.js").TemplateTopologySummary} TemplateTopologySummary */
17
+ /** @typedef {import("./types.js").TemplatePolicy} TemplatePolicy */
18
+ /** @typedef {import("./types.js").TemplatePolicyInfo} TemplatePolicyInfo */
19
+ /** @typedef {import("./types.js").TemplateUpdateDiagnostic} TemplateUpdateDiagnostic */
20
+ /** @typedef {import("./types.js").ResolvedTemplate} ResolvedTemplate */
21
+ /** @typedef {import("./types.js").CatalogTemplateProvenance} CatalogTemplateProvenance */
22
+
23
+ /**
24
+ * @param {Record<string, any>} input
25
+ * @returns {TemplateUpdateDiagnostic}
26
+ */
27
+ export function templateUpdateDiagnostic(input) {
28
+ return {
29
+ code: String(input.code || "template_update_failed"),
30
+ severity: input.severity === "warning" ? "warning" : "error",
31
+ message: String(input.message || "Template update failed."),
32
+ path: typeof input.path === "string" ? input.path : null,
33
+ suggestedFix: typeof input.suggestedFix === "string" ? input.suggestedFix : null,
34
+ step: typeof input.step === "string" ? input.step : null
35
+ };
36
+ }
37
+
38
+ /**
39
+ * @param {unknown} value
40
+ * @param {string} policyPath
41
+ * @returns {TemplatePolicy}
42
+ */
43
+ function validateTemplatePolicy(value, policyPath) {
44
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
45
+ throw new Error(`${TEMPLATE_POLICY_FILE} must contain a JSON object.`);
46
+ }
47
+ const policy = /** @type {Record<string, unknown>} */ (value);
48
+ const version = typeof policy.version === "string" && policy.version ? policy.version : "0.1";
49
+ const allowedSources = Array.isArray(policy.allowedSources) ? policy.allowedSources : ["local", "package"];
50
+ const invalidSource = allowedSources.find((source) => !["local", "package"].includes(String(source)));
51
+ if (invalidSource) {
52
+ throw new Error(`${policyPath} has invalid allowedSources value '${String(invalidSource)}'.`);
53
+ }
54
+ const allowedTemplateIds = Array.isArray(policy.allowedTemplateIds)
55
+ ? policy.allowedTemplateIds.map(String).filter(Boolean)
56
+ : [];
57
+ const allowedPackageScopes = Array.isArray(policy.allowedPackageScopes)
58
+ ? policy.allowedPackageScopes.map(String).filter(Boolean)
59
+ : [];
60
+ const executableImplementation = policy.executableImplementation === "deny" || policy.executableImplementation === "warn"
61
+ ? policy.executableImplementation
62
+ : "allow";
63
+ const pinnedVersions = policy.pinnedVersions && typeof policy.pinnedVersions === "object" && !Array.isArray(policy.pinnedVersions)
64
+ ? Object.fromEntries(Object.entries(policy.pinnedVersions).filter(([, pin]) => typeof pin === "string"))
65
+ : {};
66
+ return {
67
+ version,
68
+ allowedSources: /** @type {Array<"local"|"package">} */ (allowedSources),
69
+ allowedTemplateIds,
70
+ allowedPackageScopes,
71
+ executableImplementation,
72
+ pinnedVersions
73
+ };
74
+ }
75
+
76
+ /**
77
+ * @param {string} projectRoot
78
+ * @returns {TemplatePolicyInfo}
79
+ */
80
+ export function loadTemplatePolicy(projectRoot) {
81
+ const policyPath = path.join(projectRoot, TEMPLATE_POLICY_FILE);
82
+ if (!fs.existsSync(policyPath)) {
83
+ return {
84
+ path: policyPath,
85
+ policy: null,
86
+ exists: false,
87
+ diagnostics: []
88
+ };
89
+ }
90
+ try {
91
+ return {
92
+ path: policyPath,
93
+ policy: validateTemplatePolicy(JSON.parse(fs.readFileSync(policyPath, "utf8")), policyPath),
94
+ exists: true,
95
+ diagnostics: []
96
+ };
97
+ } catch (error) {
98
+ return {
99
+ path: policyPath,
100
+ policy: null,
101
+ exists: true,
102
+ diagnostics: [templateUpdateDiagnostic({
103
+ code: "template_policy_invalid",
104
+ message: error instanceof Error ? error.message : String(error),
105
+ path: policyPath,
106
+ suggestedFix: "Fix topogram.template-policy.json or regenerate it with `topogram template policy init`.",
107
+ step: "policy"
108
+ })]
109
+ };
110
+ }
111
+ }
112
+
113
+ /**
114
+ * @param {ResolvedTemplate} template
115
+ * @returns {TemplatePolicy}
116
+ */
117
+ export function defaultTemplatePolicyForTemplate(template) {
118
+ const allowedPackageScopes = [];
119
+ const idScope = template.source === "package"
120
+ ? packageScopeFromSpec(template.packageSpec || template.requested)
121
+ : null;
122
+ if (template.source === "package" && idScope) {
123
+ allowedPackageScopes.push(idScope);
124
+ }
125
+ return {
126
+ version: "0.1",
127
+ allowedSources: ["local", "package"],
128
+ allowedTemplateIds: [template.manifest.id],
129
+ allowedPackageScopes,
130
+ executableImplementation: "allow",
131
+ pinnedVersions: {}
132
+ };
133
+ }
134
+
135
+ /**
136
+ * @param {string} projectRoot
137
+ * @param {TemplatePolicy} policy
138
+ * @returns {TemplatePolicy}
139
+ */
140
+ export function writeTemplatePolicy(projectRoot, policy) {
141
+ fs.writeFileSync(path.join(projectRoot, TEMPLATE_POLICY_FILE), `${stableJsonStringify(policy)}\n`, "utf8");
142
+ return policy;
143
+ }
144
+
145
+ /**
146
+ * @param {string} projectRoot
147
+ * @param {Record<string, any>} projectConfig
148
+ * @returns {TemplatePolicy}
149
+ */
150
+ export function writeTemplatePolicyForProject(projectRoot, projectConfig) {
151
+ const current = currentTemplateMetadata(projectConfig);
152
+ /** @type {string[]} */
153
+ const allowedPackageScopes = [];
154
+ if (current.source === "package") {
155
+ const currentScope = packageScopeFromSpec(current.sourceSpec) ||
156
+ (current.id?.startsWith("@") ? current.id.split("/")[0] : null);
157
+ if (currentScope) {
158
+ allowedPackageScopes.push(currentScope);
159
+ }
160
+ }
161
+ return writeTemplatePolicy(projectRoot, {
162
+ version: "0.1",
163
+ allowedSources: ["local", "package"],
164
+ allowedTemplateIds: current.id ? [current.id] : [],
165
+ allowedPackageScopes,
166
+ executableImplementation: "allow",
167
+ pinnedVersions: {}
168
+ });
169
+ }
170
+
171
+ /**
172
+ * @param {TemplatePolicyInfo} policyInfo
173
+ * @param {ResolvedTemplate} template
174
+ * @param {string} step
175
+ * @returns {TemplateUpdateDiagnostic[]}
176
+ */
177
+ export function templatePolicyDiagnosticsForTemplate(policyInfo, template, step) {
178
+ if (policyInfo.diagnostics.length > 0) {
179
+ return policyInfo.diagnostics;
180
+ }
181
+ if (!policyInfo.policy) {
182
+ return [];
183
+ }
184
+ const policy = policyInfo.policy;
185
+ /** @type {TemplateUpdateDiagnostic[]} */
186
+ const diagnostics = [];
187
+ if (policy.allowedSources.length > 0 && !policy.allowedSources.includes(template.source)) {
188
+ diagnostics.push(templateUpdateDiagnostic({
189
+ code: "template_source_denied",
190
+ message: `Template source '${template.source}' is not allowed by ${TEMPLATE_POLICY_FILE}.`,
191
+ path: policyInfo.path,
192
+ suggestedFix: `Run \`topogram template policy init\` to reset from the current project, or add '${template.source}' to allowedSources after review.`,
193
+ step
194
+ }));
195
+ }
196
+ if (policy.allowedTemplateIds.length > 0 && !policy.allowedTemplateIds.includes(template.manifest.id)) {
197
+ diagnostics.push(templateUpdateDiagnostic({
198
+ code: "template_id_denied",
199
+ message: `Template '${template.manifest.id}' is not allowed by ${TEMPLATE_POLICY_FILE}.`,
200
+ path: policyInfo.path,
201
+ suggestedFix: `Run \`topogram template policy pin ${template.manifest.id}@${template.manifest.version}\` after review, or choose an allowed template.`,
202
+ step
203
+ }));
204
+ }
205
+ if (template.source === "package" && policy.allowedPackageScopes && policy.allowedPackageScopes.length > 0) {
206
+ const scope = packageScopeFromSpec(template.packageSpec || template.requested) ||
207
+ (template.manifest.id.startsWith("@") ? template.manifest.id.split("/")[0] : null);
208
+ if (!scope || !policy.allowedPackageScopes.includes(scope)) {
209
+ diagnostics.push(templateUpdateDiagnostic({
210
+ code: "template_package_scope_denied",
211
+ message: `Template package scope '${scope || "(unscoped)"}' is not allowed by ${TEMPLATE_POLICY_FILE}.`,
212
+ path: policyInfo.path,
213
+ suggestedFix: `Add '${scope || "(unscoped)"}' to allowedPackageScopes after review, or choose a package from an allowed scope.`,
214
+ step
215
+ }));
216
+ }
217
+ }
218
+ const pinnedVersion = policy.pinnedVersions?.[template.manifest.id];
219
+ if (pinnedVersion && pinnedVersion !== template.manifest.version) {
220
+ diagnostics.push(templateUpdateDiagnostic({
221
+ code: "template_version_mismatch",
222
+ message: `Template '${template.manifest.id}' is pinned to version '${pinnedVersion}', but candidate version is '${template.manifest.version}'.`,
223
+ path: policyInfo.path,
224
+ suggestedFix: `Run \`topogram template policy pin ${template.manifest.id}@${template.manifest.version}\` after review, or use version '${pinnedVersion}'.`,
225
+ step
226
+ }));
227
+ }
228
+ if (template.manifest.includesExecutableImplementation) {
229
+ if (policy.executableImplementation === "deny") {
230
+ diagnostics.push(templateUpdateDiagnostic({
231
+ code: "template_executable_denied",
232
+ message: `Template '${template.manifest.id}' includes executable implementation code, which is denied by ${TEMPLATE_POLICY_FILE}.`,
233
+ path: policyInfo.path,
234
+ suggestedFix: "Use a non-executable template, or set executableImplementation to 'allow' after reviewing implementation/.",
235
+ step
236
+ }));
237
+ } else if (policy.executableImplementation === "warn") {
238
+ diagnostics.push(templateUpdateDiagnostic({
239
+ code: "template_executable_warning",
240
+ severity: "warning",
241
+ message: `Template '${template.manifest.id}' includes executable implementation code.`,
242
+ path: policyInfo.path,
243
+ suggestedFix: "Review implementation/ before running topogram generate.",
244
+ step
245
+ }));
246
+ }
247
+ }
248
+ return diagnostics;
249
+ }
250
+
251
+ /**
252
+ * @param {string} projectRoot
253
+ * @param {ResolvedTemplate} template
254
+ * @param {string} step
255
+ * @returns {TemplateUpdateDiagnostic[]}
256
+ */
257
+ export function templatePolicyDiagnosticsForProject(projectRoot, template, step) {
258
+ return templatePolicyDiagnosticsForTemplate(loadTemplatePolicy(projectRoot), template, step);
259
+ }
260
+
261
+ /**
262
+ * @param {TemplateUpdateDiagnostic[]} diagnostics
263
+ * @returns {string[]}
264
+ */
265
+ export function issueMessagesFromDiagnostics(diagnostics) {
266
+ return diagnostics
267
+ .filter((diagnostic) => diagnostic.severity === "error")
268
+ .map((diagnostic) => diagnostic.message);
269
+ }