@topogram/cli 0.3.63 → 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 (344) hide show
  1. package/package.json +1 -1
  2. package/src/adoption/plan/index.js +703 -0
  3. package/src/adoption/plan.d.ts +6 -0
  4. package/src/adoption/plan.js +12 -703
  5. package/src/adoption/reporting.d.ts +10 -0
  6. package/src/adoption/review-groups.d.ts +6 -0
  7. package/src/agent-brief.d.ts +3 -0
  8. package/src/agent-brief.js +495 -0
  9. package/src/agent-ops/query-builders/auth.js +375 -0
  10. package/src/agent-ops/query-builders/change-risk/change-plan.js +123 -0
  11. package/src/agent-ops/query-builders/change-risk/import-plan.js +49 -0
  12. package/src/agent-ops/query-builders/change-risk/maintained.js +286 -0
  13. package/src/agent-ops/query-builders/change-risk/review-packets.js +123 -0
  14. package/src/agent-ops/query-builders/change-risk/risk.js +189 -0
  15. package/src/agent-ops/query-builders/change-risk.js +25 -0
  16. package/src/agent-ops/query-builders/common.js +149 -0
  17. package/src/agent-ops/query-builders/maintained-risk.js +539 -0
  18. package/src/agent-ops/query-builders/maintained-shared.js +120 -0
  19. package/src/agent-ops/query-builders/multi-agent.js +547 -0
  20. package/src/agent-ops/query-builders/projection-impacts.js +514 -0
  21. package/src/agent-ops/query-builders/work-packets.js +417 -0
  22. package/src/agent-ops/query-builders/workflow-context-shared.js +300 -0
  23. package/src/agent-ops/query-builders/workflow-context.js +398 -0
  24. package/src/agent-ops/query-builders/workflow-presets-core.js +676 -0
  25. package/src/agent-ops/query-builders/workflow-presets.js +341 -0
  26. package/src/agent-ops/query-builders.d.ts +26 -0
  27. package/src/agent-ops/query-builders.js +42 -5021
  28. package/src/archive/archive.d.ts +2 -0
  29. package/src/archive/compact.d.ts +1 -0
  30. package/src/archive/unarchive.d.ts +1 -0
  31. package/src/catalog/constants.js +10 -0
  32. package/src/catalog/copy.js +60 -0
  33. package/src/catalog/diagnostics.js +15 -0
  34. package/src/catalog/entries.js +42 -0
  35. package/src/catalog/files.js +67 -0
  36. package/src/catalog/provenance.js +122 -0
  37. package/src/catalog/source.js +150 -0
  38. package/src/catalog/validation.js +252 -0
  39. package/src/catalog.d.ts +12 -0
  40. package/src/catalog.js +18 -750
  41. package/src/cli/catalog-alias.d.ts +1 -0
  42. package/src/cli/command-parser.js +38 -0
  43. package/src/cli/command-parsers/core.js +102 -0
  44. package/src/cli/command-parsers/generator.js +39 -0
  45. package/src/cli/command-parsers/import.js +44 -0
  46. package/src/cli/command-parsers/legacy-workflow.js +21 -0
  47. package/src/cli/command-parsers/project.js +47 -0
  48. package/src/cli/command-parsers/sdlc.js +47 -0
  49. package/src/cli/command-parsers/shared.js +51 -0
  50. package/src/cli/command-parsers/template.js +48 -0
  51. package/src/cli/commands/agent.js +47 -0
  52. package/src/cli/commands/catalog/check.js +31 -0
  53. package/src/cli/commands/catalog/copy.js +59 -0
  54. package/src/cli/commands/catalog/doctor.js +248 -0
  55. package/src/cli/commands/catalog/help.js +21 -0
  56. package/src/cli/commands/catalog/list.js +52 -0
  57. package/src/cli/commands/catalog/runner.js +92 -0
  58. package/src/cli/commands/catalog/shared.js +17 -0
  59. package/src/cli/commands/catalog/show.js +134 -0
  60. package/src/cli/commands/catalog.js +32 -0
  61. package/src/cli/commands/check.js +268 -0
  62. package/src/cli/commands/doctor.js +268 -0
  63. package/src/cli/commands/emit.js +149 -0
  64. package/src/cli/commands/generate.js +96 -0
  65. package/src/cli/commands/generator-policy/package-info.js +162 -0
  66. package/src/cli/commands/generator-policy/payloads.js +372 -0
  67. package/src/cli/commands/generator-policy/printers.js +159 -0
  68. package/src/cli/commands/generator-policy/runner.js +81 -0
  69. package/src/cli/commands/generator-policy/shared.js +39 -0
  70. package/src/cli/commands/generator-policy.js +17 -0
  71. package/src/cli/commands/generator.js +443 -0
  72. package/src/cli/commands/import/adopt.js +170 -0
  73. package/src/cli/commands/import/check.js +91 -0
  74. package/src/cli/commands/import/diff.js +84 -0
  75. package/src/cli/commands/import/help.js +47 -0
  76. package/src/cli/commands/import/paths.js +277 -0
  77. package/src/cli/commands/import/plan.js +284 -0
  78. package/src/cli/commands/import/refresh.js +470 -0
  79. package/src/cli/commands/import/status-history.js +196 -0
  80. package/src/cli/commands/import/workspace.js +230 -0
  81. package/src/cli/commands/import-runner.js +157 -0
  82. package/src/cli/commands/import.js +35 -0
  83. package/src/cli/commands/inspect.js +55 -0
  84. package/src/cli/commands/new.js +94 -0
  85. package/src/cli/commands/package/constants.js +17 -0
  86. package/src/cli/commands/package/doctor.js +240 -0
  87. package/src/cli/commands/package/help.js +27 -0
  88. package/src/cli/commands/package/lockfile.js +135 -0
  89. package/src/cli/commands/package/npm.js +97 -0
  90. package/src/cli/commands/package/reporting.js +35 -0
  91. package/src/cli/commands/package/runner.js +33 -0
  92. package/src/cli/commands/package/shared.js +9 -0
  93. package/src/cli/commands/package/update-cli.js +252 -0
  94. package/src/cli/commands/package/versions.js +35 -0
  95. package/src/cli/commands/package.js +31 -0
  96. package/src/cli/commands/query/change-plan.js +68 -0
  97. package/src/cli/commands/query/definitions.js +202 -0
  98. package/src/cli/commands/query/import-adopt.js +121 -0
  99. package/src/cli/commands/query/runner/artifacts.js +102 -0
  100. package/src/cli/commands/query/runner/boundaries.js +211 -0
  101. package/src/cli/commands/query/runner/change.js +182 -0
  102. package/src/cli/commands/query/runner/import-adopt.js +111 -0
  103. package/src/cli/commands/query/runner/index.js +31 -0
  104. package/src/cli/commands/query/runner/output.js +12 -0
  105. package/src/cli/commands/query/runner/workflow.js +241 -0
  106. package/src/cli/commands/query/runner.js +3 -0
  107. package/src/cli/commands/query/workflow-context.js +5 -0
  108. package/src/cli/commands/query/workspace.js +274 -0
  109. package/src/cli/commands/query.js +11 -0
  110. package/src/cli/commands/release-rollout.js +257 -0
  111. package/src/cli/commands/release-shared.js +528 -0
  112. package/src/cli/commands/release-status.js +429 -0
  113. package/src/cli/commands/release.js +107 -0
  114. package/src/cli/commands/sdlc.js +168 -0
  115. package/src/cli/commands/setup.js +76 -0
  116. package/src/cli/commands/source.js +291 -0
  117. package/src/cli/commands/template/baseline.js +100 -0
  118. package/src/cli/commands/template/check.js +466 -0
  119. package/src/cli/commands/template/constants.js +8 -0
  120. package/src/cli/commands/template/diagnostics.js +26 -0
  121. package/src/cli/commands/template/help.js +28 -0
  122. package/src/cli/commands/template/lifecycle.js +404 -0
  123. package/src/cli/commands/template/list-show.js +287 -0
  124. package/src/cli/commands/template/policy.js +422 -0
  125. package/src/cli/commands/template/shared.js +127 -0
  126. package/src/cli/commands/template/updates.js +352 -0
  127. package/src/cli/commands/template-runner.js +198 -0
  128. package/src/cli/commands/template.js +43 -0
  129. package/src/cli/commands/trust.js +219 -0
  130. package/src/cli/commands/version.js +40 -0
  131. package/src/cli/commands/widget.js +168 -0
  132. package/src/cli/commands/workflow.js +63 -0
  133. package/src/cli/dispatcher.js +392 -0
  134. package/src/cli/help-dispatch.js +188 -0
  135. package/src/cli/help.js +296 -0
  136. package/src/cli/migration-guidance.js +59 -0
  137. package/src/cli/options.js +96 -0
  138. package/src/cli/output-safety.js +107 -0
  139. package/src/cli/path-normalization.js +29 -0
  140. package/src/cli.js +47 -11711
  141. package/src/example-implementation.d.ts +2 -0
  142. package/src/format.d.ts +1 -0
  143. package/src/generator/api/contracts.js +497 -0
  144. package/src/generator/api/metadata.js +221 -0
  145. package/src/generator/api/openapi.js +559 -0
  146. package/src/generator/api/schema.js +124 -0
  147. package/src/generator/api/types.d.ts +98 -0
  148. package/src/generator/api.js +3 -1195
  149. package/src/generator/check.d.ts +1 -0
  150. package/src/generator/context/bundle.d.ts +1 -0
  151. package/src/generator/context/shared/domain-sdlc.js +282 -0
  152. package/src/generator/context/shared/maintained-boundary.js +665 -0
  153. package/src/generator/context/shared/metrics.js +85 -0
  154. package/src/generator/context/shared/primitives.js +64 -0
  155. package/src/generator/context/shared/relationships.js +453 -0
  156. package/src/generator/context/shared/summaries.js +263 -0
  157. package/src/generator/context/shared/types.d.ts +207 -0
  158. package/src/generator/context/shared.d.ts +44 -0
  159. package/src/generator/context/shared.js +80 -1390
  160. package/src/generator/context/slice/core.js +397 -0
  161. package/src/generator/context/slice/sdlc.js +417 -0
  162. package/src/generator/context/slice/ui-packets.js +183 -0
  163. package/src/generator/context/slice.js +2 -859
  164. package/src/generator/native/parity-bundle.js +2 -1
  165. package/src/generator/registry/index.js +507 -0
  166. package/src/generator/registry.js +18 -504
  167. package/src/generator/runtime/environment/index.js +666 -0
  168. package/src/generator/runtime/environment.js +4 -666
  169. package/src/generator/runtime/runtime-check/index.js +554 -0
  170. package/src/generator/runtime/runtime-check.js +4 -554
  171. package/src/generator/runtime/shared/index.js +572 -0
  172. package/src/generator/runtime/shared.js +19 -570
  173. package/src/generator/shared.d.ts +2 -0
  174. package/src/generator/surfaces/shared.d.ts +3 -0
  175. package/src/generator/surfaces/web/html-escape.js +22 -0
  176. package/src/generator/surfaces/web/react.js +10 -8
  177. package/src/generator/surfaces/web/sveltekit.js +7 -5
  178. package/src/generator/surfaces/web/vanilla.js +8 -4
  179. package/src/generator/widget-conformance/behavior-report.js +258 -0
  180. package/src/generator/widget-conformance/checks.js +371 -0
  181. package/src/generator/widget-conformance/projection-context.js +200 -0
  182. package/src/generator/widget-conformance/report.js +166 -0
  183. package/src/generator/widget-conformance/types.d.ts +121 -0
  184. package/src/generator/widget-conformance.js +3 -824
  185. package/src/generator.d.ts +2 -0
  186. package/src/github-client.js +520 -0
  187. package/src/import/core/context.d.ts +3 -0
  188. package/src/import/core/contracts.d.ts +1 -0
  189. package/src/import/core/registry.d.ts +4 -0
  190. package/src/import/core/runner/candidates.js +217 -0
  191. package/src/import/core/runner/options.js +22 -0
  192. package/src/import/core/runner/reports.js +50 -0
  193. package/src/import/core/runner/run.js +79 -0
  194. package/src/import/core/runner/tracks.js +150 -0
  195. package/src/import/core/runner/ui-drafts.js +337 -0
  196. package/src/import/core/runner.js +3 -698
  197. package/src/import/core/shared/api-routes.js +221 -0
  198. package/src/import/core/shared/candidates.js +97 -0
  199. package/src/import/core/shared/files.js +177 -0
  200. package/src/import/core/shared/next-app.js +389 -0
  201. package/src/import/core/shared/types.d.ts +51 -0
  202. package/src/import/core/shared/ui-routes.js +230 -0
  203. package/src/import/core/shared.js +67 -910
  204. package/src/import/extractors/api/flutter-dio.js +4 -8
  205. package/src/import/extractors/api/react-native-repository.js +4 -8
  206. package/src/import/index.d.ts +4 -0
  207. package/src/import/provenance.d.ts +4 -0
  208. package/src/new-project/constants.js +128 -0
  209. package/src/new-project/create.js +83 -0
  210. package/src/new-project/json.js +28 -0
  211. package/src/new-project/metadata.js +96 -0
  212. package/src/new-project/package-spec.js +161 -0
  213. package/src/new-project/project-files.js +348 -0
  214. package/src/new-project/template-policy.js +269 -0
  215. package/src/new-project/template-resolution.js +368 -0
  216. package/src/new-project/template-snapshots.js +430 -0
  217. package/src/new-project/template-updates.js +512 -0
  218. package/src/new-project/types.d.ts +83 -0
  219. package/src/new-project.js +6 -2188
  220. package/src/npm-safety.js +79 -0
  221. package/src/parser.d.ts +87 -0
  222. package/src/parser.js +118 -0
  223. package/src/path-helpers.d.ts +1 -0
  224. package/src/path-helpers.js +20 -0
  225. package/src/policy/review-boundaries.d.ts +15 -0
  226. package/src/project-config/index.js +564 -0
  227. package/src/project-config.js +19 -560
  228. package/src/reconcile/docs.d.ts +8 -0
  229. package/src/reconcile/journeys.d.ts +1 -0
  230. package/src/resolver/enrich/acceptance-criterion.js +2 -0
  231. package/src/resolver/enrich/bug.js +2 -0
  232. package/src/resolver/enrich/pitch.js +2 -0
  233. package/src/resolver/enrich/requirement.js +2 -0
  234. package/src/resolver/enrich/task.js +2 -0
  235. package/src/resolver/index.js +19 -2089
  236. package/src/resolver/normalize.js +384 -1
  237. package/src/resolver/plans.js +168 -0
  238. package/src/resolver/projections-api.js +494 -0
  239. package/src/resolver/projections-db.js +133 -0
  240. package/src/resolver/projections-ui.js +317 -0
  241. package/src/resolver/shapes.js +251 -0
  242. package/src/resolver/shared.js +278 -0
  243. package/src/resolver/widgets.js +132 -0
  244. package/src/resolver.d.ts +1 -0
  245. package/src/runtime-support.js +29 -0
  246. package/src/sdlc/adopt.d.ts +1 -0
  247. package/src/sdlc/check.d.ts +1 -0
  248. package/src/sdlc/explain.d.ts +1 -0
  249. package/src/sdlc/release.d.ts +1 -0
  250. package/src/sdlc/scaffold.d.ts +1 -0
  251. package/src/sdlc/transition.d.ts +1 -0
  252. package/src/template-trust/constants.js +62 -0
  253. package/src/template-trust/content.js +258 -0
  254. package/src/template-trust/diff.js +92 -0
  255. package/src/template-trust/policy.js +61 -0
  256. package/src/template-trust/record.js +90 -0
  257. package/src/template-trust/status.js +182 -0
  258. package/src/template-trust.js +24 -687
  259. package/src/text-helpers.d.ts +7 -0
  260. package/src/text-helpers.js +245 -0
  261. package/src/topogram-config.js +306 -0
  262. package/src/topogram-types.d.ts +69 -0
  263. package/src/validator/common.js +488 -0
  264. package/src/validator/data-model.js +237 -0
  265. package/src/validator/docs.js +167 -0
  266. package/src/validator/expressions.js +146 -1
  267. package/src/validator/index.d.ts +23 -0
  268. package/src/validator/index.js +32 -3585
  269. package/src/validator/kinds.d.ts +41 -0
  270. package/src/validator/kinds.js +2 -0
  271. package/src/validator/model-helpers.js +46 -0
  272. package/src/validator/per-kind/acceptance-criterion.js +5 -0
  273. package/src/validator/per-kind/bug.js +6 -0
  274. package/src/validator/per-kind/domain.js +15 -2
  275. package/src/validator/per-kind/pitch.js +7 -0
  276. package/src/validator/per-kind/requirement.js +5 -0
  277. package/src/validator/per-kind/task.js +7 -0
  278. package/src/validator/per-kind/widget.js +14 -0
  279. package/src/validator/projections/api-http-async.js +410 -0
  280. package/src/validator/projections/api-http-authz.js +88 -0
  281. package/src/validator/projections/api-http-core.js +205 -0
  282. package/src/validator/projections/api-http-policies.js +339 -0
  283. package/src/validator/projections/api-http-responses.js +233 -0
  284. package/src/validator/projections/api-http.js +44 -0
  285. package/src/validator/projections/db.js +353 -0
  286. package/src/validator/projections/generator-defaults.js +45 -0
  287. package/src/validator/projections/helpers.js +87 -0
  288. package/src/validator/projections/ui-helpers.js +214 -0
  289. package/src/validator/projections/ui-navigation.js +344 -0
  290. package/src/validator/projections/ui-structure.js +364 -0
  291. package/src/validator/projections/ui-widgets.js +493 -0
  292. package/src/validator/projections/ui.js +46 -0
  293. package/src/validator/registry.js +48 -1
  294. package/src/validator/utils.d.ts +20 -0
  295. package/src/validator/utils.js +115 -12
  296. package/src/validator.d.ts +2 -0
  297. package/src/widget-behavior.d.ts +1 -0
  298. package/src/workflows/adoption/index.js +26 -0
  299. package/src/workflows/docs-generate.js +262 -0
  300. package/src/workflows/docs-scan.js +703 -0
  301. package/src/workflows/docs.js +15 -0
  302. package/src/workflows/import-app/api/collect.js +221 -0
  303. package/src/workflows/import-app/api/openapi.js +257 -0
  304. package/src/workflows/import-app/api/routes.js +327 -0
  305. package/src/workflows/import-app/api/sources.js +22 -0
  306. package/src/workflows/import-app/api.js +4 -0
  307. package/src/workflows/import-app/db.js +538 -0
  308. package/src/workflows/import-app/index.js +30 -0
  309. package/src/workflows/import-app/shared.js +218 -0
  310. package/src/workflows/import-app/ui.js +443 -0
  311. package/src/workflows/import-app/workflow.js +159 -0
  312. package/src/workflows/reconcile/adoption-plan/build.js +208 -0
  313. package/src/workflows/reconcile/adoption-plan/dependencies.js +75 -0
  314. package/src/workflows/reconcile/adoption-plan/outputs.js +143 -0
  315. package/src/workflows/reconcile/adoption-plan/paths.js +58 -0
  316. package/src/workflows/reconcile/adoption-plan/projection-patches.js +177 -0
  317. package/src/workflows/reconcile/adoption-plan/reasons.js +107 -0
  318. package/src/workflows/reconcile/adoption-plan.js +32 -0
  319. package/src/workflows/reconcile/auth/closures.js +115 -0
  320. package/src/workflows/reconcile/auth/formatters.js +142 -0
  321. package/src/workflows/reconcile/auth/inference.js +330 -0
  322. package/src/workflows/reconcile/auth/roles.js +122 -0
  323. package/src/workflows/reconcile/auth.js +37 -0
  324. package/src/workflows/reconcile/bundle-core/index.js +600 -0
  325. package/src/workflows/reconcile/bundle-core.js +14 -0
  326. package/src/workflows/reconcile/bundle-shared.js +75 -0
  327. package/src/workflows/reconcile/candidate-model.js +477 -0
  328. package/src/workflows/reconcile/canonical-surface.js +264 -0
  329. package/src/workflows/reconcile/gap-report.js +333 -0
  330. package/src/workflows/reconcile/ids.js +6 -0
  331. package/src/workflows/reconcile/impacts/adoption-plan.js +192 -0
  332. package/src/workflows/reconcile/impacts/indexes.js +101 -0
  333. package/src/workflows/reconcile/impacts/patches.js +252 -0
  334. package/src/workflows/reconcile/impacts/reports.js +80 -0
  335. package/src/workflows/reconcile/impacts.js +16 -0
  336. package/src/workflows/reconcile/index.js +7 -0
  337. package/src/workflows/reconcile/renderers.js +461 -0
  338. package/src/workflows/reconcile/summary.js +90 -0
  339. package/src/workflows/reconcile/workflow.js +309 -0
  340. package/src/workflows/shared.js +189 -0
  341. package/src/workflows/types.d.ts +93 -0
  342. package/src/workflows.d.ts +1 -0
  343. package/src/workflows.js +10 -7652
  344. package/src/workspace-docs.d.ts +29 -0
@@ -1,689 +1,26 @@
1
1
  // @ts-check
2
2
 
3
- import fs from "node:fs";
4
- import crypto from "node:crypto";
5
- import path from "node:path";
6
-
7
- export const TEMPLATE_TRUST_FILE = ".topogram-template-trust.json";
8
- export const TEMPLATE_TRUST_POLICY = "topogram-template-executable-implementation-v1";
9
-
10
- /**
11
- * @typedef {Object} TemplateTrustRecord
12
- * @property {string} version
13
- * @property {string} trustPolicy
14
- * @property {string} trustedAt
15
- * @property {{ id: string|null, version: string|null, source: string|null, sourceSpec: string|null, requested: string|null, sourceRoot: string|null, catalog?: Record<string, any>|null }} template
16
- * @property {{ id: string|null, module: string, export: string }} implementation
17
- * @property {{ algorithm: "sha256", root: string, digest: string, files: Array<{ path: string, sha256: string, size: number }> }} content
18
- */
19
-
20
- const IGNORED_IMPLEMENTATION_ENTRIES = new Set([".DS_Store", "node_modules", ".tmp"]);
21
- const MAX_TEXT_DIFF_BYTES = 256 * 1024;
22
- const TRUST_REVIEW_COMMANDS = "`topogram trust status`, `topogram trust diff`, and `topogram trust template`";
23
-
24
- /**
25
- * @param {string} parent
26
- * @param {string} child
27
- * @returns {boolean}
28
- */
29
- function isSameOrInside(parent, child) {
30
- const relative = path.relative(path.resolve(parent), path.resolve(child));
31
- return relative === "" || (!relative.startsWith("..") && !path.isAbsolute(relative));
32
- }
33
-
34
- /**
35
- * @param {string} value
36
- * @returns {string}
37
- */
38
- function normalizeRoot(value) {
39
- return String(value || "").replace(/\\/g, "/");
40
- }
41
-
42
- /**
43
- * @param {string} value
44
- * @returns {string}
45
- */
46
- function normalizeRelativePath(value) {
47
- return value.replace(/\\/g, "/");
48
- }
49
-
50
- /**
51
- * @param {string} relativePath
52
- * @returns {string}
53
- */
54
- function unsupportedImplementationSymlinkMessage(relativePath) {
55
- return `Template implementation contains unsupported symlink '${relativePath}'. Implementation trust hashes real files under implementation/; symlinks can point outside the trusted root. Replace symlinks with real files under implementation/, then run ${TRUST_REVIEW_COMMANDS} after review.`;
56
- }
57
-
58
- /**
59
- * @param {string} modulePath
60
- * @returns {string}
61
- */
62
- function implementationOutsideRootMessage(modulePath) {
63
- return `Template implementation module '${modulePath}' must be under implementation/ for template-attached projects. Keep executable template code inside implementation/ so the trust record covers what topogram generate may load. Move the module back under implementation/, then run ${TRUST_REVIEW_COMMANDS} after review.`;
64
- }
65
-
66
- /**
67
- * @param {string|string[]} issueOrIssues
68
- * @returns {string}
69
- */
70
- export function templateTrustRecoveryGuidance(issueOrIssues) {
71
- const issues = Array.isArray(issueOrIssues) ? issueOrIssues : [issueOrIssues];
72
- const text = issues.join("\n");
73
- if (issues.length > 0 && issues.every((issue) =>
74
- issue.includes("topogram trust status") &&
75
- issue.includes("topogram trust diff") &&
76
- issue.includes("topogram trust template")
77
- )) {
78
- return "";
79
- }
80
- if (text.includes("unsupported symlink")) {
81
- return `Replace symlinks with real files under implementation/, then run ${TRUST_REVIEW_COMMANDS} after review.`;
82
- }
83
- if (text.includes("must be under implementation/")) {
84
- return `Keep executable template code under implementation/ so it can be hashed and trusted; move the module back under implementation/, then run ${TRUST_REVIEW_COMMANDS} after review.`;
85
- }
86
- return `Run \`topogram trust status\` and \`topogram trust diff\` to review implementation changes; after review, run \`topogram trust template\` to trust the current files.`;
87
- }
88
-
89
- /**
90
- * @param {string} value
91
- * @returns {string}
92
- */
93
- function escapeDiffPath(value) {
94
- return value.replace(/\t/g, "\\t").replace(/\n/g, "\\n");
95
- }
96
-
97
- /**
98
- * @param {any} bytes
99
- * @returns {boolean}
100
- */
101
- function isLikelyText(bytes) {
102
- if (bytes.includes(0)) {
103
- return false;
104
- }
105
- const length = Math.min(bytes.length, 4096);
106
- let suspicious = 0;
107
- for (let index = 0; index < length; index += 1) {
108
- const byte = bytes[index];
109
- if (byte === 9 || byte === 10 || byte === 13) {
110
- continue;
111
- }
112
- if (byte < 32 || byte === 127) {
113
- suspicious += 1;
114
- }
115
- }
116
- return length === 0 || suspicious / length < 0.02;
117
- }
118
-
119
- /**
120
- * @param {string} text
121
- * @returns {string[]}
122
- */
123
- function linesForDiff(text) {
124
- const lines = text.split("\n");
125
- if (lines.at(-1) === "") {
126
- lines.pop();
127
- }
128
- return lines;
129
- }
130
-
131
- /**
132
- * @param {string[]} before
133
- * @param {string[]} after
134
- * @returns {Array<{ type: "same"|"added"|"removed", text: string }>}
135
- */
136
- function diffLines(before, after) {
137
- const rows = before.length;
138
- const columns = after.length;
139
- /** @type {number[][]} */
140
- const table = Array.from({ length: rows + 1 }, () => Array(columns + 1).fill(0));
141
- for (let row = rows - 1; row >= 0; row -= 1) {
142
- for (let column = columns - 1; column >= 0; column -= 1) {
143
- table[row][column] = before[row] === after[column]
144
- ? table[row + 1][column + 1] + 1
145
- : Math.max(table[row + 1][column], table[row][column + 1]);
146
- }
147
- }
148
- /** @type {Array<{ type: "same"|"added"|"removed", text: string }>} */
149
- const changes = [];
150
- let row = 0;
151
- let column = 0;
152
- while (row < rows && column < columns) {
153
- if (before[row] === after[column]) {
154
- changes.push({ type: "same", text: before[row] });
155
- row += 1;
156
- column += 1;
157
- } else if (table[row + 1][column] >= table[row][column + 1]) {
158
- changes.push({ type: "removed", text: before[row] });
159
- row += 1;
160
- } else {
161
- changes.push({ type: "added", text: after[column] });
162
- column += 1;
163
- }
164
- }
165
- while (row < rows) {
166
- changes.push({ type: "removed", text: before[row] });
167
- row += 1;
168
- }
169
- while (column < columns) {
170
- changes.push({ type: "added", text: after[column] });
171
- column += 1;
172
- }
173
- return changes;
174
- }
175
-
176
- /**
177
- * @param {string} relativePath
178
- * @param {string|null} beforeText
179
- * @param {string|null} afterText
180
- * @returns {string|null}
181
- */
182
- function unifiedTextDiff(relativePath, beforeText, afterText) {
183
- if (beforeText === null && afterText === null) {
184
- return null;
185
- }
186
- const beforeLines = beforeText === null ? [] : linesForDiff(beforeText);
187
- const afterLines = afterText === null ? [] : linesForDiff(afterText);
188
- const changes = diffLines(beforeLines, afterLines);
189
- const lines = [
190
- `--- a/implementation/${escapeDiffPath(relativePath)}`,
191
- `+++ b/implementation/${escapeDiffPath(relativePath)}`,
192
- `@@ -1,${beforeLines.length} +1,${afterLines.length} @@`
193
- ];
194
- for (const change of changes) {
195
- const prefix = change.type === "added" ? "+" : change.type === "removed" ? "-" : " ";
196
- lines.push(`${prefix}${change.text}`);
197
- }
198
- return `${lines.join("\n")}\n`;
199
- }
200
-
201
- /**
202
- * @param {string} filePath
203
- * @returns {{ text: string|null, binary: boolean, omitted: boolean }}
204
- */
205
- function readReviewText(filePath) {
206
- const bytes = fs.readFileSync(filePath);
207
- if (bytes.length > MAX_TEXT_DIFF_BYTES) {
208
- return { text: null, binary: false, omitted: true };
209
- }
210
- if (!isLikelyText(bytes)) {
211
- return { text: null, binary: true, omitted: false };
212
- }
213
- return { text: bytes.toString("utf8"), binary: false, omitted: false };
214
- }
215
-
216
- /**
217
- * @param {string} implementationRoot
218
- * @param {string} currentDir
219
- * @param {string[]} files
220
- * @returns {void}
221
- */
222
- function collectImplementationFiles(implementationRoot, currentDir, files) {
223
- for (const entry of fs.readdirSync(currentDir, { withFileTypes: true })) {
224
- if (IGNORED_IMPLEMENTATION_ENTRIES.has(entry.name)) {
225
- continue;
226
- }
227
- const entryPath = path.join(currentDir, entry.name);
228
- const relativePath = normalizeRelativePath(path.relative(implementationRoot, entryPath));
229
- if (entry.isSymbolicLink()) {
230
- throw new Error(unsupportedImplementationSymlinkMessage(relativePath));
231
- }
232
- if (entry.isDirectory()) {
233
- collectImplementationFiles(implementationRoot, entryPath, files);
234
- continue;
235
- }
236
- if (entry.isFile()) {
237
- files.push(relativePath);
238
- }
239
- }
240
- }
241
-
242
- /**
243
- * @param {string} configDir
244
- * @returns {{ algorithm: "sha256", root: string, digest: string, files: Array<{ path: string, sha256: string, size: number }> }}
245
- */
246
- export function hashImplementationContent(configDir) {
247
- const implementationRoot = path.join(configDir, "implementation");
248
- if (!fs.existsSync(implementationRoot) || !fs.statSync(implementationRoot).isDirectory()) {
249
- throw new Error(`Cannot trust template implementation because ${normalizeRoot(implementationRoot)} does not exist.`);
250
- }
251
- /** @type {string[]} */
252
- const relativePaths = [];
253
- collectImplementationFiles(implementationRoot, implementationRoot, relativePaths);
254
- relativePaths.sort((a, b) => a.localeCompare(b));
255
- const files = relativePaths.map((relativePath) => {
256
- const filePath = path.join(implementationRoot, relativePath);
257
- const bytes = fs.readFileSync(filePath);
258
- return {
259
- path: relativePath,
260
- sha256: crypto.createHash("sha256").update(bytes).digest("hex"),
261
- size: bytes.length
262
- };
263
- });
264
- const aggregate = crypto.createHash("sha256");
265
- for (const file of files) {
266
- aggregate.update(file.path);
267
- aggregate.update("\0");
268
- aggregate.update(file.sha256);
269
- aggregate.update("\0");
270
- aggregate.update(String(file.size));
271
- aggregate.update("\0");
272
- }
273
- return {
274
- algorithm: "sha256",
275
- root: "implementation",
276
- digest: aggregate.digest("hex"),
277
- files
278
- };
279
- }
280
-
281
- /**
282
- * @param {Record<string, any>} config
283
- * @returns {{ id: string|null, module: string, export: string }}
284
- */
285
- export function implementationTrustFingerprint(config) {
286
- const implementationModule = config.implementation_module || config.module;
287
- if (!implementationModule || typeof implementationModule !== "string") {
288
- throw new Error("Topogram implementation config is missing implementation module.");
289
- }
290
- return {
291
- id: config.implementation_id || config.id || null,
292
- module: implementationModule,
293
- export: config.implementation_export || config.export || "default"
294
- };
295
- }
296
-
297
- /**
298
- * @param {{ config: Record<string, any>, configDir: string }} implementationInfo
299
- * @param {Record<string, any>|null} [projectConfig]
300
- * @returns {boolean}
301
- */
302
- export function implementationRequiresTrust(implementationInfo, projectConfig = null) {
303
- const fingerprint = implementationTrustFingerprint(implementationInfo.config);
304
- const modulePath = path.resolve(implementationInfo.configDir, fingerprint.module);
305
- const implementationRoot = path.resolve(implementationInfo.configDir, "implementation");
306
- return isSameOrInside(implementationRoot, modulePath) || projectHasTemplateAttachment(projectConfig);
307
- }
308
-
309
- /**
310
- * @param {{ config: Record<string, any>, configDir: string }} implementationInfo
311
- * @returns {boolean}
312
- */
313
- function implementationModuleIsUnderRoot(implementationInfo) {
314
- const fingerprint = implementationTrustFingerprint(implementationInfo.config);
315
- const modulePath = path.resolve(implementationInfo.configDir, fingerprint.module);
316
- const implementationRoot = path.resolve(implementationInfo.configDir, "implementation");
317
- return isSameOrInside(implementationRoot, modulePath);
318
- }
319
-
320
- /**
321
- * @param {Record<string, any>|null} projectConfig
322
- * @returns {boolean}
323
- */
324
- function projectHasTemplateAttachment(projectConfig) {
325
- const template = projectConfig?.template || null;
326
- return Boolean(template?.id || template?.sourceSpec || template?.requested);
327
- }
328
-
329
- /**
330
- * @param {string} configDir
331
- * @returns {TemplateTrustRecord|null}
332
- */
333
- export function readTemplateTrustRecord(configDir) {
334
- const trustPath = path.join(configDir, TEMPLATE_TRUST_FILE);
335
- if (!fs.existsSync(trustPath)) {
336
- return null;
337
- }
338
- return /** @type {TemplateTrustRecord} */ (JSON.parse(fs.readFileSync(trustPath, "utf8")));
339
- }
340
-
341
- /**
342
- * @param {string} configDir
343
- * @param {Record<string, any>} projectConfig
344
- * @param {{ id: string|null, module: string, export: string }} implementation
345
- * @returns {TemplateTrustRecord}
346
- */
347
- function buildTrustRecord(configDir, projectConfig, implementation) {
348
- const template = projectConfig.template || {};
349
- const content = hashImplementationContent(configDir);
350
- return {
351
- version: "1",
352
- trustPolicy: TEMPLATE_TRUST_POLICY,
353
- trustedAt: new Date().toISOString(),
354
- template: {
355
- id: typeof template.id === "string" ? template.id : null,
356
- version: typeof template.version === "string" ? template.version : null,
357
- source: typeof template.source === "string" ? template.source : null,
358
- sourceSpec: typeof template.sourceSpec === "string" ? template.sourceSpec : null,
359
- requested: typeof template.requested === "string" ? template.requested : null,
360
- sourceRoot: typeof template.sourceRoot === "string" ? template.sourceRoot : null,
361
- catalog: template.catalog && typeof template.catalog === "object" && !Array.isArray(template.catalog)
362
- ? template.catalog
363
- : null
364
- },
365
- implementation,
366
- content
367
- };
368
- }
369
-
370
- /**
371
- * @param {string} configDir
372
- * @param {Record<string, any>} projectConfig
373
- * @returns {TemplateTrustRecord}
374
- */
375
- export function writeTemplateTrustRecord(configDir, projectConfig) {
376
- const implementationConfig = projectConfig.implementation;
377
- if (!implementationConfig) {
378
- throw new Error("Cannot trust template implementation because topogram.project.json has no implementation config.");
379
- }
380
- const implementationInfo = {
381
- config: implementationConfig,
382
- configDir
383
- };
384
- if (!implementationModuleIsUnderRoot(implementationInfo)) {
385
- const implementation = implementationTrustFingerprint(implementationConfig);
386
- throw new Error(implementationOutsideRootMessage(implementation.module));
387
- }
388
- const implementation = implementationTrustFingerprint(implementationConfig);
389
- const record = buildTrustRecord(configDir, projectConfig, implementation);
390
- fs.writeFileSync(path.join(configDir, TEMPLATE_TRUST_FILE), `${JSON.stringify(record, null, 2)}\n`, "utf8");
391
- return record;
392
- }
393
-
394
- /**
395
- * @param {{ config: Record<string, any>, configPath: string|null, configDir: string }} implementationInfo
396
- * @param {Record<string, any>|null} projectConfig
397
- * @returns {void}
398
- */
399
- export function assertTrustedImplementation(implementationInfo, projectConfig = null) {
400
- const status = getTemplateTrustStatus(implementationInfo, projectConfig);
401
- if (!status.requiresTrust || status.ok) {
402
- return;
403
- }
404
- const firstIssue = status.issues[0] || "implementation trust is invalid";
405
- const guidance = templateTrustRecoveryGuidance(firstIssue);
406
- throw new Error(
407
- guidance ? `${firstIssue}. ${guidance}` : firstIssue
408
- );
409
- }
410
-
411
- /**
412
- * @param {Map<string, { path: string, sha256: string, size: number }>} trustedByPath
413
- * @param {Map<string, { path: string, sha256: string, size: number }>} currentByPath
414
- * @returns {{ added: string[], removed: string[], changed: string[] }}
415
- */
416
- function diffContentFiles(trustedByPath, currentByPath) {
417
- /** @type {string[]} */
418
- const added = [];
419
- /** @type {string[]} */
420
- const removed = [];
421
- /** @type {string[]} */
422
- const changed = [];
423
- for (const [filePath, current] of currentByPath) {
424
- const trusted = trustedByPath.get(filePath);
425
- if (!trusted) {
426
- added.push(filePath);
427
- } else if (trusted.sha256 !== current.sha256 || trusted.size !== current.size) {
428
- changed.push(filePath);
429
- }
430
- }
431
- for (const filePath of trustedByPath.keys()) {
432
- if (!currentByPath.has(filePath)) {
433
- removed.push(filePath);
434
- }
435
- }
436
- return {
437
- added: added.sort((a, b) => a.localeCompare(b)),
438
- removed: removed.sort((a, b) => a.localeCompare(b)),
439
- changed: changed.sort((a, b) => a.localeCompare(b))
440
- };
441
- }
442
-
443
- /**
444
- * @param {string} configDir
445
- * @param {string} relativePath
446
- * @param {{ path: string, sha256: string, size: number }|null} file
447
- * @returns {{ path: string, sha256: string|null, size: number|null, binary: boolean, diffOmitted: boolean, text: string|null }}
448
- */
449
- function implementationReviewFile(configDir, relativePath, file) {
450
- if (!file) {
451
- return { path: relativePath, sha256: null, size: null, binary: false, diffOmitted: false, text: null };
452
- }
453
- const reviewText = readReviewText(path.join(configDir, "implementation", relativePath));
454
- return {
455
- path: relativePath,
456
- sha256: file.sha256,
457
- size: file.size,
458
- binary: reviewText.binary,
459
- diffOmitted: reviewText.omitted,
460
- text: reviewText.text
461
- };
462
- }
463
-
464
- /**
465
- * @param {{ config: Record<string, any>, configPath: string|null, configDir: string }} implementationInfo
466
- * @param {Record<string, any>|null} projectConfig
467
- * @returns {{ ok: boolean, requiresTrust: boolean, trustPath: string, trustRecord: TemplateTrustRecord|null, template: { id: string|null, version: string|null, source: string|null, sourceSpec: string|null, requested: string|null, sourceRoot: string|null, catalog?: Record<string, any>|null, includesExecutableImplementation: boolean|null }, implementation: { id: string|null, module: string|null, export: string|null }, content: { trustedDigest: string|null, currentDigest: string|null, added: string[], removed: string[], changed: string[] }, issues: string[] }}
468
- */
469
- export function getTemplateTrustStatus(implementationInfo, projectConfig = null) {
470
- const templateAttached = projectHasTemplateAttachment(projectConfig);
471
- if (!implementationRequiresTrust(implementationInfo, projectConfig)) {
472
- return {
473
- ok: true,
474
- requiresTrust: false,
475
- trustPath: path.join(implementationInfo.configDir, TEMPLATE_TRUST_FILE),
476
- trustRecord: null,
477
- template: { id: null, version: null, source: null, sourceSpec: null, requested: null, sourceRoot: null, catalog: null, includesExecutableImplementation: null },
478
- implementation: { id: null, module: null, export: null },
479
- content: { trustedDigest: null, currentDigest: null, added: [], removed: [], changed: [] },
480
- issues: []
481
- };
482
- }
483
- const fingerprint = implementationTrustFingerprint(implementationInfo.config);
484
- const moduleInsideImplementation = implementationModuleIsUnderRoot(implementationInfo);
485
- const trustRecord = readTemplateTrustRecord(implementationInfo.configDir);
486
- const configLabel = implementationInfo.configPath || "topogram.project.json";
487
- const trustPath = path.join(implementationInfo.configDir, TEMPLATE_TRUST_FILE);
488
- const projectTemplate = projectConfig?.template || null;
489
- /** @type {string[]} */
490
- const issues = [];
491
- /** @type {{ trustedDigest: string|null, currentDigest: string|null, added: string[], removed: string[], changed: string[] }} */
492
- const contentStatus = { trustedDigest: null, currentDigest: null, added: [], removed: [], changed: [] };
493
-
494
- if (templateAttached && !moduleInsideImplementation) {
495
- issues.push(implementationOutsideRootMessage(fingerprint.module));
496
- }
497
-
498
- if (!trustRecord) {
499
- issues.push(
500
- `Refusing to load executable implementation '${fingerprint.module}' from ${normalizeRoot(configLabel)} without ${TEMPLATE_TRUST_FILE}`
501
- );
502
- } else {
503
- if (trustRecord.trustPolicy !== TEMPLATE_TRUST_POLICY) {
504
- issues.push(`${TEMPLATE_TRUST_FILE} uses unsupported trust policy '${trustRecord.trustPolicy}'`);
505
- }
506
- if (trustRecord.implementation?.module !== fingerprint.module) {
507
- issues.push(`${TEMPLATE_TRUST_FILE} trusts implementation module '${trustRecord.implementation?.module}', but ${normalizeRoot(configLabel)} uses '${fingerprint.module}'`);
508
- }
509
- if (trustRecord.implementation?.export !== fingerprint.export) {
510
- issues.push(`${TEMPLATE_TRUST_FILE} trusts implementation export '${trustRecord.implementation?.export}', but ${normalizeRoot(configLabel)} uses '${fingerprint.export}'`);
511
- }
512
- const trustedId = trustRecord.implementation?.id || null;
513
- if (trustedId !== (fingerprint.id || null)) {
514
- issues.push(`${TEMPLATE_TRUST_FILE} trusts implementation id '${trustedId || ""}', but ${normalizeRoot(configLabel)} uses '${fingerprint.id || ""}'`);
515
- }
516
-
517
- if (projectTemplate?.id && trustRecord.template?.id !== projectTemplate.id) {
518
- issues.push(`${TEMPLATE_TRUST_FILE} trusts template '${trustRecord.template?.id}', but topogram.project.json declares '${projectTemplate.id}'`);
519
- }
520
- if (projectTemplate?.version && trustRecord.template?.version !== projectTemplate.version) {
521
- issues.push(`${TEMPLATE_TRUST_FILE} trusts template version '${trustRecord.template?.version}', but topogram.project.json declares '${projectTemplate.version}'`);
522
- }
523
-
524
- if (!moduleInsideImplementation) {
525
- // The module itself is outside the only supported trust root. Do not
526
- // pretend the implementation/ content digest covers the executable code.
527
- } else if (!trustRecord.content) {
528
- issues.push(`${TEMPLATE_TRUST_FILE} is missing implementation content hashes`);
529
- } else if (trustRecord.content.algorithm !== "sha256") {
530
- issues.push(`${TEMPLATE_TRUST_FILE} uses unsupported content hash algorithm '${trustRecord.content.algorithm}'`);
531
- } else {
532
- try {
533
- const currentContent = hashImplementationContent(implementationInfo.configDir);
534
- contentStatus.trustedDigest = trustRecord.content.digest;
535
- contentStatus.currentDigest = currentContent.digest;
536
- const trustedByPath = new Map((trustRecord.content.files || []).map((file) => [file.path, file]));
537
- const currentByPath = new Map(currentContent.files.map((file) => [file.path, file]));
538
- const diff = diffContentFiles(trustedByPath, currentByPath);
539
- contentStatus.added = diff.added;
540
- contentStatus.removed = diff.removed;
541
- contentStatus.changed = diff.changed;
542
- if (trustRecord.content.digest !== currentContent.digest) {
543
- issues.push(`${TEMPLATE_TRUST_FILE} implementation content changed since it was last trusted`);
544
- }
545
- } catch (error) {
546
- issues.push(error instanceof Error ? error.message : String(error));
547
- }
548
- }
549
- }
550
-
551
- return {
552
- ok: issues.length === 0,
553
- requiresTrust: true,
554
- trustPath,
555
- trustRecord,
556
- template: {
557
- id: projectTemplate?.id || trustRecord?.template?.id || null,
558
- version: projectTemplate?.version || trustRecord?.template?.version || null,
559
- source: projectTemplate?.source || trustRecord?.template?.source || null,
560
- sourceSpec: projectTemplate?.sourceSpec || trustRecord?.template?.sourceSpec || null,
561
- requested: projectTemplate?.requested || trustRecord?.template?.requested || null,
562
- sourceRoot: projectTemplate?.sourceRoot || trustRecord?.template?.sourceRoot || null,
563
- catalog: projectTemplate?.catalog || trustRecord?.template?.catalog || null,
564
- includesExecutableImplementation: typeof projectTemplate?.includesExecutableImplementation === "boolean"
565
- ? projectTemplate.includesExecutableImplementation
566
- : null
567
- },
568
- implementation: fingerprint,
569
- content: contentStatus,
570
- issues
571
- };
572
- }
573
-
574
- /**
575
- * @param {{ config: Record<string, any>, configPath: string|null, configDir: string }} implementationInfo
576
- * @param {Record<string, any>|null} projectConfig
577
- * @returns {{ ok: boolean, requiresTrust: boolean, status: ReturnType<typeof getTemplateTrustStatus>, files: Array<{ path: string, kind: "added"|"removed"|"changed", trusted: { path: string, sha256: string|null, size: number|null }|null, current: { path: string, sha256: string|null, size: number|null, binary: boolean, diffOmitted: boolean }|null, binary: boolean, diffOmitted: boolean, unifiedDiff: string|null }> }}
578
- */
579
- export function getTemplateTrustDiff(implementationInfo, projectConfig = null) {
580
- const status = getTemplateTrustStatus(implementationInfo, projectConfig);
581
- if (!status.requiresTrust || !status.trustRecord?.content) {
582
- return { ok: status.ok, requiresTrust: status.requiresTrust, status, files: [] };
583
- }
584
- let currentContent;
585
- try {
586
- currentContent = hashImplementationContent(implementationInfo.configDir);
587
- } catch (_error) {
588
- return { ok: false, requiresTrust: status.requiresTrust, status, files: [] };
589
- }
590
- const trustedByPath = new Map((status.trustRecord.content.files || []).map((file) => [file.path, file]));
591
- const currentByPath = new Map(currentContent.files.map((file) => [file.path, file]));
592
- /** @type {Array<{ path: string, kind: "added"|"removed"|"changed", trusted: { path: string, sha256: string|null, size: number|null }|null, current: { path: string, sha256: string|null, size: number|null, binary: boolean, diffOmitted: boolean }|null, binary: boolean, diffOmitted: boolean, unifiedDiff: string|null }>} */
593
- const files = [];
594
-
595
- for (const relativePath of status.content.changed) {
596
- const trusted = trustedByPath.get(relativePath) || null;
597
- const current = currentByPath.get(relativePath) || null;
598
- const currentReview = implementationReviewFile(implementationInfo.configDir, relativePath, current);
599
- files.push({
600
- path: relativePath,
601
- kind: "changed",
602
- trusted: trusted ? { path: relativePath, sha256: trusted.sha256, size: trusted.size } : null,
603
- current: {
604
- path: relativePath,
605
- sha256: currentReview.sha256,
606
- size: currentReview.size,
607
- binary: currentReview.binary,
608
- diffOmitted: currentReview.diffOmitted
609
- },
610
- binary: currentReview.binary,
611
- diffOmitted: true,
612
- unifiedDiff: null
613
- });
614
- }
615
- for (const relativePath of status.content.added) {
616
- const current = currentByPath.get(relativePath) || null;
617
- const currentReview = implementationReviewFile(implementationInfo.configDir, relativePath, current);
618
- files.push({
619
- path: relativePath,
620
- kind: "added",
621
- trusted: null,
622
- current: {
623
- path: relativePath,
624
- sha256: currentReview.sha256,
625
- size: currentReview.size,
626
- binary: currentReview.binary,
627
- diffOmitted: currentReview.diffOmitted
628
- },
629
- binary: currentReview.binary,
630
- diffOmitted: currentReview.binary || currentReview.diffOmitted,
631
- unifiedDiff: currentReview.binary || currentReview.diffOmitted
632
- ? null
633
- : unifiedTextDiff(relativePath, null, currentReview.text)
634
- });
635
- }
636
- for (const relativePath of status.content.removed) {
637
- const trusted = trustedByPath.get(relativePath) || null;
638
- files.push({
639
- path: relativePath,
640
- kind: "removed",
641
- trusted: trusted ? { path: relativePath, sha256: trusted.sha256, size: trusted.size } : null,
642
- current: null,
643
- binary: false,
644
- diffOmitted: true,
645
- unifiedDiff: null
646
- });
647
- }
648
-
649
- files.sort((a, b) => a.path.localeCompare(b.path) || a.kind.localeCompare(b.kind));
650
- return {
651
- ok: status.ok,
652
- requiresTrust: status.requiresTrust,
653
- status,
654
- files
655
- };
656
- }
657
-
658
- /**
659
- * @param {{ config: Record<string, any>, configPath: string|null, configDir: string }|null} projectConfigInfo
660
- * @returns {{ ok: boolean, errors: Array<{ message: string, loc: any }> }}
661
- */
662
- export function validateProjectImplementationTrust(projectConfigInfo) {
663
- if (!projectConfigInfo?.config?.implementation) {
664
- return { ok: true, errors: [] };
665
- }
666
- const implementationModule =
667
- projectConfigInfo.config.implementation.implementation_module ||
668
- projectConfigInfo.config.implementation.module;
669
- if (!implementationModule) {
670
- return { ok: true, errors: [] };
671
- }
672
- const implementationInfo = {
673
- config: projectConfigInfo.config.implementation,
674
- configPath: projectConfigInfo.configPath,
675
- configDir: projectConfigInfo.configDir
676
- };
677
- try {
678
- assertTrustedImplementation(implementationInfo, projectConfigInfo.config);
679
- return { ok: true, errors: [] };
680
- } catch (error) {
681
- return {
682
- ok: false,
683
- errors: [{
684
- message: error instanceof Error ? error.message : String(error),
685
- loc: null
686
- }]
687
- };
688
- }
689
- }
3
+ export {
4
+ TEMPLATE_TRUST_FILE,
5
+ TEMPLATE_TRUST_POLICY,
6
+ templateTrustRecoveryGuidance
7
+ } from "./template-trust/constants.js";
8
+ export {
9
+ hashImplementationContent
10
+ } from "./template-trust/content.js";
11
+ export {
12
+ implementationRequiresTrust,
13
+ implementationTrustFingerprint
14
+ } from "./template-trust/policy.js";
15
+ export {
16
+ readTemplateTrustRecord,
17
+ writeTemplateTrustRecord
18
+ } from "./template-trust/record.js";
19
+ export {
20
+ assertTrustedImplementation,
21
+ getTemplateTrustStatus,
22
+ validateProjectImplementationTrust
23
+ } from "./template-trust/status.js";
24
+ export {
25
+ getTemplateTrustDiff
26
+ } from "./template-trust/diff.js";