@turri-dev/cli 0.1.24 → 0.1.25-next.fb3566e
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/README.md +5 -5
- package/dist/extension.d.ts +11 -4
- package/dist/extension.js +22 -59
- package/dist/genome.d.ts +0 -3
- package/dist/genome.js +4 -7
- package/dist/graph.js +5 -2
- package/dist/jellyfish.d.ts +5 -0
- package/dist/jellyfish.js +5 -0
- package/dist/new-project.d.ts +1 -3
- package/dist/new-project.js +3 -13
- package/dist/plain/parser.d.ts +1 -0
- package/dist/plain/parser.js +1 -1
- package/dist/plain-files.d.ts +21 -0
- package/dist/plain-files.js +156 -0
- package/dist/regenerate/diff-view.d.ts +4 -1
- package/dist/regenerate/diff-view.js +6 -3
- package/dist/regenerate/stop-report.js +1 -1
- package/dist/regenerate/test-scripts.d.ts +2 -0
- package/dist/regenerate/test-scripts.js +1 -1
- package/dist/skill-progress.d.ts +4 -4
- package/dist/skill.d.ts +1 -1
- package/dist/skills.d.ts +4 -4
- package/dist/skills.js +5 -5
- package/docs/authoring-plain.md +7 -23
- package/docs/chats.md +1 -1
- package/docs/config.md +3 -3
- package/docs/development.md +0 -1
- package/docs/headless.md +2 -2
- package/docs/hosting.md +1 -1
- package/docs/regenerating.md +3 -3
- package/docs/skills-and-gates.md +17 -15
- package/docs/using.md +6 -14
- package/genome/rules/concise-specs-examples.md +1 -1
- package/genome/rules/concise-specs-integration-examples.md +1 -1
- package/genome/rules/func-specs.md +10 -2
- package/genome/rules/integration-embedded.md +2 -2
- package/genome/rules/integration-standalone.md +2 -2
- package/genome/skills/break-down-func-spec/SKILL.md +8 -8
- package/genome/skills/consolidate-concepts/SKILL.md +1 -1
- package/genome/skills/resolve-section-ownership/SKILL.md +2 -2
- package/genome/skills/resolve-spec-conflict/SKILL.md +1 -1
- package/package.json +1 -1
- package/dist/mature-tools.d.ts +0 -53
- package/dist/mature-tools.js +0 -193
- package/genome/skills/analyze-if-func-spec-too-complex/SKILL.md +0 -206
- package/genome/skills/brood/SKILL.md +0 -129
- package/genome/skills/brood/references/intent.md +0 -27
- package/genome/skills/brood/references/product.md +0 -49
- package/genome/skills/brood/references/stack.md +0 -39
- package/genome/skills/brood/references/testing.md +0 -25
- package/genome/skills/bud/SKILL.md +0 -104
- package/genome/skills/bud/references/intent.md +0 -27
- package/genome/skills/mature/SKILL.md +0 -78
- package/genome/skills/spawn/SKILL.md +0 -85
|
@@ -42,7 +42,7 @@ export function describeStop(facts) {
|
|
|
42
42
|
why = `after ${tries} attempt${tries === 1 ? "" : "s"} it still could not be built to pass its own unit tests.`;
|
|
43
43
|
todo = [
|
|
44
44
|
"The functional spec is most likely too big for one functionality, or leaves a choice open that keeps coming " +
|
|
45
|
-
"out wrong. Split it into smaller functional specs (`
|
|
45
|
+
"out wrong. Split it into smaller functional specs (`break-down-func-spec`), " +
|
|
46
46
|
"or say exactly what it should do.",
|
|
47
47
|
"If it needs something the stack does not name - a library, a tool, a service - add it to the implementation reqs.",
|
|
48
48
|
"If its unit tests have to be run in a particular way, say how in the implementation reqs.",
|
|
@@ -49,6 +49,8 @@ export interface TestScripts {
|
|
|
49
49
|
*/
|
|
50
50
|
export declare function withoutConformance(scripts: TestScripts): TestScripts;
|
|
51
51
|
export declare function scriptKind(name: string): ScriptKind | undefined;
|
|
52
|
+
/** The name the prepare script has: `prepare_environment_<lang>.sh`. */
|
|
53
|
+
export declare const PREPARE_NAME: RegExp;
|
|
52
54
|
export declare const SCRIPTS_DIR = "test_scripts";
|
|
53
55
|
/**
|
|
54
56
|
* The project a build belongs to: the folder that holds its plain_modules. A build folder
|
|
@@ -30,7 +30,7 @@ export function scriptKind(name) {
|
|
|
30
30
|
return Object.keys(NAMES).find((kind) => NAMES[kind].test(basename(name)));
|
|
31
31
|
}
|
|
32
32
|
/** The name the prepare script has: `prepare_environment_<lang>.sh`. */
|
|
33
|
-
const PREPARE_NAME = /^prepare_environment[^/]*\.sh$/i;
|
|
33
|
+
export const PREPARE_NAME = /^prepare_environment[^/]*\.sh$/i;
|
|
34
34
|
function isFile(path) {
|
|
35
35
|
try {
|
|
36
36
|
return statSync(path).isFile();
|
package/dist/skill-progress.d.ts
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* A skill's run as one line above the input: every phase by name, in order - the ones done ticked, the one it is in
|
|
3
|
-
* marked, the rest to come - and where that is, phase 2 of 4.
|
|
4
|
-
* questions and
|
|
3
|
+
* marked, the rest to come - and where that is, phase 2 of 4. A skill of many phases takes many turns of
|
|
4
|
+
* questions and ***plain changes; without it, nothing on the screen says how far through it is.
|
|
5
5
|
*
|
|
6
|
-
*
|
|
6
|
+
* onboard ✓ intent ━━━━ ⠋ stack ──── ○ product phase 2 of 3
|
|
7
7
|
* stack - the language, frameworks, storage and architecture
|
|
8
8
|
*
|
|
9
|
-
* The second line says what the phase it is in does: a phase's name alone - `
|
|
9
|
+
* The second line says what the phase it is in does: a phase's name alone - `stack` - says little to someone
|
|
10
10
|
* who has not read the skill.
|
|
11
11
|
*
|
|
12
12
|
* A skill of one phase, or none, has nothing to be partway through: its line is the spinner and its name alone.
|
package/dist/skill.d.ts
CHANGED
|
@@ -86,7 +86,7 @@ export interface Skill {
|
|
|
86
86
|
*/
|
|
87
87
|
diffPerAnswer: string[];
|
|
88
88
|
/**
|
|
89
|
-
* Started only by the person, with its slash command - `/
|
|
89
|
+
* Started only by the person, with its slash command - `/release-notes` - never by the model on its own
|
|
90
90
|
* (`metadata.command-only: true`): cultivate runs it only when the person has just typed its command.
|
|
91
91
|
*/
|
|
92
92
|
commandOnly: boolean;
|
package/dist/skills.d.ts
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* project's skill is run the same way: its steps, its gates and judges, the tools its frontmatter names.
|
|
5
5
|
*
|
|
6
6
|
* A name is one skill: turri's own come first, then `.turri/skills/`, then `.agents/skills/`, and a skill whose name is
|
|
7
|
-
* taken already is left out and said to be - a project's `
|
|
7
|
+
* taken already is left out and said to be - a project's `debug-specs` never quietly stands in for turri's.
|
|
8
8
|
*/
|
|
9
9
|
import { type Skill } from "./skill.ts";
|
|
10
10
|
/** A project's skill folders, in the order they are read, relative to the project. */
|
|
@@ -29,7 +29,7 @@ export declare function catalogueLine(skill: FoundSkill): string;
|
|
|
29
29
|
/** What is said under the list about the skills it leaves out. */
|
|
30
30
|
export declare function leftOutNote(catalogue: SkillCatalogue): string;
|
|
31
31
|
/**
|
|
32
|
-
* Which skills cultivate may start now. A skill marked command-only (`/
|
|
32
|
+
* Which skills cultivate may start now. A skill marked command-only (`/release-notes`) waits for the person to type its command:
|
|
33
33
|
* the command lets it start once, and the run starting uses that up - the model cannot start it again later on its own.
|
|
34
34
|
*/
|
|
35
35
|
export declare function createCommandGate(): {
|
|
@@ -48,7 +48,7 @@ export declare function startedByCommandOnly(name: string): string;
|
|
|
48
48
|
*/
|
|
49
49
|
export declare function startPicked(name: string, request: string): string;
|
|
50
50
|
/**
|
|
51
|
-
* The command-only skill an answer picks: an option offered to the person that starts with its command - `/
|
|
52
|
-
*
|
|
51
|
+
* The command-only skill an answer picks: an option offered to the person that starts with its command - `/release-notes - for
|
|
52
|
+
* this release` - is the person starting it, as if they typed it.
|
|
53
53
|
*/
|
|
54
54
|
export declare function chosenCommand(answer: string, skills: ReadonlyArray<Pick<FoundSkill, "name" | "commandOnly">>): string | undefined;
|
package/dist/skills.js
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* project's skill is run the same way: its steps, its gates and judges, the tools its frontmatter names.
|
|
5
5
|
*
|
|
6
6
|
* A name is one skill: turri's own come first, then `.turri/skills/`, then `.agents/skills/`, and a skill whose name is
|
|
7
|
-
* taken already is left out and said to be - a project's `
|
|
7
|
+
* taken already is left out and said to be - a project's `debug-specs` never quietly stands in for turri's.
|
|
8
8
|
*/
|
|
9
9
|
import { readdirSync } from "node:fs";
|
|
10
10
|
import { join, relative } from "node:path";
|
|
@@ -75,7 +75,7 @@ export function leftOutNote(catalogue) {
|
|
|
75
75
|
.join("");
|
|
76
76
|
}
|
|
77
77
|
/**
|
|
78
|
-
* Which skills cultivate may start now. A skill marked command-only (`/
|
|
78
|
+
* Which skills cultivate may start now. A skill marked command-only (`/release-notes`) waits for the person to type its command:
|
|
79
79
|
* the command lets it start once, and the run starting uses that up - the model cannot start it again later on its own.
|
|
80
80
|
*/
|
|
81
81
|
export function createCommandGate() {
|
|
@@ -88,7 +88,7 @@ export function createCommandGate() {
|
|
|
88
88
|
}
|
|
89
89
|
/** What cultivate says of a command-only skill the person did not start. */
|
|
90
90
|
export function startedByCommandOnly(name) {
|
|
91
|
-
return `Not started: ${name} is started only by the person, with /${name} or by picking an option starting /${name} that you offer them with ask_user.
|
|
91
|
+
return `Not started: ${name} is started only by the person, with /${name} or by picking an option starting /${name} that you offer them with ask_user. Do what they asked yourself, in the conversation.`;
|
|
92
92
|
}
|
|
93
93
|
/**
|
|
94
94
|
* What the model is told when the person picked a skill: start it now, with what they asked for - word for word, as the
|
|
@@ -100,8 +100,8 @@ export function startPicked(name, request) {
|
|
|
100
100
|
`Pass as its context everything they have told you${asked !== "" ? `, starting with what they asked for, word for word: ${JSON.stringify(asked)}` : ""}.`);
|
|
101
101
|
}
|
|
102
102
|
/**
|
|
103
|
-
* The command-only skill an answer picks: an option offered to the person that starts with its command - `/
|
|
104
|
-
*
|
|
103
|
+
* The command-only skill an answer picks: an option offered to the person that starts with its command - `/release-notes - for
|
|
104
|
+
* this release` - is the person starting it, as if they typed it.
|
|
105
105
|
*/
|
|
106
106
|
export function chosenCommand(answer, skills) {
|
|
107
107
|
const typed = /^\s*\/([\w-]+)/.exec(answer)?.[1];
|
package/docs/authoring-plain.md
CHANGED
|
@@ -87,8 +87,8 @@ Each conformance template imports its kind's `conformance/common`: a backend's t
|
|
|
87
87
|
- **No concepts.** Its requirements are on the predefined `:Implementation:`, `:UnitTests:` and `:ConformanceTests:` only, so it never collides with \*\*\*plain's own definitions.
|
|
88
88
|
- **Pinned to a toolchain.** It names its stack's toolchain - Python 3.12, Node.js 24, Java 21, Go 1.25, Rust - which the agents use from this machine; `turri doctor` says when the machine lacks it.
|
|
89
89
|
- **Not storage, not the testing scripts.** A module with a database says which on top of its stack. It still needs its unit testing script, and a conformance testing script when it has conformance tests.
|
|
90
|
-
- **Known to the agents.** The `stdlib` rule, in the system prompt of every agent that writes \*\*\*plain, says when to import which, and lists every template with what it is and the folder it is in, read from `stdlib/` when the rules load.
|
|
91
|
-
- **Only what this machine can build.** The agents use this machine's toolchains and install none, so a Go stack on a laptop without Go stops at its first build. Every agent that writes \*\*\*plain, and the conversation, are told which of the standard library's languages the machine can build now - Python needs `uv`, TypeScript and every frontend `node` and `npm`, Go `go`, Rust `cargo`, Java a JDK (`src/stack.ts`, `toolchainsBlock`) - and
|
|
90
|
+
- **Known to the agents.** The `stdlib` rule, in the system prompt of every agent that writes \*\*\*plain, says when to import which, and lists every template with what it is and the folder it is in, read from `stdlib/` when the rules load. The conversation imports a stack when the one chosen is in it, and its conformance template when conformance tests are on.
|
|
91
|
+
- **Only what this machine can build.** The agents use this machine's toolchains and install none, so a Go stack on a laptop without Go stops at its first build. Every agent that writes \*\*\*plain, and the conversation, are told which of the standard library's languages the machine can build now - Python needs `uv`, TypeScript and every frontend `node` and `npm`, Go `go`, Rust `cargo`, Java a JDK (`src/stack.ts`, `toolchainsBlock`) - and the conversation offers, and chooses, only stacks in those. A stack the person names in a language the machine lacks is not refused: they are told what to install first, and that `turri doctor` checks it.
|
|
92
92
|
|
|
93
93
|
A requirement beyond the stack - storage, an outside service - is added on top of it. A module that differs from its stack on something the stack settles - another framework, another layout - writes its own instead: an import cannot be taken back in part.
|
|
94
94
|
|
|
@@ -114,7 +114,7 @@ A requirement beyond the stack - storage, an outside service - is added on top o
|
|
|
114
114
|
|
|
115
115
|
The ones that shape \*\*\*plain most:
|
|
116
116
|
|
|
117
|
-
- **One functionality is at most ~200 lines of code.** A bigger one is split before it is written (`
|
|
117
|
+
- **One functionality is at most ~200 lines of code.** A bigger one is split before it is written (`break-down-func-spec`) - the author judges its size by the signs in `func-specs.md`.
|
|
118
118
|
- **Functional specs are in build order.** Each is regenerated on top of the ones before it - which is what a functional spec may assume, not what a change costs: a functional spec inserted anywhere is regenerated alone.
|
|
119
119
|
- **They state facts, with a concrete subject.** "A :User: can add a :Task:" - not "Implement adding tasks", not "The system should…".
|
|
120
120
|
- **Nothing a test cannot check.** No `robust`, `seamless`, `as appropriate`; state the checkable fact or cut the word.
|
|
@@ -122,22 +122,7 @@ The ones that shape \*\*\*plain most:
|
|
|
122
122
|
|
|
123
123
|
## Writing \*\*\*plain with turri
|
|
124
124
|
|
|
125
|
-
In a conversation (`turri`), say what you want and the matching skill runs
|
|
126
|
-
|
|
127
|
-
| skill | phases | how a \*\*\*plain change is written |
|
|
128
|
-
| --- | --- | --- |
|
|
129
|
-
| `brood` | intent → stack → testing → product, feature by feature | first, how many questions the stack and product phases may ask - light 3, medium 5, thorough 10 or best judgement, broader with fewer, covering the same ground - and what they leave open turri decides itself, each decision a \*\*\*plain change of its own; every product answer is still a \*\*\*plain change, built before the next question; in testing, whether there are conformance tests, their `***test reqs***` and script - written as files, building nothing; then one \*\*\*plain change per answer, across whatever sections it needs, as a git diff with `change_plain`, regenerated as a \*\*\*plain diff pulse and conformance tested as it is built |
|
|
130
|
-
| `spawn` | start → skeleton → regenerate | built for the time to first regeneration: what to build is asked only when the message does not say, and the stack in exactly one question when the message names none - stacks this machine can build, turri's choice recommended first; the rest of the technology turri chooses itself; the stack and the unit tests are written as files, with conformance tests off unless the user asks for them; then a \*\*\*plain change for the smallest thing that runs starts the first build - a module's first pulse of one functional spec is not reviewed - and the main slice of what the user described and at most one more follow - none when they described no feature, and never one of turri's own - each a functional spec on its happy path, as git diffs with `change_plain`, one after another with no answer between them. The rest - the next slices, then validation and edge cases as changes to those functional specs - is asked for in the conversation once the user has tried what landed: `go on` for the next slice it proposed |
|
|
131
|
-
|
|
132
|
-
`mature` starts from \*\*\*plain you already have, rough or hand-written, and runs on its own until it is fully regenerated - every functionality built from \*\*\*plain as it stands, its tests passing. It regenerates what is not built and watches the agents building it; when one struggles on something \*\*\*plain leaves open or unclear - sent back again and again, the judge going back and forth, a \*\*\*plain conflict - it writes what \*\*\*plain should say as a \*\*\*plain diff, stops that build, and regenerates it again. What the builds had to decide themselves - their assumptions, pitfalls, a failed build's diagnosis - goes into \*\*\*plain the same way, when a user, a caller or a test can see it. It never asks: it takes the reading that fits the rest of the \*\*\*plain description, and marks each such decision in its report. It leaves code, environment and turri problems to the builds, and stops when a functionality keeps struggling after its \*\*\*plain was clarified, when two cycles change nothing, or after twenty.
|
|
133
|
-
|
|
134
|
-
For an existing project, `bud` works the same way, scoped to one feature: intent → scope → product. It first asks how many questions the product phase may ask - light, medium, thorough or best judgement, as `brood` does - then a short feature-intent interview, then product questions - a trigger and its outcome, an edge case, a rule - and every answer is a \*\*\*plain change, a git diff written with `change_plain` and built the moment it is written, before the next question. What the questions leave open it decides itself, a \*\*\*plain change each. A change to something already specified, built or still to build, is put to the user first and is a \*\*\*plain diff too: what is left to build is worked out again from \*\*\*plain. It ends on the person's confirmation of a short summary of the feature's changes.
|
|
135
|
-
|
|
136
|
-
What keeps the interview honest is the harness, not the interviewer:
|
|
137
|
-
|
|
138
|
-
- **Every question offers choices** (`ask_user` refuses a question without them), and each answer maps to one \*\*\*plain change.
|
|
139
|
-
- **Every answer is written before the next question.** In the product phase of `brood` and `bud`, `ask_user` refuses a new question until the last answer is a \*\*\*plain change - see [skills and gates](skills-and-gates.md).
|
|
140
|
-
- **Each phase ends on the person's confirmation** of a short summary, not on the interviewer's word.
|
|
125
|
+
In a conversation (`turri`), say what you want and the matching skill runs, or the conversation writes it itself. Asked for new software - `create a small server in Java Spring Boot` - turri starts the project in the conversation, writing the module, its config and its unit testing script, then the smallest thing that runs, regenerated at once, and the first slices of what you described (see [using turri](using.md)). Every change after - a feature, a rule, a fix - is \*\*\*plain it writes and regenerates.
|
|
141
126
|
|
|
142
127
|
## The skills
|
|
143
128
|
|
|
@@ -145,10 +130,9 @@ Every skill in `genome/skills/` can be run from a conversation; most are also us
|
|
|
145
130
|
|
|
146
131
|
| to… | skills |
|
|
147
132
|
| --- | --- |
|
|
148
|
-
| start a project |
|
|
149
|
-
|
|
|
150
|
-
|
|
|
151
|
-
| check what is written | `analyze-if-func-spec-too-complex`, `analyze-2-func-specs`, `analyze-func-specs`, `resolve-section-ownership` |
|
|
133
|
+
| start a project | ask, in the conversation: turri writes it itself, with no skill |
|
|
134
|
+
| add to \*\*\*plain | ask in the conversation: it writes a concept, a functional spec, a req, an acceptance test or a linked resource itself, by the rules in its prompt, as a \*\*\*plain diff |
|
|
135
|
+
| check what is written | `analyze-2-func-specs`, `analyze-func-specs`, `resolve-section-ownership` |
|
|
152
136
|
| fix what is written | `break-down-func-spec`, `resolve-spec-conflict`, `debug-specs` |
|
|
153
137
|
| restructure modules | `refactor-module`, `consolidate-concepts` |
|
|
154
138
|
| set up the project to build | `implement-unit-testing-script`, `implement-conformance-testing-script` |
|
package/docs/chats.md
CHANGED
|
@@ -37,7 +37,7 @@ A pulse's end goes to the [sentinel](sentinel.md) - a chat of turri's own, opene
|
|
|
37
37
|
|
|
38
38
|
A line above the editor names each chat once there are two, a `│` between them: its number, its title - "new chat" until something is said in it, then its first message, until `/name` gives it one - and, after it, what it is doing: a spinner while it works, `?` while a question waits in it, `•` when it said something since you last looked. The chat shown is in the accent colour.
|
|
39
39
|
|
|
40
|
-
A message you send while a chat's agent works is shown under the chat at once, shaded and marked `not read yet`, and stays under what the agent writes and the tools it calls until the agent reads it - at the end of its current step - when it goes into the chat as a normal message, where it was read. `alt+up` takes the messages waiting back into the editor (`src/chat-waiting.ts`). While a skill runs in the chat
|
|
40
|
+
A message you send while a chat's agent works is shown under the chat at once, shaded and marked `not read yet`, and stays under what the agent writes and the tools it calls until the agent reads it - at the end of its current step - when it goes into the chat as a normal message, where it was read. `alt+up` takes the messages waiting back into the editor (`src/chat-waiting.ts`). While a skill runs in the chat its own agent is the one at work, and the chat's waits for the whole run: what you type then goes to the skill's agent, shown waiting until it reads it, at the end of its current step - and is in the chat from then, where it was read. The chat's agent is told what you said to the skill when the run ends. A message the skill's agent has not read when the run ends goes to the chat's agent instead.
|
|
41
41
|
|
|
42
42
|
On macOS, `alt` is Option (⌥), and reaches turri only when the terminal sends Option as Meta - otherwise ⌥N is the dead key for ñ and ⌥1 types `¡`, and nothing reaches turri. turri says so as a session's second chat opens. To turn it on: Ghostty, `macos-option-as-alt = true` in its config; iTerm2, Profiles → Keys → Left Option key: Esc+; Terminal.app, Profiles → Keyboard → Use Option as Meta key; Kitty, `macos_option_as_alt yes`; WezTerm, `send_composed_key_when_left_alt_is_pressed = false`. `/chat` works without it.
|
|
43
43
|
|
package/docs/config.md
CHANGED
|
@@ -170,9 +170,9 @@ A job keeps the model it was queued with: choosing another holds for what is que
|
|
|
170
170
|
|
|
171
171
|
## Conformance tests
|
|
172
172
|
|
|
173
|
-
`conformance-tests:` is the one switch, and the chat
|
|
173
|
+
`conformance-tests:` is the one switch, and the chat and every regeneration follow it. `false` turns them off entirely: no conformance tests are written, the ones already in a codebase are not run, and no agent is shown the conformance testing script. No key is the same as `false`: conformance tests are off by default. `all` (or `true`) means on wherever a module has `***test reqs***` - a module without them has nothing to write conformance tests from, so it writes none, but still keeps the codebase's existing ones passing. Asked in the conversation (`conform`), or with `--conformance-tests all|chosen|module|false` on `turri regenerate` and `change-plain` - for that run, the file left as it is - a regeneration does otherwise.
|
|
174
174
|
|
|
175
|
-
|
|
175
|
+
The chat writes `conformance-tests: false` into a new project unless you ask for tests.
|
|
176
176
|
|
|
177
177
|
## The testing scripts
|
|
178
178
|
|
|
@@ -244,7 +244,7 @@ coding-model:
|
|
|
244
244
|
Which stack builds a module, first match wins:
|
|
245
245
|
|
|
246
246
|
- **The one a call names.** `regenerate`, `conform`, `glimpse` and `change_plain` take a `stack`; the chat's model passes it for a module whose stack has a name of its own, leaving the chat's choice as it is.
|
|
247
|
-
- **The stack named after the module** - `config.turri.backend.yaml` builds `backend.plain` - with nothing to choose or pass. Name each stack after the module it builds, and a backend and a frontend are each built with their own, side by side - in the chat,
|
|
247
|
+
- **The stack named after the module** - `config.turri.backend.yaml` builds `backend.plain` - with nothing to choose or pass. Name each stack after the module it builds, and a backend and a frontend are each built with their own, side by side - in the chat, as each change is written, and on the command line.
|
|
248
248
|
- **The one chosen in the chat** - each [chat](chats.md) its own - with `/stack` - a list of the project's stacks and none - or `/stack backend`, `/stack none`. The chat's model has a `stack` tool too, and switches the stack when you ask it to. `/stack` alone says which is chosen now. On the command line, with `--stack <name>` on any command - `turri regenerate`, `conform`, `change-plain`, `glimpse`, `release`, `cultivate` - and on `turri --stack backend` for the chat; or with `TURRI_STACK` in the environment.
|
|
249
249
|
|
|
250
250
|
A chat's choice is its own, and lasts while it is open: a new chat starts with `--stack`'s, or none, and nothing remembers it - turri started again has none chosen. A stack the project has no file for is refused, with the stacks it has. With none chosen, `config.turri.yaml` alone is used.
|
package/docs/development.md
CHANGED
|
@@ -45,7 +45,6 @@ The workflow signs in to npm with npm's trusted publishing (OIDC), not a token:
|
|
|
45
45
|
| `src/pi.ts` | [pi](https://pi.dev) session factories for executors, sub-agents and judges |
|
|
46
46
|
| `src/spec-tools.ts` | `change_plain` |
|
|
47
47
|
| `src/loop-tools.ts` | `regenerate`, `drift` and `halt` - one definition each, for the conversation and the skills alike |
|
|
48
|
-
| `src/mature-tools.ts` | `mature`'s own two: `watch_jobs` and `spec_findings` |
|
|
49
48
|
| `src/plain/` | the [\*\*\*plain](https://plainlang.org/docs/) 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)) |
|
|
50
49
|
| `src/regenerate/conformance.ts` | conformance tests: writing them, and fixing the test or the code until they pass |
|
|
51
50
|
| `src/regenerate/regenerate.ts`, `src/regenerate/conform.ts` | a code job - a \*\*\*plain diff regenerated in the code - and a conformance job |
|
package/docs/headless.md
CHANGED
|
@@ -173,7 +173,7 @@ A module that `requires` others is built in its own codebase on top of theirs: t
|
|
|
173
173
|
|
|
174
174
|
**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.
|
|
175
175
|
|
|
176
|
-
**Time to first regeneration.** `pulses.first_started_seconds` is how long after the run started its first code pulse started - planning the work, and any wait for a free slot, before it - and `first_landed_seconds` how long until the first one landed, when there is first something to try. Both are `null` until it happens. In the chat,
|
|
176
|
+
**Time to first regeneration.** `pulses.first_started_seconds` is how long after the run started its first code pulse started - planning the work, and any wait for a free slot, before it - and `first_landed_seconds` how long until the first one landed, when there is first something to try. Both are `null` until it happens. In the chat, a skill that writes \*\*\*plain diffs with `change_plain` says the same in their result, counted from when the skill began.
|
|
177
177
|
|
|
178
178
|
**Gaps in \*\*\*plain.** A behaviour \*\*\*plain 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 \*\*\*plain and run again.
|
|
179
179
|
|
|
@@ -228,4 +228,4 @@ turri regenerate app.plain --harness-model amazon-bedrock/eu.anthropic.claude-op
|
|
|
228
228
|
nohup turri -p "Render app.plain as a whole module, with conformance tests" > render.log 2>&1 &
|
|
229
229
|
```
|
|
230
230
|
|
|
231
|
-
It reports what it did at the end, and nothing streams in between, so for long runs `turri regenerate` is the better choice.
|
|
231
|
+
It reports what it did at the end, and nothing streams in between, so for long runs `turri regenerate` is the better choice. A skill that interviews needs someone to answer; without one it decides and record their assumptions, and a gate that escalates to a human stops the run.
|
package/docs/hosting.md
CHANGED
|
@@ -112,7 +112,7 @@ await turri.dispose(); // every chat let go, what activate start
|
|
|
112
112
|
- **Interrupting.** `interrupt(id)` is the terminal's ctrl+c for a host's key or button: it stops what that chat is doing - its turn, a skill or a tool in it, a `!` command, a compaction - and nothing in the other chats (`stopped`). A skill stopped keeps its run, to be continued. A question of the chat's open in the host's dialogs when its turn is stopped is over - declined - and its dialog's `signal` aborted, for the host to close it. A chat doing nothing ends the session, every chat with it, as `dispose` does (`ended`): turri never exits the host's process, so the host closes what showed it. An interrupt straight after one that stopped something is `ignored`, so pressing twice to be sure ends nothing. `session.abort()` is the chat's turn alone, and never ends the session.
|
|
113
113
|
- **Declining a question.** `decline(id)` is ctrl+c at a question in turri's terminal: the question the chat asks now - its ask_user, a skill's interview, a review gate's escalation, whatever goes through the host's dialogs - declined. The asker is told "The user declined to answer the question." and the turn goes on; a review gate declined stands. It declines each question it is called at, however many in a row, and never stops the turn - that is `interrupt`. `none` when the chat asks nothing now, `missing` for a chat not open. A dialog that resolves with `undefined` is something else - dismissed, and in a question of several answers "done" with the boxes ticked so far.
|
|
114
114
|
- **The record.** The chats are recorded in `.turri/chats.json` as the terminal records them, and `restore: true` reopens the last ended session's - without it, a host starts with none and deletes nothing. A chat closed is deleted; a session's chats are kept until five newer sessions have recorded theirs.
|
|
115
|
-
- **The jobs.** The host is told of each job as it changes, and has the registry and the loop themselves, so it shows the very jobs the chats run. Its buttons are best a chat's commands - `session.prompt("/regenerate app.plain")` - so the plan is the chats'.
|
|
115
|
+
- **The jobs.** The host is told of each job as it changes, and has the registry and the loop themselves, so it shows the very jobs the chats run. Its buttons are best a chat's commands - `session.prompt("/regenerate app.plain")` - so the plan is the chats'. A job's \*\*\*plain diff is `job.diff`, and `job.diffWhere` places its lines in the `.plain` files: by a line's place in the diff, its `file`, its `line` there, the `section` it is in and `written` - the line as the file has it. The diff is \*\*\*plain as read, a link `[text](path)` in it as `` `[resource]text` ``, so a host that finds the lines again in a file's text - after an edit since the pulse started - matches them by `written`, never by the diff's text. A `job.diff` in a ledger written before `written` was added has none.
|
|
116
116
|
- **The questions.** `dialogs(chat)` gives a chat's two dialogs: `select(title, options, context, at, { signal })` and `input(title, context, { signal })`, each resolving with the answer, or undefined when the person dismissed it. `signal` is aborted when the question is over without the dialog's answer - declined by `decline`, or its turn stopped (`interrupt`, `session.abort()`): the host closes the dialog it opened for it, whatever it resolves with then being ignored. A host may leave it unread. The chat's questions, a skill's interview and a review gate's escalation are put in them; without them, in the `uiContext`'s own dialogs.
|
|
117
117
|
- **The surface.** What turri tells the model of where the person watches the work is the terminal's by default: regen, the side panel on `ctrl+h`, `/pulses`. In a host none of that is there, and the model would send the person to keys and panels that do not exist. `surface` is the host's own description of its UI - the views it has in place of the terminal's, what its marks and buttons mean, where questions appear - in its own words. Given, turri leaves every terminal instruction out of what the model is told and puts the surface in turri's identity, as a `<surface>` block, where the identity speaks of regen. It reaches the side questions and the tools' own words too: a pulse not found says the `pulses` tool lists them, not `/pulses`. Text only the person sees in the terminal - the job panel, the side panel - is the terminal's and stays as it is. Without `surface`, the prompt is the terminal's, byte for byte. What the conversation is to do once something is released belongs there too - open it in the host's preview, say what to test: with `sentinel: false`, after a pulse released under `release-after-pulse`, the conversation does what it was told to do once something is released. With the sentinel on, the sentinel tests the release, and the conversation only says it landed.
|
|
118
118
|
- **The system prompt.** `systemPrompt(prompt)` gets the whole system prompt for the turn - [pi](https://pi.dev)'s, turri's identity and blocks, the rules - after turri's own `before_agent_start` work, and returns what is sent. It is for what `surface` does not cover; to describe the UI, use `surface`, rather than words appended after turri's that contradict them.
|
package/docs/regenerating.md
CHANGED
|
@@ -123,7 +123,7 @@ The reviewer has `can_it_fail` too, three runs a review session: a test it doubt
|
|
|
123
123
|
|
|
124
124
|
### The code and its unit tests are reviewed
|
|
125
125
|
|
|
126
|
-
Passing unit tests prove only what they check, and a coding agent is quick to write tests that check next to nothing - that a function exists, that a result is not empty, that a status is 200 - or code that no test reaches. So, with `review: true` in the [config](config.md), once the unit testing script passes a reviewer on the judge model reads what the work wrote, beside \*\*\*plain, and changes nothing. It reviews both halves, with the unit testing script word for word in its prompt: a test the script never reaches is not run, and checks nothing. The review is off by default - `review: false`, or the key left out - and so is the conformance tests' review: the work goes on once its tests pass. Even on, a module's first pulse is not reviewed when it builds one functional spec - the project set up and the smallest thing that runs, as
|
|
126
|
+
Passing unit tests prove only what they check, and a coding agent is quick to write tests that check next to nothing - that a function exists, that a result is not empty, that a status is 200 - or code that no test reaches. So, with `review: true` in the [config](config.md), once the unit testing script passes a reviewer on the judge model reads what the work wrote, beside \*\*\*plain, and changes nothing. It reviews both halves, with the unit testing script word for word in its prompt: a test the script never reaches is not run, and checks nothing. The review is off by default - `review: false`, or the key left out - and so is the conformance tests' review: the work goes on once its tests pass. Even on, a module's first pulse is not reviewed when it builds one functional spec - the project set up and the smallest thing that runs, as a new project starts: a review of a scaffold is time spent before anything can be tried.
|
|
127
127
|
|
|
128
128
|
- **The code.** Every behaviour, edge case, validation rule and error \*\*\*plain states for this work is implemented as \*\*\*plain says it, with nothing left as a stub, a placeholder or a hard-coded answer. The code keeps to the implementation reqs and does not break what was already built. A behaviour with no code for it is raised as not implemented. Such a problem is of kind `wrong`.
|
|
129
129
|
- **The unit tests.** Every behaviour, edge case and error \*\*\*plain states for this work has a test asserting the exact outcome \*\*\*plain says, that fails when it breaks; that no test only checks something exists, asserts nothing, is skipped or swallows a failure; that the unit under test is never mocked - only what is outside it; and that the tests are deterministic. A test that is not there is `missing`, a weak one is `bad`.
|
|
@@ -180,7 +180,7 @@ Changes to what is built - an item edited or taken out, a functional spec built
|
|
|
180
180
|
|
|
181
181
|
Each planned job knows its cases - which functional specs it adds, changes (their text, or a concept they use) and takes out, or none: reqs and definitions only - and the loop says them, with each job's title, where the person looks (`drift`, `pulses`, and what `regenerate` returns).
|
|
182
182
|
|
|
183
|
-
**Steering.** How you want the work cut and ordered - "build the batch worker's helpers with the worker", "models first" - goes to the grouping and placement agents with every plan, as a strong preference: it never overrides the limits and checks. It comes from three places, each labelled with where it came from: the conversation (`steer`, for the whole project or one module, for the session); the project's `AGENTS.md`, every section whose heading names the queue (`## Queue guidance`), read afresh with every plan; and whatever asks for the work - `regenerate` and `
|
|
183
|
+
**Steering.** How you want the work cut and ordered - "build the batch worker's helpers with the worker", "models first" - goes to the grouping and placement agents with every plan, as a strong preference: it never overrides the limits and checks. It comes from three places, each labelled with where it came from: the conversation (`steer`, for the whole project or one module, for the session); the project's `AGENTS.md`, every section whose heading names the queue (`## Queue guidance`), read afresh with every plan; and whatever asks for the work - `regenerate` and `change_plain` take a `guidance` of their own, which the loop keeps with that module.
|
|
184
184
|
|
|
185
185
|
A plan is kept until \*\*\*plain, what it starts from or its steering changes. `steer` alone plans nothing: the next look does - a `regenerate`, a job ending, \*\*\*plain changing. Then the work not running yet is planned again to follow it, a running job going on as it is. Where a plan did not do as asked, it says so, and what `regenerate` returns lists it under "what the plan did not do as asked": each wish an agent did not follow, with why (both tools take a `notes` for that), a grouping or placement that did not hold, with what was wrong, and a group split for its size. The conversation tells the person that, rather than guessing.
|
|
186
186
|
|
|
@@ -190,7 +190,7 @@ A \*\*\*plain change is a notice, not a diff to run: **`regenerate`**, called af
|
|
|
190
190
|
|
|
191
191
|
**A change inside what the running job regenerates steers it** (`steering.ts`), before any of that: a functional spec it regenerates reworded or given a sub-bullet, a definition or an implementation req it is given edited, a file one of them links edited - a change to an item it already has, never an item added or removed. Its coding agent is told, in the same shape as its first change (`describeSpecChange`): in its turn when it is in one, else as its next turn once the step it is at ends - a test run, a review - even when the tests passed or the review approved; edits made before it is told are one steer, the newest of each item. The unit tests run again after it, and a review still to come sees the change as steered, within its two reviews. The job's target moves with it: it lands built from \*\*\*plain as it was steered to. A definition or req that functional specs built before the job use too is told to the agent but kept out of the record, so those functional specs - and this job's with them - are patched by the next job. What else changed is the next job's; a change that is partly a steer is split. Two steers a job: a third goes the way above, stopped or - with `allow-to-stop-jobs: false` - left to the next job. Conformance jobs are not steered: they are planned from what the code lands. A steer shows in the job's view in the panel (what it regenerates now), in the transcript (`⇢ steered job …`), under the job in `regenerate`'s answer, and in the job's report.
|
|
192
192
|
|
|
193
|
-
With `allow-to-stop-jobs: false` in `config.turri.yaml` it is not stopped: it runs to its end and lands what it was regenerating, and the next job regenerates what changed since as a patch on top of it. Something lands as soon as it can, and the software evolves job by job - the first build of an integration lands and throws, the next patch fills in what was missing - at the cost of regenerating what \*\*\*plain has already moved on from. Your own `halt` stops a job either way. `
|
|
193
|
+
With `allow-to-stop-jobs: false` in `config.turri.yaml` it is not stopped: it runs to its end and lands what it was regenerating, and the next job regenerates what changed since as a patch on top of it. Something lands as soon as it can, and the software evolves job by job - the first build of an integration lands and throws, the next patch fills in what was missing - at the cost of regenerating what \*\*\*plain has already moved on from. Your own `halt` stops a job either way. `change_plain` makes an edit and gives the notice in one call, and so does `turri change-plain` on the command line. What is left to regenerate, without regenerating it, is `drift`.
|
|
194
194
|
|
|
195
195
|
### Regenerating a functionality
|
|
196
196
|
|
package/docs/skills-and-gates.md
CHANGED
|
@@ -12,7 +12,7 @@ Under `metadata:`, which the Agent Skills standard reserves for arbitrary key-va
|
|
|
12
12
|
---
|
|
13
13
|
name: draft-plain
|
|
14
14
|
description: Drafts ***plain, judged before it is kept.
|
|
15
|
-
allowed-tools: [read, write, edit,
|
|
15
|
+
allowed-tools: [read, write, edit, ls, grep, find]
|
|
16
16
|
metadata:
|
|
17
17
|
gates:
|
|
18
18
|
- after: product # the ### step this gate guards
|
|
@@ -35,25 +35,27 @@ metadata:
|
|
|
35
35
|
...
|
|
36
36
|
```
|
|
37
37
|
|
|
38
|
-
A skill declares its steps with a `## Steps` section whose `###` headings are the steps; without it the whole skill is one step, which is what most skills are. Steps are never inferred from `###` headings elsewhere in the body - prose skills are full of those. Each gate becomes a conditional edge after its step; a single-step skill is gated by naming the skill itself. None of the skills turri ships declares a gate today
|
|
38
|
+
A skill declares its steps with a `## Steps` section whose `###` headings are the steps; without it the whole skill is one step, which is what most skills are. Steps are never inferred from `###` headings elsewhere in the body - prose skills are full of those. Each gate becomes a conditional edge after its step; a single-step skill is gated by naming the skill itself. None of the skills turri ships declares a gate today, but a project's own skill can.
|
|
39
39
|
|
|
40
|
-
`command-only: true` under `metadata:` keeps a skill from the model: it is started only by the person, with its slash command
|
|
40
|
+
`command-only: true` under `metadata:` keeps a skill from the model: it is started only by the person, with its slash command. None of turri's own is; turri registers the command for every such skill, the project's own too. Typed, the command puts what was typed in the chat as the person's message, and lets `cultivate` start the skill once - and so does an option starting with the command - a project's `/release-notes - for this release` - picked in the conversation's `ask_user` (`chosenCommand` in `src/skills.ts`). The answer tells the model to call `cultivate` for it at once, with the person's request word for word as its context (`startPicked`), so they never type the command after picking it; the run starting uses that up, so the model cannot start it again later on its own. Without either, `cultivate` refuses it and the model is told how the person starts it; the skill's line in `cultivate`'s list says so too.
|
|
41
41
|
|
|
42
|
-
`paced: false` under `metadata:` lets a skill write \*\*\*plain changes one after another on its own: `change_plain` otherwise refuses a new change until the user has answered a question since the last one - one answer, one change, as an interview writes them.
|
|
42
|
+
`paced: false` under `metadata:` lets a skill write \*\*\*plain changes one after another on its own: `change_plain` otherwise refuses a new change until the user has answered a question since the last one - one answer, one change, as an interview writes them. An unpaced skill is told so by the tool, and after each change to write the next at once, not to carry on with an interview it does not have.
|
|
43
43
|
|
|
44
|
-
`questioning:` under `metadata:` names the steps the questioning level caps (`src/questioning.ts`) - `
|
|
44
|
+
`questioning:` under `metadata:` names the steps the questioning level caps (`src/questioning.ts`) - `[stack, product]`, say. When there are any and someone to ask, the harness itself asks the first question of the first step: how many questions those steps may ask - light (3), medium (5), thorough (10) or best judgement - and tells the agent the answer beside each step's instructions, with how broad each question is to be: broad at light, a whole area at once, narrow at thorough, an edge case at a time. Every level covers the same ground, in less or more depth. In a capped step `ask_user` refuses a question past the cap; the step's closing confirmation, asked with `confirms_phase: true`, is never counted. `end_questions` ends the step's questions sooner - best judgement's only end. What the questions leave open, the agent decides itself and says what it chose. The level, and how many questions each step has asked, are kept in the run's `run.json`, so a resumed run keeps them. A step it does not name, or a run with nobody to ask, is not capped.
|
|
45
45
|
|
|
46
|
-
`change-plain-per-answer:` under `metadata:` names the steps where every answer is a \*\*\*plain change - `
|
|
46
|
+
`change-plain-per-answer:` under `metadata:` names the steps where every answer is a \*\*\*plain change - `[product]`, say - at every questioning level. There `ask_user` refuses the next question until the last answer is written with `change_plain` (a confirmation, or a question dismissed or declined, owes nothing), and `change_plain` no longer waits for an answer before a change: the agent writes as many as the step needs, those it decides itself included. The level limits the questions, never the \*\*\*plain changes.
|
|
47
47
|
|
|
48
48
|
A step ends when its agent's turn does. A turn that ends on a question in the reply would end the step there, half done, on a question no one reads - the person sees only what `ask_user` asks. So such a step is sent back, twice at most: to ask with `ask_user` if the question is truly the person's, or, in an unpaced skill, to decide it itself - then to carry on until the step is done.
|
|
49
49
|
|
|
50
|
-
`phases:` under `metadata:` says what each step does, in a short line a step by its name - for the person, not the agent: while the skill runs, the line above the input shows its phases, and under them the one it is in with its line (`
|
|
50
|
+
`phases:` under `metadata:` says what each step does, in a short line a step by its name - for the person, not the agent: while the skill runs, the line above the input shows its phases, and under them the one it is in with its line (`stack - the language, frameworks, storage and architecture`). A phase's name alone says little to someone who has not read the skill. Every skill with more than one step describes each; a line for a step the skill does not have is an error, as a gate after one is.
|
|
51
51
|
|
|
52
|
-
A skill gets the tools its `allowed-tools` names, and nothing else: one that names none has none. So every skill here names its own - the file tools (`read`, `write`, `edit`, `
|
|
52
|
+
A skill gets the tools its `allowed-tools` names, and nothing else: one that names none has none. So every skill here names its own - the file tools (`read`, `write`, `edit`, `ls`, `grep`, `find`) for the ones that write \*\*\*plain, resources or scripts, `bash` only for those that need a shell (running the testing script they write, deleting a module they split up), only the reading ones for those that analyse and report, and turri's own (`change_plain` and the ones below) where a skill uses them.
|
|
53
53
|
|
|
54
|
-
|
|
54
|
+
A skill's `write` and `edit` reach the project's \*\*\*plain only (`plainWriteTools` in `src/plain-files.ts`): a `.plain` file, a resource one links, a testing script, a `config.turri*.yaml`, or a new file outside `plain_modules/`, `dist/` and the hidden folders - a resource about to be linked, a script about to be named. Any other file that is there already - the code, a release, what an app it started writes - is refused, with why. Its `bash`, where it has one, is not fenced.
|
|
55
55
|
|
|
56
|
-
|
|
56
|
+
What a run changed is part of its result: every file of the project's \*\*\*plain it added, changed or removed - its `.plain` files, the resources they link, its testing scripts and `config.turri*.yaml` (`projectFiles` in `src/plain-files.ts`), never the built code, a release in `dist/` or the log of an app it started, which change while a skill runs without being its doing - or that it changed none, which is said as such, so a run that wrote nothing is never reported as done. A skill without gates says it was not reviewed. A `.plain` file a skill edited with the file tools is not regenerated by it: the result says so, for the conversation to regenerate the change.
|
|
57
|
+
|
|
58
|
+
A skill acts on the work loop with the conversation's own tools, on the conversation's loop - never copies of them (`src/loop-tools.ts`). Every skill with the loop has `drift`: what is left to regenerate, what is regenerating, and "Fully regenerated" once nothing is. One that lists `regenerate` and `halt` in `allowed-tools` can regenerate what a module's \*\*\*plain holds and halt a pulse, with why.
|
|
57
59
|
|
|
58
60
|
Each step is told where its skill's files are, so a path it names - `references/intent.md` - and a skill it says to follow - `implement-unit-testing-script` - are found without searching.
|
|
59
61
|
|
|
@@ -62,7 +64,7 @@ Each step is told where its skill's files are, so a path it names - `references/
|
|
|
62
64
|
- **Whether review happens.** The gate is an edge. There is no prompt the executor can write that skips it.
|
|
63
65
|
- **How many attempts.** `max_retries` is a counter in graph state. The executor is never asked how many tries it would like.
|
|
64
66
|
- **What "fail" means.** Judges answer through a schema (`submit_verdict`), so the router branches on a value. A judge that returns no verdict counts as a fail - silence is not consent.
|
|
65
|
-
- **What gets reviewed.** The executor's only write path for the artifact is `write_artifact`, pointed at a path the graph chose. A step that produces no artifact fails its gate without spending a single judge call.
|
|
67
|
+
- **What gets reviewed.** The executor's only write path for the artifact is `write_artifact`, pointed at a path the graph chose. A step that produces no artifact fails its gate without spending a single judge call. A step with no gate is asked for none: its reply is what it gives - an analysis's verdict (`COMPATIBLE`, or the pairs that conflict) is all it says - and the last step's is the skill's result. A skill that only reads is not told it changed nothing, as one that writes is.
|
|
66
68
|
- **Who is judging.** Each judge is a fresh session given the artifact and the rubric and nothing else. It never sees the executor's transcript, because a judge that inherits the actor's reasoning inherits its rationalisations.
|
|
67
69
|
|
|
68
70
|
`on_fail: escalate` (and `retry` once retries are exhausted) pauses the graph on a LangGraph `interrupt`: in the TUI that is a real confirm dialog; with no UI there is nobody to ask, so the gate stands and the run stops.
|
|
@@ -73,11 +75,11 @@ A gate can also have a `continuous` reviewer: a read-only agent that re-reads th
|
|
|
73
75
|
|
|
74
76
|
## What a step can do
|
|
75
77
|
|
|
76
|
-
- **Interview the user.** `ask_user` puts one question to the person. In the TUI it is a dialog; from the CLI the options are numbered. Offering choices is required, not optional - a call with neither `options` nor `free_form: true` is refused - and every list carries "Something else…". `multi_select: true` gives every option a box, ticked and unticked as it is chosen - a pick made by mistake is undone by choosing it again - until they choose Done; what they type is one more box, ticked. "Something else…", "Chat about this" and Done are no boxes. The skill's agent talks in a session of its own that the person never sees, so what the person must read to answer - the brief to confirm, the plan to approve - it passes as `ask_user`'s `context`, shown above the question in the dialog
|
|
78
|
+
- **Interview the user.** `ask_user` puts one question to the person. In the TUI it is a dialog; from the CLI the options are numbered. Offering choices is required, not optional - a call with neither `options` nor `free_form: true` is refused - and every list carries "Something else…". `multi_select: true` gives every option a box, ticked and unticked as it is chosen - a pick made by mistake is undone by choosing it again - until they choose Done; what they type is one more box, ticked. "Something else…", "Chat about this" and Done are no boxes. The skill's agent talks in a session of its own that the person never sees, so what the person must read to answer - the brief to confirm, the plan to approve - it passes as `ask_user`'s `context`, shown above the question in the dialog. The agents are told to keep it to what can be taken in at a glance, about fifteen lines; one longer scrolls in a window of its own above the question (`src/question-dialog.ts`) - PgUp/PgDn, Shift+Up/Down or the mouse wheel - so the question and its options always show below it. [pi](https://pi.dev)'s own select, which turri used before, put it all in its title, and a long brief pushed the options off the screen. Nothing else it wrote is shown there: the last thing it said before asking - its account of a probe, a regeneration - is kept with the answer, in the run's trace and the transcript, never in the dialog.
|
|
77
79
|
|
|
78
80
|
Beside "Something else…", every list an agent's question shows carries "Chat about this": the person says what they want to talk over - why it is asked, what an option means for later - and it goes back to the agent that asked, as no answer. A skill's agent answers in the `context` of the question asked again, since nothing else it writes reaches them; the conversation's agent answers in its reply and ends its turn, the person going on in the chat. It is not counted against the questioning level, and owes no \*\*\*plain change. The harness's own first question - how many the phases may ask - has no agent to answer, and does not offer it. Either choice is backed out of with Esc, or by typing nothing: the options come back, so one picked by accident costs nothing. Esc on the options themselves dismisses the question.
|
|
79
81
|
- **Be asked something on the side.** A skill runs inside one `cultivate` call, so the conversation can answer nothing until it returns. `/btw <question>` (`btw.ts`) can: [pi](https://pi.dev) runs a command at once, even mid-turn, and it starts an agent of its own - fresh, on the chat's model, reading the project and turri and changing nothing - shown the conversation, the running skill's trace and the end of its agent's session, and the pulses. It is told it is turri, as the conversation's agent is (`identity.ts`), and answers as turri, in the first person - never as an onlooker who sends the person to turri with a message to pass on. Its answer goes in the transcript as an entry, in no model's context: the conversation and the skill go on as if nothing was asked. While a question of the skill's has the keys, `/btw <question>` typed as the answer (under "Something else…") is the same: answered, and the question asked again with the answer above it - the skill's agent never sees it.
|
|
80
|
-
- **Build while it interviews.** `change_plain` (
|
|
82
|
+
- **Build while it interviews.** `change_plain` (any skill that names it in `allowed-tools`) writes a change to \*\*\*plain and queues its regeneration in one call; the interview carries on while the builds run in the background, one after another. `drift` shows how they stand. The person sees its calls as `mutate`: a tool can be shown by a name of its own (`SHOWN_AS` in `regenerate/activity.ts`), while its agent calls it by the one that says what it takes.
|
|
81
83
|
- **Regenerate, and conform.** A skill that names them in `allowed-tools` gets the conversation's own `regenerate`, `drift`, `halt` and `conform`, on its loop: `conform` schedules a conformance pulse for functional specs not built yet, as it does from the chat. The skill never waits for them.
|
|
82
84
|
- **Choose the model that regenerates**, when the user says so - `engine`, the same tool the conversation has.
|
|
83
85
|
|
|
@@ -89,13 +91,13 @@ While a skill runs in the chat, `cultivate`'s output leads with what its agent i
|
|
|
89
91
|
|
|
90
92
|
A project can have skills of its own, in the same format: a folder a skill, with its `SKILL.md` - steps, gates, rubrics in its `references/` - in `.turri/skills/` or `.agents/skills/` of the project's folder (`src/skills.ts`). turri finds them as it starts, beside its own, and `cultivate` lists each as the project's, with where it is; `turri cultivate <name>` runs one by name, as it does turri's own. They are run as turri's own are: the harness owns their steps and gates, and their agents get the tools their frontmatter names.
|
|
91
93
|
|
|
92
|
-
A name is one skill. turri's own come first, then `.turri/skills/`, then `.agents/skills/`; a skill whose name is taken already is left out, and the list says so - a project's `
|
|
94
|
+
A name is one skill. turri's own come first, then `.turri/skills/`, then `.agents/skills/`; a skill whose name is taken already is left out, and the list says so - a project's `debug-specs` never quietly stands in for turri's. A skill that does not parse is said to have failed to load, by its folder.
|
|
93
95
|
|
|
94
96
|
`.turri/` is turri's scratch folder and ignores itself, all but `skills/`: its `.gitignore` is `*`, `!skills/`, `!skills/**`, and one written before is brought up to date. [pi](https://pi.dev) reads `.agents/skills/` too, as skills of its own the chat could follow by hand - the chat is told to run any `cultivate` lists with `cultivate`, so its gates still run.
|
|
95
97
|
|
|
96
98
|
The skills [pi](https://pi.dev) offers the chat leave out what turri has already (`src/chat-skills.ts`): a skill of a name turri runs itself - its own, or the project's - and any skill about [\*\*\*plain](https://plainlang.org/docs/) or its testing scripts. [pi](https://pi.dev) gathers skills from `~/.pi/agent/skills`, `~/.agents/skills` and the `.agents/skills` of every folder from where turri started up to its git root, so a folder above the project can hold another tool's \*\*\*plain skills - a `load-plain-reference` the chat would load at the start of every session, though the whole \*\*\*plain reference, turri's [rules](#rules), is in its system prompt already. The person is told once which were left out. A skill about anything else is offered as [pi](https://pi.dev) offers it.
|
|
97
99
|
|
|
98
|
-
A skill is found once, as turri starts: one added while it runs is there the next time. A project's skill agent runs as turri's do - [pi](https://pi.dev)'s file tools and `bash` on the machine, no fence - so a project's skills are as trusted as the project.
|
|
100
|
+
A skill is found once, as turri starts: one added while it runs is there the next time. A project's skill agent runs as turri's do - [pi](https://pi.dev)'s file tools, `write` and `edit` held to the project's \*\*\*plain, and `bash` on the machine, no fence - so a project's skills are as trusted as the project.
|
|
99
101
|
|
|
100
102
|
## Rules
|
|
101
103
|
|
package/docs/using.md
CHANGED
|
@@ -7,17 +7,9 @@ cd ~/my-project
|
|
|
7
7
|
turri
|
|
8
8
|
```
|
|
9
9
|
|
|
10
|
-
That is [pi](https://pi.dev), with turri's tools, skills and themes loaded - `turri` alone, or with [pi](https://pi.dev)'s options (`turri -c`, `turri --harness-model <m>`). It holds several chats side by side, each working while another is shown: `/chat new` or `alt+n` opens one, `alt+1` … `alt+9` switch, and `turri -c` opens them all again - see [chats](chats.md). It opens with turri's own banner - the jellyfish it is named after, regenerating until the conversation fills the screen: a pixel polyp on the sea floor grows into the jellyfish - a bell from pink down to indigo, an orange gut, neon tentacles - which swims a while, sinks back to the floor, and the polyp grows again, round and round: a picture in a terminal that shows images (Kitty, Ghostty, WezTerm, Warp, iTerm2), and drawn at a third of the size in half blocks in any other, tmux included (`src/jellyfish.ts` draws it, ported from the design system's pixel kit, `scripts/jellyfish-frames.mjs` writes the pictures) - turri's version (and, run from a clone, the commit it is on, and whether the clone has changes) and where its settings are changed - and only when something is wrong, skills that failed to load or agents' commands less fenced than they should be - rather than [pi](https://pi.dev)'s startup header and resource list; `turri --verbose` shows those too. [pi](https://pi.dev)'s "Update Available" notice is left out - turri's [pi](https://pi.dev) is a pinned dependency `pi update` never touches - and the terminal's tab reads `🪼 turri - <folder>`, not [pi](https://pi.dev)'s `π`. Talk to it: ask it to add a feature to \*\*\*plain, regenerate a module, fix a module's \*\*\*plain. It decides which skill or tool applies - but not *how* a skill runs: `cultivate` hands the work to the gated runner, which owns the steps, the gates and the judges (see [skills and gates](skills-and-gates.md)).
|
|
10
|
+
That is [pi](https://pi.dev), with turri's tools, skills and themes loaded - `turri` alone, or with [pi](https://pi.dev)'s options (`turri -c`, `turri --harness-model <m>`). It holds several chats side by side, each working while another is shown: `/chat new` or `alt+n` opens one, `alt+1` … `alt+9` switch, and `turri -c` opens them all again - see [chats](chats.md). It opens with turri's own banner - the jellyfish it is named after, regenerating until the conversation fills the screen: a pixel polyp on the sea floor grows into the jellyfish - a bell from pink down to indigo, an orange gut, neon tentacles - which swims a while, sinks back to the floor, and the polyp grows again, round and round - and once the conversation fills the screen, comes to rest swimming, whole: a picture in a terminal that shows images (Kitty, Ghostty, WezTerm, Warp, iTerm2), and drawn at a third of the size in half blocks in any other, tmux included (`src/jellyfish.ts` draws it, ported from the design system's pixel kit, `scripts/jellyfish-frames.mjs` writes the pictures) - turri's version (and, run from a clone, the commit it is on, and whether the clone has changes) and where its settings are changed - and only when something is wrong, skills that failed to load or agents' commands less fenced than they should be - rather than [pi](https://pi.dev)'s startup header and resource list; `turri --verbose` shows those too. [pi](https://pi.dev)'s "Update Available" notice is left out - turri's [pi](https://pi.dev) is a pinned dependency `pi update` never touches - and the terminal's tab reads `🪼 turri - <folder>`, not [pi](https://pi.dev)'s `π`. Talk to it: ask it to add a feature to \*\*\*plain, regenerate a module, fix a module's \*\*\*plain. It decides which skill or tool applies - but not *how* a skill runs: `cultivate` hands the work to the gated runner, which owns the steps, the gates and the judges (see [skills and gates](skills-and-gates.md)).
|
|
11
11
|
|
|
12
|
-
To start a project, just say what you want - `create a small server in Java Spring Boot` - and turri
|
|
13
|
-
|
|
14
|
-
Three skills you start yourself, by their commands or by picking them when turri offers them - it never starts them on its own: `/brood`, `/spawn` and `/bud`, each with what you want after it - `/spawn a URL shortener in Go`. The skills that build from scratch:
|
|
15
|
-
|
|
16
|
-
| skill | how it works |
|
|
17
|
-
| --- | --- |
|
|
18
|
-
| `brood` | first, how many questions the stack and product phases may ask - light (3), medium (5), thorough (10) or best judgement, broader with fewer, turri deciding what they leave open; then intent, stack, then testing - conformance tests on or off (into `config.turri.yaml`), their `***test reqs***` and the conformance testing script, which build nothing - then the product feature by feature, where every answer becomes one \*\*\*plain change - a git diff of the `.plain` files, written with `change_plain` and regenerated as a \*\*\*plain diff pulse the moment it is written, with its conformance tests when they are on |
|
|
19
|
-
| `spawn` | its quick twin, for something running in minutes: asks what you want to build only when your message does not say, and the stack in one question only when you named none - turri's choice recommended first; the rest of the technology it chooses itself - conformance tests off, unless you ask for them - then writes the smallest thing that runs, which starts the first build at once, and the main slice of what you described and one more on their happy path, each a \*\*\*plain diff queued as it is written - nothing you did not describe: named only the kind of software, you get it running, and say what it does. You try each as it lands, and say `go on` for the next slice it proposed, or what to change instead - every change is a \*\*\*plain diff, and what is left to build is worked out again from \*\*\*plain |
|
|
20
|
-
| `mature` | for \*\*\*plain you already have, rough or hand-written: on its own until it is fully regenerated - regenerates what is not built, watches the agents building it, and when one struggles on something \*\*\*plain leaves open, settles it in \*\*\*plain as a \*\*\*plain diff, stops that build and regenerates it again; decides open choices itself and marks them in its report |
|
|
12
|
+
To start a project, just say what you want - `create a small server in Java Spring Boot` - and turri starts it in the conversation, without asking how: it takes the stack you named, or chooses one this machine can build, and says it in a line; writes the module importing the stack from the standard library, `config.turri.yaml` (conformance tests off unless you ask for them) and the unit testing script (with `implement-unit-testing-script`, unless there is one); then the smallest thing that runs, regenerated at once, and while it regenerates the main thing you described and at most one more, on their happy path - nothing you did not describe. You say `go on`, or what to change, and every change after is \*\*\*plain it writes and regenerates (`src/new-project.ts`).
|
|
21
13
|
|
|
22
14
|
Peeking is safe while a question is open - a skill's or the chat's: `ctrl+h` opens the side panel over the question, which waits under it as it was - the panel says `your question is waiting`, and `ctrl+h` or `esc` takes you back to answer it. (The question has the keys, so turri catches `ctrl+h` itself while one is open.) The dialog holds the question alone - no hint and no invitation; the `ctrl+h` under the job panel breathes until it is first pressed.
|
|
23
15
|
|
|
@@ -90,7 +82,7 @@ A regeneration takes minutes, so in the TUI every tool that regenerates starts t
|
|
|
90
82
|
- `turri` runs [pi](https://pi.dev) in fullscreen mode, so the conversation stays where you scroll it while the pulses redraw (PgUp/PgDn, the wheel); `--tui-mode regular` goes back to the terminal's own scrollback.
|
|
91
83
|
- **`/turri-config`** changes a setting of the project's `config.turri.yaml` - the models, review, conformance tests, any key - and says when it takes effect: now, at your next message, or at the next pulse; never only after a restart. See [changing it from the chat](config.md#changing-it-from-the-chat).
|
|
92
84
|
- **`/stack`** chooses the stack everything is built with from then on, in a project whose parts are on different stacks: a list of the project's stacks, or `/stack backend`, `/stack none`. The stack's `config.turri.<stack>.yaml` is laid over `config.turri.yaml` - its testing scripts, toolchains, models - for the session; a module already asked for keeps the stack it was asked for with. The chat model switches it too when you ask - and, to build one part, passes that part's stack to `regenerate` instead, leaving the session's as it is. See [stacks](config.md#stacks).
|
|
93
|
-
- **`/regenerate [module | file.plain] [functionality ...] [from scratch [whole chain]] [how to cut the work]`** regenerates a module from its \*\*\*plain as it is now, as the `regenerate` tool does: what is left to regenerate is planned into pulses - one functional spec at a time, a few together or the whole module in one, as the planner judges - and the first starts. Words after the module are guidance for that plan (`/regenerate app models first`). A module's name typed with spaces for its underscores is that module - `/regenerate d365_function executor` is `d365_function_executor`. With no module named: the one `module:` in `config.turri.yaml` names, else the folder's top modules, to pick from. Asked for from scratch - `from scratch`, `start over`, `delete the module` - it
|
|
85
|
+
- **`/regenerate [module | file.plain] [functionality ...] [from scratch [whole chain]] [how to cut the work]`** regenerates a module from its \*\*\*plain as it is now, as the `regenerate` tool does: what is left to regenerate is planned into pulses - one functional spec at a time, a few together or the whole module in one, as the planner judges - and the first starts. Words after the module are guidance for that plan (`/regenerate app models first`). A module's name typed with spaces for its underscores is that module - `/regenerate d365_function executor` is `d365_function_executor`. With no module named: the one `module:` in `config.turri.yaml` names, else the folder's top modules, to pick from. Asked for from scratch - `from scratch`, `start over`, `delete the module` - it starts at once, with nothing to confirm: it deletes the module's code and conformance tests in every codebase where they live, committed so `/lineage` keeps them, and regenerates the whole module from its \*\*\*plain, built though nothing in it changed: `/regenerate shorty from scratch`. The modules below it keep their code, and the module starts again from a copy of the code below it. Asked for its chain too - `/regenerate shorty from scratch, the whole chain` - the code of every module of its requires chain is deleted, the base's included, and the whole chain is regenerated from the bottom: what to do after modules were split, merged or moved, when the code below was laid out for modules that are no more. The chat's `regenerate` does the same with `from_scratch: "module"` or `"chain"`, when you ask for it; `turri regenerate --from-scratch` or `--from-scratch-chain` on the command line, not with `--dry-run`. Functionalities named after the module - `/regenerate shorty 3 5` - are regenerated though their \*\*\*plain did not change, on the model chosen for functionalities: as `turri regenerate shorty.plain 3 5` does, and the chat's `regenerate` with `functionalities`.
|
|
94
86
|
- **`/halt <id>`** halts a pulse - a queued one never starts - **`/halt <module>`** (or a planned row's id) halts a module's planned pulses until you ask for it again, a \*\*\*plain change not starting it, and **`/halt activated <name>`** halts a server `activate` started; it lists all three when no id is given.
|
|
95
87
|
- **What these commands show you, the chat knows.** What `/regenerate` and `/halt activated` tell you - \*\*\*plain that does not parse, the pulses planned, a server halted - stays in the chat under the command, and since a slash command runs outside the conversation, it is put in the conversation as well: say "fix it" next and turri knows what you are looking at. Once `/regenerate` has started pulses, turri says so in the chat too - what runs and what is planned, and that you need not wait for it - without starting anything more for it; a module already up to date gets the plan's answer alone. A halted pulse reaches it as every pulse's end does.
|
|
96
88
|
- **Nothing is queued.** What is left to build is worked out from what each codebase is built from and \*\*\*plain as it is now - again whenever a pulse ends or \*\*\*plain changes - and the first pulse of it runs; see [a whole module](regenerating.md#a-whole-module). Changes that overlap never pile up: the next pulse builds \*\*\*plain as it is. The pulses planned after the one running are listed behind it, as queued, and so is each module waiting for the one it is built on - "waits for base" - until it can be planned. A change inside what the running pulse regenerates - a functional spec it regenerates reworded or given a sub-bullet, a definition or req it is given, a file they link - steers it: its agent is told, and it lands \*\*\*plain as it is now there, twice a pulse at most. Otherwise a pulse building something \*\*\*plain no longer wants is stopped and rolled back - unless `allow-to-stop-jobs: false` in `config.turri.yaml`, which has it land first and the change regenerated on top. To drop something, take it out of \*\*\*plain - one \*\*\*plain diff. Stopping a pulse leaves its change in \*\*\*plain: the codebase waits until its \*\*\*plain changes or it is asked for again.
|
|
@@ -106,7 +98,7 @@ In `print`, `json` and `rpc` modes the process ends when the answer is written,
|
|
|
106
98
|
|
|
107
99
|
The chat reads the project's `config.turri.yaml` afresh every turn and follows it: its agent is told what it sets, and the tools that regenerate default to it - `module` when you ask to build without naming one, `conformance-tests: false` to regenerate without conformance tests (none written, none of the existing ones run) unless you ask for them, `coding-model` for what regenerations run on, and the testing scripts. `harness-model` is what `turri` starts on. What you ask for in the conversation wins. Every key, what happens without it, and how the models inherit: [the project config](config.md).
|
|
108
100
|
|
|
109
|
-
A project whose parts are on different stacks gives each stack its own `config.turri.<stack>.yaml` beside `config.turri.yaml` - `config.turri.backend.yaml`, `config.turri.frontend.yaml` - with its testing scripts, models and conformance over the project's. A stack named after its module builds it - `config.turri.backend.yaml` builds `backend.plain` - with nothing to choose, so a backend and a frontend are each built with their own, side by side
|
|
101
|
+
A project whose parts are on different stacks gives each stack its own `config.turri.<stack>.yaml` beside `config.turri.yaml` - `config.turri.backend.yaml`, `config.turri.frontend.yaml` - with its testing scripts, models and conformance over the project's. A stack named after its module builds it - `config.turri.backend.yaml` builds `backend.plain` - with nothing to choose, so a backend and a frontend are each built with their own, side by side; for a stack named otherwise the chat's model passes it as `stack`, or choose one for the session with `/stack backend`. The chat is told each stack's and which is chosen, and the status bar shows it. See [stacks](config.md#stacks).
|
|
110
102
|
|
|
111
103
|
A project with only a `config.yaml` - turri's file before it had its own name, or another tool's - is read from it until turri first writes a setting, which copies it to `config.turri.yaml`; see [the old config.yaml](config.md#the-old-configyaml).
|
|
112
104
|
|
|
@@ -133,7 +125,7 @@ Every regeneration runs on the coding model unless you say otherwise. Say it in
|
|
|
133
125
|
|
|
134
126
|
| kind of work | covers |
|
|
135
127
|
| --- | --- |
|
|
136
|
-
| \*\*\*plain changes | \*\*\*plain diff pulses, including the ones `
|
|
128
|
+
| \*\*\*plain changes | \*\*\*plain diff pulses, including the ones `change_plain` queues |
|
|
137
129
|
| functionalities | a functionality or a whole module regenerated, functionalities regenerated again, conformance |
|
|
138
130
|
| everything | both |
|
|
139
131
|
|
|
@@ -218,7 +210,7 @@ turri change-plain <file.plain> <diff> # apply a ***plain diff, and regenerat
|
|
|
218
210
|
turri conform <file.plain> 1 # black-box tests for functionality 1
|
|
219
211
|
turri lineage <module> # what each regeneration changed in a module, and when
|
|
220
212
|
turri ledger [pulse] # what turri did here: the last runs and their pulses, or one pulse
|
|
221
|
-
turri cultivate
|
|
213
|
+
turri cultivate debug-specs # run a skill directly - by name, the project's too, or by its folder
|
|
222
214
|
turri release <file.plain> # release what is regenerated to dist/<module>, and activate it here
|
|
223
215
|
```
|
|
224
216
|
|
|
@@ -401,7 +401,7 @@ GOOD — each fact in the section that owns it:
|
|
|
401
401
|
|
|
402
402
|
Each section now answers one question, and the definition, the mechanism, and the behavior are each one line to check instead of clauses buried in a paragraph about resizing. Use the split whenever a bullet answers more than one of *what is it*, *what does it do*, *how is it built*, and *how is it tested* — see `module-structure.md` for section ownership and canonical section order.
|
|
403
403
|
|
|
404
|
-
Use `resolve-section-ownership` to perform the split. It is not the same as splitting a functional spec that is simply too large — for that,
|
|
404
|
+
Use `resolve-section-ownership` to perform the split. It is not the same as splitting a functional spec that is simply too large — for that, use `break-down-func-spec`.
|
|
405
405
|
|
|
406
406
|
The most common single case of this is a functional spec that states a mechanism rather than a result — a retry schedule, a cache, a token refresh. `impl-reqs.md` owns HOW; a functional spec keeps what a caller observes. `concise-specs-integration-examples.md` works through several.
|
|
407
407
|
|
|
@@ -181,7 +181,7 @@ GOOD — one spec, with the result stated first:
|
|
|
181
181
|
- A failed catalogue read fails `fetch()` before any :Record: is requested.
|
|
182
182
|
```
|
|
183
183
|
|
|
184
|
-
Merging is not in tension with the complexity limit. Split a spec when it implies more than 200 changed lines of code (`
|
|
184
|
+
Merging is not in tension with the complexity limit. Split a spec when it implies more than 200 changed lines of code (`break-down-func-spec`); merge when several specs implement one operation that fits inside that budget. Fragment count is not a measure of size.
|
|
185
185
|
|
|
186
186
|
## The auth flow narrated step by step
|
|
187
187
|
|