@turri-dev/cli 0.0.0-stage → 0.1.1
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.
- package/.pi/themes/turri-dark.json +80 -0
- package/.pi/themes/turri-light.json +82 -0
- package/LICENSE +21 -0
- package/README.md +272 -2
- package/assets/jellyfish/dark-00.png +0 -0
- package/assets/jellyfish/dark-01.png +0 -0
- package/assets/jellyfish/dark-02.png +0 -0
- package/assets/jellyfish/dark-03.png +0 -0
- package/assets/jellyfish/dark-04.png +0 -0
- package/assets/jellyfish/dark-05.png +0 -0
- package/assets/jellyfish/dark-06.png +0 -0
- package/assets/jellyfish/dark-07.png +0 -0
- package/assets/jellyfish/dark-08.png +0 -0
- package/assets/jellyfish/dark-09.png +0 -0
- package/assets/jellyfish/dark-10.png +0 -0
- package/assets/jellyfish/dark-11.png +0 -0
- package/assets/jellyfish/dark-12.png +0 -0
- package/assets/jellyfish/dark-13.png +0 -0
- package/assets/jellyfish/dark-14.png +0 -0
- package/assets/jellyfish/fallback.txt +252 -0
- package/assets/jellyfish/light-00.png +0 -0
- package/assets/jellyfish/light-01.png +0 -0
- package/assets/jellyfish/light-02.png +0 -0
- package/assets/jellyfish/light-03.png +0 -0
- package/assets/jellyfish/light-04.png +0 -0
- package/assets/jellyfish/light-05.png +0 -0
- package/assets/jellyfish/light-06.png +0 -0
- package/assets/jellyfish/light-07.png +0 -0
- package/assets/jellyfish/light-08.png +0 -0
- package/assets/jellyfish/light-09.png +0 -0
- package/assets/jellyfish/light-10.png +0 -0
- package/assets/jellyfish/light-11.png +0 -0
- package/assets/jellyfish/light-12.png +0 -0
- package/assets/jellyfish/light-13.png +0 -0
- package/assets/jellyfish/light-14.png +0 -0
- package/bin/turri.mjs +29 -0
- package/dist/ask.js +149 -0
- package/dist/brand.js +50 -0
- package/dist/btw.js +174 -0
- package/dist/chat-models.js +37 -0
- package/dist/chat-skills.js +18 -0
- package/dist/cli.js +178 -0
- package/dist/config-command.js +197 -0
- package/dist/config-edit.js +367 -0
- package/dist/config.js +489 -0
- package/dist/conform-cli.js +29 -0
- package/dist/cultivate-cli.js +110 -0
- package/dist/drift-cli.js +25 -0
- package/dist/environment.js +70 -0
- package/dist/extension.js +2372 -0
- package/dist/genome.js +301 -0
- package/dist/graph.js +246 -0
- package/dist/identity.js +35 -0
- package/dist/init-config.js +511 -0
- package/dist/judge.js +62 -0
- package/dist/lineage-cli.js +13 -0
- package/dist/loop-cli.js +132 -0
- package/dist/loop-tools.js +225 -0
- package/dist/mature-tools.js +193 -0
- package/dist/model-check.js +117 -0
- package/dist/module-report.js +9 -0
- package/dist/pi-launch.js +31 -0
- package/dist/pi.js +813 -0
- package/dist/plain/check.js +76 -0
- package/dist/plain/concepts.js +213 -0
- package/dist/plain/errors.js +43 -0
- package/dist/plain/files.js +57 -0
- package/dist/plain/highlight.js +195 -0
- package/dist/plain/index.js +15 -0
- package/dist/plain/liquid.js +129 -0
- package/dist/plain/markdown.js +208 -0
- package/dist/plain/parser.js +752 -0
- package/dist/plain/plain.tmLanguage.json +124 -0
- package/dist/plain/spec.js +291 -0
- package/dist/plain/stdlib.js +44 -0
- package/dist/question-dialog.js +125 -0
- package/dist/questioning.js +109 -0
- package/dist/quiet.js +125 -0
- package/dist/regenerate/activity.js +187 -0
- package/dist/regenerate/agent-env.js +190 -0
- package/dist/regenerate/agent-shell.js +114 -0
- package/dist/regenerate/arbiter.js +133 -0
- package/dist/regenerate/audit.js +203 -0
- package/dist/regenerate/cases.js +83 -0
- package/dist/regenerate/change-view.js +102 -0
- package/dist/regenerate/choose-conformance.js +117 -0
- package/dist/regenerate/chunk-plan.js +625 -0
- package/dist/regenerate/codebase.js +75 -0
- package/dist/regenerate/command-check.js +312 -0
- package/dist/regenerate/compaction.js +20 -0
- package/dist/regenerate/confine.js +310 -0
- package/dist/regenerate/conform.js +133 -0
- package/dist/regenerate/conformance-agents.js +955 -0
- package/dist/regenerate/conformance.js +936 -0
- package/dist/regenerate/conversation-writes.js +100 -0
- package/dist/regenerate/credentials.js +88 -0
- package/dist/regenerate/debug-log.js +228 -0
- package/dist/regenerate/debugger.js +90 -0
- package/dist/regenerate/delivery.js +19 -0
- package/dist/regenerate/diagnose-job.js +40 -0
- package/dist/regenerate/diagnose.js +81 -0
- package/dist/regenerate/diff-view.js +240 -0
- package/dist/regenerate/drift.js +194 -0
- package/dist/regenerate/engine.js +118 -0
- package/dist/regenerate/environment-error.js +220 -0
- package/dist/regenerate/job-log.js +102 -0
- package/dist/regenerate/job-panel.js +336 -0
- package/dist/regenerate/jobs-tree.js +300 -0
- package/dist/regenerate/jobs.js +427 -0
- package/dist/regenerate/lanes.js +32 -0
- package/dist/regenerate/lineage.js +384 -0
- package/dist/regenerate/loop.js +1435 -0
- package/dist/regenerate/masking.js +95 -0
- package/dist/regenerate/notes.js +397 -0
- package/dist/regenerate/os-sandbox.js +243 -0
- package/dist/regenerate/paths.js +165 -0
- package/dist/regenerate/plain-palette.js +83 -0
- package/dist/regenerate/prompt.js +704 -0
- package/dist/regenerate/records.js +294 -0
- package/dist/regenerate/regenerate.js +598 -0
- package/dist/regenerate/regression.js +181 -0
- package/dist/regenerate/release.js +262 -0
- package/dist/regenerate/requires.js +241 -0
- package/dist/regenerate/resources.js +164 -0
- package/dist/regenerate/self-check.js +111 -0
- package/dist/regenerate/services.js +155 -0
- package/dist/regenerate/shell.js +319 -0
- package/dist/regenerate/side-panel.js +278 -0
- package/dist/regenerate/spec-map-view.js +574 -0
- package/dist/regenerate/spec-map.js +561 -0
- package/dist/regenerate/spec-steps.js +527 -0
- package/dist/regenerate/state.js +10 -0
- package/dist/regenerate/steer.js +91 -0
- package/dist/regenerate/steering.js +119 -0
- package/dist/regenerate/stop-report.js +161 -0
- package/dist/regenerate/submit-work.js +71 -0
- package/dist/regenerate/test-kinds.js +124 -0
- package/dist/regenerate/test-scripts.js +695 -0
- package/dist/regenerate/testing.js +406 -0
- package/dist/regenerate/transcript-view.js +1 -0
- package/dist/regenerate/unit-gate.js +20 -0
- package/dist/regenerate/unit-review.js +347 -0
- package/dist/regenerate/web.js +256 -0
- package/dist/regenerate/workspace.js +99 -0
- package/dist/regenerate-cli.js +68 -0
- package/dist/release-agent.js +114 -0
- package/dist/release-cli.js +131 -0
- package/dist/release-tools.js +148 -0
- package/dist/review/live.js +165 -0
- package/dist/rules.js +47 -0
- package/dist/self-invocation.js +110 -0
- package/dist/setup.js +245 -0
- package/dist/skill-progress.js +78 -0
- package/dist/skill.js +149 -0
- package/dist/skills.js +74 -0
- package/dist/spec-diff-cli.js +37 -0
- package/dist/spec-tools.js +155 -0
- package/dist/stack.js +158 -0
- package/dist/stacks.js +97 -0
- package/dist/stdlib.js +100 -0
- package/dist/terminal-images.js +46 -0
- package/dist/tools.js +242 -0
- package/dist/validate-cli.js +52 -0
- package/docs/README.md +58 -0
- package/docs/authoring-plain.md +245 -0
- package/docs/config.md +353 -0
- package/docs/deploy.md +109 -0
- package/docs/development.md +75 -0
- package/docs/headless.md +148 -0
- package/docs/images/how-turri-works.svg +50 -0
- package/docs/images/turri-banner.png +0 -0
- package/docs/installation.md +118 -0
- package/docs/parser.md +108 -0
- package/docs/regenerating.md +1116 -0
- package/docs/sandbox.md +233 -0
- package/docs/skills-and-gates.md +254 -0
- package/docs/theming.md +76 -0
- package/docs/using.md +454 -0
- package/genome/rules/bullet-continuation.md +69 -0
- package/genome/rules/concise-specs-examples.md +431 -0
- package/genome/rules/concise-specs-integration-examples.md +270 -0
- package/genome/rules/concise-specs.md +80 -0
- package/genome/rules/definitions.md +107 -0
- package/genome/rules/examples/integration-embedded-testing/run_conformance_tests_java.sh +163 -0
- package/genome/rules/examples/integration-embedded-testing/run_unittests_java.sh +51 -0
- package/genome/rules/exported-concepts.md +39 -0
- package/genome/rules/func-specs.md +129 -0
- package/genome/rules/impl-reqs.md +68 -0
- package/genome/rules/import-modules.md +48 -0
- package/genome/rules/integration-embedded-testing.md +276 -0
- package/genome/rules/integration-embedded.md +171 -0
- package/genome/rules/integration-standalone.md +135 -0
- package/genome/rules/integrations.md +137 -0
- package/genome/rules/linked-resources.md +69 -0
- package/genome/rules/module-structure.md +105 -0
- package/genome/rules/project-model.md +88 -0
- package/genome/rules/required-concepts.md +44 -0
- package/genome/rules/requires-modules.md +80 -0
- package/genome/rules/stdlib.md +73 -0
- package/genome/rules/test-reqs.md +57 -0
- package/genome/skills/add-acceptance-test/SKILL.md +100 -0
- package/genome/skills/add-concept/SKILL.md +71 -0
- package/genome/skills/add-functional-spec/SKILL.md +84 -0
- package/genome/skills/add-functional-specs/SKILL.md +118 -0
- package/genome/skills/add-implementation-requirement/SKILL.md +86 -0
- package/genome/skills/add-resource/SKILL.md +105 -0
- package/genome/skills/add-template/SKILL.md +64 -0
- package/genome/skills/add-test-requirement/SKILL.md +73 -0
- package/genome/skills/analyze-2-func-specs/SKILL.md +101 -0
- package/genome/skills/analyze-func-specs/SKILL.md +123 -0
- package/genome/skills/analyze-if-func-spec-too-complex/SKILL.md +211 -0
- package/genome/skills/break-down-func-spec/SKILL.md +205 -0
- package/genome/skills/brood/SKILL.md +229 -0
- package/genome/skills/brood/references/intent.md +35 -0
- package/genome/skills/brood/references/product.md +101 -0
- package/genome/skills/brood/references/stack.md +61 -0
- package/genome/skills/brood/references/testing.md +56 -0
- package/genome/skills/bud/SKILL.md +148 -0
- package/genome/skills/bud/references/checklist.md +44 -0
- package/genome/skills/bud/references/intent.md +30 -0
- package/genome/skills/check-plain-env/SKILL.md +285 -0
- package/genome/skills/consolidate-concepts/SKILL.md +192 -0
- package/genome/skills/create-import-module/SKILL.md +104 -0
- package/genome/skills/create-requires-module/SKILL.md +109 -0
- package/genome/skills/debug-specs/SKILL.md +188 -0
- package/genome/skills/implement-conformance-testing-script/SKILL.md +238 -0
- package/genome/skills/implement-conformance-testing-script/assets/run_conformance_tests_cypress.ps1 +337 -0
- package/genome/skills/implement-conformance-testing-script/assets/run_conformance_tests_golang.ps1 +111 -0
- package/genome/skills/implement-conformance-testing-script/assets/run_conformance_tests_java.sh +136 -0
- package/genome/skills/implement-conformance-testing-script/assets/run_conformance_tests_python.ps1 +103 -0
- package/genome/skills/implement-conformance-testing-script/assets/run_conformance_tests_python.sh +150 -0
- package/genome/skills/implement-unit-testing-script/SKILL.md +160 -0
- package/genome/skills/implement-unit-testing-script/assets/run_unittests_flutter.ps1 +87 -0
- package/genome/skills/implement-unit-testing-script/assets/run_unittests_golang.ps1 +73 -0
- package/genome/skills/implement-unit-testing-script/assets/run_unittests_java.sh +72 -0
- package/genome/skills/implement-unit-testing-script/assets/run_unittests_python.ps1 +81 -0
- package/genome/skills/implement-unit-testing-script/assets/run_unittests_python.sh +98 -0
- package/genome/skills/implement-unit-testing-script/assets/run_unittests_react.ps1 +87 -0
- package/genome/skills/init-plain-project/SKILL.md +117 -0
- package/genome/skills/init-plain-project/references/checklist.md +30 -0
- package/genome/skills/mature/SKILL.md +126 -0
- package/genome/skills/refactor-module/SKILL.md +196 -0
- package/genome/skills/resolve-section-ownership/SKILL.md +64 -0
- package/genome/skills/resolve-spec-conflict/SKILL.md +96 -0
- package/genome/skills/spawn/SKILL.md +163 -0
- package/package.json +68 -4
- package/stdlib/backend/common.plain +25 -0
- package/stdlib/backend/conformance/common.plain +12 -0
- package/stdlib/backend/conformance/go-gin.plain +9 -0
- package/stdlib/backend/conformance/go-nethttp.plain +9 -0
- package/stdlib/backend/conformance/java-springboot.plain +9 -0
- package/stdlib/backend/conformance/python-django.plain +9 -0
- package/stdlib/backend/conformance/python-fastapi.plain +9 -0
- package/stdlib/backend/conformance/python-flask.plain +9 -0
- package/stdlib/backend/conformance/rust-axum.plain +9 -0
- package/stdlib/backend/conformance/typescript-express.plain +9 -0
- package/stdlib/backend/conformance/typescript-fastify.plain +9 -0
- package/stdlib/backend/conformance/typescript-nestjs.plain +9 -0
- package/stdlib/backend/go-gin.plain +18 -0
- package/stdlib/backend/go-nethttp.plain +18 -0
- package/stdlib/backend/java-springboot.plain +19 -0
- package/stdlib/backend/python-django.plain +19 -0
- package/stdlib/backend/python-fastapi.plain +23 -0
- package/stdlib/backend/python-flask.plain +23 -0
- package/stdlib/backend/rust-axum.plain +17 -0
- package/stdlib/backend/typescript-express.plain +19 -0
- package/stdlib/backend/typescript-fastify.plain +19 -0
- package/stdlib/backend/typescript-nestjs.plain +19 -0
- package/stdlib/cli/common.plain +17 -0
- package/stdlib/cli/conformance/common.plain +11 -0
- package/stdlib/cli/conformance/go-cobra.plain +9 -0
- package/stdlib/cli/conformance/go-flag.plain +9 -0
- package/stdlib/cli/conformance/java-picocli.plain +9 -0
- package/stdlib/cli/conformance/python-argparse.plain +9 -0
- package/stdlib/cli/conformance/python-click.plain +9 -0
- package/stdlib/cli/conformance/python-typer.plain +9 -0
- package/stdlib/cli/conformance/rust-clap.plain +9 -0
- package/stdlib/cli/conformance/typescript-commander.plain +9 -0
- package/stdlib/cli/go-cobra.plain +14 -0
- package/stdlib/cli/go-flag.plain +14 -0
- package/stdlib/cli/java-picocli.plain +15 -0
- package/stdlib/cli/python-argparse.plain +18 -0
- package/stdlib/cli/python-click.plain +16 -0
- package/stdlib/cli/python-typer.plain +16 -0
- package/stdlib/cli/rust-clap.plain +14 -0
- package/stdlib/cli/typescript-commander.plain +17 -0
- package/stdlib/frontend/angular.plain +17 -0
- package/stdlib/frontend/common.plain +17 -0
- package/stdlib/frontend/conformance/angular.plain +5 -0
- package/stdlib/frontend/conformance/common.plain +11 -0
- package/stdlib/frontend/conformance/react-vite.plain +5 -0
- package/stdlib/frontend/conformance/sveltekit-spa.plain +5 -0
- package/stdlib/frontend/conformance/vue-vite.plain +5 -0
- package/stdlib/frontend/react-vite.plain +16 -0
- package/stdlib/frontend/sveltekit-spa.plain +16 -0
- package/stdlib/frontend/vue-vite.plain +16 -0
- package/stdlib/lang/go.plain +13 -0
- package/stdlib/lang/java.plain +12 -0
- package/stdlib/lang/python.plain +18 -0
- package/stdlib/lang/rust.plain +12 -0
- package/stdlib/lang/typescript.plain +13 -0
- package/stdlib/library/common.plain +15 -0
- package/stdlib/library/conformance/common.plain +9 -0
- package/stdlib/library/conformance/go.plain +9 -0
- package/stdlib/library/conformance/java-gradle.plain +9 -0
- package/stdlib/library/conformance/java-maven.plain +9 -0
- package/stdlib/library/conformance/python.plain +9 -0
- package/stdlib/library/conformance/rust.plain +9 -0
- package/stdlib/library/conformance/typescript.plain +9 -0
- package/stdlib/library/go.plain +12 -0
- package/stdlib/library/java-gradle.plain +13 -0
- package/stdlib/library/java-maven.plain +13 -0
- package/stdlib/library/python.plain +13 -0
- package/stdlib/library/rust.plain +12 -0
- package/stdlib/library/typescript.plain +15 -0
package/docs/deploy.md
ADDED
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
# Trying what is built: deploy
|
|
2
|
+
|
|
3
|
+
Ask to try it - "let me try it", "run the app" - at any time, even while a render is running. The
|
|
4
|
+
chat calls `deploy`: it puts the module's code, as it was last built, in `dist/<module>` in the
|
|
5
|
+
project, and starts it on your machine. An app with a backend and a frontend is started whole, and
|
|
6
|
+
you are told where to open your browser. For a command-line tool or a library you get a few commands
|
|
7
|
+
to paste, run from `dist/<module>`, and what they print.
|
|
8
|
+
|
|
9
|
+
Every pulse that lands code - a render, a spec diff, a conformance run, a functionality regenerated - ends with
|
|
10
|
+
"Want to try it? Just ask me - I'll set it up from what's built so far." When a render's conformance
|
|
11
|
+
tests follow as a pulse of their own, the conformance pulse says it, once they pass too.
|
|
12
|
+
|
|
13
|
+
## When the project says how it is released
|
|
14
|
+
|
|
15
|
+
Every release starts the same way: the module's last commit - what last landed whole - goes in
|
|
16
|
+
`dist/<module>`, as below. Not everything is tried from there, though. An integration that a host
|
|
17
|
+
application loads goes on into the host's own tree, with the host's own scripts, and then the host
|
|
18
|
+
is started. So the model asks itself whether there is guidance for how this project is released -
|
|
19
|
+
in what you said, the project's AGENTS.md, a skill, anything it was given - and passes it to
|
|
20
|
+
`release` whole:
|
|
21
|
+
|
|
22
|
+
- **There is guidance.** The code goes on from `dist/<module>` as the guidance says, on your machine,
|
|
23
|
+
from the host codebase - the git repository the project is in, or else where **turri** runs: the
|
|
24
|
+
scripts it names, in their order, and any change it says the host needs. The code is taken from
|
|
25
|
+
`dist`, never from `plain_modules`, where a pulse may be writing; where the host's tooling reads
|
|
26
|
+
`plain_modules` itself, the model points it at `dist`, or does by hand from `dist` what it does. When
|
|
27
|
+
to release - after every pulse, say - is the guidance's to say too. The rules below hold as they do for `dist`: the
|
|
28
|
+
model runs the project's scripts and installs what they need itself, and what keeps running - a
|
|
29
|
+
start script, a server - is started with `activate`. Nothing built is changed.
|
|
30
|
+
- **There is none.** The default: it is started from `dist/<module>`, as below, as best it can be.
|
|
31
|
+
|
|
32
|
+
Guidance is whatever says it, in any words - a line in AGENTS.md such as *integrations are released
|
|
33
|
+
with `scripts/deploy_integrations.sh`, then the app is started with `scripts/start.sh`*, or the
|
|
34
|
+
same said in the chat.
|
|
35
|
+
|
|
36
|
+
The chat reacts to every pulse that finishes as it finishes, without waiting for you to speak: guidance
|
|
37
|
+
that says to release after a build - *release each integration once it is regenerated* - is followed then.
|
|
38
|
+
It never has to work out whether what a pulse landed is already out: `release` does. When the module's
|
|
39
|
+
last commit is what its last release in this conversation shipped, it does nothing - nothing exported,
|
|
40
|
+
nothing stopped, what runs left running - and says so; `again: true` releases it anyway, for when what
|
|
41
|
+
that release said to do was not done.
|
|
42
|
+
|
|
43
|
+
## What goes in dist
|
|
44
|
+
|
|
45
|
+
- **The module on top.** Code accumulates up a `requires` chain: a module starts from a copy of the
|
|
46
|
+
code of the one it requires. So deploy always takes the module on top of the spec's chain - the
|
|
47
|
+
project's `module` from `config.turri.yaml` unless another spec is named. Before the module on top has
|
|
48
|
+
anything built, it takes the nearest one below it that has, and says so.
|
|
49
|
+
- **Its last commit.** Each `plain_modules/<module>` is a git repository (`.plaingit`), and every unit
|
|
50
|
+
of work starts from a commit and ends with one. What a pulse is writing right now is half-done, so it
|
|
51
|
+
is left out: deploy exports the commit - `git archive` of `code/` - which is the working tree with
|
|
52
|
+
every unstaged and untracked change put back, and no `node_modules`, `.venv`, `target` or other
|
|
53
|
+
build leftovers, whatever the stack. The working tree the pulse is using is never read or touched.
|
|
54
|
+
The harness's own files - `.turri.json`, the agents' notes - are left out too.
|
|
55
|
+
- **Replaced whole.** Each deploy stops what the last one of the module started, empties
|
|
56
|
+
`dist/<module>` and exports again. Change nothing there you want to keep. `dist/` is not added to
|
|
57
|
+
`.gitignore`: whether it is committed is yours to decide.
|
|
58
|
+
|
|
59
|
+
## Starting it
|
|
60
|
+
|
|
61
|
+
It runs on your machine, outside the [sandbox](sandbox.md) - with your toolchains, where your
|
|
62
|
+
browser can reach it. The chat reads how the code runs (its README, manifests and entry points),
|
|
63
|
+
then:
|
|
64
|
+
|
|
65
|
+
1. **Checks the toolchains** it needs (`node`, `python3`, `go`, ...). One is missing: it installs it
|
|
66
|
+
with the machine's package manager (`brew` on macOS, `apt` or `dnf` on Linux).
|
|
67
|
+
2. **Installs what it needs itself.** Dependencies into a virtual environment or `node_modules`, a
|
|
68
|
+
build that downloads packages, an image pull, a guided release's project scripts: it runs them in
|
|
69
|
+
its own shell, as any agent with a shell would, and fixes what fails. Nothing is put to you - there
|
|
70
|
+
is no dialog to answer.
|
|
71
|
+
3. **Starts what serves** with `activate`: in the background, in its own process group, its
|
|
72
|
+
output in `.turri/deploy/<module>-<name>.log`. It waits until something answers at the
|
|
73
|
+
server's URL - up to a minute - so the URL you are given has answered once; one that exits first
|
|
74
|
+
comes back with the end of its output. A port something else holds is said, with what holds it,
|
|
75
|
+
and nothing is started there: the app is not moved to a port you were not told about.
|
|
76
|
+
|
|
77
|
+
What it started is listed by the `pulses` tool and by **`/halt`**, and stopped by **`/halt activated <name>`**, by
|
|
78
|
+
`halt`, by the next deploy of the module, and when the chat exits.
|
|
79
|
+
|
|
80
|
+
## After every pulse
|
|
81
|
+
|
|
82
|
+
With `release-after-pulse: true` in `config.turri.yaml`, turri releases what each pulse lands itself, as the
|
|
83
|
+
pulse's last stage (`releasing what this pulse landed`): the pulse ends once it is released, so the
|
|
84
|
+
module's next pulse starts on what is out, and the chat - waiting on your answer to a question, say - has
|
|
85
|
+
no part in it. One release a pulse that lands, in order.
|
|
86
|
+
|
|
87
|
+
The release is done by an agent on the coding model, thinking low - it follows steps, and a small model does that
|
|
88
|
+
well - the same one `turri release` runs, and it finds
|
|
89
|
+
the guidance as the chat would: what was passed to `release` earlier in the conversation, the project's
|
|
90
|
+
AGENTS.md and its skills. When nothing there says to release after a build, it releases nothing and says
|
|
91
|
+
so. Its last words go in the pulse's report - "Released after the pulse: ..." - and the chat, seeing them,
|
|
92
|
+
does not release it again. A release that fails is said there too; what the pulse landed stays.
|
|
93
|
+
|
|
94
|
+
Off by default: without the key, releasing after a pulse is the chat's, when its guidance says so.
|
|
95
|
+
|
|
96
|
+
## On the command line
|
|
97
|
+
|
|
98
|
+
```bash
|
|
99
|
+
turri release [file.plain] [--in-module <module>] [--guidance <text>] [--model <provider/id>]
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
The same, without the chat: a one-off agent on `coding-model`, thinking low (or `--model`; without either,
|
|
103
|
+
pi's default) is given the project's AGENTS.md and what `--guidance <text>` says, looks for guidance in
|
|
104
|
+
them, and releases as above - as the guidance says, or to `dist/<module>` - then works out how to
|
|
105
|
+
start it and starts it here, installing what it needs without asking. It stays in the foreground with the servers'
|
|
106
|
+
output; `Ctrl-C` stops them. If it does not get there, deploy from the chat instead.
|
|
107
|
+
|
|
108
|
+
`turri regenerate`, `spec-diff` and `conform` end a build that landed with
|
|
109
|
+
`To try it: turri release <spec>`.
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
# Development
|
|
2
|
+
|
|
3
|
+
```bash
|
|
4
|
+
npm run typecheck # types
|
|
5
|
+
npm test # the whole suite, about a minute
|
|
6
|
+
```
|
|
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.
|
|
10
|
+
|
|
11
|
+
## Mutation testing
|
|
12
|
+
|
|
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`.
|
|
20
|
+
|
|
21
|
+
Working on turri itself, `npm link` points the global `turri` at this clone, so edits
|
|
22
|
+
take effect immediately. `npm run chat` starts pi with the extension loaded from `src/`.
|
|
23
|
+
|
|
24
|
+
The package on npm is built: Node runs no `.ts` file inside a `node_modules`, so `npm run build` (`scripts/build.mjs`,
|
|
25
|
+
over `tsconfig.build.json`) compiles `src/` into `dist/`, file for file - each file as deep as its source, so it finds
|
|
26
|
+
`genome/`, `stdlib/` and `.pi/` the same way - with its imports' `.ts` rewritten to `.js`, and copies the other files
|
|
27
|
+
in `src/` beside them. `npm pack` and `npm publish` run it first (`prepack`), and the package holds `dist/`, not
|
|
28
|
+
`src/`. `bin/turri.mjs` runs `src/cli.ts` where there is one and `dist/cli.js` otherwise; `cli.ts` starts pi with
|
|
29
|
+
the launcher and extension beside itself, whichever they are. To try the package as users get it:
|
|
30
|
+
`npm pack`, then `npm install -g ./turri-dev-cli-<version>.tgz`.
|
|
31
|
+
|
|
32
|
+
## Layout
|
|
33
|
+
|
|
34
|
+
| path | what it is |
|
|
35
|
+
|---|---|
|
|
36
|
+
| `bin/turri.mjs` | the launcher: checks the Node version, then loads `src/cli.ts` |
|
|
37
|
+
| `src/cli.ts` | the `turri` command and its subcommands |
|
|
38
|
+
| `src/setup.ts`, `src/environment.ts`, `src/stack.ts` | `turri doctor`, and what it checks - this machine, and in a project what its modules need of it |
|
|
39
|
+
| `src/extension.ts` | the pi extension: every tool and command the conversation has |
|
|
40
|
+
| `src/skill.ts` | parses `SKILL.md` into steps and gates |
|
|
41
|
+
| `src/graph.ts` | compiles a skill into a LangGraph `StateGraph`: step -> gate -> route |
|
|
42
|
+
| `src/judge.ts` | runs independent judges, applies the quorum, builds the critique |
|
|
43
|
+
| `src/genome.ts` | `runSkill()` - the one entry point the CLI and the extension share |
|
|
44
|
+
| `src/pi.ts` | pi session factories for executors, sub-agents and judges |
|
|
45
|
+
| `src/spec-tools.ts` | `write_spec_diff` |
|
|
46
|
+
| `src/loop-tools.ts` | `regenerate`, `drift` and `halt` - one definition each, for the conversation and the skills alike |
|
|
47
|
+
| `src/mature-tools.ts` | `mature`'s own two: `watch_jobs` and `spec_findings` |
|
|
48
|
+
| `src/plain/` | the \*\*\*plain parser - see [parser](parser.md) - and `highlight.ts`, which highlights \*\*\*plain with plyn's grammar, `plain.tmLanguage.json` (`npm run grammar:sync` refreshes it from [plyn](https://github.com/plainlang/plyn)) |
|
|
49
|
+
| `src/regenerate/conformance.ts` | conformance tests: writing them, and fixing the test or the code until they pass |
|
|
50
|
+
| `src/regenerate/regenerate.ts`, `src/regenerate/conform.ts` | a code job - a spec diff rendered in the code - and a conformance job |
|
|
51
|
+
| `src/regenerate/loop.ts`, `src/regenerate/records.ts`, `src/regenerate/drift.ts`, `src/regenerate/jobs.ts` | the work loop, the records and the work derived from them, and background jobs |
|
|
52
|
+
| `src/regenerate/chunk-plan.ts`, `src/regenerate/cases.ts` | cutting the work into jobs, and what a job's agents are told by its cases |
|
|
53
|
+
| `src/regenerate/regression.ts` | the whole conformance suite run, and the repair agent that may not touch tests |
|
|
54
|
+
| `src/regenerate/lineage.ts` | module history in `.plaingit` |
|
|
55
|
+
| `src/regenerate/release.ts`, `src/release-tools.ts`, `src/release-cli.ts` | deploy: the last commit to `dist/<module>`, what installs software, what it starts on the host - see [deploy](deploy.md) |
|
|
56
|
+
| `src/regenerate/confine.ts`, `src/regenerate/agent-shell.ts`, `src/regenerate/command-check.ts`, `src/regenerate/os-sandbox.ts`, `src/regenerate/audit.ts`, `src/regenerate/agent-env.ts`, `src/regenerate/shell.ts` | the file-tool fence and the walls around every agent command: the check, the OS sandbox, the audit, the module's environment, the host runner - see [sandbox](sandbox.md) |
|
|
57
|
+
| `src/regenerate/engine.ts` | choosing and resolving render models |
|
|
58
|
+
| `src/regenerate/job-panel.ts`, `src/regenerate/transcript-view.ts`, `src/regenerate/diff-view.ts` | the job panel, the side panel's colours, and a spec diff as they show it - highlighted and banded |
|
|
59
|
+
| `src/regenerate/spec-map.ts`, `src/regenerate/spec-map-view.ts` | regen: each item's mark, from the records, the `.plain` files and the work loop's plan (`mapPlan`), and the map drawn |
|
|
60
|
+
| `src/regenerate/debug-log.ts` | each render agent's whole conversation, always streamed to `.turri/debug/` |
|
|
61
|
+
| `genome/` | what turri knows and can do: `genome/skills/`, the skill library, and `genome/rules/`, the \*\*\*plain authoring rules |
|
|
62
|
+
|
|
63
|
+
## Known edges
|
|
64
|
+
|
|
65
|
+
- Judges default to the executor's model. Real separation wants a different provider - set
|
|
66
|
+
`model:` on the gate. An unconfigured model falls back to the executor's with a note rather than
|
|
67
|
+
failing the run.
|
|
68
|
+
- The executor keeps one session across steps and retries, so context builds up with attempts. Long
|
|
69
|
+
skills want compaction or a session per step.
|
|
70
|
+
- A gate's escalation lives in memory (LangGraph's `MemorySaver`): it must be answered in the same
|
|
71
|
+
run.
|
|
72
|
+
- The gate machinery has no automated coverage; see
|
|
73
|
+
[trying the gate semantics](skills-and-gates.md#trying-the-gate-semantics).
|
|
74
|
+
- Jobs live in memory: there is no resuming a job after pi exits. The work itself is on disk, so a
|
|
75
|
+
killed job leaves a partial build rather than nothing.
|
package/docs/headless.md
ADDED
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
# Running unattended
|
|
2
|
+
|
|
3
|
+
Given a spec, build it without a conversation - overnight, in CI, on a spare machine.
|
|
4
|
+
|
|
5
|
+
## Before you leave it
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
turri doctor
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
Everything marked `✗` stops the run, so fix those first. Two more things matter for a long run:
|
|
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.
|
|
18
|
+
- **Credentials that do not expire mid-run.** An API key in the environment
|
|
19
|
+
(`ANTHROPIC_API_KEY`, `OPENAI_API_KEY`, `GEMINI_API_KEY`, ...) or pi's saved login - whatever
|
|
20
|
+
`doctor` shows as the model provider.
|
|
21
|
+
|
|
22
|
+
Check the spec parses, without spending anything:
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
cd ~/my-project # the directory that holds the spec
|
|
26
|
+
turri validate app.plain
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
## Run it
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
turri regenerate app.plain --model anthropic/claude-sonnet-5
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
That builds every functionality in order - code and unit tests, then conformance tests when the
|
|
36
|
+
module has `***test reqs***`, then every conformance test so far - and advances only when they
|
|
37
|
+
pass. A module that `requires` others builds them first, bottom up. When it is done it prints
|
|
38
|
+
`To try it: turri release <spec>` - see [deploy](deploy.md).
|
|
39
|
+
|
|
40
|
+
Keep it running when the terminal closes and the laptop would sleep, and keep the log:
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
# macOS: caffeinate keeps the machine awake for as long as the command runs
|
|
44
|
+
nohup caffeinate -is turri regenerate app.plain --model anthropic/claude-sonnet-5 > render.log 2>&1 &
|
|
45
|
+
|
|
46
|
+
# Linux
|
|
47
|
+
nohup turri regenerate app.plain --model anthropic/claude-sonnet-5 > render.log 2>&1 &
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
Or run it in `tmux` / `screen` to look in on it. `tail -f render.log` follows it either way.
|
|
51
|
+
|
|
52
|
+
The project's `config.turri.yaml`, in the folder you run it from, can give the defaults, so a run can
|
|
53
|
+
be just `turri regenerate`:
|
|
54
|
+
|
|
55
|
+
```yaml
|
|
56
|
+
module: app.plain # what to build when no file is given
|
|
57
|
+
coding-model: # the model every agent runs on, and how hard it thinks
|
|
58
|
+
model: anthropic/claude-sonnet-5
|
|
59
|
+
thinking: medium
|
|
60
|
+
judge-model: anthropic/claude-opus-5-5 # the judge and the test review (default: coding-model)
|
|
61
|
+
conformance-tests: true # write and run conformance tests where there are test reqs (default: false)
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
The same file names the [testing scripts](regenerating.md#the-testing-scripts); every key is in
|
|
65
|
+
[the project config](config.md). Options on the command
|
|
66
|
+
line win over it; `turri regenerate`, `spec-diff` and `conform` take its
|
|
67
|
+
`coding-model` too, and `--coding-model` means the same as `--model` on every one of them.
|
|
68
|
+
A project whose parts are on different stacks keeps each stack's settings in
|
|
69
|
+
`config.turri.<stack>.yaml` beside it ([stacks](config.md#stacks)). Choose the one to build with
|
|
70
|
+
`--stack <name>` - on any command: `regenerate`, `conform`, `spec-diff`, `glimpse`, `release`, `cultivate` -
|
|
71
|
+
or `TURRI_STACK`: `turri regenerate backend.plain --stack backend`. With none chosen,
|
|
72
|
+
`config.turri.yaml` alone is used; a stack the project has no file for is refused.
|
|
73
|
+
|
|
74
|
+
Useful options - `turri regenerate --help` lists all of them:
|
|
75
|
+
|
|
76
|
+
| | |
|
|
77
|
+
| --- | --- |
|
|
78
|
+
| `--model <provider/id>` | the model every agent runs on (`--coding-model` too), with `:level` for its thinking (`provider/id:high`); default, `config.turri.yaml`'s `coding-model`, then its `harness-model`, then pi's configured model |
|
|
79
|
+
| `--judge-model <provider/id>` | `conformance`: the model of the judge and the test review; default, `config.turri.yaml`'s `judge-model`, then the coding model |
|
|
80
|
+
| `--stack <name>` | build with the stack's `config.turri.<name>.yaml` over `config.turri.yaml`; default, `TURRI_STACK`, else none |
|
|
81
|
+
| `--to <frid>` | queue functionalities only up to this one |
|
|
82
|
+
| `--only` | build just this module, not the ones it requires |
|
|
83
|
+
| `--build-dir <dir>` | where the code goes; default `plain_modules/<module>/code` |
|
|
84
|
+
|
|
85
|
+
`turri regenerate` queues the jobs the chat's `regenerate` does - a spec diff that brings what
|
|
86
|
+
is built in line, when it no longer matches the spec, then one render functionality job per
|
|
87
|
+
functional spec not built, each with its conformance job - and runs them all to the end, the queue
|
|
88
|
+
master reworking them as in the chat (see [a whole module](regenerating.md#a-whole-module)).
|
|
89
|
+
Conformance tests follow `conformance-tests:` in `config.turri.yaml`.
|
|
90
|
+
|
|
91
|
+
## In the morning
|
|
92
|
+
|
|
93
|
+
The command exits `0` only when every module is complete. The log says, per functionality, what
|
|
94
|
+
was built and whether its tests passed; the last lines summarise each module.
|
|
95
|
+
|
|
96
|
+
| | |
|
|
97
|
+
| --- | --- |
|
|
98
|
+
| `plain_modules/<module>/code/` | the code and unit tests |
|
|
99
|
+
| `plain_modules/<module>/tests/` | the conformance tests |
|
|
100
|
+
| `plain_modules/<module>/code/.turri.json` | the marshalled plain tree the code is built from |
|
|
101
|
+
|
|
102
|
+
And what happened, step by step, in the module's history:
|
|
103
|
+
|
|
104
|
+
```bash
|
|
105
|
+
git --git-dir=plain_modules/<module>/.plaingit --work-tree=plain_modules/<module> log --stat
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
## If it stopped
|
|
109
|
+
|
|
110
|
+
Run the same command again. Functionalities already built from the current spec are left alone,
|
|
111
|
+
and modules that are complete are skipped, so it picks up where it stopped - after a crash, a
|
|
112
|
+
provider outage, or a machine that went to sleep anyway. Whatever the stopped run left
|
|
113
|
+
half-written is undone first: each step starts from the module's last commit. A functionality whose spec has changed
|
|
114
|
+
since counts as not built, so editing the spec and re-running rebuilds what changed. `--force`
|
|
115
|
+
rebuilds everything.
|
|
116
|
+
|
|
117
|
+
## Rebuilding one functionality
|
|
118
|
+
|
|
119
|
+
```bash
|
|
120
|
+
turri regenerate app.plain 3
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
It rebuilds just functionality 3 over the code that already exists - after its spec changed, or as
|
|
124
|
+
it is - without rebuilding the ones before it. To change what it does, change its spec first: the
|
|
125
|
+
spec is what the code is built from, and a change made there lasts. Then it updates the
|
|
126
|
+
functionality's conformance tests and runs every conformance test in the codebase, since a change can break something built
|
|
127
|
+
later as easily as something built earlier. It works in the codebase the functionality now lives
|
|
128
|
+
in: when later modules are built on top of this one, the furthest-built of them (`--in <module>`
|
|
129
|
+
picks one when there are several). It exits `0` when everything passes, and refuses when there is
|
|
130
|
+
no code yet - build it with `module` first.
|
|
131
|
+
|
|
132
|
+
Several changes do not need one command each: after editing the spec, re-running `turri regenerate`
|
|
133
|
+
brings the code up to it - one spec diff for what changed in what is built, then the functional
|
|
134
|
+
specs not built yet.
|
|
135
|
+
|
|
136
|
+
## Asking in words instead
|
|
137
|
+
|
|
138
|
+
pi's print mode runs one request and exits, with the render tools waiting for their work rather
|
|
139
|
+
than starting background jobs:
|
|
140
|
+
|
|
141
|
+
```bash
|
|
142
|
+
nohup turri -p "Render app.plain as a whole module, with conformance tests" > render.log 2>&1 &
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
It reports what it did at the end, and nothing streams in between, so for long runs
|
|
146
|
+
`turri regenerate` is the better choice. The interview skill (`brood`) needs
|
|
147
|
+
someone to answer; without one they decide and record their assumptions, and a gate that escalates
|
|
148
|
+
to a human stops the run.
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="16 -5 1100 490" width="1100" height="490" role="img" aria-labelledby="d-title d-desc">
|
|
2
|
+
<title id="d-title">How turri works</title>
|
|
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
|
+
<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,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) 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; }
|
|
15
|
+
@media (prefers-reduced-motion: no-preference) {
|
|
16
|
+
.spin { transform-box: fill-box; transform-origin: 50% 58%; animation: turn 6s linear infinite; }
|
|
17
|
+
@keyframes turn { to { transform: rotate(360deg); } }
|
|
18
|
+
}
|
|
19
|
+
</style>
|
|
20
|
+
<rect x="16" y="-5" width="1100" height="490" rx="18" fill="#e0ff6e"/>
|
|
21
|
+
<defs>
|
|
22
|
+
<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"/>
|
|
24
|
+
</marker>
|
|
25
|
+
</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)"/>
|
|
29
|
+
<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)"/>
|
|
31
|
+
<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)"/>
|
|
33
|
+
<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)"/>
|
|
35
|
+
<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)"/>
|
|
37
|
+
<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)"/>
|
|
39
|
+
<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>
|
|
50
|
+
</svg>
|
|
Binary file
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
# Installation
|
|
2
|
+
|
|
3
|
+
**turri** runs wherever pi runs - macOS, Linux, WSL - with git, bash and Node 22.19 or newer (a Linux
|
|
4
|
+
distribution's own `nodejs` package is often older: take it from nodejs.org, nvm or NodeSource). Native Windows is
|
|
5
|
+
best effort: it needs bash on the PATH (Git for Windows' Git Bash), and has no OS sandbox under the agents' commands.
|
|
6
|
+
|
|
7
|
+
The agents' commands run on this machine, with its toolchains - see [the sandbox](sandbox.md). Each is checked before
|
|
8
|
+
it runs and audited after, on every platform; under them an OS sandbox fences it where the OS has one: Seatbelt on
|
|
9
|
+
macOS, always there, and bubblewrap on Linux and WSL 2 - `apt install bubblewrap`, or your distribution's package.
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
npm install -g @turri-dev/cli # puts `turri` on your PATH
|
|
13
|
+
turri doctor # checks the machine: what turri needs, and what is missing
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
Then, in the project that holds your specs:
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
cd ~/my-project
|
|
20
|
+
turri init-config # a config.turri.yaml with the model you talk to and the one that writes the code, every other setting at its default
|
|
21
|
+
turri # pi, with turri's tools, skills and themes loaded
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
The package is `@turri-dev/cli` - npm keeps the name `turri` back, as too like another
|
|
25
|
+
package's - and the command it puts on your PATH is `turri`.
|
|
26
|
+
|
|
27
|
+
## From a clone
|
|
28
|
+
|
|
29
|
+
To work on turri itself, put the clone on your PATH instead:
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
git clone git@github.com:plainlang/turri.git && cd turri
|
|
33
|
+
npm install
|
|
34
|
+
npm link # puts `turri` on your PATH, pointing at this clone
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
A clone runs its TypeScript sources directly - Node strips the types - so edits take effect immediately. The package
|
|
38
|
+
on npm runs JavaScript compiled from them instead (`npm run build`, into `dist/`, run by `npm pack` and `npm publish`
|
|
39
|
+
before they pack), as Node runs no `.ts` file inside a `node_modules`. `bin/turri.mjs` runs `src/` where there is one,
|
|
40
|
+
and `dist/` otherwise.
|
|
41
|
+
|
|
42
|
+
## In Docker
|
|
43
|
+
|
|
44
|
+
`docker/Dockerfile` builds an image with Node, git, bash, bubblewrap and **turri** linked; the agents' commands and the
|
|
45
|
+
testing scripts run inside it, so add the toolchains your projects need first. It runs **turri** as the user who owns
|
|
46
|
+
the project folder mounted at `/work`, never as root - bubblewrap as root in a container lacks the capability to make
|
|
47
|
+
its namespaces - and with seccomp and AppArmor unconfined the OS sandbox starts. `systempaths=unconfined` lets it mount
|
|
48
|
+
a `/proc` of its own; without it, it binds the container's. The build, the run, Windows and the keys:
|
|
49
|
+
[turri in Docker](../docker/README.md).
|
|
50
|
+
|
|
51
|
+
## What `turri doctor` checks
|
|
52
|
+
|
|
53
|
+
It checks, in order, and says exactly what to run for anything missing:
|
|
54
|
+
|
|
55
|
+
| what | why | if it is missing |
|
|
56
|
+
| --- | --- | --- |
|
|
57
|
+
| Node 22.19+ | pi needs 22.19, as does a clone running turri's `.ts` sources directly | install a newer Node |
|
|
58
|
+
| bash | every command an agent runs, and every testing script, runs with it | on Windows, Git for Windows; elsewhere, the system's package manager |
|
|
59
|
+
| the OS sandbox | the wall under the command check and the audit | macOS has Seatbelt; on Linux, `apt install bubblewrap`. Where it does not start it says why and how to give it one - user namespaces off (Docker: a user that is not root, `--security-opt seccomp=unconfined`), AppArmor in the way (Docker: `--security-opt apparmor=unconfined`; Ubuntu 24.04: `sysctl -w kernel.apparmor_restrict_unprivileged_userns=0`), Docker's masked `/proc` (`--security-opt systempaths=unconfined`, or nothing: turri binds the container's `/proc` instead). Without one, the check and the audit hold alone |
|
|
60
|
+
| a model provider | the agents need a model | run `pi` and `/login`, or set an API key (`ANTHROPIC_API_KEY`, `OPENAI_API_KEY`, `GEMINI_API_KEY`, ...) |
|
|
61
|
+
| `turri` on PATH | so it runs from any project | `npm install -g @turri-dev/cli`, or `npm link` in a clone |
|
|
62
|
+
| turri and `pi` apart | `turri` starts pi with turri; a plain `pi` stays pi | an older `turri setup` wrote a file that loads turri into every `pi` - `doctor` offers to remove it |
|
|
63
|
+
|
|
64
|
+
In a project - a folder with `config.turri.yaml` (or the old `config.yaml`) or `plain_modules` - it checks what the project needs of this machine too:
|
|
65
|
+
|
|
66
|
+
| what | |
|
|
67
|
+
| --- | --- |
|
|
68
|
+
| each module's toolchains | what its implementation reqs name - Python, Node, Java, Go, Rust, C/C++, Zig, ... - against this machine's PATH: the agents use this machine's toolchains and install none, so a missing one is yours to install |
|
|
69
|
+
| the testing scripts | what in them this machine does not have: a program named by its whole path that is not here (a macOS-only `/usr/libexec/java_home` on Linux), a toolchain they run that is not on the PATH |
|
|
70
|
+
| the models | each model `config.turri.yaml` names - `harness-model`, `coding-model`, `judge-model`, every role under `models:` - sent one short request through pi, with the credentials turri's agents will use, for any provider pi supports. One that answers is ready; for one that does not, it says what the provider said and what to do: the credentials pi reads for that provider (`AWS_PROFILE` and `AWS_REGION` for Amazon Bedrock, `OPENAI_API_KEY`, `/login`, ...), model access in the Bedrock console, the closest model ids pi knows for one it does not. `--no-models` skips it |
|
|
71
|
+
|
|
72
|
+
Removing that file is the one thing `doctor` changes, and it asks first; `turri doctor --yes` says yes. Run without a
|
|
73
|
+
terminal - piped, or in CI - it asks nothing and changes nothing. Run it again whenever something breaks;
|
|
74
|
+
`turri doctor --no-models` leaves out the models' requests.
|
|
75
|
+
|
|
76
|
+
The chat is told every turn which wall is up and what the project's modules need of this machine that it lacks. What they need is worked out from every spec of the project - seconds, on a project of long requires chains - so it is worked out once the chat is up and again after a turn in which a spec changed, never while your message waits: a turn is told what was known last.
|
|
77
|
+
|
|
78
|
+
## turri and `pi`
|
|
79
|
+
|
|
80
|
+
`turri` runs pi from this project's `node_modules`, with turri loaded, so no global pi install is needed. A plain
|
|
81
|
+
`pi` is left as it is: starting pi does not start turri, and turri puts nothing into pi's own folders.
|
|
82
|
+
|
|
83
|
+
An older `turri setup` could load turri into every `pi`, with a one-line file in `~/.pi/agent/extensions/` pointing at
|
|
84
|
+
this clone. `turri doctor` says when it is there, and offers to remove it. Until it is gone, `turri`
|
|
85
|
+
leaves out its own `-e`: pi treats a tool registered twice as an error.
|
|
86
|
+
|
|
87
|
+
`turri` starts in the `turri-dark` theme. A plain `pi` keeps its own theme; to give it
|
|
88
|
+
`turri-dark` too, in `~/.pi/agent/settings.json`:
|
|
89
|
+
|
|
90
|
+
```json
|
|
91
|
+
{ "theme": "turri-dark" }
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
### Project trust
|
|
95
|
+
|
|
96
|
+
pi will not auto-discover a project-local extension until you trust the project, and `-p` has no
|
|
97
|
+
way to ask - so `npm run chat` and `npm run ask` pass `-e` explicitly. Once you have run `pi`
|
|
98
|
+
interactively in this directory and accepted the trust prompt, plain `pi` picks the extension up
|
|
99
|
+
on its own. A `defaultProjectTrust` key in `.pi/settings.json` does *not* work - a project cannot
|
|
100
|
+
vouch for itself; that setting only counts in `~/.pi/agent/settings.json`.
|
|
101
|
+
|
|
102
|
+
## Environment
|
|
103
|
+
|
|
104
|
+
Models and credentials come from your pi configuration (`~/.pi/agent`) or provider environment
|
|
105
|
+
variables - the same resolution `pi` itself uses. Put the rest in your shell profile:
|
|
106
|
+
|
|
107
|
+
```bash
|
|
108
|
+
export PLAIN_TEMPLATE_DIRS=~/path/to/standard_template_library # shared templates
|
|
109
|
+
export GEMINI_API_KEY=... # and leave GOOGLE_API_KEY unset
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
For Gemini, `GEMINI_API_KEY` is what makes pi *offer* the google provider, but when
|
|
113
|
+
`GOOGLE_API_KEY` is also set pi uses that one for the calls and says so.
|
|
114
|
+
|
|
115
|
+
| variable | |
|
|
116
|
+
| --- | --- |
|
|
117
|
+
| `PLAIN_TEMPLATE_DIRS` | colon-separated template directories, e.g. the standard library |
|
|
118
|
+
| `TURRI_COMMAND_TIMEOUT` | seconds a command may run before it is stopped - an agent's own, its tests (default `120`) |
|
package/docs/parser.md
ADDED
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
# The \*\*\*plain parser
|
|
2
|
+
|
|
3
|
+
`src/plain/` is a TypeScript port of the reference \*\*\*plain parser - `plain_file.py`,
|
|
4
|
+
`plain_spec.py`, `concept_utils.py`, and the `file_utils.py` helpers they depend on.
|
|
5
|
+
|
|
6
|
+
| module | ports |
|
|
7
|
+
|---|---|
|
|
8
|
+
| `src/plain/parser.ts` | `plain_file.py` - sections, acceptance tests, imports/requires, marshalling |
|
|
9
|
+
| `src/plain/spec.ts` | `plain_spec.py` - functionality ids, the per-functionality context, linked resources, ranges |
|
|
10
|
+
| `src/plain/concepts.ts` | `concept_utils.py` - concept extraction, validation, topological ordering |
|
|
11
|
+
| `src/plain/markdown.ts` | mistletoe's AST + `PlainRenderer` (mdast + a matching serializer) |
|
|
12
|
+
| `src/plain/liquid.ts` | `plain2code_nodes.py` + the two filters in `plain_spec.py` (liquidjs) |
|
|
13
|
+
| `src/plain/files.ts` | `file_utils.open_from` / `get_loaded_templates`, base64 blob detection |
|
|
14
|
+
| `src/plain/stdlib.ts` | nothing - turri's own: `stdlib/` is a reserved import prefix, read from turri's standard library only, and a project module at such a path is an error (see [the standard library](authoring-plain.md#the-standard-library)) |
|
|
15
|
+
|
|
16
|
+
## Checking a spec
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
turri validate <file.plain> # one spec
|
|
20
|
+
turri validate . # every .plain file in a directory
|
|
21
|
+
turri validate <file.plain> --json
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
It uses this repo's own parser, so it needs no external tool, no API key and no credits: it parses
|
|
25
|
+
the file, resolves its imports and required modules, and reports every syntax, concept and
|
|
26
|
+
structure error with the line it is on.
|
|
27
|
+
|
|
28
|
+
```
|
|
29
|
+
PASS d365_export.plain 6 functionalities (1, 2, 3, 4, 5, 6), requires d365_restore_api
|
|
30
|
+
|
|
31
|
+
FAIL broken.plain PlainSyntaxError
|
|
32
|
+
Plain syntax error: Syntax error at line 3: Invalid specification heading
|
|
33
|
+
(`Nope:`). Allowed headings: definitions, implementation reqs, test reqs, ...
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
The exit code is 0 only when every file passed. In a conversation the same check is `validate`.
|
|
37
|
+
|
|
38
|
+
## Two rules of inheritance
|
|
39
|
+
|
|
40
|
+
- A module that **`requires`** another inherits its functional specs, transitively: it is built on
|
|
41
|
+
top of that module's code.
|
|
42
|
+
- A module that **`import`s** another inherits its definitions, implementation reqs and test reqs.
|
|
43
|
+
|
|
44
|
+
## The functional-spec iterator
|
|
45
|
+
|
|
46
|
+
`getFrids` walks functionalities depth-first through nested sections; `getSpecificationsForFrid`
|
|
47
|
+
builds what a single functionality is rendered with. `iterateFunctionalSpecs` pairs them:
|
|
48
|
+
|
|
49
|
+
```ts
|
|
50
|
+
import { plainFileParser, iterateFunctionalSpecs } from "./src/plain/index.ts";
|
|
51
|
+
|
|
52
|
+
const [moduleName, tree] = plainFileParser("my_app.plain", ["."]);
|
|
53
|
+
|
|
54
|
+
for (const { frid, specifications, codeVariables } of iterateFunctionalSpecs(tree)) {
|
|
55
|
+
// definitions and reqs in scope from the outside in, every functionality up to and
|
|
56
|
+
// including this one, and only this one's acceptance tests
|
|
57
|
+
console.log(frid, specifications["functional specs"].length);
|
|
58
|
+
}
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
## Parsing once
|
|
62
|
+
|
|
63
|
+
A module's parse reads its imports and the whole of its `requires` chain, so a long chain asks for the same
|
|
64
|
+
files over and over: bringing an 18-module chain up to its spec parsed for minutes. Two caches keep it to once.
|
|
65
|
+
Inside `withParseCache`, one question about the specs, each file is parsed once. Between calls, a parse is kept
|
|
66
|
+
with a fingerprint of every file directly in its template folders and the current one - name, size,
|
|
67
|
+
modification time, and a `.plain` file's text - and handed out again, as a copy, while the fingerprint is the
|
|
68
|
+
same; a spec that does not parse is kept as that too. What a parse returns is unchanged: the cache only decides
|
|
69
|
+
whether to parse again.
|
|
70
|
+
|
|
71
|
+
## Fidelity
|
|
72
|
+
|
|
73
|
+
The port is checked against the Python implementation directly, not only against the ported
|
|
74
|
+
assertions. Both parsers are run over a corpus and their module name, marshalled tree, required
|
|
75
|
+
modules, functionality ids and per-functionality specifications compared:
|
|
76
|
+
|
|
77
|
+
| corpus | files | identical | differences |
|
|
78
|
+
|---|---|---|---|
|
|
79
|
+
| `tests/data/` fixtures | 84 | 80 | 4 error strings (below) |
|
|
80
|
+
| `hycu-dynamics-365-modules` | 47 | 47 | none |
|
|
81
|
+
|
|
82
|
+
The 4 differences are all the same thing: template partials (`implement.plain`,
|
|
83
|
+
`implement_2.plain`, `header.plain`, `pr_implement.plain`) are meant to be included with
|
|
84
|
+
arguments, not parsed alone. Both implementations reject them for the same reason - an undefined
|
|
85
|
+
Liquid variable - but the message text comes from the template engine, and liquidjs phrases it
|
|
86
|
+
differently from liquid2.
|
|
87
|
+
|
|
88
|
+
Real specs exposed three places where mdast and mistletoe disagree, each covered by
|
|
89
|
+
`tests/plain/renderer.test.ts`:
|
|
90
|
+
|
|
91
|
+
- **Inline decoding.** mdast resolves `\"` to `"` and forgets whether emphasis was written with `_`
|
|
92
|
+
or `*`; mistletoe round-trips the source. Inline leaf nodes are pinned back to their original
|
|
93
|
+
source slice at parse time.
|
|
94
|
+
- **List geometry.** mistletoe indents an item's continuation lines by its `prepend`, measured in
|
|
95
|
+
coordinates with the enclosing item's indentation stripped - not by the column gap between parent
|
|
96
|
+
and child. An acceptance test rendered detached from its functionality keeps 6 and 10 spaces where
|
|
97
|
+
the naive reading gives 4 and 8.
|
|
98
|
+
- **Lazy continuation lines.** A paragraph's continuation lines carry leading whitespace that is not
|
|
99
|
+
part of the text; keeping it in the raw slice indents the line twice.
|
|
100
|
+
|
|
101
|
+
Two deliberate differences from the Python source:
|
|
102
|
+
|
|
103
|
+
- The `test_plainfile.py` tests drive the acceptance-test helpers with `MagicMock` tokens and patch
|
|
104
|
+
the inner helper to count calls. ESM has no equivalent, so those tests build real ASTs from
|
|
105
|
+
markdown and assert the same observable outcome.
|
|
106
|
+
- `networkx`'s `topological_sort` is reimplemented rather than replaced with a generic one: it
|
|
107
|
+
flattens `topological_generations`, so the ordering of independent concepts follows graph
|
|
108
|
+
insertion order, and the Python tests assert that exact sequence.
|