@turri-dev/cli 0.1.4-next.318a98a → 0.1.4-next.4342332

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 (100) hide show
  1. package/.pi/themes/turri-dark.json +67 -66
  2. package/.pi/themes/turri-light.json +71 -73
  3. package/README.md +39 -127
  4. package/assets/jellyfish/dark-00.png +0 -0
  5. package/assets/jellyfish/dark-01.png +0 -0
  6. package/assets/jellyfish/dark-02.png +0 -0
  7. package/assets/jellyfish/dark-03.png +0 -0
  8. package/assets/jellyfish/dark-04.png +0 -0
  9. package/assets/jellyfish/dark-05.png +0 -0
  10. package/assets/jellyfish/dark-06.png +0 -0
  11. package/assets/jellyfish/dark-07.png +0 -0
  12. package/assets/jellyfish/dark-08.png +0 -0
  13. package/assets/jellyfish/dark-09.png +0 -0
  14. package/assets/jellyfish/dark-10.png +0 -0
  15. package/assets/jellyfish/dark-11.png +0 -0
  16. package/assets/jellyfish/dark-12.png +0 -0
  17. package/assets/jellyfish/dark-13.png +0 -0
  18. package/assets/jellyfish/dark-14.png +0 -0
  19. package/assets/jellyfish/light-00.png +0 -0
  20. package/assets/jellyfish/light-01.png +0 -0
  21. package/assets/jellyfish/light-02.png +0 -0
  22. package/assets/jellyfish/light-03.png +0 -0
  23. package/assets/jellyfish/light-04.png +0 -0
  24. package/assets/jellyfish/light-05.png +0 -0
  25. package/assets/jellyfish/light-06.png +0 -0
  26. package/assets/jellyfish/light-07.png +0 -0
  27. package/assets/jellyfish/light-08.png +0 -0
  28. package/assets/jellyfish/light-09.png +0 -0
  29. package/assets/jellyfish/light-10.png +0 -0
  30. package/assets/jellyfish/light-11.png +0 -0
  31. package/assets/jellyfish/light-12.png +0 -0
  32. package/assets/jellyfish/light-13.png +0 -0
  33. package/assets/jellyfish/light-14.png +0 -0
  34. package/dist/ask.js +34 -15
  35. package/dist/brand.js +7 -7
  36. package/dist/cli-options.js +12 -6
  37. package/dist/config-edit.js +1 -0
  38. package/dist/config.js +5 -2
  39. package/dist/cultivate-cli.js +16 -23
  40. package/dist/extension.js +41 -29
  41. package/dist/init-config.js +5 -0
  42. package/dist/loop-cli.js +19 -7
  43. package/dist/loop-tools.js +11 -5
  44. package/dist/module-report.js +5 -4
  45. package/dist/pi.js +76 -19
  46. package/dist/question-dialog.js +1 -0
  47. package/dist/regenerate/activity.js +21 -0
  48. package/dist/regenerate/list-arguments.js +121 -0
  49. package/dist/regenerate/loop.js +21 -8
  50. package/dist/regenerate/plain-palette.js +51 -36
  51. package/dist/regenerate/regenerate.js +4 -2
  52. package/dist/regenerate/self-check.js +13 -6
  53. package/dist/regenerate/side-panel.js +1 -1
  54. package/dist/regenerate/spec-map-view.js +9 -5
  55. package/dist/regenerate/test-scripts.js +54 -10
  56. package/dist/regenerate/unit-review.js +1 -1
  57. package/dist/regenerate-cli.js +11 -5
  58. package/dist/release-cli.js +3 -8
  59. package/dist/review/live.js +2 -1
  60. package/dist/run-report.js +39 -1
  61. package/dist/validate-cli.js +3 -8
  62. package/docs/README.md +5 -12
  63. package/docs/authoring-plain.md +37 -119
  64. package/docs/config.md +53 -178
  65. package/docs/deploy.md +21 -80
  66. package/docs/development.md +13 -41
  67. package/docs/headless.md +45 -121
  68. package/docs/images/how-turri-works.svg +29 -31
  69. package/docs/installation.md +13 -40
  70. package/docs/parser.md +15 -43
  71. package/docs/regenerating.md +229 -938
  72. package/docs/sandbox.md +38 -152
  73. package/docs/skills-and-gates.md +50 -171
  74. package/docs/theming.md +45 -46
  75. package/docs/using.md +69 -345
  76. package/genome/rules/bullet-continuation.md +5 -13
  77. package/genome/rules/concise-specs-examples.md +2 -4
  78. package/genome/rules/concise-specs-integration-examples.md +2 -6
  79. package/genome/rules/concise-specs.md +1 -2
  80. package/genome/rules/func-specs.md +1 -0
  81. package/genome/rules/integration-embedded-testing.md +1 -4
  82. package/genome/rules/integrations.md +1 -2
  83. package/genome/rules/project-model.md +14 -35
  84. package/genome/rules/stdlib.md +14 -49
  85. package/genome/rules/turri-regeneration.md +48 -0
  86. package/genome/skills/add-implementation-requirement/SKILL.md +1 -2
  87. package/genome/skills/analyze-if-func-spec-too-complex/SKILL.md +3 -8
  88. package/genome/skills/brood/SKILL.md +37 -127
  89. package/genome/skills/brood/references/intent.md +4 -12
  90. package/genome/skills/brood/references/product.md +24 -76
  91. package/genome/skills/brood/references/stack.md +7 -29
  92. package/genome/skills/brood/references/testing.md +11 -42
  93. package/genome/skills/bud/SKILL.md +32 -109
  94. package/genome/skills/bud/references/intent.md +4 -11
  95. package/genome/skills/implement-conformance-testing-script/SKILL.md +3 -6
  96. package/genome/skills/implement-unit-testing-script/SKILL.md +1 -2
  97. package/genome/skills/mature/SKILL.md +21 -69
  98. package/genome/skills/refactor-module/SKILL.md +24 -2
  99. package/genome/skills/spawn/SKILL.md +28 -113
  100. package/package.json +1 -1
@@ -5,48 +5,26 @@ npm run typecheck # types
5
5
  npm test # the whole suite, about a minute
6
6
  ```
7
7
 
8
- The suite runs the projects' testing scripts on this machine, as turri does. The fixture scripts stage each run in a
9
- folder of their own, as test files running side by side would otherwise share one.
8
+ The suite runs the projects' testing scripts on this machine, as turri does. The fixture scripts stage each run in a folder of their own, as test files running side by side would otherwise share one.
10
9
 
11
10
  ## Mutation testing
12
11
 
13
- `npm run mutation` runs [Stryker](https://stryker-mutator.io) over the core of regeneration - the parts in
14
- `stryker.config.mjs` - one part at a time, each part's report in `reports/mutation/<part>/`. A part with a report is
15
- skipped, so a run that is stopped goes on from the part it was in; `npm run mutation -- <part>` runs one again, and
16
- `npm run mutation -- --summary` sums up the reports there are. It takes hours: each part runs the suite once to find
17
- which tests reach which mutant (`vitest.stryker.config.ts`, the suite without the files that run real commands behind the sandbox), then each
18
- mutant against those tests. `scripts/mutation.mjs` sets two things in Stryker's vitest runner the suite needs - forked
19
- workers, and a test-name filter that matches vitest 5's names - again after every `npm install`.
12
+ `npm run mutation` runs [Stryker](https://stryker-mutator.io) over the core of regeneration - the parts in `stryker.config.mjs` - one part at a time, each part's report in `reports/mutation/<part>/`. A part with a report is skipped, so a run that is stopped goes on from the part it was in; `npm run mutation -- <part>` runs one again, and `npm run mutation -- --summary` sums up the reports there are. It takes hours: each part runs the suite once to find which tests reach which mutant (`vitest.stryker.config.ts`, the suite without the files that run real commands behind the sandbox), then each mutant against those tests. `scripts/mutation.mjs` sets two things in Stryker's vitest runner the suite needs - forked workers, and a test-name filter that matches vitest 5's names - again after every `npm install`.
20
13
 
21
- The `mutation` workflow (`.github/workflows/mutation.yml`) runs it on GitHub, started by hand: Actions, mutation, Run
22
- workflow. It takes the parts to run, space-separated (empty for every part), each a job of its own side by side, on
23
- four cores (`TURRI_MUTATION_CONCURRENCY`; six here). Each part's `mutation.html` and `mutation.json` are uploaded as an
24
- artifact, `mutation-<part>`, and summed up on the run's page. Stryker's incremental file is cached from run to run, so
25
- a run tests again only the mutants whose code or tests changed; tick `fresh` to test every one again.
14
+ The `mutation` workflow (`.github/workflows/mutation.yml`) runs it on GitHub, started by hand: Actions, mutation, Run workflow. It takes the parts to run, space-separated (empty for every part), each a job of its own side by side, on four cores (`TURRI_MUTATION_CONCURRENCY`; six here). Each part's `mutation.html` and `mutation.json` are uploaded as an artifact, `mutation-<part>`, and summed up on the run's page. Stryker's incremental file is cached from run to run, so a run tests again only the mutants whose code or tests changed; tick `fresh` to test every one again.
26
15
 
27
- Working on turri itself, `npm link` points the global `turri` at this clone, so edits
28
- take effect immediately. `npm run chat` starts pi with the extension loaded from `src/`.
16
+ Working on turri itself, `npm link` points the global `turri` at this clone, so edits take effect immediately. `npm run chat` starts pi with the extension loaded from `src/`.
29
17
 
30
- The package on npm is built: Node runs no `.ts` file inside a `node_modules`, so `npm run build` (`scripts/build.mjs`,
31
- over `tsconfig.build.json`) compiles `src/` into `dist/`, file for file - each file as deep as its source, so it finds
32
- `genome/`, `stdlib/` and `.pi/` the same way - with its imports' `.ts` rewritten to `.js`, and copies the other files
33
- in `src/` beside them. `npm pack` and `npm publish` run it first (`prepack`), and the package holds `dist/`, not
34
- `src/`. `bin/turri.mjs` runs `src/cli.ts` where there is one and `dist/cli.js` otherwise; `cli.ts` starts pi with
35
- the launcher and extension beside itself, whichever they are. To try the package as users get it:
36
- `npm pack`, then `npm install -g ./turri-dev-cli-<version>.tgz`.
18
+ The package on npm is built: Node runs no `.ts` file inside a `node_modules`, so `npm run build` (`scripts/build.mjs`, over `tsconfig.build.json`) compiles `src/` into `dist/`, file for file - each file as deep as its source, so it finds `genome/`, `stdlib/` and `.pi/` the same way - with its imports' `.ts` rewritten to `.js`, and copies the other files in `src/` beside them. `npm pack` and `npm publish` run it first (`prepack`), and the package holds `dist/`, not `src/`. `bin/turri.mjs` runs `src/cli.ts` where there is one and `dist/cli.js` otherwise; `cli.ts` starts pi with the launcher and extension beside itself, whichever they are. To try the package as users get it: `npm pack`, then `npm install -g ./turri-dev-cli-<version>.tgz`.
37
19
 
38
20
  ## Publishing
39
21
 
40
22
  `.github/workflows/publish.yml` publishes `@turri-dev/cli`, after the typecheck and the suite pass:
41
23
 
42
- - every push or merge into `main` publishes a prerelease under the `next` dist-tag - the patch after the `latest` on
43
- npm, with the commit's sha: `0.1.2-next.<sha>`. `latest` is not touched; `npm install -g @turri-dev/cli@next` gets it.
44
- - a GitHub Release tagged `vX.Y.Z`, published by hand, publishes `X.Y.Z` as `latest` (or under `next`, if the release
45
- is marked a pre-release). The tag is the version: it is set in the runner and never committed, so `package.json`'s
46
- version on `main` is stale by design.
24
+ - every push or merge into `main` publishes a prerelease under the `next` dist-tag - the patch after the `latest` on npm, with the commit's sha: `0.1.2-next.<sha>`. `latest` is not touched; `npm install -g @turri-dev/cli@next` gets it.
25
+ - a GitHub Release tagged `vX.Y.Z`, published by hand, publishes `X.Y.Z` as `latest` (or under `next`, if the release is marked a pre-release). The tag is the version: it is set in the runner and never committed, so `package.json`'s version on `main` is stale by design.
47
26
 
48
- The workflow signs in to npm with npm's trusted publishing (OIDC), not a token: the package's settings on npmjs.com
49
- trust `turri-oss/turri`, the workflow `publish.yml` and the environment `npm`. Renaming the workflow file breaks it.
27
+ The workflow signs in to npm with npm's trusted publishing (OIDC), not a token: the package's settings on npmjs.com trust `turri-oss/turri`, the workflow `publish.yml` and the environment `npm`. Renaming the workflow file breaks it.
50
28
 
51
29
  ## Layout
52
30
 
@@ -82,14 +60,8 @@ trust `turri-oss/turri`, the workflow `publish.yml` and the environment `npm`. R
82
60
 
83
61
  ## Known edges
84
62
 
85
- - Judges default to the executor's model. Real separation wants a different provider - set
86
- `model:` on the gate. An unconfigured model falls back to the executor's with a note rather than
87
- failing the run.
88
- - The executor keeps one session across steps and retries, so context builds up with attempts. Long
89
- skills want compaction or a session per step.
90
- - A gate's escalation lives in memory (LangGraph's `MemorySaver`): it must be answered in the same
91
- run.
92
- - The gate machinery has no automated coverage; see
93
- [trying the gate semantics](skills-and-gates.md#trying-the-gate-semantics).
94
- - Jobs live in memory: there is no resuming a job after pi exits. The work itself is on disk, so a
95
- killed job leaves a partial build rather than nothing.
63
+ - Judges default to the executor's model. Real separation wants a different provider - set `model:` on the gate. An unconfigured model falls back to the executor's with a note rather than failing the run.
64
+ - The executor keeps one session across steps and retries, so context builds up with attempts. Long skills want compaction or a session per step.
65
+ - A gate's escalation lives in memory (LangGraph's `MemorySaver`): it must be answered in the same run.
66
+ - The gate machinery has no automated coverage; see [trying the gate semantics](skills-and-gates.md#trying-the-gate-semantics).
67
+ - Jobs live in memory: there is no resuming a job after pi exits. The work itself is on disk, so a killed job leaves a partial build rather than nothing.
package/docs/headless.md CHANGED
@@ -10,15 +10,9 @@ turri doctor
10
10
 
11
11
  Everything marked `✗` stops the run, so fix those first. Two more things matter for a long run:
12
12
 
13
- - **The toolchains.** The agents use this machine's: run `turri doctor` in the project, and it says which toolchain
14
- a module's implementation reqs name that this machine lacks.
15
- - **The models.** In a project, `turri doctor` sends each model `config.turri.yaml` names one short request, as turri's
16
- agents will - so a key the provider refuses, a model pi does not know or an account without access to it shows now,
17
- not an hour into the run. Models the run is given on the command line are given to doctor the same way -
18
- `--harness-model`, `--coding-model`, `--judge-model`, read as the config's keys - and are checked with the config's.
19
- - **Credentials that do not expire mid-run.** An API key in the environment
20
- (`ANTHROPIC_API_KEY`, `OPENAI_API_KEY`, `GEMINI_API_KEY`, ...) or pi's saved login - whatever
21
- `doctor` shows as the model provider.
13
+ - **The toolchains.** The agents use this machine's: run `turri doctor` in the project, and it says which toolchain a module's implementation reqs name that this machine lacks.
14
+ - **The models.** In a project, `turri doctor` sends each model `config.turri.yaml` names one short request, as turri's agents will - so a key the provider refuses, a model pi does not know or an account without access to it shows now, not an hour into the run. Models the run is given on the command line are given to doctor the same way - `--harness-model`, `--coding-model`, `--judge-model`, read as the config's keys - and are checked with the config's.
15
+ - **Credentials that do not expire mid-run.** An API key in the environment (`ANTHROPIC_API_KEY`, `OPENAI_API_KEY`, `GEMINI_API_KEY`, ...) or pi's saved login - whatever `doctor` shows as the model provider.
22
16
 
23
17
  Check the spec parses, without spending anything:
24
18
 
@@ -33,10 +27,7 @@ turri validate app.plain
33
27
  turri regenerate app.plain --model anthropic/claude-sonnet-5
34
28
  ```
35
29
 
36
- That builds every functionality in order - code and unit tests, then conformance tests when the
37
- module has `***test reqs***`, then every conformance test so far - and advances only when they
38
- pass. A module that `requires` others builds them first, bottom up. When it is done it prints
39
- `To try it: turri release <spec>` - see [deploy](deploy.md).
30
+ That builds every functionality in order - code and unit tests, then conformance tests when the module has `***test reqs***`, then every conformance test so far - and advances only when they pass. A module that `requires` others builds them first, bottom up. When it is done it prints `To try it: turri release <spec>` - see [deploy](deploy.md).
40
31
 
41
32
  Keep it running when the terminal closes and the laptop would sleep, and keep the log:
42
33
 
@@ -50,8 +41,7 @@ nohup turri regenerate app.plain --model anthropic/claude-sonnet-5 > render.log
50
41
 
51
42
  Or run it in `tmux` / `screen` to look in on it. `tail -f render.log` follows it either way.
52
43
 
53
- The project's `config.turri.yaml`, in the folder you run it from, can give the defaults, so a run can
54
- be just `turri regenerate`:
44
+ The project's `config.turri.yaml`, in the folder you run it from, can give the defaults, so a run can be just `turri regenerate`:
55
45
 
56
46
  ```yaml
57
47
  module: app.plain # what to build when no file is given
@@ -62,20 +52,7 @@ judge-model: anthropic/claude-opus-5-5 # the judge and the test review (defau
62
52
  conformance-tests: true # write and run conformance tests where there are test reqs (default: false)
63
53
  ```
64
54
 
65
- The same file names the [testing scripts](regenerating.md#the-testing-scripts); every key is in
66
- [the project config](config.md). `turri regenerate`, `spec-diff` and `conform` take five of its keys on
67
- the command line too - `--harness-model`, `--coding-model` (`--model` is the same), `--judge-model`,
68
- `--review` and, but for `conform`, `--conformance-tests`. Each is read as if the file had it, in its place:
69
- it takes that key's value, and every key keeps its place in the order the config resolves them in - a
70
- `models:` entry still wins over `harness-model`, `coding-model` or `judge-model`, the planner and the
71
- conformance chooser run on the harness model, the judge falls back on the coding model. So a run with
72
- the options is the same as one, or a chat, with those keys in `config.turri.yaml` - and a `config.yaml`
73
- the project has is left as it is.
74
- A project whose parts are on different stacks keeps each stack's settings in
75
- `config.turri.<stack>.yaml` beside it ([stacks](config.md#stacks)). Choose the one to build with
76
- `--stack <name>` - on any command: `regenerate`, `conform`, `spec-diff`, `glimpse`, `release`, `cultivate` -
77
- or `TURRI_STACK`: `turri regenerate backend.plain --stack backend`. With none chosen,
78
- `config.turri.yaml` alone is used; a stack the project has no file for is refused.
55
+ The same file names the [testing scripts](regenerating.md#the-testing-scripts); every key is in [the project config](config.md). `turri regenerate`, `spec-diff` and `conform` take five of its keys on the command line too - `--harness-model`, `--coding-model` (`--model` is the same), `--judge-model`, `--review` and, but for `conform`, `--conformance-tests`. Each is read as if the file had it, in its place: it takes that key's value, and every key keeps its place in the order the config resolves them in - a `models:` entry still wins over `harness-model`, `coding-model` or `judge-model`, the planner and the conformance chooser run on the harness model, the judge falls back on the coding model. So a run with the options is the same as one, or a chat, with those keys in `config.turri.yaml` - and a `config.yaml` the project has is left as it is. A project whose parts are on different stacks keeps each stack's settings in `config.turri.<stack>.yaml` beside it ([stacks](config.md#stacks)). Choose the one to build with `--stack <name>` - on any command: `regenerate`, `conform`, `spec-diff`, `glimpse`, `release`, `cultivate` - or `TURRI_STACK`: `turri regenerate backend.plain --stack backend`. With none chosen, `config.turri.yaml` alone is used; a stack the project has no file for is refused.
79
56
 
80
57
  Useful options - `turri regenerate --help` lists all of them:
81
58
 
@@ -86,26 +63,21 @@ Useful options - `turri regenerate --help` lists all of them:
86
63
  | `--judge-model <provider/id>` | `judge-model`: the judge and the test review; not given, the coding model |
87
64
  | `--conformance-tests <all\|chosen\|module\|false>` | `conformance-tests`, with its values (`true` is `all`) |
88
65
  | `--review <true\|false>` | `review`: whether the code and its tests are reviewed once they pass |
66
+ | `--mutation-testing <true\|false>` | `mutation-testing`: whether the coding agent and the reviewer have `can_it_fail` |
89
67
  | `--stack <name>` | build with the stack's `config.turri.<name>.yaml` over `config.turri.yaml`; default, `TURRI_STACK`, else none |
68
+ | `--from-scratch` | delete the module's code and conformance tests (its lineage keeps them) and regenerate all of it |
69
+ | `--from-scratch-chain` | the same for every module of its requires chain, the base's included, built again from the bottom |
90
70
  | `--start-over` | start over, unasked, a codebase built without a module it now requires - without a terminal, a run that needs it and lacks it stops |
91
71
  | `--build-dir <dir>` | as the run ends, export the code there - see below |
92
72
  | `--report <file.json>` | write [the run report](#the-run-report) there |
93
73
 
94
74
  An option a command does not take is refused, not ignored.
95
75
 
96
- `turri regenerate` queues the jobs the chat's `regenerate` does - a spec diff that brings what
97
- is built in line, when it no longer matches the spec, then one render functionality job per
98
- functional spec not built, each with its conformance job - and runs them all to the end, the queue
99
- master reworking them as in the chat (see [a whole module](regenerating.md#a-whole-module)).
100
- Conformance tests follow `conformance-tests:` in `config.turri.yaml`, or `--conformance-tests`.
76
+ `turri regenerate` queues the jobs the chat's `regenerate` does - a spec diff that brings what is built in line, when it no longer matches the spec, then one render functionality job per functional spec not built, each with its conformance job - and runs them all to the end, the queue master reworking them as in the chat (see [a whole module](regenerating.md#a-whole-module)). Conformance tests follow `conformance-tests:` in `config.turri.yaml`, or `--conformance-tests`.
101
77
 
102
78
  ## In the morning
103
79
 
104
- The command exits `0` only when every module is complete: its code regenerated from the spec as it is
105
- now, and its conformance tests written for what is built when they are on - no pulse failed, nothing
106
- halted, nothing waiting for a start over. Anything else exits `1`, and the last lines say which
107
- module is not complete, and why. The log says, per functionality, what was built and whether its
108
- tests passed.
80
+ The command exits `0` only when every module is complete: its code regenerated from the spec as it is now, and its conformance tests written for what is built when they are on - no pulse failed, nothing halted, nothing waiting for a start over. Anything else exits `1`, and the last lines say which module is not complete, and why. A behaviour the spec leaves open that an agent had to decide is printed as a `spec gap` under its pulse, with what was decided meanwhile. The log says, per functionality, what was built and whether its tests passed.
109
81
 
110
82
  | | |
111
83
  | --- | --- |
@@ -123,24 +95,11 @@ git --git-dir=plain_modules/<module>/.plaingit --work-tree=plain_modules/<module
123
95
 
124
96
  ### Where the code is
125
97
 
126
- A module that `requires` others is built in its own codebase on top of theirs: the code of
127
- `plain_modules/<module>/code` holds every module below it. `--build-dir <dir>` exports it as the run
128
- ends, however it ends - done, failed, stopped by a signal: the last commit of the furthest-built
129
- codebase of the chain, with the harness's own files (`.turri.json`, the notes) left out, so what is in
130
- it is only what landed, its unit tests passing. The folder's contents are replaced each time; a
131
- folder that holds the project, or one inside `plain_modules`, is refused. Point an external test
132
- suite at it.
98
+ A module that `requires` others is built in its own codebase on top of theirs: the code of `plain_modules/<module>/code` holds every module below it. `--build-dir <dir>` exports it as the run ends, however it ends - done, failed, stopped by a signal: the last commit of the furthest-built codebase of the chain, with the harness's own files (`.turri.json`, the notes) left out, so what is in it is only what landed, its unit tests passing. The folder's contents are replaced each time; a folder that holds the project, or one inside `plain_modules`, is refused. Point an external test suite at it.
133
99
 
134
100
  ### The run report
135
101
 
136
- `--report <file.json>` writes one JSON summary of the run, for a benchmark or CI to read rather than
137
- the log. It is written as the run starts, again as each pulse ends, and last as the process exits -
138
- whether the run is done, a pulse failed, the provider failed, it crashed, or `SIGTERM` / `SIGINT` /
139
- `SIGHUP` stopped it. Only a process killed outright (`SIGKILL`) keeps the one written as its last pulse
140
- ended. Each write replaces the file whole (written beside it, then renamed), so a reader never sees
141
- half of one. Keys are snake_case, numbers are numbers, and `schema_version` changes only when a field
142
- changes meaning or goes; new fields may appear in the same version. Paths in `pulses`,
143
- `functionalities` and `logs` are relative to `project_dir`.
102
+ `--report <file.json>` writes one JSON summary of the run, for a benchmark or CI to read rather than the log. It is written as the run starts, again as each pulse ends, and last as the process exits - whether the run is done, a pulse failed, the provider failed, it crashed, or `SIGTERM` / `SIGINT` / `SIGHUP` stopped it. Only a process killed outright (`SIGKILL`) keeps the one written as its last pulse ended. Each write replaces the file whole (written beside it, then renamed), so a reader never sees half of one. Keys are snake_case, numbers are numbers, and `schema_version` changes only when a field changes meaning or goes; new fields may appear in the same version. Paths in `pulses`, `functionalities` and `logs` are relative to `project_dir`.
144
103
 
145
104
  ```jsonc
146
105
  {
@@ -150,6 +109,7 @@ changes meaning or goes; new fields may appear in the same version. Paths in `pu
150
109
  "spec_file": "/work/app.plain", "project_dir": "/work", "build_dir": "/work/out", // build_dir: null without one
151
110
  "conformance_tests": "all", // all | chosen | module | false
152
111
  "review": false,
112
+ "mutation_testing": true,
153
113
  "models": {
154
114
  "harness": { "model": "…", "thinking": "medium" }, // the planner, the chooser
155
115
  "coding": { "model": "…", "thinking": "low" },
@@ -165,7 +125,7 @@ changes meaning or goes; new fields may appear in the same version. Paths in `pu
165
125
  "usage": {
166
126
  "total": {
167
127
  "input_tokens": 0, "output_tokens": 0, "cache_read_tokens": 0, "cache_write_tokens": 0, "cost_usd": 0,
168
- "llm_calls": 0, "agent_turns": 0, "compactions": 0, "model_seconds": 0
128
+ "llm_calls": 0, "agent_turns": 0, "compactions": 0, "provider_retries": 0, "provider_stalls": 0, "turns_stopped": 0, "model_seconds": 0
169
129
  },
170
130
  "in_pulses": { }, // the pulses' usage added up - the same fields
171
131
  "outside_pulses": { }, // the planner, the conformance chooser, a failed pulse's diagnosis
@@ -191,43 +151,34 @@ changes meaning or goes; new fields may appear in the same version. Paths in `pu
191
151
  ],
192
152
  "codebases": ["plain_modules/app/code"]
193
153
  },
154
+ "spec_gaps": [{ // [] when no agent met one
155
+ "module": "app", "pulse": 3, "pulse_id": "2a058729", "landed": true,
156
+ "question": "What happens to a contact archived in HubSpot after it was synced?",
157
+ "assumption": "An archived contact is left as synced - asserted in sync.test.ts, archived contacts"
158
+ }],
159
+ "turn_incidents": [ // [] when none
160
+ { "kind": "provider_retry", "pulse_id": "2a058729", "role": "coding", "model": "…", "error": "…: 529 overloaded", "attempt": 1, "of": 6, "wait_seconds": 15.3 },
161
+ { "kind": "provider_stall", "pulse_id": "2a058729", "role": "coding", "model": "…", "silent_seconds": 120 },
162
+ { "kind": "stopped", "pulse_id": "5c1e0b2f", "role": "conformance explorer", "model": "…", "reason": "10 submit_exploration calls failed in a row" }
163
+ ],
194
164
  "build_dir_export": { "exported": true, "dir": "/work/out", "module": "app", "commit": "8036b3b", "files": 23 },
195
165
  "logs": { "jobs_dir": ".turri/jobs", "debug_dir": ".turri/debug", "sessions_dir": ".turri/sessions" }
196
166
  }
197
167
  ```
198
168
 
199
- **Usage.** Every model response an agent gets counts, in a pulse or not. `total` is `in_pulses` plus
200
- `outside_pulses`, and `in_pulses` is the pulses' own usage added up - the figures their lines in the
201
- notes and the pulse logs print - so the totals can be checked by hand against them. `llm_calls` counts
202
- model responses, a compaction's included; `agent_turns` the harness's requests to an agent, each as
203
- many responses as it takes; `model_seconds` the time from each request to its response's end, added
204
- up - several agents working at once each count theirs, so it can exceed the run's duration. Tokens and
205
- cost are as the provider reports them to pi. `by_model`, `by_role` and `by_pulse_kind` split the same
206
- calls; a role or model turri could not tell is `unknown`.
207
-
208
- **Pulses.** `landed`: its work is committed. `rolled_back`: it ended without landing - failed,
209
- stopped, or did not pass - and everything it wrote was undone. A pulse that failed is tried once more
210
- before it counts as failed; both tries are the one pulse.
211
-
212
- **Completed functional specs** - the pass rate, the figure to compare runs by.
213
- A functional spec is completed when the code record of the codebase the run delivers - the
214
- furthest-built of the chain, the one `--build-dir` exports - as last committed, holds it as the spec has
215
- it now: the same text, every file it links holding the same, no definition it uses changed, and not
216
- marked to regenerate again. A record moves only when a pulse lands, and a pulse lands only once the
217
- code's unit tests pass (its conformance tests too, when they are on), so completed means landed and
218
- passing. Each module counts its own functional specs: a module below the tip counts from what the
219
- tip's code was built on, and a module above the delivered codebase has none completed.
220
- `conformance_tested` is those of the completed ones whose conformance tests are recorded as written
221
- for them as they are now (`plain_modules/<module>/tests/.turri.json`).
169
+ **Usage.** Every model response an agent gets counts, in a pulse or not. `total` is `in_pulses` plus `outside_pulses`, and `in_pulses` is the pulses' own usage added up - the figures their lines in the notes and the pulse logs print - so the totals can be checked by hand against them. `llm_calls` counts model responses, a compaction's included; `agent_turns` the harness's requests to an agent, each as many responses as it takes; `model_seconds` the time from each request to its response's end, added up - several agents working at once each count theirs, so it can exceed the run's duration. Tokens and cost are as the provider reports them to pi. `by_model`, `by_role` and `by_pulse_kind` split the same calls; a role or model turri could not tell is `unknown`.
170
+
171
+ **Pulses.** `landed`: its work is committed. `rolled_back`: it ended without landing - failed, stopped, or did not pass - and everything it wrote was undone. A pulse that failed is tried once more before it counts as failed; both tries are the one pulse.
172
+
173
+ **Spec gaps.** A behaviour the spec leaves open that an agent met and decided on its own - reported with `report_spec_gap` (see [regenerating](regenerating.md)). In the chat they are put to you as the pulse ends; a headless run has no one to ask, so they are listed here, in the order the pulses started, and printed under the pulse that met them. `assumption` is what the agent decided meanwhile, also in the codebase's notes' assumptions while its pulse is `landed`; a pulse rolled back takes it out of the notes, but the question still stands. Settle the ones you care about in the spec and run again.
174
+
175
+ **Turn incidents.** Each time the model provider failed for a moment and pi or turri waited it out before trying the request again, each time the provider sent nothing back on a request for two minutes, and each turn turri stopped itself - an agent going round in a circle, its calls failing again and again, its time up - after which the run carried on. The provider failing or stalling is printed as it happens, in orange, under the pulse; a stop is the work's normal course and is only listed. They are counted in the usage as `provider_retries`, `provider_stalls` and `turns_stopped`. Many of any point at the model, not the spec: a provider at its limits, or a model too small for the role it was given.
176
+
177
+ **Completed functional specs** - the pass rate, the figure to compare runs by. A functional spec is completed when the code record of the codebase the run delivers - the furthest-built of the chain, the one `--build-dir` exports - as last committed, holds it as the spec has it now: the same text, every file it links holding the same, no definition it uses changed, and not marked to regenerate again. A record moves only when a pulse lands, and a pulse lands only once the code's unit tests pass (its conformance tests too, when they are on), so completed means landed and passing. Each module counts its own functional specs: a module below the tip counts from what the tip's code was built on, and a module above the delivered codebase has none completed. `conformance_tested` is those of the completed ones whose conformance tests are recorded as written for them as they are now (`plain_modules/<module>/tests/.turri.json`).
222
178
 
223
179
  ## If it stopped
224
180
 
225
- Run the same command again. Functionalities already built from the current spec are left alone,
226
- and modules that are complete are skipped, so it picks up where it stopped - after a crash, a
227
- provider outage, or a machine that went to sleep anyway. Whatever the stopped run left
228
- half-written is undone first: each step starts from the module's last commit. A functionality whose spec has changed
229
- since counts as not built, so editing the spec and re-running rebuilds what changed. `--from-scratch`
230
- rebuilds everything.
181
+ Run the same command again. Functionalities already built from the current spec are left alone, and modules that are complete are skipped, so it picks up where it stopped - after a crash, a provider outage, or a machine that went to sleep anyway. Whatever the stopped run left half-written is undone first: each step starts from the module's last commit. A functionality whose spec has changed since counts as not built, so editing the spec and re-running rebuilds what changed. `--from-scratch` rebuilds the module; `--from-scratch-chain` rebuilds every module of its chain.
231
182
 
232
183
  ## Rebuilding one functionality
233
184
 
@@ -235,17 +186,9 @@ rebuilds everything.
235
186
  turri regenerate app.plain 3
236
187
  ```
237
188
 
238
- It rebuilds just functionality 3 over the code that already exists - after its spec changed, or as
239
- it is - without rebuilding the ones before it. To change what it does, change its spec first: the
240
- spec is what the code is built from, and a change made there lasts. Then it updates the
241
- functionality's conformance tests and runs every conformance test in the codebase, since a change can break something built
242
- later as easily as something built earlier. It works in the codebase the functionality now lives
243
- in: when later modules are built on top of this one, the furthest-built of them. It exits `0` when
244
- everything passes, and refuses when there is no code yet - regenerate the module first.
189
+ It rebuilds just functionality 3 over the code that already exists - after its spec changed, or as it is - without rebuilding the ones before it. To change what it does, change its spec first: the spec is what the code is built from, and a change made there lasts. Then it updates the functionality's conformance tests and runs every conformance test in the codebase, since a change can break something built later as easily as something built earlier. It works in the codebase the functionality now lives in: when later modules are built on top of this one, the furthest-built of them. It exits `0` when everything passes, and refuses when there is no code yet - regenerate the module first.
245
190
 
246
- Several changes do not need one command each: after editing the spec, re-running `turri regenerate`
247
- brings the code up to it - one spec diff for what changed in what is built, then the functional
248
- specs not built yet.
191
+ Several changes do not need one command each: after editing the spec, re-running `turri regenerate` brings the code up to it - one spec diff for what changed in what is built, then the functional specs not built yet.
249
192
 
250
193
  ## In a container
251
194
 
@@ -258,12 +201,7 @@ turri regenerate app.plain --harness-model amazon-bedrock/eu.anthropic.claude-op
258
201
  --start-over --build-dir /out/build --report /out/report.json < /dev/null
259
202
  ```
260
203
 
261
- - **What it needs:** Node 22.19 or newer, `bash`, `git` (each module's history, and the spec diffs
262
- the work is planned from), `/bin/sh` with `kill` and `sleep`, a writable `HOME` (pi keeps
263
- `~/.pi/agent/auth.json` there, even with no login - or set `PI_CODING_AGENT_DIR`) and `TMPDIR`, and
264
- the toolchains the project's testing scripts and implementation reqs name. `turri doctor` in the project, given
265
- the run's models, checks them all and that each model answers - at the start of the task's log, not when a pulse
266
- fails:
204
+ - **What it needs:** Node 22.19 or newer, `bash`, `git` (each module's history, and the spec diffs the work is planned from), `/bin/sh` with `kill` and `sleep`, a writable `HOME` (pi keeps `~/.pi/agent/auth.json` there, even with no login - or set `PI_CODING_AGENT_DIR`) and `TMPDIR`, and the toolchains the project's testing scripts and implementation reqs name. `turri doctor` in the project, given the run's models, checks them all and that each model answers - at the start of the task's log, not when a pulse fails:
267
205
 
268
206
  ```bash
269
207
  turri doctor --harness-model amazon-bedrock/eu.anthropic.claude-opus-5-5 \
@@ -271,32 +209,18 @@ turri regenerate app.plain --harness-model amazon-bedrock/eu.anthropic.claude-op
271
209
  ```
272
210
 
273
211
  It exits 1 on anything marked `✗`.
274
- - **Without bubblewrap:** a container that cannot make user namespaces - no
275
- `--security-opt seccomp=unconfined` - has no OS sandbox. turri runs anyway, the command check and the
276
- audit holding alone under the agents' commands; `turri doctor` shows it as `!`, not `✗`. Installing
277
- the `bubblewrap` package is worth it where it can start.
278
- - **As another user:** run it as the user who will own `plain_modules/`. git refuses a repository
279
- another user made (`dubious ownership`), so do not let root create it first.
280
- - **Amazon Bedrock:** `AWS_BEARER_TOKEN_BEDROCK` (or the usual AWS credentials) and `AWS_REGION` are
281
- enough - no pi login. An `eu.` inference profile needs an EU region. A model id pi does not know
282
- stops the first pulse; `turri --list-models bedrock` lists the ones it does.
283
- - **A project with only a `config.yaml`:** it is read while there is no `config.turri*.yaml` -
284
- `unittests-script`, `conformance-tests-script` and `prepare-environment-script` included, relative to
285
- the project; the keys turri does not know are ignored. Do not write a `config.turri.yaml` beside it to
286
- turn on conformance tests or review: from then on `config.yaml` is not read. The options above do it instead.
287
- - **Long test suites:** a testing script run is stopped after 120 seconds; set `TURRI_COMMAND_TIMEOUT`
288
- (seconds) for more.
212
+ - **Without bubblewrap:** a container that cannot make user namespaces - no `--security-opt seccomp=unconfined` - has no OS sandbox. turri runs anyway, the command check and the audit holding alone under the agents' commands; `turri doctor` shows it as `!`, not `✗`. Installing the `bubblewrap` package is worth it where it can start.
213
+ - **As another user:** run it as the user who will own `plain_modules/`. git refuses a repository another user made (`dubious ownership`), so do not let root create it first.
214
+ - **Amazon Bedrock:** `AWS_BEARER_TOKEN_BEDROCK` (or the usual AWS credentials) and `AWS_REGION` are enough - no pi login. An `eu.` inference profile needs an EU region. A model id pi does not know stops the first pulse; `turri --list-models bedrock` lists the ones it does.
215
+ - **A project with only a `config.yaml`:** it is read while there is no `config.turri*.yaml` - `unittests-script`, `conformance-tests-script` and `prepare-environment-script` included, relative to the project; the keys turri does not know are ignored. Do not write a `config.turri.yaml` beside it to turn on conformance tests or review: from then on `config.yaml` is not read. The options above do it instead.
216
+ - **Long test suites:** a testing script run is stopped after 120 seconds; set `TURRI_COMMAND_TIMEOUT` (seconds) for more.
289
217
 
290
218
  ## Asking in words instead
291
219
 
292
- pi's print mode runs one request and exits, with the render tools waiting for their work rather
293
- than starting background jobs:
220
+ pi's print mode runs one request and exits, with the render tools waiting for their work rather than starting background jobs:
294
221
 
295
222
  ```bash
296
223
  nohup turri -p "Render app.plain as a whole module, with conformance tests" > render.log 2>&1 &
297
224
  ```
298
225
 
299
- It reports what it did at the end, and nothing streams in between, so for long runs
300
- `turri regenerate` is the better choice. The interview skill (`brood`) needs
301
- someone to answer; without one they decide and record their assumptions, and a gate that escalates
302
- to a human stops the run.
226
+ It reports what it did at the end, and nothing streams in between, so for long runs `turri regenerate` is the better choice. The interview skill (`brood`) needs someone to answer; without one they decide and record their assumptions, and a gate that escalates to a human stops the run.
@@ -2,49 +2,47 @@
2
2
  <title id="d-title">How turri works</title>
3
3
  <desc id="d-desc">You describe changes to turri; turri asks and reports back. turri writes the ***plain specs, which you read and review with turri. Then turri loops without end: it reads the specs, regenerates the code from them, and tests the code against them.</desc>
4
4
  <style>
5
- @font-face { font-family: "Archivo"; font-weight: 900; src: url(data:font/woff2;base64,d09GMgABAAAAAAN0ABAAAAAABuQAAAMZAAIAQgAAAAAAAAAAAAAAAAAAAAAAAAAAGhYbehwqBmA/U1RBVFoARBEICoIAgV8BNgIkAxQLDAAEIAWESgcgDAcbnQUgrgN4MoaXUIIRo4bSsCiga71j9UcNccO+yjyC+P1os19OzJJmreeIVRLJ+4WIehOXTKRJJRSJ8Pyncf+de2ce5wNCgxqZAC0ct6/blrChgUdrbfZNUdmKmpz5LBKSlaKpfEIsfsMjiZDIAa9FmMiRBO5xu8oTYkgzRXzs+9+lkO3UTW0sDfC/Eg80wzWI0sVrASYYaPgX/QUUUZwVWGASpe6a4qnU8vN++BgB6gBkCJEIgRo+ovDC4Pg8xPbtoA4xEkgQEHs5W4cACBAkpkf0mAcJRkVgEgAAgTGBCIQQJjCRXOsngM9xiaHI0vMFJWTvskM4oi4XP9X6lXQAfsTmjZMjkCBh+cB/UGwghwgga+BnGCIQoidAGwEm8do+ioSAtZ0iEiB2445KnDAtK24IZGGvBwgEUSMWkGMczoAAZBA8JHWfFAGq6DHYzcYSIRCVLqQT6bgAkGIzEQeUgBqc3whsB6D43F9FhbTpcmwPhZZSSaVRteGjj59qv27LeOjVk2PYcSj5hTCSqg9DMZq5r2qx/exN7XkyKJ65eHCjDr99TrmSxrrQ/ddvbRP77/bfbnTXBClNV3Wb/V8OzhdlfN+zmyYuvQ78yq+N29b1NhUoOpktvThzPnRiPpgdaR7/8x/4q21F2Fy8K2X997rzFrMa3zXdVG/2h9xezABAgEzWr9dWPOVvgPgG+P6P4MdMl+X/1lRRbBQToAIDAqr30kH43z8jcK3MVqyLdCnxtj+kNzsyh0CpnxcQsFXvUo4KA+AWpkJC3CvMzFdFyPA9lG6KhlN4yVpBkZIhoC76oBCMMUIa2QnfHuyS7wR203cG/ql5BtMxm0QgQlSUiOkoDzVEHCqTbY9IcsBAVbAZZJw96CIyCAw2JwwIhkImR6tw4fpJ462YXZW6GTRVyml0kl2Mo0KNYyZOVdLpkkNE1TjaAtmLq9pHZVHt2KOYxREwuYutVpgIESIlKzdh0oi2kNVNXUEsmhfLSQw6qnQVhlhynlg3gWQfui5Ikww2zQ7VCFSTkj2zjI2LdG6ANOUYfxXpj+RRAAA=) format("woff2"); }
6
- @font-face { font-family: "Archivo"; font-weight: 800; src: url(data:font/woff2;base64,d09GMgABAAAAAARoABAAAAAACJwAAAQNAAIAQgAAAAAAAAAAAAAAAAAAAAAAAAAAGhYbgRAcKgZgP1NUQVRaAEwRCAqEcIQUATYCJAMcCxAABCAFhGoHIAwHGxgHKB6FccNdgjLFEIv53uLyOp7///3+N9e+57sm0yTgybwE8f4rdCIhkRhdNGRC4VMilffzedb7TYcZyRkQmwJOF4CK41InIf9hXSYupz+WPr61fmGAXUCBJToX/m9+bNFHYwImlGY9dw/0QE2Pto0FGJB3TbabIke9urYiGAWUiH4RDNuQg3atvrUXPF6zdQrw0ODXaHrF1yjAmY3SFD8GlZ0L4lAlTLPL0UnJ/fIYmVhii1q6qEK3JOFBTgBoVdFFcaB+0WYB77Aof+fwXxCQP/HwfnsTXAAuqlP8D7R7aBUQqHHaPmAFJmJQh1kYDtBOeDoRQuDkmUiHQF7o2DIUgqbftAgIlUA8IEKGsCigUyqhAAGF/VwQSXc+N1xZ2/KEkh7VAzxCmfp/naUWmJuTGJHdgwkwAMYxhN8AGQCgAaKUEtmc9w139ZoOjmDPstnQRLsdn221WsH17b5TrrNOHABvDt7EZ5/ej/AJFoulj7zk6slgHOF0137XPZb+2ecPIzRxk+LQSZsywKgp5fju1U3FMD5x3a5jGsN7hCMCUv/s2/bKh24R+AZxs8+EMJw2YeXOrcHkMM/CjjsWtnQtADFmhTLwvqUbNwbjy9fvuxc4gA/usTywFOADFFFpJskqM0X0FzGC6/Pju4o3xkiPRid8ITPi0smIxJgrY2JWFcZ31+UzQ+DvSA2byKWYZJ6JSVRFzr371fH3Fss0vNFpV08yjN+40JocFHFQyw2Nv1ucGptSfAcqRobHllTrLa6KGk0M9BuKy4zMWHMKFZ4SPLLsid/6rJjmmhxW8IG2RyHXZqYFxn1fC5dHBscQ1WaSrKbGEINDFJACzdaG5ExZeny6PCM5KUPe/wZZJnzE+0aR7FFb6vz357/zCT+1Lixvi+d0l2an4GfVfrDwStzwZlFdmIlTdd9jf9tr5vOXtY5l75dtLqt+s/noT/ekh+bWKAAIypVR2eOTTM/Sf84Y9hbw52/YQcBpUYX3/7hedptMjwL6FQifI/1JhvhvHAOxWw3ULLhbPIgvagCl4Gh7PRBVjiYY8IyDzTa73ZgP0ElzAT4CCh3HdtBl3CndueX9YJOk+ywnx9fvMK0NCIxmHKQRDGcW7QLTWZIJhOi2TAhEDzkL5ZpzcJWSCkVDTEhEJ0qOrIsYpZcO8o0qGlyKIWagnMFOs5IEH5dOitTTuZU0tLkke/N0VLRKZMokJF6+lR5HBi4luUwd5BTEOPS00vWiqPBl+h2dHIfRg09OA6tFGd5C1jHpGmyOfzc+IT3Zp2jkyJAlSzZChzbtmhDWW7fB0sH+aK6TxJQUolCvLQIDyepHKC4xHl076Vx7fblybDJNsUetGFc3Je9KChjQk0mzV39D/6dOnwsA) format("woff2"); }
7
- @font-face { font-family: "JetBrains Mono"; font-weight: 500; src: url(data:font/woff2;base64,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) format("woff2"); }
8
- @font-face { font-family: "JetBrains Mono"; font-weight: 700; src: url(data:font/woff2;base64,d09GMgABAAAAAAXcABAAAAAACxAAAAWAAAI2BAAAAAAAAAAAAAAAAAAAAAAAAAAAGhwbIBwqBmA/U1RBVEwAgRQRCAqKFId8ATYCJANMCygABCAFhCYHIAwHG7wIAC4KbGPY6Kmv4iVmaXOe4rF+1lGC5/n3/H7tnVQn6QXQ3pGUEZTOuHfE9wX9Cs480B1+3NubqG94Yt2d6zkBCMlRTjAE7PJKMIIo4Rx+LHt7WpPXyn9YgPwvLPufY6b7i7K9gXQAjoZY0Vg4iwaa4ICiLxOjwQ6P7uje8QHC+VsvYzHcoknnne+prqDv3hcBXABAUQQHIU4WggCGCqqQwUN5MlCKDZABAHDoCZrQ8apXzY8FQCsT2NvWaXo/IdC0nAZg1Hy1zAEi/QEKMQZFYePIgQBwNTWss/79/4HLL615b6AJDYAaQghCFAUKUKAB3brUFcsDlm8y+/KyEWxK5KeRsN4POg2g3wMWgkpOfVCLlU+Nq+ckG/9uKsjBwqWGlgyEDhseIqSYceMjQCgDlDwAmhzh35z5yedGg1atlEslYpEwYZARJINsiPl8PQGUgJwC+Rlwv0uLdYCQFE1ohu89BZBoLD8Wc43rhvOy4rFWlRUmu6ikurfrzHVmG89MnlnhXftm2aVHFU7IaX7xn9eNPPXGwugp5ZbUZfSbhph4A2VYLSQbx8VOdcSxjMQx6hBRbseZn37zTW7gfyvc5okrfbhsZ171HjdqVVNHZuoc//UF566eWCWNe4qJrlnwpy5TTveUNF5yHEltp6fEUCejRkwr/5HX/JbIlWvx1/ZUYom3pVpnbL9zes3hdE+F6rszT0HQTepSYOpbmClom18T4T0T/jnVox5+6KHiop2HTyY8pfzoMw8UFh86e62Igx9dXVK4/9Zzyaly2ZHHHzcF5288UDpARqFc7dYycym7uWTlzoOSGpzmDr+tv+v5NXe/4ydvvms+FKFoz8RjV2ePre2rHvsQY4IPV+y0KecmmJyRucL+E0dnhWgiWu+CsENON/Jjp05fuy232yBr021XIYsAN3hn5Cok1s6oo2MdPhRHG9Eh/nXJhf3rOguogjtX97rZuqr1Sa/VQLNm9/+za65/HgFnsHsJIgYmYkOXwBAmCoGyK98hBtPuIJCJCPv/ww9FQAkwOBQKhxGgDMAPfhmxCom10ag4uxfJTrRuJzp/7tT5U+ctYCo2avUcRle9uw0JgkLcad14LD2pWd6kC4sJSK1jjuHJ+oB290QIOqcfH17zeC2KJm1ISsAavFDwfWzKfDVP7FVat9s14abGpIJM6ZjtWqM9ZlPE06ZGDF7N7NPDKfAJ6mbiSkOSB3gbbg+6aX31+VMbWNLjJ6ZE8Eun17cZ7e7vPhaGn8i4+ztpAi5dbPJq3fM6CHxmmglTAd5tAeruvP5HtCdKrWHT3v6tDWIoiqZvTL1eae1zFhXOKmE7sKmzMyroDnQauGFnEnfePPPYP4WFq6e2DmwZdpNbksQX/w2aXPKAGh5itd/4uWng/w/7vaZSW5ynNhY1/X38mdfToad4fSQ5sXhicusbrVLDd/bVXhyevQ3QBC4QJi8PkAOMBmhbKw8BtKv9UoePI3Oze/8WtawvAa68euNVx9mDl3/+y2Sqdab1NUIYBUDg19Op0xj4/3NHkMdQytdpaCwMAB+4NpUfnDYWBmhA2XgWOjB86P5NAAgQIJ/TRLmdcL3zWSjgkMZC7BhwP6yZQgX3M5UobzMtuvIh0yYhRcwQZdKbEaZBPCEBV3Z0hXO5JBvDHLpdDkbOMIggIdJEcOPERYxeOtLehmqY7AW7aaSGYlCY3GbcBIhyjyFIgGBoPBEt82DHSo65DCBODBdBImxLU+7dYoSIgqEd7ViKm/U5cSxMs5AgfmYqNfbraw8/1pmJYrl92IDQ1tRBQr0YiQyLzWqfimzlnvVMUKjv0uJGu3rrtV4HlPrPvZxNDDO+h7QVCUH8mL+tAAAyAQGEofJukXn7OAAA) format("woff2"); }
9
- text { font-variant-ligatures: none; font-feature-settings: "calt" 0, "liga" 0; }
10
- .lbl { font-family: "JetBrains Mono", ui-monospace, Menlo, monospace; font-size: 15px; font-weight: 500; fill: #0f0f0f; }
11
- .node { font-family: "Archivo", "Helvetica Neue", Arial, sans-serif; font-weight: 800; font-size: 22px; }
12
- .sub { font-family: "JetBrains Mono", ui-monospace, Menlo, monospace; font-weight: 700; font-size: 12px; letter-spacing: 0.06em; }
13
- .flow { fill: none; stroke: #0f0f0f; stroke-width: 2.25; stroke-linecap: round; }
14
- .review { stroke-dasharray: 9 7; }
5
+ @font-face { font-family: "Geist Mono"; font-weight: 400; src: url(data:font/woff2;base64,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) format("woff2"); }
6
+ @font-face { font-family: "Geist Mono"; font-weight: 500; src: url(data:font/woff2;base64,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) format("woff2"); }
7
+ text { font-family: "Geist Mono", ui-monospace, Menlo, monospace; font-variant-ligatures: none; font-feature-settings: "calt" 0, "liga" 0; }
8
+ .lbl { font-family: "Geist Mono", ui-monospace, Menlo, monospace; font-size: 15px; font-weight: 400; fill: #85a3b2; }
9
+ .node { font-family: "Geist Mono", ui-monospace, Menlo, monospace; font-weight: 500; font-size: 22px; letter-spacing: -0.01em; }
10
+ .sub { font-family: "Geist Mono", ui-monospace, Menlo, monospace; font-weight: 400; font-size: 12px; letter-spacing: 0.04em; fill: #85a3b2; }
11
+ .flow { fill: none; stroke: #85a3b2; stroke-width: 2; }
12
+ .review { stroke: #3e5a6b; stroke-width: 4; stroke-dasharray: 8 8; }
15
13
  @media (prefers-reduced-motion: no-preference) {
16
- .spin { transform-box: fill-box; transform-origin: 50% 58%; animation: turn 6s linear infinite; }
14
+ .spin { transform-box: fill-box; transform-origin: 50% 58%; animation: turn 6s steps(8) infinite; }
17
15
  @keyframes turn { to { transform: rotate(360deg); } }
18
16
  }
19
17
  </style>
20
- <rect x="16" y="-5" width="1100" height="490" rx="18" fill="#e0ff6e"/>
18
+ <rect x="16" y="-5" width="1100" height="490" fill="#0a0f1a"/>
21
19
  <defs>
22
20
  <marker id="head" viewBox="0 0 10 10" refX="10" refY="5" markerWidth="7" markerHeight="7" orient="auto-start-reverse">
23
- <path d="M0 0 L10 5 L0 10 z" fill="#0f0f0f"/>
21
+ <path d="M0 0 L10 5 L0 10 z" fill="#85a3b2"/>
24
22
  </marker>
25
23
  </defs>
26
- <path class="flow review" d="M120 142 V 70 Q 120 40 150 40 H 955 Q 985 40 985 70 V 158"/>
27
- <text class="lbl" x="552" y="28" text-anchor="middle">review loop</text>
28
- <path class="flow" d="M174.4 180 L 474.8 180" marker-end="url(#head)"/>
24
+ <path class="flow review" d="M120 140 V 40 H 985 V 155"/>
25
+ <text class="lbl" x="552" y="24" text-anchor="middle">review loop</text>
26
+ <path class="flow" d="M180 180 L 466 180" marker-end="url(#head)"/>
29
27
  <text class="lbl" x="324" y="166" text-anchor="middle">describe changes</text>
30
- <path class="flow" d="M474.8 220 L 174.4 220" marker-end="url(#head)"/>
28
+ <path class="flow" d="M466 220 L 180 220" marker-end="url(#head)"/>
31
29
  <text class="lbl" x="324" y="246" text-anchor="middle">asks · reports</text>
32
- <path class="flow" d="M641.2 180 L 900 180" marker-end="url(#head)"/>
30
+ <path class="flow" d="M650 180 L 896 180" marker-end="url(#head)"/>
33
31
  <text class="lbl" x="771" y="166" text-anchor="middle">writes</text>
34
- <path class="flow" d="M900 220 L 641.2 220" marker-end="url(#head)"/>
32
+ <path class="flow" d="M896 220 L 650 220" marker-end="url(#head)"/>
35
33
  <text class="lbl" x="771" y="246" text-anchor="middle">reads</text>
36
- <path class="flow" d="M538 244 L 538 380" marker-end="url(#head)"/>
34
+ <path class="flow" d="M538 247 L 538 376" marker-end="url(#head)"/>
37
35
  <text class="lbl" x="524" y="318" text-anchor="end">regenerates</text>
38
- <path class="flow" d="M578 380 L 578 244" marker-end="url(#head)"/>
36
+ <path class="flow" d="M578 376 L 578 247" marker-end="url(#head)"/>
39
37
  <text class="lbl" x="592" y="318" text-anchor="start">tests against ***plain</text>
40
- <text class="spin" x="880" y="372" text-anchor="middle" font-family="Archivo, sans-serif" font-weight="900" font-size="44" fill="#0f0f0f">↻</text>
41
- <circle cx="120" cy="200" r="58" fill="none" stroke="#0f0f0f" stroke-width="2.5"/>
42
- <text class="node" x="120" y="208" text-anchor="middle" fill="#0f0f0f">you</text>
43
- <rect x="470" y="156" width="176" height="88" rx="44" fill="#0f0f0f"/>
44
- <text x="558" y="212" text-anchor="middle" font-family="Archivo, sans-serif" font-weight="900" font-size="34" letter-spacing="-1" fill="#e0ff6e">turri</text>
45
- <rect x="900" y="158" width="170" height="84" rx="6" fill="#223936"/>
46
- <text x="985" y="208" text-anchor="middle" font-family="JetBrains Mono, ui-monospace, monospace" font-weight="700" font-size="24" fill="#c5dcd9">***plain</text>
47
- <rect x="473" y="380" width="170" height="84" rx="6" fill="#e0ff6e" stroke="#0f0f0f" stroke-width="2.5" stroke-dasharray="7 6"/>
48
- <text class="node" x="558" y="420" text-anchor="middle" fill="#0f0f0f">code</text>
49
- <text class="sub" x="558" y="444" text-anchor="middle" fill="#0f0f0f">DISPOSABLE</text>
38
+ <text class="spin" x="880" y="372" text-anchor="middle" font-weight="500" font-size="44" fill="#5ce1e6">↻</text>
39
+ <rect x="66" y="146" width="108" height="108" fill="#142030"/><rect x="66" y="143" width="108" height="3" fill="#85a3b2"/><rect x="66" y="254" width="108" height="3" fill="#85a3b2"/><rect x="63" y="146" width="3" height="108" fill="#85a3b2"/><rect x="174" y="146" width="3" height="108" fill="#85a3b2"/>
40
+ <text class="node" x="120" y="208" text-anchor="middle" fill="#f4f7f9">you</text>
41
+ <rect x="470" y="156" width="176" height="88" fill="#142030"/><rect x="470" y="153" width="176" height="3" fill="#85a3b2"/><rect x="470" y="244" width="176" height="3" fill="#85a3b2"/><rect x="467" y="156" width="3" height="88" fill="#85a3b2"/><rect x="646" y="156" width="3" height="88" fill="#85a3b2"/>
42
+ <text x="558" y="211" text-anchor="middle" font-weight="500" font-size="34" letter-spacing="-0.02em" fill="#ff5c8d">turri</text>
43
+ <rect x="900" y="158" width="170" height="84" fill="#142030"/><rect x="900" y="155" width="170" height="3" fill="#85a3b2"/><rect x="900" y="242" width="170" height="3" fill="#85a3b2"/><rect x="897" y="158" width="3" height="84" fill="#85a3b2"/><rect x="1070" y="158" width="3" height="84" fill="#85a3b2"/>
44
+ <text x="985" y="208" text-anchor="middle" font-weight="500" font-size="24" fill="#e9d8c8">***plain</text>
45
+ <rect x="473" y="380" width="170" height="84" fill="none" stroke="#3e5a6b" stroke-width="4" stroke-dasharray="8 8"/>
46
+ <text class="node" x="558" y="420" text-anchor="middle" fill="#f4f7f9">code</text>
47
+ <text class="sub" x="558" y="444" text-anchor="middle">disposable</text>
50
48
  </svg>