@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,245 @@
1
+ # Authoring \*\*\*plain
2
+
3
+ Rendering turns a spec into code; authoring is the half that decides what gets built. The code is
4
+ only ever as good as the spec - a fact in the wrong section is silently ignored, a functional spec
5
+ that is too big comes out half-built, two specs that contradict each other break each other's
6
+ tests. So this repo carries as much for writing specs as for rendering them: the authoring rules,
7
+ a library of skills that apply them, and independent review of what they write.
8
+
9
+ The rule underneath all of it: **the specs are the source of truth**. The generated code in
10
+ `plain_modules/` is never edited by hand. A bug is fixed in the spec, and a change that is wanted
11
+ goes into the spec - that is what persists and what the next render builds from.
12
+
13
+ ## A \*\*\*plain project
14
+
15
+ ```
16
+ my-project/
17
+ app.plain # a root module: what the software does
18
+ users.plain # another module, perhaps one that `requires` app
19
+ template/ # import modules: shared definitions and reqs
20
+ resources/ # files specs link to: schemas, API specs, sample data
21
+ test_scripts/ # scripts that run the unit and conformance tests
22
+ plain_modules/ # generated - code, tests, history; never edited by hand
23
+ ```
24
+
25
+ ## What a spec is made of
26
+
27
+ A `.plain` file is one module: optional YAML frontmatter, then up to four sections, in this order.
28
+
29
+ | section | holds | e.g. |
30
+ | --- | --- | --- |
31
+ | `***definitions***` | the concepts, written `:CamelCase:` - every concept is defined before it is used | `- :Task: is a unit of work with a title and a due date.` |
32
+ | `***implementation reqs***` | HOW it is built: stack, architecture, coding standards, and everything about unit tests | `- :Implementation: is written in Python 3.12.` |
33
+ | `***test reqs***` | everything about the black-box conformance tests: framework, how they run, mocking | `- :ConformanceTests: use pytest and call the CLI as a subprocess.` |
34
+ | `***functional specs***` | WHAT it does - observable, language-agnostic behaviour, one functionality per item, in the order they are built | `- A :User: can add a :Task:.` |
35
+
36
+ Under a functional spec, a nested `***acceptance tests***` list states how that one
37
+ functionality is checked end to end.
38
+
39
+ The frontmatter connects modules:
40
+
41
+ | field | |
42
+ | --- | --- |
43
+ | `description` | one line on what the module is |
44
+ | `import` | take the definitions, implementation reqs and test reqs of an import module - shared content, no functional specs of its own |
45
+ | `requires` | build on top of another module: its functional specs and its generated code come first |
46
+ | `exported_concepts` | which concepts a module that `requires` this one may use |
47
+ | `required_concepts` | concepts an import module expects the importing module to define |
48
+
49
+ Beyond that: a spec links to files under `resources/` with markdown link syntax, and the file's
50
+ contents go to the renderer with the spec; lines starting with `>` are comments for people; and
51
+ files are processed with Liquid first, so a template can be included with parameters.
52
+
53
+ **Section ownership is the rule that bites.** The renderer reads each kind of fact only from the
54
+ section that owns it. A tech choice written into a functional spec, or a behaviour written into
55
+ the implementation reqs, is not flagged - it is simply not read.
56
+
57
+ ## The standard library
58
+
59
+ turri ships a standard library of import modules in `stdlib/` that settle everything about a
60
+ generic piece of software on one stack except what it does: its language and framework, layout,
61
+ configuration, error handling, logging, shutdown, and how it is unit and conformance tested. A spec
62
+ imports one and writes only its own concepts and functional specs:
63
+
64
+ ```plain
65
+ ---
66
+ import:
67
+ - stdlib/backend/python-flask
68
+ - stdlib/backend/conformance/python-flask # leave it out to skip conformance tests
69
+ ---
70
+
71
+ ***functional specs***
72
+
73
+ - ...
74
+ ```
75
+
76
+ | kind | stacks - each with `stdlib/<kind>/conformance/<stack>` beside it |
77
+ | --- | --- |
78
+ | `stdlib/backend/` | `python-fastapi`, `python-flask`, `python-django`, `typescript-express`, `typescript-fastify`, `typescript-nestjs`, `java-springboot`, `go-nethttp`, `go-gin`, `rust-axum` |
79
+ | `stdlib/frontend/` | `react-vite`, `vue-vite`, `sveltekit-spa`, `angular` |
80
+ | `stdlib/cli/` | `python-typer`, `python-click`, `python-argparse`, `typescript-commander`, `go-cobra`, `go-flag`, `rust-clap`, `java-picocli` |
81
+ | `stdlib/library/` | `python`, `typescript`, `java-maven`, `java-gradle`, `go`, `rust` |
82
+
83
+ It is built in three layers, and a stack imports the other two:
84
+
85
+ - **The language** - `stdlib/lang/python`, `typescript`, `java`, `go`, `rust`: the version, the
86
+ dependency manager, formatting, linting and type checking (ruff and mypy for Python; Prettier, ESLint
87
+ and `tsc --noEmit` for TypeScript), how a failure is reported (an exception, a returned error, a
88
+ `Result`), the logging library, and the unit test framework and layout. A module on a stack the library does not have can import its language alone.
89
+ The formatter, linter and type checker a language names are enforced by the testing scripts, which
90
+ run them with the tests - the unit testing script on the code, the conformance testing script on the
91
+ tests - and count them as the tests (see [the static checks](regenerating.md#the-static-checks)).
92
+ - **The kind's `common`** - what every stack of a kind shares:
93
+
94
+ | kind | settles |
95
+ | --- | --- |
96
+ | backend | settings from the environment into one typed object, checked at start (exit 1 when one is missing); handler, service and repository layers; domain errors turned in one place into `{"error": {"code", "message"}}` - `not_found` 404, `conflict` 409, `validation_error` 422 with `details` per field, `unauthenticated` 401, `forbidden` 403, `internal_error` 500 with no message and the stack trace logged; JSON logs on standard output with `timestamp`, `level`, `logger`, `message` and `request_id`, level from `LOG_LEVEL`, one line per request, never a secret or a body; `X-Request-ID` taken or made and sent back; shutdown on SIGTERM within 10 seconds |
97
+ | frontend | one API layer that turns a failed call into an `ApiError` with the backend's error code, or `network_error`; errors shown in place, and a page for anything else at the root; one logger module, no `console` elsewhere; accessible roles and test ids |
98
+ | cli | settings from flags, then `<TOOL>_<SETTING>`, then defaults; the work apart from parsing; errors as `Error: <message>` on standard error, exit 0, 1, 2 for a usage error, 130 on Ctrl-C, a stack trace only with `--debug`; logs on standard error, warnings by default, `--verbose`, `--debug` |
99
+ | library | one public entry point; one base error type, with a dependency's errors wrapped; logs through the language's logging with no output set up; no environment variables, output or global state |
100
+
101
+ - **The stack** - the framework and how it does each of those: for `python-flask`, an application
102
+ factory, one Blueprint per resource, Pydantic for bodies, `@app.errorhandler` for the error body,
103
+ structlog JSON logs with the request id bound in a hook, Gunicorn. For `go-cobra`, `RunE`
104
+ commands with Cobra's own error printing off, so `main` alone writes the error and picks the exit
105
+ code, and slog on standard error.
106
+
107
+ Each conformance template imports its kind's `conformance/common`: a backend's tests start the
108
+ built service on a free port and reach it over HTTP only, checking a failure by status and
109
+ `error.code`; a frontend's run the served build in Chromium through Playwright with the backend
110
+ stood in; a command-line tool's run the built tool as a process; a library's go through its public
111
+ entry point.
112
+
113
+ - **Conformance tests apart.** A spec without the conformance template has no test reqs, so no
114
+ conformance tests are written or run for it. Turning them on later is adding that one import.
115
+ - **A reserved name.** An import that starts with `stdlib/` is always turri's template, never
116
+ a file of the project's. A project with a module at such a path - `stdlib/backend/python-fastapi.plain`
117
+ beside the spec, in its `template/` folder or in `PLAIN_TEMPLATE_DIRS` - gets an error saying the
118
+ name is reserved, so which one a spec builds on never depends on what is on disk beside it.
119
+ - **No concepts.** Its requirements are on the predefined `:Implementation:`, `:UnitTests:` and
120
+ `:ConformanceTests:` only, so it never collides with a spec's own definitions.
121
+ - **Pinned to a toolchain.** It names its stack's toolchain - Python 3.12, Node.js 24, Java 21, Go 1.25, Rust -
122
+ which the agents use from this machine; `turri doctor` says when the machine lacks it.
123
+ - **Not storage, not the testing scripts.** A module with a database says which on top of its
124
+ stack. It still needs its unit testing script, and a conformance testing script when it has
125
+ conformance tests.
126
+ - **Known to the agents.** The `stdlib` rule, in every spec-writing agent's system prompt, says when
127
+ to import which, and lists every template with what it is and the folder it is in, read from
128
+ `stdlib/` when the rules load. `spawn` and `brood` import a stack when the one chosen
129
+ is in it, and its conformance template when conformance tests are on.
130
+ - **Only what this machine can build.** The agents use this machine's toolchains and install none, so
131
+ a Go stack on a laptop without Go stops at its first build. Every spec-writing agent and the
132
+ conversation are told which of the standard library's languages the machine can build now - Python
133
+ needs `uv`, TypeScript and every frontend `node` and `npm`, Go `go`, Rust `cargo`, Java a JDK
134
+ (`src/stack.ts`, `toolchainsBlock`) - and `spawn` and `brood` offer, and choose, only stacks in those.
135
+ A stack the person names in a language the machine lacks is not refused: they are told what to install
136
+ first, and that `turri doctor` checks it.
137
+
138
+ A requirement beyond the stack - storage, an outside service - is added on top of it. A module that
139
+ differs from its stack on something the stack settles - another framework, another layout - writes
140
+ its own instead: an import cannot be taken back in part.
141
+
142
+ ## The rules
143
+
144
+ `genome/rules/` holds the authoring rules. They are in the system prompt of every agent that
145
+ writes specs - the conversation, the interview skills, the reviewers - so they are not something
146
+ an agent has to decide to go and read.
147
+
148
+ | rule | covers |
149
+ | --- | --- |
150
+ | `module-structure.md` | the file skeleton, frontmatter fields, section order, module kinds, section ownership |
151
+ | `definitions.md` | concept syntax, predefined concepts, define before use, no circular definitions, granularity |
152
+ | `func-specs.md` | voice, the complexity limit, chronological order, no conflicts, language agnosticism, testability, acceptance tests |
153
+ | `impl-reqs.md` | HOW not WHAT, unit tests live here, what belongs and what does not |
154
+ | `test-reqs.md` | conformance tests only, what belongs, test types |
155
+ | `concise-specs.md` (+ two example banks) | keeping specs short enough for a person to review: no obvious lines, plain words, no uncheckable adjectives, no hedging |
156
+ | `bullet-continuation.md` | bullets and continuation lines in every section |
157
+ | `linked-resources.md` | a linked resource is one text file on disk, or a PNG or JPEG image for a model that reads images; protocol artifacts are linked, never transcribed |
158
+ | `import-modules.md`, `requires-modules.md` | the two ways modules build on each other |
159
+ | `exported-concepts.md`, `required-concepts.md` | which concepts cross module boundaries |
160
+ | `integrations.md` (+ standalone, embedded, embedded testing) | specs for REST API integrations, deployed standalone or embedded in an existing codebase |
161
+ | `stdlib.md` | the standard library: when to import which template, and every template with what it is and where it is, read from `stdlib/` |
162
+
163
+ The ones that shape a spec most:
164
+
165
+ - **One functionality is at most ~200 lines of code.** A bigger one is split before it is written
166
+ (`analyze-if-func-spec-too-complex`, `break-down-func-spec`).
167
+ - **Functional specs are in build order.** Each is rendered on top of the ones before it.
168
+ - **They state facts, with a concrete subject.** "A :User: can add a :Task:" - not "Implement
169
+ adding tasks", not "The system should…".
170
+ - **Nothing a test cannot check.** No `robust`, `seamless`, `as appropriate`; state the checkable
171
+ fact or cut the word.
172
+ - **No two specs contradict each other.** A conflict is found and resolved when a spec is added,
173
+ not when its tests fail.
174
+
175
+ ## Writing specs with turri
176
+
177
+ In a conversation (`turri`), say what you want and the matching skill runs. Two build a
178
+ project from scratch, and write each spec change as a git diff that is built in the background the
179
+ moment it is written. `brood` interviews you one question at a time and writes the specs as
180
+ you answer; `spawn`, its quick twin, asks only what you want to build and which stack - or leaves the stack
181
+ to it - and writes the rest on its own:
182
+
183
+ | skill | phases | how a spec change is written |
184
+ | --- | --- | --- |
185
+ | `brood` | intent → stack → testing → product, feature by feature | first, how many questions the stack and product phases may ask - light 3, medium 5, thorough 10 or best judgement, broader with fewer, covering the same ground - and what they leave open turri decides itself, each decision a spec change of its own; every product answer is still a spec change, built before the next question; in testing, whether there are conformance tests, their `***test reqs***` and script - written as files, building nothing; then one spec change per answer, across whatever sections it needs, as a git diff with `write_spec_diff`, rendered as a render spec diff job and conformance tested as it is built |
186
+ | `spawn` | intent → groundwork → product | only the intent and the stack are asked - the stack one question, `You choose` first, skipped when the user named one; the stack and the unit tests are written as files, with conformance tests off unless the user asks for them; then one spec change per feature, in build order, as a git diff with `write_spec_diff`, one after another with no answer between them. Changes after it are asked for in the conversation, as spec diffs, and what is left to build is worked out again from the spec |
187
+
188
+ `mature` starts from a spec you already have, rough or hand-written, and runs on its own until
189
+ it is fully rendered - every functionality built from the spec as it stands, its tests passing. It
190
+ renders what is not built and watches the agents building it; when one struggles on something the
191
+ spec leaves open or unclear - sent back again and again, the judge going back and forth, a spec
192
+ conflict - it writes what the spec should say as a spec diff, stops that build, and renders it again.
193
+ What the builds had to decide themselves - their assumptions, pitfalls, a failed build's diagnosis -
194
+ goes into the spec the same way, when a user, a caller or a test can see it. It never asks: it takes
195
+ the reading that fits the rest of the spec, and marks each such decision in its report. It leaves
196
+ code, environment and turri problems to the builds, and stops when a functionality keeps
197
+ struggling after the spec was clarified, when two cycles change nothing, or after twenty.
198
+
199
+ For an existing project, `bud` works the same way, scoped to one feature: intent → scope →
200
+ product → review. After a short feature-intent interview it asks product questions one at a time -
201
+ a trigger and its outcome, an edge case, a rule - and every answer is one spec change, a git diff
202
+ written with `write_spec_diff` and built the moment it is written. A change to something already
203
+ specified, built or still to build, is put to the user first and is a spec diff too: what is left to
204
+ build is worked out again from the spec.
205
+
206
+ What keeps the interview honest is the harness, not the interviewer:
207
+
208
+ - **Every question offers choices** (`ask_user` refuses a question without them), and each
209
+ answer maps to one spec change.
210
+ - **Every answer is written before the next question.** In `brood`'s product phase `ask_user` refuses
211
+ a new question until the last answer is a spec change - see [skills and gates](skills-and-gates.md).
212
+ - **Each phase ends on the person's confirmation** of a short summary, not on the interviewer's word.
213
+
214
+ ## The skills
215
+
216
+ Every skill in `genome/skills/` can be run from a conversation; most are also used by the bigger ones. A project's own,
217
+ in `.turri/skills/` or `.agents/skills/`, can be too (see [skills and gates](skills-and-gates.md#a-projects-own-skills)).
218
+
219
+ | to… | skills |
220
+ | --- | --- |
221
+ | start a project | `brood`, `spawn`, `init-plain-project` |
222
+ | make a rough spec build | `mature` |
223
+ | add to a spec | `bud`, `add-functional-spec`, `add-functional-specs`, `add-concept`, `add-implementation-requirement`, `add-test-requirement`, `add-acceptance-test`, `add-resource`, `add-template` |
224
+ | check what is written | `analyze-if-func-spec-too-complex`, `analyze-2-func-specs`, `analyze-func-specs`, `resolve-section-ownership` |
225
+ | fix what is written | `break-down-func-spec`, `resolve-spec-conflict`, `debug-specs` |
226
+ | restructure modules | `create-import-module`, `create-requires-module`, `refactor-module`, `consolidate-concepts` |
227
+ | set up the project to build | `implement-unit-testing-script`, `implement-conformance-testing-script`, `check-plain-env` |
228
+
229
+ `debug-specs` is the one for a running application that misbehaves: it reads the generated code
230
+ to find the cause, traces it back to the spec, and fixes only the `.plain` files.
231
+
232
+ ## Checking a spec
233
+
234
+ ```bash
235
+ turri validate app.plain # or validate in a conversation
236
+ ```
237
+
238
+ It parses the spec with this repo's own parser, resolves its imports and required modules, and
239
+ reports every syntax, concept and structure error with its line - no model, no API key. The
240
+ interview skills run it before a phase ends, and `write_spec_diff`
241
+ refuses a change that leaves a file that does not parse. Pass the project directory to check every
242
+ module at once.
243
+
244
+ What `check` cannot see - a fact in the wrong section, a spec that is too big, two that conflict,
245
+ wording no test can check - is what the rules, the skills and the reviewers are for.
package/docs/config.md ADDED
@@ -0,0 +1,353 @@
1
+ # The project config
2
+
3
+ A project says how its specs are built in `config.turri.yaml`, at the project root. Everything in it
4
+ is optional: with no file at all, turri finds the testing scripts on its own, turns conformance
5
+ tests on wherever a module has test reqs, and runs on pi's configured model. A project whose
6
+ parts are on different stacks - a backend and a frontend - adds a file per stack,
7
+ `config.turri.<stack>.yaml`, beside it, and chooses the stack to build with (see
8
+ [stacks](#stacks)). turri never writes a `config.yaml`: it may be another tool's - codeplain's - and
9
+ is read only by a project that has no file of turri's yet (see [the old config.yaml](#the-old-configyaml)).
10
+
11
+ `turri init-config` starts one: the two models, a strong one to talk to and a smaller one to write the code. It
12
+ asks which vendor's models you use - `--vendor` says it without asking, and with neither, as from a script, it is
13
+ `other`:
14
+
15
+ | vendor | harness-model | coding-model | key pi reads |
16
+ | --- | --- | --- | --- |
17
+ | `anthropic` | `anthropic/claude-opus-5-5` | `anthropic/claude-haiku-5-5` | `ANTHROPIC_API_KEY` |
18
+ | `openai` | `openai/gpt-6-astra` | `openai/gpt-6-luna` | `OPENAI_API_KEY` |
19
+ | `google` | `google/gemini-3.1-pro-preview` | `google/gemini-3.1-flash-lite` | `GEMINI_API_KEY` |
20
+ | `amazon-bedrock`, Anthropic | `amazon-bedrock/<region>.anthropic.claude-opus-5-5` | `amazon-bedrock/<region>.anthropic.claude-haiku-5-5` | `AWS_PROFILE` or `AWS_BEARER_TOKEN_BEDROCK`, and `AWS_REGION` |
21
+ | `amazon-bedrock`, OpenAI | `amazon-bedrock/<region>.openai.gpt-6-astra` | `amazon-bedrock/<region>.openai.gpt-6-luna` | `AWS_PROFILE` or `AWS_BEARER_TOKEN_BEDROCK`, and `AWS_REGION` |
22
+ | `other` | `anthropic/claude-opus-5-5` | `google/gemini-3.1-flash-lite` | `ANTHROPIC_API_KEY`, `GEMINI_API_KEY` |
23
+
24
+ Amazon Bedrock asks two more things - whose models, Anthropic's or OpenAI's (`--maker`), and the region of the
25
+ inference profile they run in (`--region`): `global`, `us`, `eu`, `jp` or `au` for Anthropic's, `global` or `us` for
26
+ OpenAI's. It says which `AWS_REGION` to set for it - pi calls Bedrock in `us-east-1` when none is. A `config.turri.yaml`
27
+ already there keeps everything but its two models, which it sets; `--force` writes the whole file again.
28
+
29
+ Each is written with how hard it thinks - `harness-model: openai/gpt-6-astra:medium`, `coding-model:
30
+ openai/gpt-6-luna:low` - so the file shows it. The reviews and judges run on the coding model but think medium (see
31
+ [models per role](#models-per-role)).
32
+
33
+ They are an example, not a requirement - any model [pi](https://pi.dev) supports works, signed in any way pi
34
+ supports; `other` is the starting point for another provider. What we recommend is that split, a capable model to
35
+ talk to and a smaller, cheaper one for the coding. Once written, it says what to do next: the key to set in your
36
+ shell - or `/login` in turri, which pi keeps in `~/.pi/agent/auth.json` - and that the file belongs to that folder:
37
+ turri reads the nearest `config.turri.yaml` upwards from where it starts, so one written anywhere else is not seen.
38
+
39
+ Below them it writes every other setting out, each with what it does and the value turri uses for it - `review: false`,
40
+ `turn-limit: 120`, `parallel-jobs` as this machine's cores make it - so the file says how the project is built, and you
41
+ change one by changing it. The optional ones, which have no value of their own - the testing scripts (written in by the
42
+ skill that writes them), `module`, `conformance-budget`, `judge-model`, `models` - are there commented out, each with an
43
+ example: uncomment one to use it. A key taken out of the file is still read as the table below says.
44
+
45
+ Beside a `config.yaml` - codeplain's - the new file takes from it the keys turri reads too, each in its place, its
46
+ testing scripts above all, and says which it left (see [the old config.yaml](#the-old-configyaml)); `config.yaml`
47
+ is left as it is. A `config.turri.yaml` already there keeps everything but its two models, which it sets - `--force` writes the whole
48
+ file again - and a `config.yaml` turri read until then is copied to it first. It takes a folder other than the current
49
+ one as its argument.
50
+
51
+ ## Where it is read from
52
+
53
+ A spec belongs to its **project**: the nearest folder, from the spec's own folder up, that holds a
54
+ `config.turri.yaml`, a stack's `config.turri.<stack>.yaml`, the old `config.yaml` or a `plain_modules/`.
55
+ Without any of them, the spec's own folder is the project. That folder's config is the one read, and
56
+ its `plain_modules/` is where the code is built, wherever turri was started. So a project that keeps
57
+ its specs in `plain/`, with `plain/config.turri.yaml`, builds `plain/app.plain` by
58
+ `plain/config.turri.yaml` even when `turri` runs in the folder above.
59
+
60
+ When it is read:
61
+
62
+ - **In the chat**, afresh: the chat's agent is told what it says on every turn, and each render
63
+ reads it when it is queued. An edit to the file holds from the next turn and the next render, with
64
+ no restart. The agent is told the config of the session's folder and of the projects one folder
65
+ down.
66
+ - **On the command line**, once, when the command starts. `turri regenerate` takes `module:` from
67
+ the folder it is run in, and the rest from the spec's own project.
68
+
69
+ ## Changing it from the chat
70
+
71
+ `/turri-config` changes any key in the chat, and writes it into the `config.turri.yaml` of the folder turri was started in -
72
+ its comments, and the commented-out examples, kept. Alone, it lists every key with what it is now, and asks for the
73
+ value of the one you pick the way the key takes it: `true` or `false`, one of its words, a number from a few offered or
74
+ typed, a model from those this machine can reach and then how hard it thinks, a script or a `.plain` file the project
75
+ has, rules ticked on and off. `/turri-config review` asks for review's value; `/turri-config review false` sets it, and
76
+ `/turri-config review unset` takes it out, back to its default. Completion offers the keys, then each one's values.
77
+ Each role's model and turn limit is `models.<role>` and `models.<role>.turn-limit` - `models.coding`,
78
+ `models.planner.turn-limit`.
79
+
80
+ Each change says when it takes effect. None needs turri started again - what turri fixes as it starts is changed as it
81
+ runs:
82
+
83
+ | when | keys |
84
+ | --- | --- |
85
+ | now | `harness-model` and `models.chat` - the conversation switches to it; `parallel-jobs` - a pulse waiting for a slot starts if there is one now; `allow-to-stop-jobs` |
86
+ | your next message | `rules-off` - for the conversation; a skill's agents from the next skill run |
87
+ | the next pulse - one running keeps what it started with | `coding-model`, `judge-model`, every other `models.<role>`, `review`, `turn-limit`, `compact-at`, `agent-env`, `toolchains` |
88
+ | the next time the work is planned | `conformance-tests`, `conformance-budget`, `max-specs-per-pulse`, `max-lines-per-pulse` |
89
+ | the next test run - the running pulse's too | the testing scripts, `testing-scripts-run-on` |
90
+ | the next pulse that lands - the running one too | `release-after-pulse` |
91
+ | the next regeneration that names no `.plain` file | `module` |
92
+
93
+ A coding model chosen this session - `--coding-model`, or `engine` - gives way to one set here: the last word wins. It
94
+ says, too, what wins over the change: `models.chat` over `harness-model` (and so on), and a stack's
95
+ `config.turri.<stack>.yaml`, which is not changed here, while that stack is chosen. In a project still read from
96
+ `config.yaml`, the first change copies it to `config.turri.yaml` first - see [the old config.yaml](#the-old-configyaml).
97
+
98
+ ## The keys
99
+
100
+ ```yaml
101
+ module: app.plain # what to build when no .plain file is named
102
+ harness-model: # the model you chat with
103
+ model: anthropic/claude-opus-5-5
104
+ thinking: high
105
+ coding-model: # every render (default: harness-model)
106
+ model: amazon-bedrock/eu.anthropic.claude-sonnet-5
107
+ thinking: low
108
+ judge-model: # the verdicts of a conformance run (default: coding-model)
109
+ model: amazon-bedrock/eu.anthropic.claude-sonnet-5
110
+ thinking: medium
111
+ conformance-tests: true # write and run conformance tests where there are test reqs (default: false)
112
+ unittests-script: test_scripts/run_unittests_python.sh
113
+ conformance-tests-script: test_scripts/run_conformance_tests_python.sh
114
+ testing-scripts-run-on: copy # what the testing scripts run on: copy - a fresh copy (default: in-place - the code itself)
115
+ turn-limit: 150 # seconds each agent turn has (default: 120; 0 for none)
116
+ models: # each agent role's model, thinking and turn limit - see below
117
+ default: amazon-bedrock/global.openai.gpt-6-luna
118
+ coding: { model: amazon-bedrock/eu.anthropic.claude-sonnet-5, thinking: high, turn-limit: 180 }
119
+ explorer: google/gemini-3.1-flash-lite
120
+ ```
121
+
122
+ | key | what it sets | left out |
123
+ | --- | --- | --- |
124
+ | `module` | the `.plain` file `turri regenerate` and the chat's `regenerate` build when none is named, from the project root | a file has to be named |
125
+ | `harness-model` | the model `turri` starts on - the one you talk to - the one that diagnoses a job that failed for good, and, in the conversation, the planner that cuts the work into jobs (on whatever model the chat is on when it plans); under `turri regenerate`, the planner too | pi's configured model |
126
+ | `coding-model` | every render: functionalities, spec diffs, conformance tests, repairs, planning spec diffs, the explorer | `harness-model` |
127
+ | `judge-model` | the judge and the test review of a conformance run | `coding-model` |
128
+ | `conformance-tests` | `all` (or `true`): conformance tests for every functional spec of every module with test reqs, written once each module's code is built, before anything is built on it. `chosen`: for the functional specs chosen - every one with acceptance tests, and those the chooser picks as each code pulse ends, from what that pulse regenerated. `module`: the same, but the chooser is asked once, when the whole module is regenerated, and plans the conformance pulses too. Under both, the choice is made inside the pulse, before anything is built on the module, and the pulse's report says what was chosen and why; those the person asks for with `conform` are written too; once chosen, a functional spec keeps them, and they are rewritten when it changes - see [who chooses, and when](regenerating.md#conformance-tests-in-the-work-loop). `false`: none written, and none of the existing ones run - until the conversation asks with `conform` | `false` |
129
+ | `conformance-budget` | under `conformance-tests: chosen` or `module`: how many of a module's functional specs may be chosen for conformance tests, beside those with acceptance tests - a count (`2`) or a share of its functional specs (`30%`, rounded up, at least one). Every choice for the module counts, whenever it was made; the chooser is told what is left, and can choose no more. Your own `conform` is not limited. A stack's `config.turri.<stack>.yaml` can set its own | no limit |
130
+ | `review` | `true` or `false`: whether the code and its unit tests - and the conformance tests - are reviewed once they pass: two reviews at most, the second weighed by the arbiter; off, a job lands once its tests pass - see [regenerating the code](regenerating.md#the-code-and-its-unit-tests-are-reviewed) | `false` |
131
+ | `rules-off` | a list of rule names - `integrations`, `concise-specs`, the files of `genome/rules/` without `.md` - left out of the conversation's prompt and every spec-writing agent's: a rule this project's own skills or AGENTS.md replace, such as one that says to probe a live API before any spec is written. The project's alone | none |
132
+ | `allow-to-stop-jobs` | `true` or `false`: whether turri stops a running job the spec has moved on from - a functional spec it regenerates changed, or a file one links was edited - rolls it back and regenerates the spec as it now is. `false`: the job runs to its end and lands, and what changed since is regenerated on top of it by the next job - see [regenerating the code](regenerating.md). Your own `halt` stops a job either way | `true` |
133
+ | `release-after-pulse` | `true` or `false`: whether every pulse that lands code releases it, in the chat, as its last stage - before the module's next pulse starts, whatever the chat is doing: an agent on the coding model looks for the project's release guidance as the chat does (what was passed to `release` this conversation, AGENTS.md, the project's skills) and, when it says to release after a build, does what it says - see [deploy](deploy.md#after-every-pulse). One release a pulse that lands; a release that fails is said in the pulse's report, and what it landed stays | `false` |
134
+ | `unittests-script` | the unit testing script, by path from the project root - it runs the code's static checks too, exiting 80 when only they fail (see [the static checks](regenerating.md#the-static-checks)) | looked for - see below |
135
+ | `conformance-tests-script` | the conformance testing script, by path from the project root | looked for - see below |
136
+ | `prepare-environment-script` | optional, as codeplain has it: run as `<script> <build>` before every run of the conformance testing script, which runs only once it passes - see [the testing scripts](regenerating.md#the-testing-scripts). turri never writes one | the one `prepare_environment*.sh` at the root or in `test_scripts/`, else none |
137
+ | `compact-at` | how many tokens of work - past its instructions - an agent kept through a job holds before the harness compacts it between turns; at least 1000; the project's alone | 100,000 - see [agent sessions](regenerating.md#agent-sessions) |
138
+ | `max-specs-per-pulse` | the most functional specs the planner groups into one pulse - what builds one feature goes together, up to this many; a stack's file wins | 3 |
139
+ | `max-lines-per-pulse` | the most lines of spec in one pulse - its functional specs with the definitions they bring; a group past it is split again, a functional spec a pulse; a stack's file wins | 120 |
140
+ | `parallel-jobs` | how many jobs the chat runs at once - modules that share nothing build side by side up to it; read when the chat starts, from the folder it starts in | half this machine's cores, between one and four - see [how many jobs at once](#how-many-jobs-at-once) |
141
+ | `agent-env` | credentials the agents' commands need while they work - a live API probed, tests run against a real service: `files: [...]`, dotenv files by path from the project root, every variable in them; `vars: [...]`, variables by name from the environment turri was started in. A stack's `config.turri.<stack>.yaml` adds its own to the project's. See [credentials for the agents](#credentials-for-the-agents) | none: an agent's commands get no credential |
142
+ | `toolchains` | folders of this machine an agent's commands may read beside the system's, the `PATH`'s and those the toolchain variables point at - a toolchain installed somewhere unusual (`[/srv/zig]`). A stack's add to the project's. See [the OS sandbox](sandbox.md#the-os-sandbox) | none |
143
+ | `turn-limit` | seconds each turn of an agent a job starts has, for every role without its own (`models:`); `0` for none. The config is the only place it is set. A turn past it ends at its next request to the model - see [regenerating the code](regenerating.md) | 120 |
144
+ | `models` | each agent role's own model, thinking and turn limit - see [models per role](#models-per-role) | every role on `coding-model`, the conversation's side on `harness-model` |
145
+ | `testing-scripts-run-on` | what the testing scripts run on - always on this machine, in your environment. `in-place`: the real build and tests folders, the script at its own path, from the project - so a script that finds a host codebase from its own path finds it. `copy`: a fresh copy of the code, staged in `plain_modules/.turri/scripts/`, with the script copied beside it; it gives the coding agent `can_it_fail` (see [the testing scripts](regenerating.md#the-testing-scripts)) | `in-place` |
146
+
147
+ Keys are written with dashes; underscores are read the same (`conformance_tests`). A key turri
148
+ does not know is ignored - a codeplain `config.yaml`, read while it is the old file, carries several - and so is a value of the
149
+ wrong type, as if it were not there. `model:`, from before there were separate models, is read as
150
+ `coding-model`.
151
+
152
+ ## Models per role
153
+
154
+ `models:` gives each kind of agent its own model, thinking level and turn limit. Each entry is a model
155
+ (`provider/id`, or `provider/id:thinking`) or `{ model, thinking, turn-limit }`, any of which may be left out:
156
+
157
+ | role | the agents |
158
+ | --- | --- |
159
+ | `default` | every role not named below - but the conversation's side |
160
+ | `chat` | `turri`'s model - the same as `harness-model`, which it wins over |
161
+ | `chooser`, `planner`, `diagnosis` | the conversation's side: the conformance chooser of `turri regenerate`, the planner's agents, the diagnosis of a failed job. Unnamed, the chat's model |
162
+ | `release` | `turri release`'s agent and release-after-pulse's: it follows steps - a script, a start command - so it runs on the coding model, thinking low |
163
+ | `coding` | the coding agent, the code fixer of a conformance run and the repair agent - the same as `coding-model`, which it wins over. `--model` on the command line, or a model chosen in the conversation (`engine`), wins over both |
164
+ | `conformance` | the conformance agent |
165
+ | `explorer` | the explorers of a conformance run |
166
+ | `reviewer` | the review of the code and its unit tests |
167
+ | `test-reviewer` | the review of the conformance tests - `judge-model` too, when it is set |
168
+ | `judge` | the judge of a failing conformance run - the same as `judge-model`, which it wins over |
169
+ | `arbiter` | the arbiters that weigh a second review |
170
+ | `debugger` | the fresh look at unit tests that keep failing the same way, or a coding agent going round in circles. Unnamed, `judge-model` when it is set, else the harness model - not `default` |
171
+
172
+ What an entry leaves out it takes from `default`: `reviewer: { thinking: low }` runs the default model, thinking
173
+ little. A turn limit is the role's own, else `default`'s, else `turn-limit:`, else two minutes. A stack's `config.turri.<stack>.yaml` lays its own `models:` over the project's, role by role - all but
174
+ `chat`, the project's alone.
175
+
176
+ A role whose thinking nothing names - neither its entry, `default`'s, nor a `:level` on the model it runs on - thinks
177
+ as its kind of work needs: **low** for the agents that do the work (`coding`, `conformance`, `explorer`, `release`),
178
+ which work from specs that leave little to guess and take many turns, and **medium** for the ones that judge
179
+ (`reviewer`, `test-reviewer`, `judge`, `arbiter`, `debugger`, `planner`, `chooser`, `diagnosis`), where a wrong
180
+ call costs pulses. A judging role that falls back on the coding model takes its model alone, not its thinking. The
181
+ conversation keeps its own level, as pi sets it.
182
+
183
+ The thinking level reaches the model as each provider takes it. OpenAI's models on Bedrock are sent it as their
184
+ reasoning effort (`none`, `low`, `medium`, `high`, `xhigh`) by turri itself, as pi sends them none.
185
+
186
+ ## How many jobs at once
187
+
188
+ Jobs on the same module, or on modules of one `requires` chain, always run one after another.
189
+ Modules that share nothing - a backend, a frontend, a worker, ten of them - build side by side, each
190
+ pulse's builds and tests running on this machine beside your own work. So the chat runs at most `parallel-jobs` jobs
191
+ at once; a job whose turn has come waits for a free slot past that, and its line says so. Unset, it is half this
192
+ machine's cores, at least one and at most four. The config is the only place it is set. It is the project's alone,
193
+ never read from a stack's file.
194
+
195
+ The testing scripts all run on this machine, each on a copy of its module's code, so they never
196
+ share files - but they do share its network: two modules whose conformance scripts start a server
197
+ on the same port collide when they run at once. Give each its own.
198
+
199
+ ## Models
200
+
201
+ Each model is a model reference and a thinking level - `off`, `minimal`, `low`, `medium`, `high`,
202
+ `xhigh` or `max` - and either may be left out:
203
+
204
+ ```yaml
205
+ coding-model:
206
+ model: google/gemini-3.1-flash # the provider and the model id, as pi names them
207
+ thinking: medium
208
+ judge-model: google/gemini-3.1-pro:high # the same, as one string
209
+ harness-model: anthropic/claude-opus-5-5 # a model alone
210
+ ```
211
+
212
+ What a model leaves out it takes from the one it inherits: `judge-model` from `coding-model` - its model, not its
213
+ thinking, which the judging roles have their own of - and `coding-model` from `harness-model`. So with only a harness model, everything runs on it;
214
+ `coding-model: {thinking: low}` renders on the harness model, thinking less.
215
+
216
+ They are defaults. What is asked for wins over them:
217
+
218
+ | model | wins over the config |
219
+ | --- | --- |
220
+ | harness | `turri --harness-model <m>` (or pi's own `--model`), and pi's `/model` in the conversation |
221
+ | coding | `turri --coding-model <m>`, `engine` in the conversation, and `--model` / `--coding-model` on `turri regenerate`, `conform` and `spec-diff` |
222
+ | judge | `--judge-model` on `turri conform` |
223
+
224
+ A job keeps the model it was queued with: choosing another holds for what is queued after.
225
+
226
+ ## Conformance tests
227
+
228
+ `conformance-tests:` is the one switch, and the chat, every render and `brood`'s testing phase
229
+ follow it. `false` turns them off entirely: no conformance tests are written, the ones already in a
230
+ codebase are not run, and no agent is shown the conformance testing script. No key is the same as
231
+ `false`: conformance tests are off by default. `all` (or `true`) means on wherever a module has
232
+ `***test reqs***` - a module without them has nothing to write conformance tests from, so it writes
233
+ none, but still keeps the codebase's existing ones passing. Asked in the
234
+ conversation, or with `--conformance` / `--no-conformance` on the command line, a render does
235
+ otherwise.
236
+
237
+ `spawn` writes `conformance-tests: false` into a new project unless you ask for tests;
238
+ `brood` asks, whatever the key says, offering what it says first.
239
+
240
+ ## The testing scripts
241
+
242
+ A build needs a unit testing script - and, with conformance on, a conformance testing script - to
243
+ decide when it is done. They are looked for at the project root, in this order:
244
+
245
+ 1. named in the config - the chosen stack's file first, then `config.turri.yaml` - by path from the root;
246
+ 2. the one `run_unittests*.sh` / `run_conformance_tests*.sh` in the project root;
247
+ 3. the one of each in `test_scripts/`.
248
+
249
+ The optional prepare script - `prepare-environment-script:`, else the one `prepare_environment*.sh` - is looked for the
250
+ same way, and run on the build before the conformance testing script.
251
+
252
+ A folder holding two of a kind is not guessed between: name the one to use. A script named inside
253
+ `plain_modules/` is refused - agents write there, so it could be changed by the work it judges. The
254
+ scripts are never linked from the specs. What they are and how they run is in
255
+ [regenerating the code](regenerating.md#the-testing-scripts).
256
+
257
+ ## Credentials for the agents
258
+
259
+ An agent's commands do not get the environment turri was started in: it holds your providers' API keys, cloud
260
+ credentials and tokens. They get only the path, the locale, the terminal and the user, the variables that locate your
261
+ toolchains (`JAVA_HOME`, `NVM_DIR`, `RUSTUP_HOME`, ... - the ones in your home found even when your shell sets none),
262
+ and turri's own: a home folder of the module's own (`plain_modules/.turri/home/<module>`, kept between pulses, with a
263
+ git identity of its own), a `TMPDIR`, the package caches in `plain_modules/.turri/cache`, and git stopped at the module
264
+ folder, so a project that is itself a repository is never found from the code.
265
+
266
+ A project whose agents need a credential while they work names it:
267
+
268
+ ```yaml
269
+ agent-env:
270
+ files: [.env] # every variable of these dotenv files, by path from the project root
271
+ vars: [D365_TENANT_ID] # these variables, from the environment turri was started in
272
+ ```
273
+
274
+ What it names reaches every agent with a shell, as environment variables of its commands only - the file stays where
275
+ it is, outside every agent's reach - and never replaces turri's own home, `TMPDIR` or caches. A stack's file adds
276
+ to the project's. A file that is not there, or a variable unset or empty, is reported as missing. The testing scripts
277
+ are not affected: they run in your own environment.
278
+
279
+ Their values never appear in what an agent is shown or what turri writes: each is replaced by its name -
280
+ `[D365_CLIENT_SECRET]` - whole, URL-encoded, or split over a command's output as it streams: in every tool result and
281
+ refusal an agent is given (and so in the session transcripts, which hold only those), in the whole outputs kept for
282
+ `read_output`, and in the job and debug logs. A value shorter than four characters is not masked - `1` or `eu` is
283
+ everywhere in ordinary output.
284
+
285
+ ## Stacks
286
+
287
+ One project can hold parts that share nothing but a common module - a Python backend and a React
288
+ frontend side by side, each importing `base` from `template/`, neither requiring the other - each
289
+ needing its own testing scripts. Each stack gets a file of its own beside `config.turri.yaml`,
290
+ `config.turri.<stack>.yaml`, holding what differs for it - typically its testing scripts, its
291
+ `toolchains:`, its models:
292
+
293
+ ```
294
+ config.turri.yaml # the project's: harness-model, coding-model, conformance-tests
295
+ config.turri.backend.yaml # the stack backend: its testing scripts
296
+ config.turri.frontend.yaml # the stack frontend: its testing scripts, and a coding model of its own
297
+ backend.plain # import: [base, python_fastapi_template]
298
+ frontend.plain # import: [base, react_vite_template]
299
+ template/ # base.plain, python_fastapi_template.plain, react_vite_template.plain
300
+ test_scripts/ # both stacks' scripts
301
+ ```
302
+
303
+ ```yaml
304
+ # config.turri.frontend.yaml
305
+ unittests-script: test_scripts/run_unittests_typescript.sh
306
+ conformance-tests-script: test_scripts/run_conformance_tests_typescript.sh
307
+ coding-model:
308
+ thinking: high
309
+ ```
310
+
311
+ A stack is chosen, never worked out from the spec:
312
+
313
+ - **In the chat**, with `/stack` - a list of the project's stacks and none - or `/stack backend`, `/stack none`.
314
+ The chat's model has a `stack` tool too, and switches the stack when you ask it to, or for work on a part another
315
+ stack builds. `/stack` alone says which is chosen now.
316
+ - **On the command line**, with `--stack <name>` on any command - `turri regenerate`, `conform`, `spec-diff`, `glimpse`,
317
+ `release`, `cultivate` - and on `turri --stack backend` for the chat; or with `TURRI_STACK` in the environment.
318
+
319
+ The choice lasts for the session only: nothing remembers it, and turri started again has none chosen. A stack the
320
+ project has no file for is refused, with the stacks it has. With none chosen, `config.turri.yaml` alone is used.
321
+
322
+ How a stack's config is made:
323
+
324
+ - **Laid over `config.turri.yaml`, key by key.** What the stack's file gives wins; what it leaves out
325
+ is the project's. A model it names takes what it leaves out from the project's same model - above,
326
+ with the stack frontend chosen, everything renders on the project's coding model, thinking `high`.
327
+ - **For everything built while it is chosen.** Every pulse that starts, every test run and every
328
+ script found from then on is the chosen stack's; a pulse already running keeps the stack it
329
+ started with. Choose the stack before building the part it is for: `/stack backend`, then
330
+ `regenerate backend.plain`.
331
+ - **The project's alone.** `module:`, `harness-model:`, `parallel-jobs:`, `rules-off:` and `compact-at:` are read
332
+ only from `config.turri.yaml` - there is one conversation, one module built when none is named, one machine.
333
+ `agent-env:` and `toolchains:` in a stack's file add to the project's.
334
+
335
+ The chat is told each stack's config and the one chosen. The skills that write testing scripts put
336
+ each stack's in its own file once a project has two stacks, and say which stack to choose before
337
+ building each part.
338
+
339
+ ## The old config.yaml
340
+
341
+ turri's settings were once in `config.yaml`, and a stack's in `config.<stack>.yaml`. A `config.yaml`
342
+ may be another tool's, so turri never writes one. While a project folder has no `config.turri*.yaml`
343
+ at all, turri still reads its `config.yaml`, and each `config.<name>.yaml` as the stack `<name>`.
344
+
345
+ The first time turri itself writes a setting there with `/turri-config`, it copies `config.yaml` to
346
+ `config.turri.yaml` and each `config.<name>.yaml` to `config.turri.<name>.yaml` first, leaves the old
347
+ files as they are, and from then on reads only the new ones. `turri init-config` copies the stacks'
348
+ files the same way, but writes `config.turri.yaml` as its quick start, taking from `config.yaml` only
349
+ the keys turri reads too - codeplain's `unittests-script`, `conformance-tests-script` and
350
+ `prepare-environment-script`, say - each in its place in the file, and saying which keys it left:
351
+ `template_dir`, `verbose`, `api`, codeplain's alone. The two models are the vendor's, never taken. To change a
352
+ setting by hand in such a project, do the same: copy the files to their new names, and edit the
353
+ copies - never `config.yaml`.