@turri-dev/cli 0.0.0-stage → 0.1.2-next.48dacfc

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 (315) hide show
  1. package/.pi/themes/turri-dark.json +80 -0
  2. package/.pi/themes/turri-light.json +82 -0
  3. package/LICENSE +21 -0
  4. package/README.md +272 -2
  5. package/assets/jellyfish/dark-00.png +0 -0
  6. package/assets/jellyfish/dark-01.png +0 -0
  7. package/assets/jellyfish/dark-02.png +0 -0
  8. package/assets/jellyfish/dark-03.png +0 -0
  9. package/assets/jellyfish/dark-04.png +0 -0
  10. package/assets/jellyfish/dark-05.png +0 -0
  11. package/assets/jellyfish/dark-06.png +0 -0
  12. package/assets/jellyfish/dark-07.png +0 -0
  13. package/assets/jellyfish/dark-08.png +0 -0
  14. package/assets/jellyfish/dark-09.png +0 -0
  15. package/assets/jellyfish/dark-10.png +0 -0
  16. package/assets/jellyfish/dark-11.png +0 -0
  17. package/assets/jellyfish/dark-12.png +0 -0
  18. package/assets/jellyfish/dark-13.png +0 -0
  19. package/assets/jellyfish/dark-14.png +0 -0
  20. package/assets/jellyfish/fallback.txt +252 -0
  21. package/assets/jellyfish/light-00.png +0 -0
  22. package/assets/jellyfish/light-01.png +0 -0
  23. package/assets/jellyfish/light-02.png +0 -0
  24. package/assets/jellyfish/light-03.png +0 -0
  25. package/assets/jellyfish/light-04.png +0 -0
  26. package/assets/jellyfish/light-05.png +0 -0
  27. package/assets/jellyfish/light-06.png +0 -0
  28. package/assets/jellyfish/light-07.png +0 -0
  29. package/assets/jellyfish/light-08.png +0 -0
  30. package/assets/jellyfish/light-09.png +0 -0
  31. package/assets/jellyfish/light-10.png +0 -0
  32. package/assets/jellyfish/light-11.png +0 -0
  33. package/assets/jellyfish/light-12.png +0 -0
  34. package/assets/jellyfish/light-13.png +0 -0
  35. package/assets/jellyfish/light-14.png +0 -0
  36. package/bin/turri.mjs +29 -0
  37. package/dist/ask.js +149 -0
  38. package/dist/brand.js +50 -0
  39. package/dist/btw.js +174 -0
  40. package/dist/chat-models.js +37 -0
  41. package/dist/chat-skills.js +18 -0
  42. package/dist/cli.js +178 -0
  43. package/dist/config-command.js +197 -0
  44. package/dist/config-edit.js +367 -0
  45. package/dist/config.js +489 -0
  46. package/dist/conform-cli.js +29 -0
  47. package/dist/cultivate-cli.js +110 -0
  48. package/dist/drift-cli.js +25 -0
  49. package/dist/environment.js +70 -0
  50. package/dist/extension.js +2372 -0
  51. package/dist/genome.js +301 -0
  52. package/dist/graph.js +246 -0
  53. package/dist/identity.js +35 -0
  54. package/dist/init-config.js +511 -0
  55. package/dist/judge.js +62 -0
  56. package/dist/lineage-cli.js +13 -0
  57. package/dist/loop-cli.js +132 -0
  58. package/dist/loop-tools.js +225 -0
  59. package/dist/mature-tools.js +193 -0
  60. package/dist/model-check.js +117 -0
  61. package/dist/module-report.js +9 -0
  62. package/dist/pi-launch.js +31 -0
  63. package/dist/pi.js +813 -0
  64. package/dist/plain/check.js +76 -0
  65. package/dist/plain/concepts.js +213 -0
  66. package/dist/plain/errors.js +43 -0
  67. package/dist/plain/files.js +57 -0
  68. package/dist/plain/highlight.js +195 -0
  69. package/dist/plain/index.js +15 -0
  70. package/dist/plain/liquid.js +129 -0
  71. package/dist/plain/markdown.js +208 -0
  72. package/dist/plain/parser.js +752 -0
  73. package/dist/plain/plain.tmLanguage.json +124 -0
  74. package/dist/plain/spec.js +291 -0
  75. package/dist/plain/stdlib.js +44 -0
  76. package/dist/question-dialog.js +125 -0
  77. package/dist/questioning.js +109 -0
  78. package/dist/quiet.js +125 -0
  79. package/dist/regenerate/activity.js +187 -0
  80. package/dist/regenerate/agent-env.js +190 -0
  81. package/dist/regenerate/agent-shell.js +114 -0
  82. package/dist/regenerate/arbiter.js +133 -0
  83. package/dist/regenerate/audit.js +203 -0
  84. package/dist/regenerate/cases.js +83 -0
  85. package/dist/regenerate/change-view.js +102 -0
  86. package/dist/regenerate/choose-conformance.js +117 -0
  87. package/dist/regenerate/chunk-plan.js +625 -0
  88. package/dist/regenerate/codebase.js +75 -0
  89. package/dist/regenerate/command-check.js +312 -0
  90. package/dist/regenerate/compaction.js +20 -0
  91. package/dist/regenerate/confine.js +310 -0
  92. package/dist/regenerate/conform.js +133 -0
  93. package/dist/regenerate/conformance-agents.js +955 -0
  94. package/dist/regenerate/conformance.js +936 -0
  95. package/dist/regenerate/conversation-writes.js +100 -0
  96. package/dist/regenerate/credentials.js +88 -0
  97. package/dist/regenerate/debug-log.js +228 -0
  98. package/dist/regenerate/debugger.js +90 -0
  99. package/dist/regenerate/delivery.js +19 -0
  100. package/dist/regenerate/diagnose-job.js +40 -0
  101. package/dist/regenerate/diagnose.js +81 -0
  102. package/dist/regenerate/diff-view.js +240 -0
  103. package/dist/regenerate/drift.js +194 -0
  104. package/dist/regenerate/engine.js +118 -0
  105. package/dist/regenerate/environment-error.js +220 -0
  106. package/dist/regenerate/job-log.js +102 -0
  107. package/dist/regenerate/job-panel.js +336 -0
  108. package/dist/regenerate/jobs-tree.js +300 -0
  109. package/dist/regenerate/jobs.js +427 -0
  110. package/dist/regenerate/lanes.js +32 -0
  111. package/dist/regenerate/lineage.js +384 -0
  112. package/dist/regenerate/loop.js +1435 -0
  113. package/dist/regenerate/masking.js +95 -0
  114. package/dist/regenerate/notes.js +397 -0
  115. package/dist/regenerate/os-sandbox.js +243 -0
  116. package/dist/regenerate/paths.js +165 -0
  117. package/dist/regenerate/plain-palette.js +83 -0
  118. package/dist/regenerate/prompt.js +704 -0
  119. package/dist/regenerate/records.js +294 -0
  120. package/dist/regenerate/regenerate.js +598 -0
  121. package/dist/regenerate/regression.js +181 -0
  122. package/dist/regenerate/release.js +262 -0
  123. package/dist/regenerate/requires.js +241 -0
  124. package/dist/regenerate/resources.js +164 -0
  125. package/dist/regenerate/self-check.js +111 -0
  126. package/dist/regenerate/services.js +155 -0
  127. package/dist/regenerate/shell.js +320 -0
  128. package/dist/regenerate/side-panel.js +278 -0
  129. package/dist/regenerate/spec-map-view.js +574 -0
  130. package/dist/regenerate/spec-map.js +561 -0
  131. package/dist/regenerate/spec-steps.js +527 -0
  132. package/dist/regenerate/state.js +10 -0
  133. package/dist/regenerate/steer.js +91 -0
  134. package/dist/regenerate/steering.js +119 -0
  135. package/dist/regenerate/stop-report.js +161 -0
  136. package/dist/regenerate/submit-work.js +71 -0
  137. package/dist/regenerate/test-kinds.js +124 -0
  138. package/dist/regenerate/test-scripts.js +695 -0
  139. package/dist/regenerate/testing.js +406 -0
  140. package/dist/regenerate/transcript-view.js +1 -0
  141. package/dist/regenerate/unit-gate.js +20 -0
  142. package/dist/regenerate/unit-review.js +347 -0
  143. package/dist/regenerate/web.js +256 -0
  144. package/dist/regenerate/workspace.js +99 -0
  145. package/dist/regenerate-cli.js +68 -0
  146. package/dist/release-agent.js +114 -0
  147. package/dist/release-cli.js +131 -0
  148. package/dist/release-tools.js +148 -0
  149. package/dist/review/live.js +165 -0
  150. package/dist/rules.js +47 -0
  151. package/dist/self-invocation.js +110 -0
  152. package/dist/setup.js +245 -0
  153. package/dist/skill-progress.js +78 -0
  154. package/dist/skill.js +149 -0
  155. package/dist/skills.js +74 -0
  156. package/dist/spec-diff-cli.js +37 -0
  157. package/dist/spec-tools.js +155 -0
  158. package/dist/stack.js +158 -0
  159. package/dist/stacks.js +97 -0
  160. package/dist/stdlib.js +100 -0
  161. package/dist/terminal-images.js +46 -0
  162. package/dist/tools.js +242 -0
  163. package/dist/validate-cli.js +52 -0
  164. package/docs/README.md +58 -0
  165. package/docs/authoring-plain.md +245 -0
  166. package/docs/config.md +353 -0
  167. package/docs/deploy.md +109 -0
  168. package/docs/development.md +88 -0
  169. package/docs/headless.md +148 -0
  170. package/docs/images/how-turri-works.svg +50 -0
  171. package/docs/images/turri-banner.png +0 -0
  172. package/docs/installation.md +119 -0
  173. package/docs/parser.md +108 -0
  174. package/docs/regenerating.md +1116 -0
  175. package/docs/sandbox.md +233 -0
  176. package/docs/skills-and-gates.md +254 -0
  177. package/docs/theming.md +76 -0
  178. package/docs/using.md +454 -0
  179. package/genome/rules/bullet-continuation.md +69 -0
  180. package/genome/rules/concise-specs-examples.md +431 -0
  181. package/genome/rules/concise-specs-integration-examples.md +270 -0
  182. package/genome/rules/concise-specs.md +80 -0
  183. package/genome/rules/definitions.md +107 -0
  184. package/genome/rules/examples/integration-embedded-testing/run_conformance_tests_java.sh +163 -0
  185. package/genome/rules/examples/integration-embedded-testing/run_unittests_java.sh +51 -0
  186. package/genome/rules/exported-concepts.md +39 -0
  187. package/genome/rules/func-specs.md +129 -0
  188. package/genome/rules/impl-reqs.md +68 -0
  189. package/genome/rules/import-modules.md +48 -0
  190. package/genome/rules/integration-embedded-testing.md +276 -0
  191. package/genome/rules/integration-embedded.md +171 -0
  192. package/genome/rules/integration-standalone.md +135 -0
  193. package/genome/rules/integrations.md +137 -0
  194. package/genome/rules/linked-resources.md +69 -0
  195. package/genome/rules/module-structure.md +105 -0
  196. package/genome/rules/project-model.md +88 -0
  197. package/genome/rules/required-concepts.md +44 -0
  198. package/genome/rules/requires-modules.md +80 -0
  199. package/genome/rules/stdlib.md +73 -0
  200. package/genome/rules/test-reqs.md +57 -0
  201. package/genome/skills/add-acceptance-test/SKILL.md +100 -0
  202. package/genome/skills/add-concept/SKILL.md +71 -0
  203. package/genome/skills/add-functional-spec/SKILL.md +84 -0
  204. package/genome/skills/add-functional-specs/SKILL.md +118 -0
  205. package/genome/skills/add-implementation-requirement/SKILL.md +86 -0
  206. package/genome/skills/add-resource/SKILL.md +105 -0
  207. package/genome/skills/add-template/SKILL.md +64 -0
  208. package/genome/skills/add-test-requirement/SKILL.md +73 -0
  209. package/genome/skills/analyze-2-func-specs/SKILL.md +101 -0
  210. package/genome/skills/analyze-func-specs/SKILL.md +123 -0
  211. package/genome/skills/analyze-if-func-spec-too-complex/SKILL.md +211 -0
  212. package/genome/skills/break-down-func-spec/SKILL.md +205 -0
  213. package/genome/skills/brood/SKILL.md +229 -0
  214. package/genome/skills/brood/references/intent.md +35 -0
  215. package/genome/skills/brood/references/product.md +101 -0
  216. package/genome/skills/brood/references/stack.md +61 -0
  217. package/genome/skills/brood/references/testing.md +56 -0
  218. package/genome/skills/bud/SKILL.md +148 -0
  219. package/genome/skills/bud/references/checklist.md +44 -0
  220. package/genome/skills/bud/references/intent.md +30 -0
  221. package/genome/skills/check-plain-env/SKILL.md +285 -0
  222. package/genome/skills/consolidate-concepts/SKILL.md +192 -0
  223. package/genome/skills/create-import-module/SKILL.md +104 -0
  224. package/genome/skills/create-requires-module/SKILL.md +109 -0
  225. package/genome/skills/debug-specs/SKILL.md +188 -0
  226. package/genome/skills/implement-conformance-testing-script/SKILL.md +238 -0
  227. package/genome/skills/implement-conformance-testing-script/assets/run_conformance_tests_cypress.ps1 +337 -0
  228. package/genome/skills/implement-conformance-testing-script/assets/run_conformance_tests_golang.ps1 +111 -0
  229. package/genome/skills/implement-conformance-testing-script/assets/run_conformance_tests_java.sh +136 -0
  230. package/genome/skills/implement-conformance-testing-script/assets/run_conformance_tests_python.ps1 +103 -0
  231. package/genome/skills/implement-conformance-testing-script/assets/run_conformance_tests_python.sh +150 -0
  232. package/genome/skills/implement-unit-testing-script/SKILL.md +160 -0
  233. package/genome/skills/implement-unit-testing-script/assets/run_unittests_flutter.ps1 +87 -0
  234. package/genome/skills/implement-unit-testing-script/assets/run_unittests_golang.ps1 +73 -0
  235. package/genome/skills/implement-unit-testing-script/assets/run_unittests_java.sh +72 -0
  236. package/genome/skills/implement-unit-testing-script/assets/run_unittests_python.ps1 +81 -0
  237. package/genome/skills/implement-unit-testing-script/assets/run_unittests_python.sh +98 -0
  238. package/genome/skills/implement-unit-testing-script/assets/run_unittests_react.ps1 +87 -0
  239. package/genome/skills/init-plain-project/SKILL.md +117 -0
  240. package/genome/skills/init-plain-project/references/checklist.md +30 -0
  241. package/genome/skills/mature/SKILL.md +126 -0
  242. package/genome/skills/refactor-module/SKILL.md +196 -0
  243. package/genome/skills/resolve-section-ownership/SKILL.md +64 -0
  244. package/genome/skills/resolve-spec-conflict/SKILL.md +96 -0
  245. package/genome/skills/spawn/SKILL.md +163 -0
  246. package/package.json +68 -4
  247. package/stdlib/backend/common.plain +25 -0
  248. package/stdlib/backend/conformance/common.plain +12 -0
  249. package/stdlib/backend/conformance/go-gin.plain +9 -0
  250. package/stdlib/backend/conformance/go-nethttp.plain +9 -0
  251. package/stdlib/backend/conformance/java-springboot.plain +9 -0
  252. package/stdlib/backend/conformance/python-django.plain +9 -0
  253. package/stdlib/backend/conformance/python-fastapi.plain +9 -0
  254. package/stdlib/backend/conformance/python-flask.plain +9 -0
  255. package/stdlib/backend/conformance/rust-axum.plain +9 -0
  256. package/stdlib/backend/conformance/typescript-express.plain +9 -0
  257. package/stdlib/backend/conformance/typescript-fastify.plain +9 -0
  258. package/stdlib/backend/conformance/typescript-nestjs.plain +9 -0
  259. package/stdlib/backend/go-gin.plain +18 -0
  260. package/stdlib/backend/go-nethttp.plain +18 -0
  261. package/stdlib/backend/java-springboot.plain +19 -0
  262. package/stdlib/backend/python-django.plain +19 -0
  263. package/stdlib/backend/python-fastapi.plain +23 -0
  264. package/stdlib/backend/python-flask.plain +23 -0
  265. package/stdlib/backend/rust-axum.plain +17 -0
  266. package/stdlib/backend/typescript-express.plain +19 -0
  267. package/stdlib/backend/typescript-fastify.plain +19 -0
  268. package/stdlib/backend/typescript-nestjs.plain +19 -0
  269. package/stdlib/cli/common.plain +17 -0
  270. package/stdlib/cli/conformance/common.plain +11 -0
  271. package/stdlib/cli/conformance/go-cobra.plain +9 -0
  272. package/stdlib/cli/conformance/go-flag.plain +9 -0
  273. package/stdlib/cli/conformance/java-picocli.plain +9 -0
  274. package/stdlib/cli/conformance/python-argparse.plain +9 -0
  275. package/stdlib/cli/conformance/python-click.plain +9 -0
  276. package/stdlib/cli/conformance/python-typer.plain +9 -0
  277. package/stdlib/cli/conformance/rust-clap.plain +9 -0
  278. package/stdlib/cli/conformance/typescript-commander.plain +9 -0
  279. package/stdlib/cli/go-cobra.plain +14 -0
  280. package/stdlib/cli/go-flag.plain +14 -0
  281. package/stdlib/cli/java-picocli.plain +15 -0
  282. package/stdlib/cli/python-argparse.plain +18 -0
  283. package/stdlib/cli/python-click.plain +16 -0
  284. package/stdlib/cli/python-typer.plain +16 -0
  285. package/stdlib/cli/rust-clap.plain +14 -0
  286. package/stdlib/cli/typescript-commander.plain +17 -0
  287. package/stdlib/frontend/angular.plain +17 -0
  288. package/stdlib/frontend/common.plain +17 -0
  289. package/stdlib/frontend/conformance/angular.plain +5 -0
  290. package/stdlib/frontend/conformance/common.plain +11 -0
  291. package/stdlib/frontend/conformance/react-vite.plain +5 -0
  292. package/stdlib/frontend/conformance/sveltekit-spa.plain +5 -0
  293. package/stdlib/frontend/conformance/vue-vite.plain +5 -0
  294. package/stdlib/frontend/react-vite.plain +16 -0
  295. package/stdlib/frontend/sveltekit-spa.plain +16 -0
  296. package/stdlib/frontend/vue-vite.plain +16 -0
  297. package/stdlib/lang/go.plain +13 -0
  298. package/stdlib/lang/java.plain +12 -0
  299. package/stdlib/lang/python.plain +18 -0
  300. package/stdlib/lang/rust.plain +12 -0
  301. package/stdlib/lang/typescript.plain +13 -0
  302. package/stdlib/library/common.plain +15 -0
  303. package/stdlib/library/conformance/common.plain +9 -0
  304. package/stdlib/library/conformance/go.plain +9 -0
  305. package/stdlib/library/conformance/java-gradle.plain +9 -0
  306. package/stdlib/library/conformance/java-maven.plain +9 -0
  307. package/stdlib/library/conformance/python.plain +9 -0
  308. package/stdlib/library/conformance/rust.plain +9 -0
  309. package/stdlib/library/conformance/typescript.plain +9 -0
  310. package/stdlib/library/go.plain +12 -0
  311. package/stdlib/library/java-gradle.plain +13 -0
  312. package/stdlib/library/java-maven.plain +13 -0
  313. package/stdlib/library/python.plain +13 -0
  314. package/stdlib/library/rust.plain +12 -0
  315. package/stdlib/library/typescript.plain +15 -0
@@ -0,0 +1,294 @@
1
+ import { createHash } from "node:crypto";
2
+ import { mkdirSync, readFileSync, writeFileSync } from "node:fs";
3
+ import { join, resolve } from "node:path";
4
+ import { ACCEPTANCE_TESTS, DEFINITIONS, FUNCTIONAL_REQUIREMENTS, NON_FUNCTIONAL_REQUIREMENTS, REQUIRED_FUNCTIONAL_SPECS, TEST_REQUIREMENTS, } from "../plain/spec.js";
5
+ import { committedText } from "./lineage.js";
6
+ /**
7
+ * What a module is built from, in two records - see docs/regenerating.md.
8
+ *
9
+ * - The code record, `code/.turri.json`: the spec the code is built from, without what is only
10
+ * for conformance - the test reqs, and the acceptance tests under each functional spec.
11
+ * - The tests record, `tests/.turri.json`: the functional specs the conformance tests are
12
+ * written for - a subset of the code record's, each with its number and its acceptance tests - the
13
+ * definitions and implementation reqs they were written against, and the test reqs they follow.
14
+ *
15
+ * The tests record is always a substate of the code record: a conformance test exercises
16
+ * functionality, and only functionality in the code can be tested. Each record is committed with
17
+ * what it describes, in the module's `.plaingit`, so rolling a unit of work back puts it back too.
18
+ */
19
+ export const RECORD_FILE = ".turri.json";
20
+ /**
21
+ * A functional spec in the code record marked to regenerate again: its code is built, and is to be regenerated again
22
+ * all the same - `regenerate` with functionalities named. The mark makes the record differ from the spec at that item,
23
+ * so the work loop plans it as it plans any change, and the job that regenerates it writes the item back without it.
24
+ */
25
+ export const REGENERATE_AGAIN = "regenerate_again";
26
+ /** Whether a record's item is marked to regenerate again. */
27
+ export function marksRegenerateAgain(item) {
28
+ return typeof item === "object" && item !== null && item[REGENERATE_AGAIN] === true;
29
+ }
30
+ const itemsOf = (tree, heading) => (tree?.[heading] ?? []).slice();
31
+ /** A tree without the sections it has nothing in, as the parser leaves them out. */
32
+ function withoutEmpty(tree) {
33
+ return Object.fromEntries(Object.entries(tree).filter(([, value]) => !Array.isArray(value) || value.length > 0));
34
+ }
35
+ /** A functional spec as the code sees it: without its acceptance tests. */
36
+ function withoutAcceptanceTests(item) {
37
+ const { [ACCEPTANCE_TESTS]: _tests, ...rest } = item;
38
+ return rest;
39
+ }
40
+ /** The spec as the code is built from it: no test reqs, and no acceptance tests under the functional specs. */
41
+ export function codeTree(tree) {
42
+ const { [TEST_REQUIREMENTS]: _testReqs, ...rest } = tree;
43
+ return withoutEmpty({
44
+ ...rest,
45
+ [FUNCTIONAL_REQUIREMENTS]: itemsOf(tree, FUNCTIONAL_REQUIREMENTS).map(withoutAcceptanceTests),
46
+ [REQUIRED_FUNCTIONAL_SPECS]: itemsOf(tree, REQUIRED_FUNCTIONAL_SPECS).map(withoutAcceptanceTests),
47
+ });
48
+ }
49
+ /** Every linked resource in a tree, wherever an item has them - a functional spec, its acceptance tests, a definition, a req. */
50
+ function mapResources(value, map) {
51
+ if (Array.isArray(value))
52
+ return value.map((entry) => mapResources(entry, map));
53
+ if (value === null || typeof value !== "object")
54
+ return value;
55
+ return Object.fromEntries(Object.entries(value).map(([key, entry]) => key === "linked_resources" && Array.isArray(entry) ? [key, entry.map(map)] : [key, mapResources(entry, map)]));
56
+ }
57
+ /**
58
+ * A module's tree with the hash of each linked resource's content beside its link: the file is part of the spec,
59
+ * so a change to what it holds is a change to the item that links it - and, a definition linking it, to every
60
+ * functional spec using the concept. Found as the parser finds it: the spec's own folder first, then the other
61
+ * template folders, then the current directory.
62
+ */
63
+ export function withResourceDigests(tree, dirs) {
64
+ return mapResources(tree, (resource) => {
65
+ for (const path of [...dirs.map((dir) => resolve(dir, resource.target)), resolve(resource.target)]) {
66
+ try {
67
+ return { ...resource, digest: createHash("sha256").update(readFileSync(path)).digest("hex") };
68
+ }
69
+ catch {
70
+ /* not here */
71
+ }
72
+ }
73
+ return resource;
74
+ });
75
+ }
76
+ /**
77
+ * A record written before linked resources were hashed, with the hashes of `spec` where its links are the same:
78
+ * what it was built from is taken to be the resource as it is now, rather than every item linking one built again.
79
+ */
80
+ export function adoptDigests(record, spec) {
81
+ if (record === null)
82
+ return null;
83
+ const digests = new Map();
84
+ mapResources(spec, (resource) => {
85
+ if (resource.digest !== undefined)
86
+ digests.set(`${resource.text}\n${resource.target}`, resource.digest);
87
+ return resource;
88
+ });
89
+ return mapResources(record, (resource) => {
90
+ if (resource.digest !== undefined)
91
+ return resource;
92
+ const digest = digests.get(`${resource.text}\n${resource.target}`);
93
+ return digest === undefined ? resource : { ...resource, digest };
94
+ });
95
+ }
96
+ /** Items of a section, tagged with the module they come from, unless they already say. */
97
+ const taggedWith = (items, module) => items.map((item) => (item.module !== undefined ? item : { ...item, module }));
98
+ /**
99
+ * The spec a codebase is built from: the spec of its whole requires chain, as its code holds it. A
100
+ * codebase is built on the code of the modules below it, so its code implements their definitions and
101
+ * implementation reqs too: those come tagged with their module, bottom of the chain first, and the
102
+ * module's own untagged after them. An item a lower module already has - the same text, from an import
103
+ * they share - is not the module's own again: its code implements it already. The functional specs of the
104
+ * modules below are the ones it inherits, as the parser gives them; its test reqs are its own.
105
+ *
106
+ * `trees`: every module of the chain, bottom first - the module itself last.
107
+ */
108
+ export function chainTree(trees) {
109
+ const own = trees.at(-1);
110
+ if (own === undefined)
111
+ return {};
112
+ const lower = trees.slice(0, -1);
113
+ const section = (heading) => {
114
+ const seen = new Set();
115
+ const out = [];
116
+ for (const { module, tree } of lower) {
117
+ for (const item of taggedWith(itemsOf(tree, heading), module)) {
118
+ const text = item.markdown.trim();
119
+ if (seen.has(text))
120
+ continue;
121
+ seen.add(text);
122
+ out.push(item);
123
+ }
124
+ }
125
+ for (const item of itemsOf(own.tree, heading))
126
+ if (!seen.has(item.markdown.trim()))
127
+ out.push(item);
128
+ return out;
129
+ };
130
+ return withoutEmpty({
131
+ ...own.tree,
132
+ [DEFINITIONS]: section(DEFINITIONS),
133
+ [NON_FUNCTIONAL_REQUIREMENTS]: section(NON_FUNCTIONAL_REQUIREMENTS),
134
+ });
135
+ }
136
+ /**
137
+ * What a new module's codebase starts from, copied from the code of the module it requires: that module's
138
+ * record, as a lower module's - its own definitions and implementation reqs tagged with it, its functional
139
+ * specs among the ones inherited. The new module has built nothing of its own yet.
140
+ */
141
+ export function inheritedRecord(source, sourceModule) {
142
+ const inherited = [
143
+ ...itemsOf(source, REQUIRED_FUNCTIONAL_SPECS),
144
+ ...itemsOf(source, FUNCTIONAL_REQUIREMENTS).map((item, index) => ({ ...withoutAcceptanceTests(item), module: sourceModule, frid: String(index + 1) })),
145
+ ];
146
+ return withoutEmpty({
147
+ [DEFINITIONS]: taggedWith(itemsOf(source, DEFINITIONS), sourceModule),
148
+ [NON_FUNCTIONAL_REQUIREMENTS]: taggedWith(itemsOf(source, NON_FUNCTIONAL_REQUIREMENTS), sourceModule),
149
+ [REQUIRED_FUNCTIONAL_SPECS]: inherited,
150
+ });
151
+ }
152
+ /**
153
+ * What the conformance tests are to be written for, now: of the functional specs the code is built
154
+ * from, those chosen, each as the spec has it now with its acceptance tests - and the code's
155
+ * definitions and implementation reqs, with the test reqs as the spec has them now.
156
+ *
157
+ * Conformance follows the code, never goes ahead of it: a functional spec whose code is not caught up
158
+ * with the spec - its text in the code record is not the spec's, or the code record does not have it at
159
+ * all, as while it is regenerated again - stays as the tests record has it, until the code lands. Its tests are
160
+ * taken out only once the spec and the code have both dropped it.
161
+ */
162
+ export function conformanceTarget(code, tests, spec, chosen = () => true) {
163
+ const built = itemsOf(code, FUNCTIONAL_REQUIREMENTS);
164
+ const now = itemsOf(spec, FUNCTIONAL_REQUIREMENTS);
165
+ const tested = new Map(itemsOf(tests, FUNCTIONAL_REQUIREMENTS).map((item) => [item.frid, item]));
166
+ const inCode = new Set(built.map((item) => item.markdown.trim()));
167
+ const specs = [];
168
+ for (let index = 0; index < Math.max(built.length, now.length); index += 1) {
169
+ const frid = String(index + 1);
170
+ const current = now[index];
171
+ if (current !== undefined && built[index]?.markdown.trim() === current.markdown.trim()) {
172
+ if (chosen(frid))
173
+ specs.push({ ...current, frid });
174
+ continue;
175
+ }
176
+ // The code is behind the spec here - built from other text, not built, or being regenerated again: its tests stay
177
+ // as they are until it catches up. Taken out of the spec, they go once the code has dropped it too.
178
+ const kept = tested.get(frid);
179
+ if (kept === undefined)
180
+ continue;
181
+ if (current !== undefined || inCode.has(kept.markdown.trim()))
182
+ specs.push(kept);
183
+ }
184
+ return withoutEmpty({
185
+ [DEFINITIONS]: itemsOf(code, DEFINITIONS),
186
+ [NON_FUNCTIONAL_REQUIREMENTS]: itemsOf(code, NON_FUNCTIONAL_REQUIREMENTS),
187
+ [TEST_REQUIREMENTS]: itemsOf(spec, TEST_REQUIREMENTS),
188
+ [FUNCTIONAL_REQUIREMENTS]: specs,
189
+ });
190
+ }
191
+ /**
192
+ * Of the functional specs given, by number, those the code record does not have as `tree` has them - not built,
193
+ * or built from other text: no conformance test can be written for them yet. `tree` is a spec, its functional
194
+ * specs numbered by place, or what conformance tests are written for, each with its number.
195
+ */
196
+ export function notBuilt(code, tree, frids) {
197
+ const built = itemsOf(code, FUNCTIONAL_REQUIREMENTS);
198
+ const wanted = itemsOf(tree, FUNCTIONAL_REQUIREMENTS);
199
+ const numbered = wanted.some((item) => item.frid !== undefined);
200
+ return frids.filter((frid) => {
201
+ const item = numbered ? wanted.find((entry) => entry.frid === frid) : wanted[Number(frid) - 1];
202
+ const have = built[Number(frid) - 1];
203
+ return item === undefined || have === undefined || have.markdown.trim() !== item.markdown.trim();
204
+ });
205
+ }
206
+ const asItem = (markdown, acceptanceTests) => ({
207
+ markdown,
208
+ ...(acceptanceTests !== undefined && acceptanceTests.length > 0 ? { [ACCEPTANCE_TESTS]: acceptanceTests.map((test) => ({ markdown: test })) } : {}),
209
+ });
210
+ /**
211
+ * An older record, as the tree it describes. Its own functional specs are taken up to the first
212
+ * that did not build: the tree says what is built, and the one that failed is not.
213
+ */
214
+ function treeFromV2(stored) {
215
+ const own = stored[FUNCTIONAL_REQUIREMENTS].filter((entry) => entry.module === stored.module).sort((a, b) => Number(a.frid) - Number(b.frid));
216
+ const built = [];
217
+ for (const [index, entry] of own.entries()) {
218
+ if (Number(entry.frid) !== index + 1 || entry.unitTests !== "passed")
219
+ break;
220
+ built.push(asItem(entry.spec, entry["acceptance tests"]));
221
+ }
222
+ const required = stored[FUNCTIONAL_REQUIREMENTS]
223
+ .filter((entry) => entry.module !== stored.module)
224
+ .map((entry) => ({ ...asItem(entry.spec, entry["acceptance tests"]), module: entry.module, frid: entry.frid }));
225
+ return withoutEmpty({
226
+ [DEFINITIONS]: (stored[DEFINITIONS] ?? []).map((text) => asItem(text)),
227
+ [NON_FUNCTIONAL_REQUIREMENTS]: (stored[NON_FUNCTIONAL_REQUIREMENTS] ?? []).map((text) => asItem(text)),
228
+ [TEST_REQUIREMENTS]: (stored[TEST_REQUIREMENTS] ?? []).map((text) => asItem(text)),
229
+ [FUNCTIONAL_REQUIREMENTS]: built,
230
+ [REQUIRED_FUNCTIONAL_SPECS]: required,
231
+ });
232
+ }
233
+ function treeFromLegacy(stored) {
234
+ const entries = Object.values(stored.functionalities).sort((a, b) => Number(a.frid) - Number(b.frid));
235
+ const latest = entries.at(-1)?.specifications;
236
+ const built = [];
237
+ for (const [index, entry] of entries.entries()) {
238
+ if (Number(entry.frid) !== index + 1 || entry.unitTests !== "passed")
239
+ break;
240
+ built.push(asItem(entry.specifications[FUNCTIONAL_REQUIREMENTS]?.at(-1) ?? "", entry.specifications[ACCEPTANCE_TESTS]));
241
+ }
242
+ return withoutEmpty({
243
+ [DEFINITIONS]: (latest?.[DEFINITIONS] ?? []).map((text) => asItem(text)),
244
+ [NON_FUNCTIONAL_REQUIREMENTS]: (latest?.[NON_FUNCTIONAL_REQUIREMENTS] ?? []).map((text) => asItem(text)),
245
+ [TEST_REQUIREMENTS]: (latest?.[TEST_REQUIREMENTS] ?? []).map((text) => asItem(text)),
246
+ [FUNCTIONAL_REQUIREMENTS]: built,
247
+ });
248
+ }
249
+ /** A record's text as the tree it holds, whatever layout wrote it - or null when it is not one. */
250
+ function readRecordText(text) {
251
+ if (text === undefined)
252
+ return null;
253
+ let raw;
254
+ try {
255
+ raw = JSON.parse(text);
256
+ }
257
+ catch {
258
+ return null;
259
+ }
260
+ if (raw === null || typeof raw !== "object" || Array.isArray(raw))
261
+ return null;
262
+ if (typeof raw.version === "number")
263
+ return treeFromV2(raw);
264
+ if ("functionalities" in raw)
265
+ return treeFromLegacy(raw);
266
+ return raw;
267
+ }
268
+ function readFileText(path) {
269
+ try {
270
+ return readFileSync(path, "utf8");
271
+ }
272
+ catch {
273
+ return undefined;
274
+ }
275
+ }
276
+ /**
277
+ * A record in `dir`: as the module's last commit has it, by default - what the harness itself wrote
278
+ * when the last unit of work landed, never what an agent's shell changed since - or, with `onDisk`,
279
+ * the file as it is. Null when there is none.
280
+ */
281
+ export function readRecord(dir, options = {}) {
282
+ const committed = options.onDisk === true ? undefined : committedText(dir, RECORD_FILE);
283
+ return readRecordText(committed ?? readFileText(join(dir, RECORD_FILE)));
284
+ }
285
+ /** Write the code record: the tree, as the code sees it. */
286
+ export function writeCodeRecord(codeDir, tree) {
287
+ mkdirSync(codeDir, { recursive: true });
288
+ writeFileSync(join(codeDir, RECORD_FILE), `${JSON.stringify(codeTree(tree), null, 2)}\n`);
289
+ }
290
+ /** Write the tests record: a conformance target, as it now is built. */
291
+ export function writeTestsRecord(testsDir, tree) {
292
+ mkdirSync(testsDir, { recursive: true });
293
+ writeFileSync(join(testsDir, RECORD_FILE), `${JSON.stringify(withoutEmpty(tree), null, 2)}\n`);
294
+ }