@turri-dev/cli 0.1.7 → 0.1.8-next.91b957c

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (96) hide show
  1. package/README.md +6 -8
  2. package/dist/btw.d.ts +2 -0
  3. package/dist/btw.js +2 -2
  4. package/dist/cli.js +6 -2
  5. package/dist/config-edit.d.ts +1 -1
  6. package/dist/config-edit.js +4 -0
  7. package/dist/config.d.ts +27 -0
  8. package/dist/config.js +28 -1
  9. package/dist/extension.d.ts +11 -0
  10. package/dist/extension.js +118 -14
  11. package/dist/genome.js +2 -0
  12. package/dist/host.d.ts +3 -1
  13. package/dist/host.js +4 -1
  14. package/dist/identity.d.ts +6 -1
  15. package/dist/identity.js +33 -7
  16. package/dist/init-config.d.ts +3 -0
  17. package/dist/init-config.js +15 -0
  18. package/dist/ledger-cli.d.ts +1 -0
  19. package/dist/ledger-cli.js +50 -0
  20. package/dist/ledger-tools.d.ts +10 -0
  21. package/dist/ledger-tools.js +64 -0
  22. package/dist/loop-cli.js +4 -0
  23. package/dist/loop-tools.js +3 -1
  24. package/dist/pi.d.ts +2 -0
  25. package/dist/pi.js +24 -4
  26. package/dist/regenerate/activity.d.ts +9 -0
  27. package/dist/regenerate/activity.js +11 -0
  28. package/dist/regenerate/agent-env.d.ts +7 -0
  29. package/dist/regenerate/agent-env.js +126 -5
  30. package/dist/regenerate/agent-shell.js +5 -0
  31. package/dist/regenerate/cases.js +3 -0
  32. package/dist/regenerate/chunk-plan.d.ts +5 -6
  33. package/dist/regenerate/chunk-plan.js +43 -10
  34. package/dist/regenerate/conformance-agents.js +2 -2
  35. package/dist/regenerate/conformance.js +6 -3
  36. package/dist/regenerate/debug-log.d.ts +3 -1
  37. package/dist/regenerate/debug-log.js +10 -2
  38. package/dist/regenerate/diagnose.js +12 -2
  39. package/dist/regenerate/environment-error.d.ts +6 -0
  40. package/dist/regenerate/environment-error.js +19 -1
  41. package/dist/regenerate/job-log.d.ts +3 -1
  42. package/dist/regenerate/job-log.js +8 -5
  43. package/dist/regenerate/jobs-tree.d.ts +7 -3
  44. package/dist/regenerate/jobs-tree.js +21 -6
  45. package/dist/regenerate/jobs.d.ts +18 -0
  46. package/dist/regenerate/jobs.js +62 -5
  47. package/dist/regenerate/ledger-view.d.ts +92 -0
  48. package/dist/regenerate/ledger-view.js +345 -0
  49. package/dist/regenerate/ledger.d.ts +275 -0
  50. package/dist/regenerate/ledger.js +349 -0
  51. package/dist/regenerate/lineage.d.ts +9 -1
  52. package/dist/regenerate/lineage.js +64 -12
  53. package/dist/regenerate/loop.d.ts +6 -0
  54. package/dist/regenerate/loop.js +36 -6
  55. package/dist/regenerate/pulse-files.d.ts +14 -0
  56. package/dist/regenerate/pulse-files.js +63 -0
  57. package/dist/regenerate/records.d.ts +5 -3
  58. package/dist/regenerate/records.js +6 -4
  59. package/dist/regenerate/regenerate.js +7 -5
  60. package/dist/regenerate/regression.js +4 -2
  61. package/dist/regenerate/services.js +3 -0
  62. package/dist/regenerate/side-panel.d.ts +2 -0
  63. package/dist/regenerate/side-panel.js +4 -3
  64. package/dist/regenerate/spec-map.js +8 -5
  65. package/dist/regenerate/test-scripts.js +18 -5
  66. package/dist/regenerate/testing.js +3 -0
  67. package/dist/regenerate/unit-review.js +3 -0
  68. package/dist/run-report.d.ts +1 -1
  69. package/dist/run-report.js +15 -5
  70. package/dist/self-invocation.d.ts +1 -1
  71. package/dist/self-invocation.js +3 -2
  72. package/dist/skill-progress.d.ts +1 -1
  73. package/dist/skill.d.ts +5 -0
  74. package/dist/skill.js +1 -0
  75. package/dist/skills.d.ts +14 -0
  76. package/dist/skills.js +19 -1
  77. package/dist/surface.d.ts +5 -0
  78. package/dist/surface.js +18 -0
  79. package/docs/authoring-plain.md +2 -2
  80. package/docs/config.md +24 -3
  81. package/docs/development.md +3 -1
  82. package/docs/headless.md +9 -4
  83. package/docs/hosting.md +46 -0
  84. package/docs/installation.md +1 -0
  85. package/docs/regenerating.md +11 -11
  86. package/docs/sandbox.md +5 -5
  87. package/docs/skills-and-gates.md +3 -1
  88. package/docs/using.md +39 -8
  89. package/genome/rules/func-specs.md +1 -0
  90. package/genome/rules/turri-regeneration.md +3 -0
  91. package/genome/skills/add-functional-spec/SKILL.md +12 -3
  92. package/genome/skills/add-functional-specs/SKILL.md +12 -2
  93. package/genome/skills/brood/SKILL.md +2 -0
  94. package/genome/skills/bud/SKILL.md +2 -0
  95. package/genome/skills/spawn/SKILL.md +47 -35
  96. package/package.json +1 -1
@@ -3,6 +3,7 @@ import { defineTool } from "@earendil-works/pi-coding-agent";
3
3
  import { Type } from "typebox";
4
4
  import { ask, createExecutor } from "../pi.js";
5
5
  import { reportActivity } from "./activity.js";
6
+ import { record } from "./ledger.js";
6
7
  import { diffFiles, digestFiles } from "./codebase.js";
7
8
  import { confinedTools } from "./confine.js";
8
9
  import { REVIEWER_CALLS, reviewKind } from "./conformance-agents.js";
@@ -288,6 +289,7 @@ async function reviewLoop(options, reviewer) {
288
289
  letGo = [];
289
290
  options.stage(`reviewing the code and its unit tests (review ${reviews + 1}): does the code do what the spec says, and does each test fail when that breaks`);
290
291
  const review = await reviewer.review(request);
292
+ record({ kind: "review.verdict", reviewer: "unit", review: reviews + 1, pass: review.pass, problems: review.pass ? 0 : review.problems.length });
291
293
  if (review.pass) {
292
294
  options.stage("the review has nothing left to improve in the code or its unit tests");
293
295
  return waived.length > 0 ? { passed: true, waived, reviews: reviews + 1 } : { passed: true, reviews: reviews + 1 };
@@ -316,6 +318,7 @@ async function reviewLoop(options, reviewer) {
316
318
  });
317
319
  // No ruling: the review stands, as it did before there was an arbiter.
318
320
  if (ruling !== undefined) {
321
+ record({ kind: "arbiter.ruling", upheld: ruling.upheld.length, dismissed: ruling.dismissed.length });
319
322
  waived.push(...ruling.dismissed.map((entry) => entry.problem));
320
323
  if (ruling.dismissed.length > 0)
321
324
  recordNote(options.buildDir, "pitfalls", dismissedNote(ruling, "unit test"), `${options.label} unit tests`);
@@ -1,4 +1,4 @@
1
- import type { Job } from "./regenerate/jobs.ts";
1
+ import { type Job } from "./regenerate/jobs.ts";
2
2
  /**
3
3
  * A headless run's report: one JSON file, `turri regenerate --report <file>`, saying what the run did - how it ended,
4
4
  * what it spent, which pulses landed, how many functional specs are completed - for a benchmark or CI to read rather
@@ -1,7 +1,9 @@
1
1
  import { mkdirSync, renameSync, rmSync, writeFileSync } from "node:fs";
2
2
  import { basename, dirname, join, relative, resolve } from "node:path";
3
+ import { debugMode } from "./config.js";
3
4
  import { turriIdentity } from "./environment.js";
4
5
  import { onModelEvent } from "./regenerate/activity.js";
6
+ import { firstRegeneration } from "./regenerate/jobs.js";
5
7
  /**
6
8
  * A headless run's report: one JSON file, `turri regenerate --report <file>`, saying what the run did - how it ended,
7
9
  * what it spent, which pulses landed, how many functional specs are completed - for a benchmark or CI to read rather
@@ -177,7 +179,12 @@ export function startRunReport(options) {
177
179
  };
178
180
  });
179
181
  const ran = list.filter((pulse) => pulse.status !== "queued");
182
+ const first = firstRegeneration(jobs, startedAt);
183
+ const seconds = (ms) => (ms !== undefined ? Math.round(ms / 100) / 10 : null);
180
184
  return {
185
+ // The time to first regeneration - the first code pulse started - and to the first one landed, from the run's start.
186
+ first_started_seconds: seconds(first.started),
187
+ first_landed_seconds: seconds(first.landed),
181
188
  counts: {
182
189
  total: ran.length,
183
190
  landed: ran.filter((pulse) => pulse.landed).length,
@@ -284,11 +291,14 @@ export function startRunReport(options) {
284
291
  spec_gaps: specGapsOf(jobs),
285
292
  turn_incidents: turnIncidentsOf(),
286
293
  ...(exitParts ?? {}),
287
- logs: {
288
- jobs_dir: fromProject(join(projectRoot, ".turri", "jobs")),
289
- debug_dir: fromProject(join(projectRoot, ".turri", "debug")),
290
- sessions_dir: fromProject(join(projectRoot, ".turri", "sessions")),
291
- },
294
+ // Kept only in debug mode: otherwise each pulse's go as it ends, and the sessions are never written.
295
+ logs: debugMode(projectRoot)
296
+ ? {
297
+ jobs_dir: fromProject(join(projectRoot, ".turri", "jobs")),
298
+ debug_dir: fromProject(join(projectRoot, ".turri", "debug")),
299
+ sessions_dir: fromProject(join(projectRoot, ".turri", "sessions")),
300
+ }
301
+ : null,
292
302
  };
293
303
  };
294
304
  const write = (exitCode) => {
@@ -20,6 +20,6 @@ export declare function selfInvocation(command: string): string | undefined;
20
20
  */
21
21
  export declare function askedForCommandLine(message: string): boolean;
22
22
  /** What the agent is told when the call is refused. */
23
- export declare function refusal(what: string): string;
23
+ export declare function refusal(what: string, surface?: string): string;
24
24
  /** The instruction that goes into the conversation's system prompt. */
25
25
  export declare const TOOLS_NOT_CLI: string;
@@ -88,12 +88,13 @@ export function askedForCommandLine(message) {
88
88
  /(^|\s)pi\s+-p\b/.test(text));
89
89
  }
90
90
  /** What the agent is told when the call is refused. */
91
- export function refusal(what) {
91
+ export function refusal(what, surface) {
92
92
  const instead = what === "pi"
93
93
  ? "Do not start another agent: do the work here, with this conversation's tools."
94
94
  : `Use ${TOOL_FOR[what] ?? "the matching turri tool"} instead.`;
95
95
  return (`Not run: that starts turri${what === "pi" ? "'s agent" : ` ${what}`} again from the shell - a separate, headless ` +
96
- "run outside this conversation, its jobs, the panel and /halt. " +
96
+ // In a host there is no panel: the person watches the pulses where its surface says.
97
+ (surface !== undefined ? "run outside this conversation and its pulses, where the person can neither watch it nor halt it. " : "run outside this conversation, its jobs, the panel and /halt. ") +
97
98
  `${instead} Run turri from the command line only when the user asks for that.`);
98
99
  }
99
100
  /** The instruction that goes into the conversation's system prompt. */
@@ -6,7 +6,7 @@
6
6
  * brood ✓ intent ━━━━ ⠋ stack ──── ○ testing ──── ○ product phase 2 of 4
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 - `groundwork` - says little to someone
9
+ * The second line says what the phase it is in does: a phase's name alone - `skeleton` - 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
@@ -85,6 +85,11 @@ export interface Skill {
85
85
  * last answer is written with `write_spec_diff`, and changes the agent decides itself need no answer before them.
86
86
  */
87
87
  diffPerAnswer: string[];
88
+ /**
89
+ * Started only by the person, with its slash command - `/brood` - never by the model on its own
90
+ * (`metadata.command-only: true`): cultivate runs it only when the person has just typed its command.
91
+ */
92
+ commandOnly: boolean;
88
93
  }
89
94
  export declare function loadSkill(skillDir: string): Skill;
90
95
  export declare function readRubric(skill: Skill, review: {
package/dist/skill.js CHANGED
@@ -131,6 +131,7 @@ export function loadSkill(skillDir) {
131
131
  paced: data.metadata?.paced !== false,
132
132
  questioning: capped,
133
133
  diffPerAnswer,
134
+ commandOnly: data.metadata?.["command-only"] === true,
134
135
  };
135
136
  }
136
137
  export function readRubric(skill, review) {
package/dist/skills.d.ts CHANGED
@@ -28,3 +28,17 @@ export declare function discoverSkills(packageRoot: string, projectDir: string |
28
28
  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
+ /**
32
+ * Which skills cultivate may start now. A skill marked command-only (`/brood`) waits for the person to type its command:
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
+ */
35
+ export declare function createCommandGate(): {
36
+ /** The person typed the skill's command. */
37
+ typed(name: string): void;
38
+ /** Whether cultivate may start it now. */
39
+ mayStart(skill: Pick<FoundSkill, "name" | "commandOnly">): boolean;
40
+ /** Its run started: the command is used up. */
41
+ started(name: string): void;
42
+ };
43
+ /** What cultivate says of a command-only skill the person did not start. */
44
+ export declare function startedByCommandOnly(name: string): string;
package/dist/skills.js CHANGED
@@ -61,7 +61,9 @@ export function discoverSkills(packageRoot, projectDir) {
61
61
  /** A skill's line in cultivate's list: a project's says so, and where it is. */
62
62
  export function catalogueLine(skill) {
63
63
  const whose = skill.origin === "turri" ? "" : ` (this project's, ${skill.origin.project})`;
64
- return `- ${skill.name}${whose}: ${skill.description}`;
64
+ // One the person starts themselves: the model points them to its command rather than running it.
65
+ const byCommand = skill.commandOnly ? ` Started only by the person, with /${skill.name}: never run it unasked - when it fits, tell them to type /${skill.name}.` : "";
66
+ return `- ${skill.name}${whose}: ${skill.description}${byCommand}`;
65
67
  }
66
68
  /** What is said under the list about the skills it leaves out. */
67
69
  export function leftOutNote(catalogue) {
@@ -72,3 +74,19 @@ export function leftOutNote(catalogue) {
72
74
  .map((line) => `\n\n${line}`)
73
75
  .join("");
74
76
  }
77
+ /**
78
+ * Which skills cultivate may start now. A skill marked command-only (`/brood`) waits for the person to type its command:
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
+ */
81
+ export function createCommandGate() {
82
+ const typed = new Set();
83
+ return {
84
+ typed: (name) => void typed.add(name),
85
+ mayStart: (skill) => !skill.commandOnly || typed.has(skill.name),
86
+ started: (name) => void typed.delete(name),
87
+ };
88
+ }
89
+ /** What cultivate says of a command-only skill the person did not start. */
90
+ export function startedByCommandOnly(name) {
91
+ return `Not started: ${name} is started only by the person, with /${name}. If it fits what they want, tell them to type /${name} - with what they want after it, if they like.`;
92
+ }
@@ -0,0 +1,5 @@
1
+ import type { JobRegistry } from "./regenerate/jobs.ts";
2
+ /** The host's surface for the conversation these jobs are: blank or undefined is the terminal. */
3
+ export declare function setSurface(jobs: JobRegistry, surface: string | undefined): void;
4
+ /** The host's surface, in its own words - undefined in the terminal. */
5
+ export declare function surfaceOf(jobs: JobRegistry | undefined): string | undefined;
@@ -0,0 +1,18 @@
1
+ /**
2
+ * Where the person sees turri's work: the terminal - regen, its keys, /pulses - unless a host that runs the conversation
3
+ * itself describes its own (TurriHost.surface). Kept with the conversation's job registry, so what is told the model
4
+ * deep in a tool - a pulse not found - says the host's words and not the terminal's.
5
+ */
6
+ const surfaces = new WeakMap();
7
+ /** The host's surface for the conversation these jobs are: blank or undefined is the terminal. */
8
+ export function setSurface(jobs, surface) {
9
+ const text = surface?.trim() ?? "";
10
+ if (text !== "")
11
+ surfaces.set(jobs, text);
12
+ else
13
+ surfaces.delete(jobs);
14
+ }
15
+ /** The host's surface, in its own words - undefined in the terminal. */
16
+ export function surfaceOf(jobs) {
17
+ return jobs !== undefined ? surfaces.get(jobs) : undefined;
18
+ }
@@ -122,12 +122,12 @@ The ones that shape a spec most:
122
122
 
123
123
  ## Writing specs with turri
124
124
 
125
- In a conversation (`turri`), say what you want and the matching skill runs. Two build a project from scratch, and write each spec change as a git diff that is built in the background the moment it is written. `brood` interviews you one question at a time and writes the specs as you answer; `spawn`, its quick twin, asks only what you want to build and which stack - or leaves the stack to it - and writes the rest on its own:
125
+ In a conversation (`turri`), say what you want and the matching skill runs - but the three that start a project or a feature you start yourself, by their commands: `/brood`, `/spawn` or `/bud`, with what you want after it. Two build a project from scratch, and write each spec change as a git diff that is built in the background the moment it is written. `brood` interviews you one question at a time and writes the specs as you answer; `spawn`, its quick twin, gets something running as soon as it can - the smallest thing that runs first, then the product a slice at a time - asking only what your message leaves open:
126
126
 
127
127
  | skill | phases | how a spec change is written |
128
128
  | --- | --- | --- |
129
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 spec change of its own; every product answer is still a spec change, built before the next question; in testing, whether there are conformance tests, their `***test reqs***` and script - written as files, building nothing; then one spec change per answer, across whatever sections it needs, as a git diff with `write_spec_diff`, rendered as a render spec diff job and conformance tested as it is built |
130
- | `spawn` | intent → groundwork → product | only the intent and the stack are asked - the stack one question, `You choose` first, skipped when the user named one; the stack and the unit tests are written as files, with conformance tests off unless the user asks for them; then one spec change per feature, in build order, as a git diff with `write_spec_diff`, one after another with no answer between them. Changes after it are asked for in the conversation, as spec diffs, and what is left to build is worked out again from the spec |
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 only when the user named one this machine cannot build - otherwise turri chooses one; the stack and the unit tests are written as files, with conformance tests off unless the user asks for them; then a spec 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 `write_spec_diff`, one after another with no answer between them. The rest - the next slices, then validation and edge cases as changes to those specs - is asked for in the conversation once the user has tried what landed: `go on` for the next slice it proposed |
131
131
 
132
132
  `mature` starts from a spec you already have, rough or hand-written, and runs on its own until it is fully rendered - every functionality built from the spec as it stands, its tests passing. It renders what is not built and watches the agents building it; when one struggles on something the spec leaves open or unclear - sent back again and again, the judge going back and forth, a spec conflict - it writes what the spec should say as a spec diff, stops that build, and renders it again. What the builds had to decide themselves - their assumptions, pitfalls, a failed build's diagnosis - goes into the spec 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 spec, 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 the spec was clarified, when two cycles change nothing, or after twenty.
133
133
 
package/docs/config.md CHANGED
@@ -42,11 +42,12 @@ Each change says when it takes effect. None needs turri started again - what tur
42
42
  | --- | --- |
43
43
  | now | `harness-model` and `models.chat` - the conversation switches to it; `parallel-jobs` - a pulse waiting for a slot starts if there is one now; `allow-to-stop-jobs` |
44
44
  | your next message | `rules-off` - for the conversation; a skill's agents from the next skill run |
45
- | the next pulse - one running keeps what it started with | `coding-model`, `judge-model`, every other `models.<role>`, `review`, `mutation-testing`, `turn-limit`, `compact-at`, `agent-env`, `toolchains` |
45
+ | the next pulse - one running keeps what it started with | `coding-model`, `judge-model`, every other `models.<role>`, `review`, `mutation-testing`, `turn-limit`, `compact-at`, `agent-env`, `toolchains`, `debug` |
46
46
  | the next time the work is planned | `conformance-tests`, `conformance-budget`, `max-specs-per-pulse`, `max-lines-per-pulse` |
47
47
  | the next test run - the running pulse's too | the testing scripts, `testing-scripts-run-on` |
48
48
  | the next pulse that lands - the running one too | `release-after-pulse` |
49
49
  | the next regeneration that names no `.plain` file | `module` |
50
+ | the next time turri starts here | `ledger-keep`, `cache-keep` |
50
51
 
51
52
  A coding model chosen this session - `--coding-model`, or `engine` - gives way to one set here: the last word wins. It says, too, what wins over the change: `models.chat` over `harness-model` (and so on), and a stack's `config.turri.<stack>.yaml`, which is not changed here, while that stack is chosen. In a project still read from `config.yaml`, the first change copies it to `config.turri.yaml` first - see [the old config.yaml](#the-old-configyaml).
52
53
 
@@ -82,7 +83,7 @@ models: # each agent role's model, thinking and tur
82
83
  | `judge-model` | the judge and the test review of a conformance run | `coding-model` |
83
84
  | `conformance-tests` | `all` (or `true`): conformance tests for every functional spec of every module with test reqs, written once each module's code is built, before anything is built on it. `chosen`: for the functional specs chosen - every one with acceptance tests, and those the chooser picks as each code pulse ends, from what that pulse regenerated. `module`: the same, but the chooser is asked once, when the whole module is regenerated, and plans the conformance pulses too. Under both, the choice is made inside the pulse, before anything is built on the module, and the pulse's report says what was chosen and why; those the person asks for with `conform` are written too; once chosen, a functional spec keeps them, and they are rewritten when it changes - see [who chooses, and when](regenerating.md#conformance-tests-in-the-work-loop). `false`: none written, and none of the existing ones run - until the conversation asks with `conform` | `false` |
84
85
  | `conformance-budget` | under `conformance-tests: chosen` or `module`: how many of a module's functional specs may be chosen for conformance tests, beside those with acceptance tests - a count (`2`) or a share of its functional specs (`30%`, rounded up, at least one). Every choice for the module counts, whenever it was made; the chooser is told what is left, and can choose no more. Your own `conform` is not limited. A stack's `config.turri.<stack>.yaml` can set its own | no limit |
85
- | `review` | `true` or `false`: whether the code and its unit tests - and the conformance tests - are reviewed once they pass: two reviews at most, the second weighed by the arbiter; off, a job lands once its tests pass - see [regenerating the code](regenerating.md#the-code-and-its-unit-tests-are-reviewed) | `false` |
86
+ | `review` | `true` or `false`: whether the code and its unit tests - and the conformance tests - are reviewed once they pass: two reviews at most, the second weighed by the arbiter; off, a job lands once its tests pass. A module's first pulse of one functional spec - the project set up and the smallest thing that runs - lands on its unit tests with no review - see [regenerating the code](regenerating.md#the-code-and-its-unit-tests-are-reviewed) | `false` |
86
87
  | `mutation-testing` | `true` or `false`: whether the coding agent and the reviewer have `can_it_fail` - one change put in the code for one run of the unit testing script, to see that a test catches it; on a copy of the code, or in place and put back after the run (see [regenerating the code](regenerating.md#checking-its-own-work)). `turri regenerate --mutation-testing false` for one run | `true` |
87
88
  | `rules-off` | a list of rule names - `integrations`, `concise-specs`, the files of `genome/rules/` without `.md` - left out of the conversation's prompt and every spec-writing agent's: a rule this project's own skills or AGENTS.md replace, such as one that says to probe a live API before any spec is written. The project's alone | none |
88
89
  | `allow-to-stop-jobs` | `true` or `false`: whether turri stops a running job the spec has moved on from - a functional spec it regenerates changed, or a file one links was edited - rolls it back and regenerates the spec as it now is. `false`: the job runs to its end and lands, and what changed since is regenerated on top of it by the next job - see [regenerating the code](regenerating.md). Your own `halt` stops a job either way | `true` |
@@ -91,6 +92,9 @@ models: # each agent role's model, thinking and tur
91
92
  | `conformance-tests-script` | the conformance testing script, by path from the project root | looked for - see below |
92
93
  | `prepare-environment-script` | optional: run as `<script> <build>` before every run of the conformance testing script, which runs only once it passes - see [the testing scripts](regenerating.md#the-testing-scripts). turri never writes one | the one `prepare_environment*.sh` at the root or in `test_scripts/`, else none |
93
94
  | `compact-at` | how many tokens of work - past its instructions - an agent kept through a job holds before the harness compacts it between turns; at least 1000; the project's alone | 100,000 - see [agent sessions](regenerating.md#agent-sessions) |
95
+ | `ledger-keep` | how many megabytes of the ledger - what turri did, as facts, in `.turri/ledger` - the project keeps: past it, the oldest runs' are pruned when a run first writes; `0` keeps none, and writes none; the project's alone. See [the ledger](using.md#the-ledger) | 100 |
96
+ | `debug` | `true` or `false`: whether turri keeps what it writes only for a person debugging it - every agent's whole log and saved session, each pulse's log, the whole outputs of its test runs and services, its agents' temporary files, and the dependencies installed in the modules a chain has left behind. `false`: only what turri reads to work is kept. `TURRI_DEBUG=1` - or `turri --debug` - turns it on for one run, and `TURRI_DEBUG=0` off; the project's alone. See [debug mode](#debug-mode) | `false` |
97
+ | `cache-keep` | how many megabytes of package caches the project keeps - `plain_modules/.turri/cache`, and the caches in the agents' home folders: past it, the least recently written - by the newest file in each - are removed when a run starts its first agent command, each downloaded again when it is wanted; `0` keeps none; the project's alone | 4096 |
94
98
  | `max-specs-per-pulse` | the most functional specs the planner groups into one pulse - what builds one feature goes together, up to this many; a stack's file wins | 3 |
95
99
  | `max-lines-per-pulse` | the most lines of spec in one pulse - its functional specs with the definitions they bring; a group past it is split again, a functional spec a pulse; a stack's file wins | 120 |
96
100
  | `parallel-jobs` | how many jobs the chat runs at once - modules that share nothing build side by side up to it; read when the chat starts, from the folder it starts in | half this machine's cores, between one and four - see [how many jobs at once](#how-many-jobs-at-once) |
@@ -178,6 +182,23 @@ The optional prepare script - `prepare-environment-script:`, else the one `prepa
178
182
 
179
183
  A folder holding two of a kind is not guessed between: name the one to use. A script named inside `plain_modules/` is refused - agents write there, so it could be changed by the work it judges. The scripts are never linked from the specs. What they are and how they run is in [regenerating the code](regenerating.md#the-testing-scripts).
180
184
 
185
+ ## Debug mode
186
+
187
+ turri writes much more than it needs to work, for a person working out why it did what it did - each agent's whole conversation, each pulse's whole transcript, every test run's whole output. Most of it nothing of turri ever reads again. So by default it is not kept:
188
+
189
+ | | outside debug mode | in debug mode |
190
+ | --- | --- | --- |
191
+ | each pulse's log (`.turri/jobs/`) and its agents' (`.turri/debug/`) | written while the pulse runs - the diagnosis of a failure reads them - and removed as it ends | kept |
192
+ | the logs of agents outside a pulse - the release agent, a side question | not written | kept |
193
+ | the agents' saved sessions (`.turri/sessions/`) | not written - each is kept in memory while it works; a gated skill's alone is saved, as its run resumes from it | kept |
194
+ | whole outputs of test runs and services (`plain_modules/.turri/logs/`, `services/`) | read by the pulse's agents while it runs, removed as it ends | kept, the last 100 outputs |
195
+ | a module's `TMPDIR` (`plain_modules/.turri/tmp/<module>`) | emptied as each of its pulses ends | kept |
196
+ | dependencies installed in the modules below a pulse's own in its chain (`node_modules`, `.venv`) | removed as the pulse ends: the chain is built in its tip, and a module built on again installs them again, from the cache | kept |
197
+
198
+ Kept either way: the ledger - what each pulse did, how it ended, what it cost (see [the ledger](using.md#the-ledger)) - the code, its tests and their history, a gated skill's run, what a conformance explorer found, what `deploy` started, and the package caches, held to `cache-keep:`. The testing scripts install what they run with themselves, so nothing removed is ever missed by a test run.
199
+
200
+ Turn it on in `config.turri.yaml` (`debug: true`, or `/turri-config debug true`), or for one run with `TURRI_DEBUG=1` or `turri --debug`.
201
+
181
202
  ## Credentials for the agents
182
203
 
183
204
  An agent's commands do not get the environment turri was started in: it holds your providers' API keys, cloud credentials and tokens. They get only the path, the locale, the terminal and the user, the variables that locate your toolchains (`JAVA_HOME`, `NVM_DIR`, `RUSTUP_HOME`, ... - the ones in your home found even when your shell sets none), and turri's own: a home folder of the module's own (`plain_modules/.turri/home/<module>`, kept between pulses, with a git identity of its own), a `TMPDIR`, the package caches in `plain_modules/.turri/cache`, and git stopped at the module folder, so a project that is itself a repository is never found from the code.
@@ -229,7 +250,7 @@ How a stack's config is made:
229
250
  - **Laid over `config.turri.yaml`, key by key.** What the stack's file gives wins; what it leaves out is the project's. A model it names takes what it leaves out from the project's same model - above, with the stack frontend chosen, everything renders on the project's coding model, thinking `high`.
230
251
  - **Pinned to a module when it is asked for.** A module is built with the stack it was asked for with, from its first pulse to its last: the pulses planned after it, its test runs, the scripts found, its plans and its conformance tests, whatever stack is chosen meanwhile. A pulse waiting for a free slot keeps the stack it was queued with. A change to its spec keeps its stack - unless it was built with none and has a stack of its own now; asked for again, its stack is worked out again. What `regenerate` says back names each module's stack, and so does a pulse's report.
231
252
  - **Refused with none, when that cannot build.** In a project of stacks, a module asked for with no stack - none named, none named after it, none chosen - is refused before anything is planned when `config.turri.yaml` alone names no unit testing script: every pulse would stop for want of one. The refusal names the stacks and the script each names. A spec change `write_spec_diff` writes is never refused: it is written, and said not to be built yet - passed the stack, everything written is built. `stack: none` builds with `config.turri.yaml` alone all the same.
232
- - **The project's alone.** `module:`, `harness-model:`, `parallel-jobs:`, `rules-off:` and `compact-at:` are read only from `config.turri.yaml` - there is one conversation, one module built when none is named, one machine. `agent-env:` and `toolchains:` in a stack's file add to the project's.
253
+ - **The project's alone.** `module:`, `harness-model:`, `parallel-jobs:`, `rules-off:`, `compact-at:`, `ledger-keep:`, `debug:` and `cache-keep:` are read only from `config.turri.yaml` - there is one conversation, one module built when none is named, one machine. `agent-env:` and `toolchains:` in a stack's file add to the project's.
233
254
 
234
255
  The chat is told each stack's config and the one chosen. The skills that write testing scripts put each stack's in its own file once a project has two stacks, each stack named after the module it builds.
235
256
 
@@ -56,7 +56,9 @@ The workflow signs in to npm with npm's trusted publishing (OIDC), not a token:
56
56
  | `src/regenerate/side-panel.ts`, `src/regenerate/panel-dock.ts` | the side panel, and the chat narrowed beside it when it is docked |
57
57
  | `src/screen-padding.ts` | the blank row above and below the whole screen and the two columns either side, in both of pi's ways of drawing it |
58
58
  | `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 |
59
- | `src/regenerate/debug-log.ts` | each render agent's whole conversation, always streamed to `.turri/debug/` |
59
+ | `src/regenerate/debug-log.ts` | each render agent's whole conversation, streamed to `.turri/debug/` - while its pulse runs, and kept in debug mode |
60
+ | `src/regenerate/pulse-files.ts` | what a pulse writes only for a person - its logs, outputs, temporary files, its chain's installed dependencies - removed as it ends outside debug mode |
61
+ | `src/regenerate/ledger.ts`, `src/regenerate/ledger-view.ts`, `src/ledger-tools.ts`, `src/ledger-cli.ts` | the ledger: what turri did, as typed facts, in `.turri/ledger/` - `record()`, read back for the `ledger` tool, `pulses`, `diagnose` and `turri ledger` - see [the ledger](using.md#the-ledger) |
60
62
  | `genome/` | what turri knows and can do: `genome/skills/`, the skill library, and `genome/rules/`, the \*\*\*plain authoring rules |
61
63
 
62
64
  ## Known edges
package/docs/headless.md CHANGED
@@ -84,8 +84,9 @@ The command exits `0` only when every module is complete: its code regenerated f
84
84
  | `plain_modules/<module>/code/` | the code and unit tests |
85
85
  | `plain_modules/<module>/tests/` | the conformance tests |
86
86
  | `plain_modules/<module>/code/.turri.json` | the marshalled plain tree the code is built from |
87
- | `.turri/jobs/` | each pulse's log: its agents, its test runs, how it ended |
88
- | `.turri/debug/` | each agent's whole conversation |
87
+ | `.turri/ledger/` | what each pulse did, how it ended and what it cost - `turri ledger` |
88
+ | `.turri/jobs/` | each pulse's log: its agents, its test runs, how it ended - with `--debug` (or `debug: true`) only |
89
+ | `.turri/debug/` | each agent's whole conversation - with `--debug` only |
89
90
 
90
91
  And what happened, step by step, in the module's history:
91
92
 
@@ -134,13 +135,15 @@ A module that `requires` others is built in its own codebase on top of theirs: t
134
135
  "by_pulse_kind": { "code": { }, "conformance": { } }
135
136
  },
136
137
  "pulses": {
138
+ "first_started_seconds": 41.2, // the time to first regeneration: the first code pulse started - null before
139
+ "first_landed_seconds": 233.9, // the first code pulse landed: something to try - null before
137
140
  "counts": { "total": 7, "landed": 5, "failed": 1, "stopped": 0, "rolled_back": 2, "running": 0, "not_started": 0 },
138
141
  "list": [{
139
142
  "id": "2a058729", "module": "app", "number": 1, "kind": "code", "title": "…",
140
143
  "status": "succeeded", "landed": true, "failed_for_good": false,
141
144
  "started_at": "…", "duration_seconds": 512.3, "usage": { },
142
145
  "agents": [{ "label": "app 1 coding agent", "model": "…", "turns": 14, "compactions": 0, "input_tokens": 0, "…": 0 }],
143
- "log_file": ".turri/jobs/….log", "error": null
146
+ "log_file": ".turri/jobs/….log", "error": null // log_file null outside debug mode
144
147
  }]
145
148
  },
146
149
  "functionalities": {
@@ -162,7 +165,7 @@ A module that `requires` others is built in its own codebase on top of theirs: t
162
165
  { "kind": "stopped", "pulse_id": "5c1e0b2f", "role": "conformance explorer", "model": "…", "reason": "10 submit_exploration calls failed in a row" }
163
166
  ],
164
167
  "build_dir_export": { "exported": true, "dir": "/work/out", "module": "app", "commit": "8036b3b", "files": 23 },
165
- "logs": { "jobs_dir": ".turri/jobs", "debug_dir": ".turri/debug", "sessions_dir": ".turri/sessions" }
168
+ "logs": { "jobs_dir": ".turri/jobs", "debug_dir": ".turri/debug", "sessions_dir": ".turri/sessions" } // null outside debug mode
166
169
  }
167
170
  ```
168
171
 
@@ -170,6 +173,8 @@ A module that `requires` others is built in its own codebase on top of theirs: t
170
173
 
171
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.
172
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, `spawn` and the other skills that write spec diffs say the same in their result, counted from when the skill began.
177
+
173
178
  **Spec gaps.** A behaviour the spec leaves open that an agent met and decided on its own - reported with `report_spec_gap` (see [regenerating](regenerating.md)). In the chat they are put to you as the pulse ends; a headless run has no one to ask, so they are listed here, in the order the pulses started, and printed under the pulse that met them. `assumption` is what the agent decided meanwhile, also in the codebase's notes' assumptions while its pulse is `landed`; a pulse rolled back takes it out of the notes, but the question still stands. Settle the ones you care about in the spec and run again.
174
179
 
175
180
  **Turn incidents.** Each time the model provider failed for a moment and pi or turri waited it out before trying the request again, each time the provider sent nothing back on a request for two minutes, and each turn turri stopped itself - an agent going round in a circle, its calls failing again and again, its time up - after which the run carried on. The provider failing or stalling is printed as it happens, in orange, under the pulse; a stop is the work's normal course and is only listed. They are counted in the usage as `provider_retries`, `provider_stalls` and `turns_stopped`. Many of any point at the model, not the spec: a provider at its limits, or a model too small for the role it was given.
package/docs/hosting.md CHANGED
@@ -11,11 +11,13 @@
11
11
  | `createTurriExtension(host)` | turri's conversation as an extension of pi's: every tool, slash command, rule and skill the terminal has. The host is told of its jobs and asks its questions - see below. |
12
12
  | `pi` | pi itself, as turri's extension is built against it. Make the session with this one - `pi.createAgentSession`, `pi.DefaultResourceLoader` - not another copy. |
13
13
  | `readProjectConfig`, `projectModels`, `modelSpec`, `configForSpec`, `hasProjectConfig`, `stacksOf` | the project's settings, as [config.turri.yaml](config.md) has them. |
14
+ | `debugMode` | whether turri keeps, in a project, what it writes only for debugging it - `debug:`, or `TURRI_DEBUG` over it (see [debug mode](config.md#debug-mode)). |
14
15
  | `initConfig(dir, { vendor, bedrock, force })`, `VENDORS`, `BEDROCK_REGIONS`, `BEDROCK_DEFAULT`, `bedrockModels` | a project's first config.turri.yaml, as `turri init-config` writes it: the host asks which vendor's models - `VENDORS` has each one's label and its two models, `BEDROCK_REGIONS` where Bedrock serves each maker's - and `initConfig` writes it, saying what it did. One already there keeps everything but its two models. |
15
16
  | `checkPlainFile`, `plainFileParser`, `resolveTemplateDirs`, `iterateFunctionalSpecs`, `FUNCTIONAL_REQUIREMENTS` | a spec checked and parsed, its functional specs one by one - as `turri validate` reads it. |
16
17
  | `highlightLines`, `roleOf` | a spec's lines coloured by turri's own \*\*\*plain grammar. |
17
18
  | `functionalityCompletion`, `createWorkLoop`, `createJobRegistry`, `promptOf`, `describeModules` | how far each functional spec is built, and - on a loop of the host's own with `dryRun` - the work a spec change would plan, as `turri glimpse` does. |
18
19
  | `onModelEvent`, `describeTurnIncident` | a provider failing and waited out, a turn turri ended itself. |
20
+ | `onLedgerEvent`, `readLedger`, `ledgerDir`, `runViews`, `pulseView`, `describePulseView`, `describeRuns`, `ledgerProjects`, `earlierPulses`, `asJob` | what turri did, as facts - the [ledger](using.md#the-ledger): each event heard as it is recorded, and every run and pulse read back, earlier sessions' too - see [the ledger in a host](#the-ledger-in-a-host). |
19
21
  | `sweepOrphanedGroups` | stop what a turri killed outright left running, before anything starts. |
20
22
 
21
23
  It is turri's own code, as the terminal runs it: a 0.x release may change any of it.
@@ -35,6 +37,8 @@ const loader = new pi.DefaultResourceLoader({
35
37
  onJobChange: (job) => {}, // a job started, moved on, did something, or ended
36
38
  onPlanChange: () => {}, // what is planned changed with no job
37
39
  dialogs: { select, input }, // ask_user's questions, put in the host's own dialogs
40
+ surface: SURFACE, // the host's UI, in its own words, told to the model in place of the terminal's
41
+ systemPrompt: (prompt) => prompt, // the last word on the whole system prompt, each turn
38
42
  }),
39
43
  ],
40
44
  });
@@ -45,8 +49,50 @@ await session.bindExtensions({ uiContext, mode: "tui" });
45
49
 
46
50
  - **The jobs.** The pulses the conversation starts - its tools, `/regenerate`, a spec change - run in its own job registry and work loop, the project's one. The host is handed both as they are made, and told of each job as it changes, so it shows the very jobs the conversation runs. Its buttons are best the conversation's commands - `session.prompt("/regenerate app.plain")` - so there is never a second loop on the same project.
47
51
  - **The questions.** `dialogs` is `ask_user`'s two dialogs: `select(title, options, context, at)` and `input(title, context)`, each resolving with the answer, or undefined when the person dismissed it. Given, the conversation's questions and a skill's interview are put in them; without it, in the terminal's own dialog.
52
+ - **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.
53
+ - **The system prompt.** `systemPrompt(prompt)` gets the whole system prompt for the turn - pi'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.
48
54
  - **The mode.** `mode: "tui"` runs the work in the background and keeps the conversation free, as the terminal does; `rpc`, `json` and `print` wait for each job. The extension's other UI calls - its notices, its working line, a confirm - come to the host's `uiContext`, as pi's [SDK](https://github.com/earendil-works/pi) describes it.
49
55
 
56
+ A surface for a desktop app might read:
57
+
58
+ ```js
59
+ const SURFACE = `The person uses turri's desktop app, not a terminal: there are no keys to press. Slash commands are typed in
60
+ the chat, and the toolbar's buttons send them too.
61
+ - The Specs view shows each .plain file, every functional spec marked: a green dot is built, a pulsing ring regenerating,
62
+ a hollow ring planned, grey waiting.
63
+ - The Pulses view lists every pulse - running, planned and finished. A row opens what its agents did and its log; its
64
+ Halt button stops it.
65
+ - Your questions appear as cards above the message box: the person picks an option or types an answer there.
66
+ - The Run button releases what is built and opens it.`;
67
+ ```
68
+
69
+ ## The ledger in a host
70
+
71
+ The conversation's extension starts the ledger itself, in the folder the host made the session in - so a host that runs it there, the project's, needs nothing to have one: its pulses are recorded as `host` runs, and the conversation's `pulses` and `diagnose` already tell the model of earlier sessions' pulses. What the host can do with it is show it.
72
+
73
+ ```js
74
+ import { onLedgerEvent, pulseView, readLedger, runViews } from "@turri-dev/cli/host";
75
+
76
+ // Each event as it is recorded - written or not, so a project with ledger-keep: 0 is heard too.
77
+ const stop = onLedgerEvent((entry, project) => {
78
+ if (entry.kind === "healer.failed") notify(`the ${entry.healer} failed: ${entry.error}`);
79
+ if (entry.kind === "provider.error" && !entry.transient) notify(`the model provider: ${entry.error}`);
80
+ });
81
+
82
+ // The project's runs, the most recent first, each with its pulses - how each ended, how long, what it cost.
83
+ const runs = runViews(projectDir);
84
+
85
+ // One pulse, from this session or an earlier one: its rounds, test runs, verdicts, healers, refusals, history.
86
+ const view = pulseView("646d", readLedger(projectDir, { job: "646d" }));
87
+
88
+ // Earlier sessions' pulses as jobs, to list beside this session's - none this session's registry knows.
89
+ const { jobs, more } = earlierPulses(ledgerProjects(projectDir), new Set(registry.list().map((job) => job.id)));
90
+ ```
91
+
92
+ - **Live.** `onLedgerEvent(listener)` is told every event as turri records it, with the project it is about, until what it returns is called; a listener that throws misses that event and nothing else. It is the pulse's own detail - its stages, test runs, verdicts, refusals - where `onJobChange` is the job as a whole.
93
+ - **Read back.** `readLedger(project, { job, run, kinds, since })` is the project's events, oldest first, the gzipped runs too; `runViews` and `pulseView` gather them into runs and pulses, and `describePulseView` and `describeRuns` say them in lines, as `turri ledger` prints them. `ledgerProjects(dir)` finds the ledgers of a folder and the projects under it. `earlierPulses(projects, known, runs = 3)` gives the last runs before this one as `Job`s - the shape a host already draws for this session's - each marked `earlier: { run, runMode, lines }`, `lines` what the ledger says of it; `more` is how many runs are beyond them. They are read only: never started, halted or waited for. turri's own side panel lists them so, as **earlier sessions**.
94
+ - **What it is for.** Looking up, never deciding: what is built and what is left come from the records and the work loop, as before. Its events and their fields are `LedgerEvent`'s - a kind is added, never changed, and every event carries `v: 1`.
95
+
50
96
  ## Running it
51
97
 
52
98
  The host entry is compiled JavaScript, so any Node from 22.19 runs it - Electron's own included; nothing strips types. It finds turri's own folders - `genome/`, `stdlib/`, `.pi/` - beside `dist/` in the package, wherever npm installed it.
@@ -93,3 +93,4 @@ For Gemini, `GEMINI_API_KEY` is what makes pi *offer* the google provider, but w
93
93
  | --- | --- |
94
94
  | `PLAIN_TEMPLATE_DIRS` | colon-separated template directories, e.g. the standard library |
95
95
  | `TURRI_COMMAND_TIMEOUT` | seconds a command may run before it is stopped - an agent's own, its tests (default `120`) |
96
+ | `TURRI_LEDGER` | `off`: write no [ledger](using.md#the-ledger) at all, whatever `ledger-keep:` says - the test suite sets it |
@@ -33,12 +33,12 @@ The harness's scratch - each ignores itself, and is never part of a module's his
33
33
  | | |
34
34
  | --- | --- |
35
35
  | `plain_modules/.turri/` | what turri keeps beside the code - see [sandbox](sandbox.md#what-turri-keeps-beside-the-code) |
36
- | `plain_modules/.turri/cache/` | the package caches, so a dependency is fetched once |
36
+ | `plain_modules/.turri/cache/` | the package caches, so a dependency is fetched once - held to `cache-keep:` |
37
37
  | `plain_modules/.turri/scripts/` | the copies each testing script run happens in, one folder a run |
38
- | `plain_modules/.turri/logs/` | the whole output of each run or command an agent was shown cut short, by time and what ran - read by agents only through `read_output` |
39
- | `.turri/jobs/` | each job's log: its diff, its stages, its agents and what they cost, and which walls were up under its agents' commands |
40
- | `.turri/debug/` | each agent's log: its prompt, every tool call and every answer |
41
- | `.turri/sessions/` | the agents' saved sessions |
38
+ | `plain_modules/.turri/logs/` | the whole output of each run or command an agent was shown cut short, by time and what ran - read by agents only through `read_output`; removed as the pulse ends, outside [debug mode](config.md#debug-mode) |
39
+ | `.turri/jobs/` | each job's log: its diff, its stages, its agents and what they cost, and which walls were up under its agents' commands - kept in debug mode only |
40
+ | `.turri/debug/` | each agent's log: its prompt, every tool call and every answer - kept in debug mode only |
41
+ | `.turri/sessions/` | the agents' saved sessions - in debug mode, and a gated skill's run's |
42
42
  | `.turri/runs/<skill>/` | a gated skill's run: what an interrupted one resumes from, and its gates' evidence |
43
43
  | `.turri/deploy/` | the output of what `deploy` started |
44
44
  | `dist/<module>/` | what `deploy` puts out: the module's code as its last commit has it, replaced whole each time - see [deploy](deploy.md) |
@@ -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 the spec, 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.
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 the spec, 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 `spawn` starts one: 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 the spec states for this work is implemented as the spec 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 the spec states for this work has a test asserting the exact outcome the spec 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`.
@@ -281,11 +281,11 @@ It is told, too, what to do with each functional spec the job touches, by its ca
281
281
  | fresh | the module requires none and nothing is built | set up the project and its unit tests where the unit testing script runs them, then build | set up `tests/` so the conformance testing script, which brings the tests together with `code/`, runs them, then write |
282
282
  | add | a functional spec is new | build it and write its unit tests | write its tests |
283
283
  | patch | its text changed, or a concept it uses did | change the existing code and tests to match - never build it again from scratch | rewrite its existing tests to match |
284
- | again | `regenerate` named it: it is built, and marked in the code record to be regenerated again | replace its implementation - rewrite it or start it over, doing what the spec says now; no second implementation beside the old, nothing of it left unused; its unit tests may be rewritten | none: its tests stay as they are |
284
+ | again | `regenerate` named it: it is built, and marked in the code record to be regenerated again | replace its implementation - rewrite it or start it over, doing what the spec says now; no second implementation beside the old, nothing of it left unused; its unit tests may be rewritten | its tests were written against the code it replaced: run against the new code, what they check that the spec says stays checked, and what they check that the spec leaves open - a path, a field, a message the old code chose - is rewritten to what the new code does, or taken out |
285
285
  | delete | a functional spec is gone | take out its code and the tests only it had | take out its tests |
286
286
  | mechanical | no functional spec is touched - only reqs, or definitions none uses | adapt the code; write no new test; an existing one may change only mechanically - an import, a fixture, its setup - never what it asserts | the same, for test reqs, definitions or implementation reqs alone |
287
287
 
288
- **Regenerating again.** `regenerate` with functionalities named regenerates them again though their spec did not change - made better, on another model, after many patches. Each is marked in the code record of every codebase it is built in (`regenerate_again`, committed), and nothing else changes: the record now differs from the spec at that item, so the work loop plans it as it plans any change. A functional spec of a module below is regenerated again where its code lives - the codebase at the tip of each chain built on it, never the module's own codebase left behind - marked among that codebase's required functional specs. The planner groups the ones regenerated again as it groups new ones - up to three a job, only those next to each other in one module together - and the job is given the definitions and implementation reqs as every job is; `<spec change>` lists each under "To regenerate again", with its text as it is now. The job that regenerates it records it as the spec has it, without the mark. Its conformance tests compare its text, which did not change: they stay as they are, and are not rewritten.
288
+ **Regenerating again.** `regenerate` with functionalities named regenerates them again though their spec did not change - made better, on another model, after many patches. Each is marked in the code record of every codebase it is built in (`regenerate_again`, committed), and nothing else changes: the record now differs from the spec at that item, so the work loop plans it as it plans any change. A functional spec of a module below is regenerated again where its code lives - the codebase at the tip of each chain built on it, never the module's own codebase left behind - marked among that codebase's required functional specs. The planner groups the ones regenerated again as it groups new ones - up to three a job, only those next to each other in one module together - and the job is given the definitions and implementation reqs as every job is; `<spec change>` lists each under "To regenerate again", with its text as it is now. The job that regenerates it records it as the spec has it, without the mark. Its conformance tests are marked with it, in the tests record of the codebase whose own functional spec it is: they were written against the code being replaced, and may hold the new code to what only the old code chose - `/healthcheck` where the spec says only that there is a health check, and the new code answers `/health`. While the code regenerates they stay as they are; once it lands, the mark is conformance work like any change, and a conformance job writes them again, in the again case above, and runs the whole suite. Until it lands, regen shows its tests as written for an earlier build, and they are not counted as conformance tested.
289
289
 
290
290
  A functional spec is patched when a concept it uses changed as well as when its own text did: the harness follows each functional spec's concepts through the definitions. Fresh is only where nothing exists: a module that requires another starts from what it inherits, and its first job adds or patches.
291
291
 
@@ -411,7 +411,7 @@ The agents that work through a job keep **one session for it**: the coding agent
411
411
 
412
412
  An agent kept through a job re-reads everything it has done on every call. So between turns, once its conversation - what it has done, past its instructions - passes `compact-at` tokens (100,000 unless [the project config](config.md) says otherwise), the harness compacts it, with instructions for its role: the coding agent and the fixer keep what is done, what still fails and why, the decisions and every review problem's fate; a reviewer keeps every problem it raised and where each stands, and what it has already checked. pi's own compaction, when the context window is nearly full, stays on beneath it.
413
413
 
414
- What each agent of a job did - its turns, its compactions, its tokens and cost - is at the end of the job's log in `.turri/jobs/`, and in `pulses`.
414
+ What each agent of a job did - its turns, its compactions, its tokens and cost - is at the end of the job's log in `.turri/jobs/` (kept in [debug mode](config.md#debug-mode)), and in `pulses`.
415
415
 
416
416
  ## Working notes
417
417
 
@@ -462,14 +462,14 @@ Two records per module, each committed with what it describes in the module's `.
462
462
  | `code/.turri.json` | the code record: the spec the code is built from - its whole requires chain's: the definitions and implementation reqs of the modules below, each tagged with its module, their functional specs among the ones it inherits, and its own - without the test reqs and the acceptance tests, which are for conformance only |
463
463
  | `tests/.turri.json` | the tests record: the functional specs the conformance tests are written for - of the code record's, with their acceptance tests - the definitions and implementation reqs they were written against, and the test reqs they follow |
464
464
 
465
- Nothing about how a build went: whether tests passed and which files a job touched are in the history. And nothing else is stored: what is left to build is worked out from the records whenever a job ends or the spec changes - the code work from the code record to the spec now (`src/regenerate/drift.ts`), the conformance work from the tests record to what the tests are to be written for. Only the running job is kept; what the panel shows as planned is worked out again. The tests record never goes ahead of the code's: a conformance test exercises functionality, and only what is in the code can be tested.
465
+ Nothing about how a build went: whether tests passed and which files a job touched are in the history, and how each pulse went is in the [ledger](using.md#the-ledger), which nothing reads to decide what to build. And nothing else is stored: what is left to build is worked out from the records whenever a job ends or the spec changes - the code work from the code record to the spec now (`src/regenerate/drift.ts`), the conformance work from the tests record to what the tests are to be written for. Only the running job is kept; what the panel shows as planned is worked out again. The tests record never goes ahead of the code's: a conformance test exercises functionality, and only what is in the code can be tested.
466
466
 
467
467
  What goes into them:
468
468
 
469
469
  - **A code job landed:** the code record becomes the spec the job ends on.
470
470
  - **A conformance job landed:** the tests record becomes what its tests are written for.
471
471
  - **A module started from another's code:** that code's record, as a lower module's - see [where things go](#where-things-go) - and an empty tests record: the tests it inherits are the module below's.
472
- - **A functional spec named to `regenerate`:** it is marked to be regenerated again in the code record, so the next job regenerates it again; that job's record drops the mark.
472
+ - **A functional spec named to `regenerate`:** it is marked to be regenerated again in the code record, so the next job regenerates it again; that job's record drops the mark. Its conformance tests, when it has them, are marked in the tests record too, and the conformance job that writes them again for the new code drops that mark.
473
473
 
474
474
  A unit of work - a code job, a conformance job - lands whole or is undone whole. It starts from the module's last commit, putting back whatever an interrupted run left; a module with no commit yet is given one first. When it does not pass, the module goes back to that commit: its changes, the files it added and any commit it made on the way are gone, with the record they came with. Installed dependencies and caches stay. So the code, its tests and the records of what they were built from never disagree.
475
475
 
package/docs/sandbox.md CHANGED
@@ -107,12 +107,12 @@ It lives in `plain_modules/.turri`, which ignores itself in git:
107
107
 
108
108
  | | |
109
109
  | --- | --- |
110
- | `cache/` | package caches - pip, npm, cargo, Maven, Gradle, Go - so a dependency downloads once per project |
111
- | `home/<module>` | each module's home folder for its agents' commands, kept between pulses |
112
- | `tmp/<module>` | each module's `TMPDIR` |
110
+ | `cache/` | package caches - pip, npm, cargo, Maven, Gradle, Go, node-gyp's headers, the browsers Playwright, Puppeteer and Cypress download - so a dependency downloads once per project; held to `cache-keep:` megabytes, the least recently written going first |
111
+ | `home/<module>` | each module's home folder for its agents' commands, kept between pulses; the caches its tools keep there count towards `cache-keep:` |
112
+ | `tmp/<module>` | each module's `TMPDIR`, emptied as each of its pulses ends outside [debug mode](config.md#debug-mode) |
113
113
  | `scripts/` | the throwaway copies test runs happen in, emptied after each run |
114
- | `logs/` | whole outputs cut short for an agent, read through `read_output` |
115
- | `services/` | what the services agents started printed |
114
+ | `logs/` | whole outputs cut short for an agent, read through `read_output` - removed as the pulse ends, outside debug mode |
115
+ | `services/` | what the services agents started printed - removed as the pulse ends, outside debug mode |
116
116
 
117
117
  ## What the machine lacks
118
118
 
@@ -37,6 +37,8 @@ metadata:
37
37
 
38
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 - `brood`'s phases end on the person's confirmation alone - 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 - `/brood`, `/spawn` and `/bud` are. 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; the run starting uses that up, so the model cannot start it again later on its own. Without the command, `cultivate` refuses it and the model is told to name the command to the person instead - the skill's line in `cultivate`'s list says so too.
41
+
40
42
  `paced: false` under `metadata:` lets a skill write spec changes one after another on its own: `write_spec_diff` 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. `spawn` is unpaced: the tool tells it so, and after each change to write the next at once, not to carry on with an interview it does not have.
41
43
 
42
44
  `questioning:` under `metadata:` names the steps the questioning level caps (`src/questioning.ts`) - `brood`'s `[stack, product]`, `bud`'s `[product]`. 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,7 +47,7 @@ A skill declares its steps with a `## Steps` section whose `###` headings are th
45
47
 
46
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.
47
49
 
48
- `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 (`groundwork - 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.
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 (`skeleton - sets up the project and writes the smallest spec that runs, so the first build can start`). 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.
49
51
 
50
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`, `bash`, `ls`, `grep`, `find`) for the ones that write specs, resources or scripts, only the reading ones for those that analyse and report, and turri's own (`write_spec_diff` and the ones below) where a skill uses them.
51
53