@turri-dev/cli 0.1.26 → 0.1.27-next.3f1cbf4
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/dist/extension.js +2 -2
- package/dist/host.d.ts +1 -1
- package/dist/regenerate/cases.js +1 -1
- package/dist/regenerate/jobs.d.ts +19 -0
- package/dist/regenerate/jobs.js +2 -0
- package/dist/regenerate/ledger-view.js +10 -2
- package/dist/regenerate/ledger.d.ts +2 -0
- package/dist/regenerate/loop.d.ts +3 -1
- package/dist/regenerate/loop.js +27 -6
- package/docs/config.md +2 -2
- package/docs/hosting.md +2 -2
- package/docs/regenerating.md +5 -5
- package/docs/using.md +4 -4
- package/genome/rules/turri-regeneration.md +2 -1
- package/package.json +1 -1
package/dist/extension.js
CHANGED
|
@@ -1364,14 +1364,14 @@ export function projectConfigBlock(root) {
|
|
|
1364
1364
|
...[
|
|
1365
1365
|
config.module !== undefined && of !== "stack" ? `- module: ${config.module}${where} - the module to build when the user does not name one.` : undefined,
|
|
1366
1366
|
config.conformanceTests === false
|
|
1367
|
-
? "- conformance-tests: false - conformance tests are OFF:
|
|
1367
|
+
? "- conformance-tests: false - conformance tests are OFF: no new ones are written, unless the user asks for them in this conversation. The ones there are are kept up to date: a functional spec that has conformance tests and is regenerated - changed, regenerated again or taken out - gets a conformance pulse for it alone once its code lands. A functional spec with none gets none."
|
|
1368
1368
|
: config.conformanceTests === "chosen"
|
|
1369
1369
|
? "- conformance-tests: chosen - conformance tests are on for the functional specs chosen: those with acceptance tests always, those the chooser picks as each code pulse ends from what it regenerated, and those the user asks for with conform. A conformance pulse starts once the module's code is built, before anything is built on it. Once chosen, a functional spec keeps them."
|
|
1370
1370
|
: config.conformanceTests === "module"
|
|
1371
1371
|
? "- conformance-tests: module - conformance tests are on for the functional specs chosen: those with acceptance tests always, those the chooser picks - and plans into pulses - once the whole module is regenerated, and those the user asks for with conform. They run before anything is built on the module. Once chosen, a functional spec keeps them."
|
|
1372
1372
|
: config.conformanceTests === true
|
|
1373
1373
|
? "- conformance-tests: all - conformance tests are on, for every functional spec of every module that has test reqs."
|
|
1374
|
-
: "- conformance-tests: not set - conformance tests are OFF, as with false:
|
|
1374
|
+
: "- conformance-tests: not set - conformance tests are OFF, as with false: no new ones are written, unless the user asks for them in this conversation; the ones there are are kept up to date - a functional spec that has them and is regenerated gets a conformance pulse for it alone once its code lands.",
|
|
1375
1375
|
config.review === true
|
|
1376
1376
|
? "- review: true - the code and its tests are reviewed once they pass: two reviews at most, the arbiter weighing the second."
|
|
1377
1377
|
: `- review: ${config.review === false ? "false" : "not set, so off"} - the code and its tests are not reviewed: a pulse lands once its tests pass.`,
|
package/dist/host.d.ts
CHANGED
|
@@ -25,7 +25,7 @@ export { conceptBeingTyped, findConcepts } from "./plain/concepts.ts";
|
|
|
25
25
|
export { plainGraph, type PlainConceptNode, type PlainGraph, type PlainItemNode, type PlainLink, type PlainModuleNode, type PlainSection } from "./plain/graph.ts";
|
|
26
26
|
export { isStack, STDLIB_KINDS, stdlibTemplates, type StdlibKind, type StdlibTemplate } from "./stdlib.ts";
|
|
27
27
|
export { createWorkLoop, describeModules, functionalityCompletion, promptOf, type DriftReport, type ModuleStatus, type WorkLoop } from "./regenerate/loop.ts";
|
|
28
|
-
export { createJobRegistry, defaultParallelJobs, type Job, type JobRegistry } from "./regenerate/jobs.ts";
|
|
28
|
+
export { createJobRegistry, defaultParallelJobs, type Job, type JobRegistry, type PulseCases, type PulseSpec } from "./regenerate/jobs.ts";
|
|
29
29
|
export { describeTurnIncident, onModelEvent, type TranscriptEntry } from "./regenerate/activity.ts";
|
|
30
30
|
export { sweepOrphanedGroups } from "./regenerate/shell.ts";
|
|
31
31
|
export { ledgerDir, onLedgerEvent, readLedger, type AgentRef, type LedgerEntry, type LedgerEvent, type LedgerKind, type LedgerQuery } from "./regenerate/ledger.ts";
|
package/dist/regenerate/cases.js
CHANGED
|
@@ -65,7 +65,7 @@ export function conformanceCasesBlock(cases) {
|
|
|
65
65
|
lines.push(`- Changed - its text, its acceptance tests, or a concept it uses: ${named(cases.patch)}.`, " Its conformance tests exist: rewrite them to what it says now. The tests of everything else stay as they are.");
|
|
66
66
|
}
|
|
67
67
|
if (cases.again.length > 0) {
|
|
68
|
-
lines.push(`- Regenerated again - its code was built anew, and its conformance tests were written against the code it replaced: ${named(cases.again)}.`, " Its text did not change, but the code did. Run its tests against the new code.
|
|
68
|
+
lines.push(`- Regenerated again - its code was built anew, and its conformance tests were written against the code it replaced: ${named(cases.again)}.`, " Its text did not change, but the code did. Run its tests against the new code. Those that pass are tests of the", " new code too - conformance tests are black box - and are not rewritten. Fix what fails against the", " specification: what a failing test checks that the specification says stays checked, the test changed only as", " the new code needs it reached; what it checks that the specification leaves open - a path, a field, a message,", " a format the old code chose and the specification never names - is the new code's choice now: rewrite that", " check to what the new code does, or take it out. No test may hold the new code to what only the old code did.", " The tests of everything else stay as they are.");
|
|
69
69
|
}
|
|
70
70
|
if (cases.delete.length > 0)
|
|
71
71
|
lines.push(`- Taken out - remove its conformance tests: ${named(cases.delete)}.`);
|
|
@@ -8,6 +8,21 @@ import { type AgentTally, type BuildingSpec, type SpecConflict, type TranscriptE
|
|
|
8
8
|
* carries on while the agents work.
|
|
9
9
|
*/
|
|
10
10
|
export type JobStatus = "queued" | "running" | "succeeded" | "failed" | "stopped";
|
|
11
|
+
/** A functional spec a pulse covers: the module whose spec it is - the pulse's own, or one below it - and its number there. */
|
|
12
|
+
export interface PulseSpec {
|
|
13
|
+
module: string;
|
|
14
|
+
frid: string;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* The functional specs a pulse covers, by what it does to each (`cases.ts`): new, changed, regenerated again - its text
|
|
18
|
+
* unchanged - or taken out. A pulse of none is mechanical: reqs, or definitions none uses.
|
|
19
|
+
*/
|
|
20
|
+
export interface PulseCases {
|
|
21
|
+
add: PulseSpec[];
|
|
22
|
+
patch: PulseSpec[];
|
|
23
|
+
again: PulseSpec[];
|
|
24
|
+
delete: PulseSpec[];
|
|
25
|
+
}
|
|
11
26
|
export interface Job {
|
|
12
27
|
id: string;
|
|
13
28
|
kind: string;
|
|
@@ -23,6 +38,8 @@ export interface Job {
|
|
|
23
38
|
diff?: string;
|
|
24
39
|
/** Where its diff's lines are in the .plain files - their files and line numbers there - when they could be placed. */
|
|
25
40
|
diffWhere?: import("./diff-view.ts").DiffWhere;
|
|
41
|
+
/** The functional specs it covers, by case: what it does to each. */
|
|
42
|
+
cases?: PulseCases;
|
|
26
43
|
/** For a conformance job: the render job it is the conformance of. */
|
|
27
44
|
follows?: string;
|
|
28
45
|
/** A note on it, for the person: why it did not run, or what became of it. */
|
|
@@ -118,6 +135,8 @@ export interface StartJobInput {
|
|
|
118
135
|
diff?: string;
|
|
119
136
|
/** Where the diff's lines are in the .plain files, when they could be placed. */
|
|
120
137
|
diffWhere?: import("./diff-view.ts").DiffWhere;
|
|
138
|
+
/** The functional specs it covers, by case. */
|
|
139
|
+
cases?: PulseCases;
|
|
121
140
|
/** "provider/model-id" its agents run on, to show where the job is shown. */
|
|
122
141
|
model?: string;
|
|
123
142
|
/** For a conformance job: the render it is the conformance of. */
|
package/dist/regenerate/jobs.js
CHANGED
|
@@ -79,6 +79,7 @@ export function createJobRegistry(options) {
|
|
|
79
79
|
...(input.title !== undefined ? { title: input.title } : {}),
|
|
80
80
|
diff: input.diff,
|
|
81
81
|
...(input.diffWhere !== undefined ? { diffWhere: input.diffWhere } : {}),
|
|
82
|
+
...(input.cases !== undefined ? { cases: input.cases } : {}),
|
|
82
83
|
model: input.model,
|
|
83
84
|
...(stack !== undefined ? { stack } : {}),
|
|
84
85
|
...(chat !== undefined ? { chat } : {}),
|
|
@@ -107,6 +108,7 @@ export function createJobRegistry(options) {
|
|
|
107
108
|
...(input.title !== undefined ? { title: input.title } : {}),
|
|
108
109
|
...(input.model !== undefined ? { model: input.model } : {}),
|
|
109
110
|
...(stack !== undefined ? { stack } : {}),
|
|
111
|
+
...(input.cases !== undefined ? { cases: input.cases } : {}),
|
|
110
112
|
waitingOn: blockers,
|
|
111
113
|
...(input.follows !== undefined ? { follows: input.follows } : {}),
|
|
112
114
|
}, place);
|
|
@@ -133,10 +133,17 @@ function nameOf(view) {
|
|
|
133
133
|
return queued.summary;
|
|
134
134
|
return queued.pulse !== undefined ? `${queued.module} · ${queued.jobKind} pulse ${queued.pulse}` : `${queued.module} ${queued.jobKind}`;
|
|
135
135
|
}
|
|
136
|
+
/**
|
|
137
|
+
* How a pulse ended. One not ended yet is still running, or waiting, when it is this run's - the run that is reading the
|
|
138
|
+
* ledger; another run's never will: that run stopped while it ran.
|
|
139
|
+
*/
|
|
136
140
|
function outcomeOf(view) {
|
|
137
141
|
const finished = view.finished;
|
|
138
|
-
if (finished === undefined)
|
|
142
|
+
if (finished === undefined) {
|
|
143
|
+
if (view.run === ledgerRun())
|
|
144
|
+
return view.started !== undefined ? "still running" : "queued - not started yet";
|
|
139
145
|
return view.started !== undefined ? "never ended - its run stopped while it ran" : "never started";
|
|
146
|
+
}
|
|
140
147
|
const landed = finished.landed === true ? ", landed" : finished.landed === false ? ", did not land" : "";
|
|
141
148
|
const stopped = finished.stoppedBy !== undefined ? ` by ${finished.stoppedBy === "turri" ? "turri" : "the person"}` : "";
|
|
142
149
|
return `${finished.status}${stopped}${landed}, after ${formatElapsed(finished.ms)}`;
|
|
@@ -251,7 +258,7 @@ export function describeRuns(runs) {
|
|
|
251
258
|
const started = view.started;
|
|
252
259
|
const when = started?.ts !== undefined ? started.ts.slice(0, 19).replace("T", " ") : "";
|
|
253
260
|
const version = started?.version !== undefined && started.version !== null ? ` · turri ${started.version}${started.commit !== null && started.commit !== undefined ? ` (${started.commit.slice(0, 7)}${started.dirty === true ? ", changed" : ""})` : ""}` : "";
|
|
254
|
-
const head = `run ${view.run} · ${started?.mode ?? "?"} · ${when}${view.ended !== undefined ? ` · ${formatElapsed(view.ended.ms)}` : " · still running, or killed"}${version}`;
|
|
261
|
+
const head = `run ${view.run} · ${started?.mode ?? "?"} · ${when}${view.ended !== undefined ? ` · ${formatElapsed(view.ended.ms)}` : view.run === ledgerRun() ? " · this run, still running" : " · still running, or killed"}${version}`;
|
|
255
262
|
const pulses = view.pulses.map((pulse) => ` ${pulse.id} ${nameOf(pulse).padEnd(32)} ${outcomeOf(pulse)}${pulse.usage !== undefined && describeUsage(pulse.usage) !== "" ? ` · ${describeUsage(pulse.usage)}` : ""}`);
|
|
256
263
|
return [head, ...(pulses.length > 0 ? pulses : [" no pulses"])].join("\n");
|
|
257
264
|
})
|
|
@@ -288,6 +295,7 @@ export function asJob(view) {
|
|
|
288
295
|
...(view.queued?.pulse !== undefined ? { pulse: view.queued.pulse } : {}),
|
|
289
296
|
...(view.queued?.title !== undefined ? { title: view.queued.title } : {}),
|
|
290
297
|
...(view.queued?.model !== undefined ? { model: view.queued.model } : {}),
|
|
298
|
+
...(view.queued?.cases !== undefined ? { cases: view.queued.cases } : {}),
|
|
291
299
|
// Its ***plain diff, shown a file at a time as this session's pulses' are.
|
|
292
300
|
...(view.diff !== undefined ? { diff: view.diff.diff, ...(view.diff.where !== undefined ? { diffWhere: view.diff.where } : {}) } : {}),
|
|
293
301
|
status,
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { DiffWhere } from "./diff-view.ts";
|
|
2
|
+
import type { PulseCases } from "./jobs.ts";
|
|
2
3
|
import { type UsageReport } from "./activity.ts";
|
|
3
4
|
/**
|
|
4
5
|
* The ledger: what turri does, as typed facts, one JSON line each, written as it happens - to
|
|
@@ -78,6 +79,7 @@ export type LedgerEvent = {
|
|
|
78
79
|
title?: string;
|
|
79
80
|
model?: string;
|
|
80
81
|
stack?: string;
|
|
82
|
+
cases?: PulseCases;
|
|
81
83
|
waitingOn: string[];
|
|
82
84
|
follows?: string;
|
|
83
85
|
} | {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { type ChunkAgents } from "./chunk-plan.ts";
|
|
2
2
|
import { type SpecGap } from "./notes.ts";
|
|
3
|
-
import { type Job, type JobRegistry } from "./jobs.ts";
|
|
3
|
+
import { type Job, type JobRegistry, type PulseCases } from "./jobs.ts";
|
|
4
4
|
import type { Steering } from "./steer.ts";
|
|
5
5
|
import { type ChainEntry } from "./requires.ts";
|
|
6
6
|
import { type SpecSnapshot } from "./regenerate.ts";
|
|
@@ -385,6 +385,8 @@ export declare function isUpToDate(status: ModuleStatus): boolean;
|
|
|
385
385
|
export declare function describeModules(statuses: ModuleStatus[], options?: {
|
|
386
386
|
for?: "chat" | "command line";
|
|
387
387
|
}): string;
|
|
388
|
+
/** The functional specs a pulse covers, by case, each with the module whose spec it is: a host's to light and mark. */
|
|
389
|
+
export declare function pulseCases(cases: JobCases, moduleName: string): PulseCases;
|
|
388
390
|
/**
|
|
389
391
|
* A module's first pulse when it builds one functional spec - the project set up and the smallest thing that runs: it lands on its
|
|
390
392
|
* unit tests alone, with no review. A reviewer, and an arbiter after it, on a scaffold is time spent before anything can
|
package/dist/regenerate/loop.js
CHANGED
|
@@ -236,16 +236,19 @@ export function createWorkLoop(options) {
|
|
|
236
236
|
* Which functional specs the conformance tests are for: every one - unless the config has them for the chosen ones,
|
|
237
237
|
* or the conversation named some over a config that has them off. Then: those named, those with acceptance tests
|
|
238
238
|
* when the config chooses, and those tested already - a choice is kept, and tests are never taken out because
|
|
239
|
-
* other functional specs were named.
|
|
239
|
+
* other functional specs were named. Off in the config and not asked for: those tested already alone - their tests
|
|
240
|
+
* kept up to date, and none written for any other.
|
|
240
241
|
*/
|
|
241
242
|
const chosenOf = (state, spec, tests) => {
|
|
242
243
|
const named = state.conformance?.chosen;
|
|
243
244
|
const choosing = configChosen(tipEntry(state).plainFile);
|
|
245
|
+
const tested = new Set((tests?.[FUNCTIONAL_REQUIREMENTS] ?? []).map((item) => item.frid));
|
|
246
|
+
if (state.conformance === undefined && !configConformance(tipEntry(state).plainFile))
|
|
247
|
+
return (frid) => tested.has(frid);
|
|
244
248
|
if (!choosing && (state.conformance === undefined || named === undefined || configConformance(tipEntry(state).plainFile)))
|
|
245
249
|
return undefined;
|
|
246
250
|
if (choosing && state.conformance !== undefined && named === undefined)
|
|
247
251
|
return undefined;
|
|
248
|
-
const tested = new Set((tests?.[FUNCTIONAL_REQUIREMENTS] ?? []).map((item) => item.frid));
|
|
249
252
|
const specs = spec.tree[FUNCTIONAL_REQUIREMENTS] ?? [];
|
|
250
253
|
const accepted = (frid) => (specs[Number(frid) - 1]?.[ACCEPTANCE_TESTS] ?? []).length > 0;
|
|
251
254
|
return (frid) => tested.has(frid) || named?.has(frid) === true || (choosing && accepted(frid));
|
|
@@ -326,10 +329,21 @@ export function createWorkLoop(options) {
|
|
|
326
329
|
return [];
|
|
327
330
|
});
|
|
328
331
|
};
|
|
329
|
-
/**
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
332
|
+
/**
|
|
333
|
+
* Whether a codebase's conformance tests are written: asked for, or on in the config - with test reqs to write them
|
|
334
|
+
* from, and code to test. Off and not asked for, the tests there are are still kept up to date: wanted when a
|
|
335
|
+
* functional spec that has conformance tests was added, changed, regenerated again or taken out in the code - the
|
|
336
|
+
* work then is for those alone (chosenOf). Work for the test reqs or definitions alone does not start it.
|
|
337
|
+
*/
|
|
338
|
+
const conformanceWanted = (state, spec) => {
|
|
339
|
+
if ((spec.tree[TEST_REQUIREMENTS] ?? []).length === 0 || readRecord(tipEntry(state).buildDir) === null)
|
|
340
|
+
return false;
|
|
341
|
+
if (state.conformance !== undefined || configConformance(tipEntry(state).plainFile))
|
|
342
|
+
return true;
|
|
343
|
+
const tested = testedOf(state);
|
|
344
|
+
const cases = casesOf(tested, workOf("conformance", tested, state, spec), { module: tipEntry(state).moduleName });
|
|
345
|
+
return cases.add.length + cases.patch.length + cases.again.length + cases.delete.length > 0;
|
|
346
|
+
};
|
|
333
347
|
/**
|
|
334
348
|
* The functional specs a conformance job is scheduled for, as the spec has them now - refused when one is not in
|
|
335
349
|
* the spec, or the module has no test reqs to write tests from: no code job would build it, or no test be written.
|
|
@@ -522,6 +536,8 @@ export function createWorkLoop(options) {
|
|
|
522
536
|
diff: shown.diff,
|
|
523
537
|
// Its lines where they are written: the module's .plain file, those it imports, and those of the modules below it.
|
|
524
538
|
diffWhere: shown.where,
|
|
539
|
+
// The functional specs it covers, by case - its own module's, and those of modules below it.
|
|
540
|
+
cases: pulseCases(planned.cases, tip.moduleName),
|
|
525
541
|
...(state.model !== undefined ? { model: state.model } : {}),
|
|
526
542
|
lanes: [...moduleLanes(tip.plainFile)],
|
|
527
543
|
rerun: `turri regenerate ${relative(options.cwd, tip.plainFile) || basename(tip.plainFile)}`,
|
|
@@ -1682,6 +1698,11 @@ const defaultConformanceRunner = async (job, onDelta) => {
|
|
|
1682
1698
|
}
|
|
1683
1699
|
return { landed: result.passed, report: describeSpecDiffConformance(result), ...(result.specGaps !== undefined ? { specGaps: result.specGaps } : {}) };
|
|
1684
1700
|
};
|
|
1701
|
+
/** The functional specs a pulse covers, by case, each with the module whose spec it is: a host's to light and mark. */
|
|
1702
|
+
export function pulseCases(cases, moduleName) {
|
|
1703
|
+
const of = (specs) => specs.map((spec) => ({ module: spec.module ?? moduleName, frid: spec.frid }));
|
|
1704
|
+
return { add: of(cases.add), patch: of(cases.patch), again: of(cases.again), delete: of(cases.delete) };
|
|
1705
|
+
}
|
|
1685
1706
|
/**
|
|
1686
1707
|
* A module's first pulse when it builds one functional spec - the project set up and the smallest thing that runs: it lands on its
|
|
1687
1708
|
* unit tests alone, with no review. A reviewer, and an arbiter after it, on a scaffold is time spent before anything can
|
package/docs/config.md
CHANGED
|
@@ -83,7 +83,7 @@ models: # each agent role's model, thinking and tur
|
|
|
83
83
|
| `harness-model` | the model `turri` starts on - the one you talk to - and, in the conversation, the planner that cuts the work into jobs (on whatever model the chat is on when it plans); under `turri regenerate`, the planner too | [pi](https://pi.dev)'s configured model |
|
|
84
84
|
| `coding-model` | every regeneration: functionalities, \*\*\*plain diffs, conformance tests, repairs, planning \*\*\*plain diffs, the explorer | `harness-model` |
|
|
85
85
|
| `judge-model` | the judge and the test review of a conformance run | `coding-model` |
|
|
86
|
-
| `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`:
|
|
86
|
+
| `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`: no new ones written - until the conversation asks with `conform` - and the existing ones kept up to date: a functional spec that has conformance tests and is regenerated gets a conformance pulse for it alone once its code lands | `false` |
|
|
87
87
|
| `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 |
|
|
88
88
|
| `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` |
|
|
89
89
|
| `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` |
|
|
@@ -170,7 +170,7 @@ 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 and every regeneration follow it. `false`
|
|
173
|
+
`conformance-tests:` is the one switch, and the chat and every regeneration follow it. `false` writes no new conformance tests, and keeps the ones there are up to date: a functional spec that has conformance tests - it is in `tests/.turri.json` - and is added, changed, regenerated again or taken out in the code gets a conformance pulse once its code lands, for it alone, the whole suite run with it. A functional spec with none gets none, though others of its module have them, and a module with none gets no conformance pulse at all. 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
|
|
package/docs/hosting.md
CHANGED
|
@@ -113,7 +113,7 @@ await turri.dispose(); // every chat let go, what activate start
|
|
|
113
113
|
- **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.
|
|
114
114
|
- **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.
|
|
115
115
|
- **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.
|
|
116
|
-
- **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
|
+
- **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. `job.cases` is the functional specs the pulse covers, as planned, by what it does to each - `{ add, patch, again, delete }`, each a list of `{ module, frid }`, `module` the pulse's own or one below it: new, changed, regenerated again with its text unchanged, taken out. A host lights those specs from it, and marks a line of the diff as regenerated again from `again` - never from the diff, where such a line is unchanged. A pulse of none is mechanical - reqs, or definitions none uses; a job that is not a pulse has no `cases`. The ledger's `job.queued` carries them too.
|
|
117
117
|
- **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.
|
|
118
118
|
- **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.
|
|
119
119
|
- **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.
|
|
@@ -192,7 +192,7 @@ const { jobs, more } = earlierPulses(ledgerProjects(projectDir), new Set(turri.j
|
|
|
192
192
|
```
|
|
193
193
|
|
|
194
194
|
- **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.
|
|
195
|
-
- **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, and with its `diff` and `diffWhere` - the \*\*\*plain diff it regenerated, as last steered, and where its lines are written - as a pulse of this session has them (a pulse recorded before `job.diff` was has none); `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**.
|
|
195
|
+
- **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, and with its `cases`, `diff` and `diffWhere` - the \*\*\*plain diff it regenerated, as last steered, and where its lines are written - as a pulse of this session has them (a pulse recorded before `job.diff` was has none); `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**.
|
|
196
196
|
- **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`.
|
|
197
197
|
|
|
198
198
|
## Running it
|
package/docs/regenerating.md
CHANGED
|
@@ -224,7 +224,7 @@ The same `config.turri.yaml` holds the project's other defaults - `module`, `mod
|
|
|
224
224
|
|
|
225
225
|
They are required: a build with no unit testing script, or with conformance on and no conformance testing script, stops before it starts and says which skill adds it.
|
|
226
226
|
|
|
227
|
-
**The prepare script** is optional: turri never writes one, and runs one the project has - `prepare-environment-script:` in the config, else the one `prepare_environment*.sh` at the root, else in `test_scripts/` - on the build, before every run of the conformance testing script: the harness's own, and the agents' `run_conformance_tests`. Both get the same build folder - the code in place, or the same fresh copy - so what it sets up for that build, the conformance testing script finds. With conformance off it
|
|
227
|
+
**The prepare script** is optional: turri never writes one, and runs one the project has - `prepare-environment-script:` in the config, else the one `prepare_environment*.sh` at the root, else in `test_scripts/` - on the build, before every run of the conformance testing script: the harness's own, and the agents' `run_conformance_tests`. Both get the same build folder - the code in place, or the same fresh copy - so what it sets up for that build, the conformance testing script finds. With conformance off it runs only in a conformance pulse that keeps existing tests up to date. Every agent shown the conformance testing script - the explorer, the conformance agent, the judge, the code fixer and the repair agent - is shown it too, before the conformance testing script.
|
|
228
228
|
|
|
229
229
|
**Where they run.** On this machine, as you would run them: unfenced - no sandbox, no command check, no audit - in your own environment with this machine's toolchains, turri's model providers' API keys alone left out. They are yours, and whatever they do is your doing. That is the only way the two scripts are ever run: the harness's own runs, and the agents' `run_unit_tests` and `run_conformance_tests`, which run them exactly as the harness does - through the host runner, in a process group of their own, so the time limit and a stopped job end the script and all it started. A toolchain the script needs and this machine lacks - exit 69, exit 127, or a run that ended on a `command not found` / `failed to run command`, the last line it printed (`missingToolchain`) - is yours to install. Only on that evidence: a `command not found` earlier in the output, the run going on after it and failing for its own reasons, is a warning - a shell profile's, an optional tool probed for - and the failure is the agent's to fix. A missing toolchain is yours to install, never the agent's: the agent is told to report it with `report_environment_error` rather than work around it, and the pulse stops at the harness's first run of either script - unit or conformance - that cannot find it, rolled back, saying what to install (see [when the environment lacks something](#when-the-environment-lacks-something)). The code and the tests an agent wrote run unfenced inside the script too: what an agent runs with its own shell is fenced, what your script runs is not - an accepted gap.
|
|
230
230
|
|
|
@@ -284,11 +284,11 @@ It is told, too, what to do with each functional spec the job touches, by its ca
|
|
|
284
284
|
| 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 |
|
|
285
285
|
| add | a functional spec is new | build it and write its unit tests | write its tests |
|
|
286
286
|
| 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 |
|
|
287
|
-
| again | `regenerate` named it: it is built, and marked in the code record to be regenerated again | take its implementation out and build it anew, doing what \*\*\*plain says now - the old code already doing it is no reason to keep it; no second implementation beside the old, nothing of it left unused; its unit tests written anew. A coding agent that writes nothing is sent back twice to build it anew, and then the pulse does not land | its tests were written against the code it replaced: run against the new code, what
|
|
287
|
+
| again | `regenerate` named it: it is built, and marked in the code record to be regenerated again | take its implementation out and build it anew, doing what \*\*\*plain says now - the old code already doing it is no reason to keep it; no second implementation beside the old, nothing of it left unused; its unit tests written anew. A coding agent that writes nothing is sent back twice to build it anew, and then the pulse does not land | its tests were written against the code it replaced: run against the new code - black box, those that pass are its tests too, and are not rewritten; one that fails is judged against \*\*\*plain like any failure: what it checks that \*\*\*plain says stays checked, and what it checks that \*\*\*plain leaves open - a path, a field, a message the old code chose - is rewritten to what the new code does, or taken out |
|
|
288
288
|
| delete | a functional spec is gone | take out its code and the tests only it had | take out its tests |
|
|
289
289
|
| 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 |
|
|
290
290
|
|
|
291
|
-
**Regenerating again.** `regenerate` with functionalities named regenerates them again though their \*\*\*plain 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 \*\*\*plain 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 \*\*\*plain 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 \*\*\*plain 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
|
|
291
|
+
**Regenerating again.** `regenerate` with functionalities named regenerates them again though their \*\*\*plain 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 \*\*\*plain 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 \*\*\*plain 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 \*\*\*plain 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 - with `conformance-tests: false` too, its tests being there - and a conformance job runs them on the new code, in the again case above, with the whole suite. The mark means its tests have not yet run on its new code: the job drops it once the whole suite passes, and one that does not land keeps it. Until it lands, regen shows its tests as written for an earlier build, and they are not counted as conformance tested.
|
|
292
292
|
|
|
293
293
|
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.
|
|
294
294
|
|
|
@@ -321,7 +321,7 @@ A conformance job is a \*\*\*plain diff's conformance run: the tests of its chan
|
|
|
321
321
|
|
|
322
322
|
**Up the chain.** A module built on another starts from its code and its conformance tests, copied up, and its conformance testing script runs those with its own. So a module waits for the one below it until that one's code is built and its conformance tests - those wanted, chosen or scheduled - are written and pass; only then is its codebase started from there.
|
|
323
323
|
|
|
324
|
-
**Who chooses, and when.** With `conformance-tests: all` every functional spec is tested, and with `false` none is unless the person asks with `conform`, for the session. With `chosen` or `module`, every one with acceptance tests - the author said what to check - and those the chooser picks: an agent on `harness-model` (or `models: chooser`), in the chat and on the command line alike. It reads the module's \*\*\*plain and names those worth black-box tests - what a user or a client relies on, not a detail the unit tests cover - and why.
|
|
324
|
+
**Who chooses, and when.** With `conformance-tests: all` every functional spec is tested, and with `false` none is unless the person asks with `conform`, for the session - but the tests there are are kept up to date: a functional spec in the tests record that a code pulse added, changed, regenerated again or took out gets a conformance pulse once its code lands, for it alone. Work for the test reqs or definitions alone starts none, and a functional spec with no conformance tests never gets them so. A module waits for that pulse below it, and drift shows it, as any conformance work. With `chosen` or `module`, every one with acceptance tests - the author said what to check - and those the chooser picks: an agent on `harness-model` (or `models: chooser`), in the chat and on the command line alike. It reads the module's \*\*\*plain and names those worth black-box tests - what a user or a client relies on, not a detail the unit tests cover - and why.
|
|
325
325
|
|
|
326
326
|
- `chosen`: it is asked as each code pulse ends, as the pulse's last stage, of what that pulse regenerated that no conformance test is for.
|
|
327
327
|
- `module`: it is asked once, when the module's last code pulse lands, of every functional spec of the module that is built and has none - and it plans them too, into groups, each a conformance pulse of its own.
|
|
@@ -473,7 +473,7 @@ What goes into them:
|
|
|
473
473
|
- **A code job landed:** the code record becomes the \*\*\*plain description the job ends on.
|
|
474
474
|
- **A conformance job landed:** the tests record becomes what its tests are written for.
|
|
475
475
|
- **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.
|
|
476
|
-
- **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
|
|
476
|
+
- **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 runs them on the new code drops that mark once the whole suite passes.
|
|
477
477
|
|
|
478
478
|
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.
|
|
479
479
|
|
package/docs/using.md
CHANGED
|
@@ -88,7 +88,7 @@ A regeneration takes minutes, so in the TUI every tool that regenerates starts t
|
|
|
88
88
|
- **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.
|
|
89
89
|
- **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.
|
|
90
90
|
- **A change to a module below** is made in the codebase at the tip of the chain, where its code lives; the codebase below is left as it was, and is brought up to its own \*\*\*plain only when a new module is to start from it. \*\*\*plain moved into a module the code was not built on stops the codebase: no change to its code brings that module in, so it has to be started over - its code and conformance tests taken out, kept in its history, and built again from the bottom of the chain. That is never done unasked: the chat asks you, saying why, and regenerates it from scratch once you agree; on the command line `turri regenerate` asks at the terminal, and `--start-over` agrees in advance.
|
|
91
|
-
- **Conformance tests are a track of their own.** A code pulse lands once its unit tests pass and are reviewed. Then, before the next one builds on it, a conformance pulse writes and runs the tests of what it built - from the test reqs and acceptance tests - with the whole suite the module inherits, and, where one fails, fixes the test or the code, whichever is wrong; what they are written for is recorded in `tests/.turri.json`. With `conformance-tests: false` none is written until you ask - `conform`, for a module or some of its functional specs. A conformance pulse that does not pass is undone whole and halts the conformance tests alone: the code goes on being built.
|
|
91
|
+
- **Conformance tests are a track of their own.** A code pulse lands once its unit tests pass and are reviewed. Then, before the next one builds on it, a conformance pulse writes and runs the tests of what it built - from the test reqs and acceptance tests - with the whole suite the module inherits, and, where one fails, fixes the test or the code, whichever is wrong; what they are written for is recorded in `tests/.turri.json`. With `conformance-tests: false` none is written until you ask - `conform`, for a module or some of its functional specs - but those there are are kept up to date: a functional spec that has conformance tests and is regenerated gets a conformance pulse for it alone. A conformance pulse that does not pass is undone whole and halts the conformance tests alone: the code goes on being built.
|
|
92
92
|
- **A pulse that fails** - its work errors, or it does not pass - is run once more; one stopped on what only you can fix - the machine lacks something, a testing script no code could pass - is not. Failing again, it stops, and so does every pulse queued behind it on the same module, without running - the other modules' pulses go on. The harness then works out why: an agent that can read the project's \*\*\*plain, code, tests, testing scripts and logs, and turri's own source, skills and rules, says what happened, why, whether the fix belongs in \*\*\*plain, the environment, the code or turri itself, and exactly what to do.
|
|
93
93
|
- **When a pulse finishes** the transcript shows its line - `✓`, `✗` or `■`, its module, kind and number, how long it took and what it cost, never what it regenerated - `✓ shorty · conformance pulse 4 finished after 3m 47s · 488.6k tok · $0.02` - and the chat, which gets its whole report, reacts then and there, whatever else is running or queued, without waiting for you to speak: at once when it is idle, else as soon as the reply it is in ends. It does what the project's guidance says to do when a pulse finishes - release what was built, say - or tells you what finished and what it means. Ask to try it ("let me try it") and the chat releases what is built to `dist/<module>` and starts it on your machine, even while other pulses run. See [deploy](deploy.md). The chat reacts to it then and there, whatever else is running or queued, without waiting for you to speak - at once when it is idle, else as soon as the reply it is in ends: it does what the project's guidance says to do when a pulse finishes (release what was built, say), or tells you in a line or two what finished.
|
|
94
94
|
- **When a pulse stops** - a gate still failing after 20 attempts - it says where it stopped, why in plain words, and what to change in \*\*\*plain - which section, and how. Running the same build again picks up from there.
|
|
@@ -97,7 +97,7 @@ In `print`, `json` and `rpc` modes the process ends when the answer is written,
|
|
|
97
97
|
|
|
98
98
|
## The project's config.turri.yaml
|
|
99
99
|
|
|
100
|
-
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
|
|
100
|
+
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 writing new conformance tests unless you ask for them (the ones there are kept up to date), `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).
|
|
101
101
|
|
|
102
102
|
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).
|
|
103
103
|
|
|
@@ -178,7 +178,7 @@ It is in `.turri/ledger/` in the project, a JSON line an event, one file a run -
|
|
|
178
178
|
| | |
|
|
179
179
|
| --- | --- |
|
|
180
180
|
| runs | `run.started` (how it was started, turri's version and commit, the machine), `run.ended` |
|
|
181
|
-
| pulses | `job.queued
|
|
181
|
+
| pulses | `job.queued` (with the functional specs it covers, by case), `job.diff` (its \*\*\*plain diff, whole, and where its lines are written - again each time steering moves it), `job.started`, every `job.stage`, `job.retried`, `job.finished` - its status, whether it landed, how long it ran, what it cost, how it ended |
|
|
182
182
|
| the work loop | `loop.halted`, `loop.stopped` (a pulse \*\*\*plain moved on from), `planner.failed`, `planner.fellBack` |
|
|
183
183
|
| the model | `model.call` (time and tokens), `model.turn`, `model.compaction`, `provider.retry`, `provider.stall`, `provider.error`, `turn.stopped` (a turn turri ended itself, and why) |
|
|
184
184
|
| tools and tests | `tool.call` (the tool, how long, whether it failed - never its arguments or result), `script.run` (the testing script, its exit, how long, whether the time limit stopped it, the failure's fingerprint, and `mutated` for a `can_it_fail` run on code broken on purpose) |
|
|
@@ -191,7 +191,7 @@ It is in `.turri/ledger/` in the project, a JSON line an event, one file a run -
|
|
|
191
191
|
|
|
192
192
|
Never in it: prompts, replies, thinking, tool arguments and results, file contents - the one body it keeps is each pulse's \*\*\*plain diff, so what an earlier session regenerated can be read back. What an event does carry - a stage, an error's first line - is masked as the logs are.
|
|
193
193
|
|
|
194
|
-
`turri ledger` prints the last runs, a line a pulse - runs and pulses newest first, as every list of the ledger's is, the events and a pulse's history and stages too; `turri ledger <pulse>` one pulse - its rounds, its test runs and how many failed the same way, the verdicts, the healers, what turri refused, what its history recorded, its log, and the \*\*\*plain diff it regenerated; `--json` the events themselves, for `jq`. In the chat, `ledger` is the way in - a pulse of this session or an earlier one, the events of some kinds counted (how often the debugger failed this week: `kinds: [healer.failed], since: 7d`), the last runs - and `pulses` lists the last three earlier runs under this session's pulses, `pulses` and `diagnose` tell one of them by its id, and `diagnose` adds the ledger's facts to a pulse of this session; the side panel's pulses list has them as **earlier sessions** (see [the side panel](#in-a-conversation)).
|
|
194
|
+
`turri ledger` prints the last runs, a line a pulse - runs and pulses newest first, as every list of the ledger's is, the events and a pulse's history and stages too; `turri ledger <pulse>` one pulse - its rounds, its test runs and how many failed the same way, the verdicts, the healers, what turri refused, what its history recorded, its log, and the \*\*\*plain diff it regenerated; `--json` the events themselves, for `jq`. A pulse not ended is still running when it is of the run reading the ledger, and never ended - its run stopped while it ran - when it is another's. In the chat, `ledger` is the way in - a pulse of this session or an earlier one, the events of some kinds counted (how often the debugger failed this week: `kinds: [healer.failed], since: 7d`), the last runs - and `pulses` lists the last three earlier runs under this session's pulses, `pulses` and `diagnose` tell one of them by its id, and `diagnose` adds the ledger's facts to a pulse of this session; the side panel's pulses list has them as **earlier sessions** (see [the side panel](#in-a-conversation)).
|
|
195
195
|
|
|
196
196
|
It is for looking up, never for deciding: what to build comes from the records alone (see [what the code is built from](regenerating.md#what-the-code-is-built-from)), so deleting `.turri/ledger/` loses nothing but what can be looked up. When a run first writes to the project, the files of runs that have ended are gzipped and the oldest pruned past `ledger-keep:` megabytes - 100 unset, `0` for none at all; a run still going is never touched. `.turri` ignores itself, so the ledger is never committed, and nothing of it is sent anywhere. A ledger that cannot be written is said once and given up on, never failing the work.
|
|
197
197
|
|
|
@@ -26,7 +26,8 @@ The other rules say what a spec means. This one says what turri does with a chan
|
|
|
26
26
|
- A definition changed patches every functional spec that uses its concept, directly or through other definitions. So does a change to a linked resource: the spec or definition linking it is changed though its text is not. A widely used definition is the costly edit - not a spec's position
|
|
27
27
|
- Implementation reqs alone, or definitions no functional spec uses, are a mechanical change: the code adapts, and no test is written or changes what it asserts
|
|
28
28
|
- Test reqs and acceptance tests reach the conformance tests only, never the code
|
|
29
|
-
- A functional spec regenerated again by name has its code taken out and built anew - never left as it was, even where it already does what the spec says; a pulse that changes nothing does not land. Its conformance tests are
|
|
29
|
+
- A functional spec regenerated again by name has its code taken out and built anew - never left as it was, even where it already does what the spec says; a pulse that changes nothing does not land. Its conformance tests, when it has them, are run on its new code once it lands: those that pass are kept as they are; one that fails is judged against the spec - what it checks that the spec leaves open, a path, a field, a message the old code chose, goes by the new code
|
|
30
|
+
- `conformance-tests: false` writes no new conformance tests, but keeps those there are up to date: a functional spec that has conformance tests and is changed, regenerated again or taken out gets a conformance pulse for it alone once its code lands. One with none gets none
|
|
30
31
|
- Definitions moved to a common module and reqs reordered are recorded, not rendered
|
|
31
32
|
- A functional spec written on its happy path and hardened later - its validation, edge cases and errors added to it - is patched in place each time: growing a spec a step at a time costs no more than writing it whole up front
|
|
32
33
|
- A module's first pulse, when it builds one functional spec - the project set up and the smallest thing that runs - lands on its unit tests with no review
|