@turri-dev/cli 0.1.7 → 0.1.8-next.b03162b
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +5 -7
- package/dist/btw.d.ts +2 -0
- package/dist/btw.js +2 -2
- package/dist/cli.js +2 -1
- package/dist/config-edit.d.ts +1 -1
- package/dist/config-edit.js +2 -0
- package/dist/config.d.ts +6 -0
- package/dist/config.js +5 -1
- package/dist/extension.d.ts +11 -0
- package/dist/extension.js +112 -13
- package/dist/host.d.ts +2 -0
- package/dist/host.js +3 -0
- package/dist/identity.d.ts +6 -1
- package/dist/identity.js +33 -7
- package/dist/init-config.d.ts +1 -0
- package/dist/init-config.js +5 -0
- package/dist/ledger-cli.d.ts +1 -0
- package/dist/ledger-cli.js +50 -0
- package/dist/loop-cli.js +4 -0
- package/dist/loop-tools.js +3 -1
- package/dist/pi.js +17 -0
- package/dist/regenerate/activity.d.ts +9 -0
- package/dist/regenerate/activity.js +11 -0
- package/dist/regenerate/agent-shell.js +5 -0
- package/dist/regenerate/cases.js +3 -0
- package/dist/regenerate/chunk-plan.d.ts +0 -6
- package/dist/regenerate/chunk-plan.js +16 -4
- package/dist/regenerate/conformance-agents.js +2 -2
- package/dist/regenerate/conformance.js +6 -3
- package/dist/regenerate/diagnose.js +4 -2
- package/dist/regenerate/environment-error.d.ts +6 -0
- package/dist/regenerate/environment-error.js +19 -1
- package/dist/regenerate/job-log.d.ts +3 -1
- package/dist/regenerate/job-log.js +8 -5
- package/dist/regenerate/jobs-tree.d.ts +7 -3
- package/dist/regenerate/jobs-tree.js +21 -6
- package/dist/regenerate/jobs.d.ts +18 -0
- package/dist/regenerate/jobs.js +49 -3
- package/dist/regenerate/ledger-view.d.ts +73 -0
- package/dist/regenerate/ledger-view.js +286 -0
- package/dist/regenerate/ledger.d.ts +274 -0
- package/dist/regenerate/ledger.js +349 -0
- package/dist/regenerate/lineage.d.ts +9 -1
- package/dist/regenerate/lineage.js +64 -12
- package/dist/regenerate/loop.d.ts +6 -0
- package/dist/regenerate/loop.js +36 -6
- package/dist/regenerate/records.d.ts +5 -3
- package/dist/regenerate/records.js +6 -4
- package/dist/regenerate/regenerate.js +7 -5
- package/dist/regenerate/regression.js +4 -2
- package/dist/regenerate/side-panel.d.ts +2 -0
- package/dist/regenerate/side-panel.js +4 -3
- package/dist/regenerate/spec-map.js +8 -5
- package/dist/regenerate/test-scripts.js +12 -1
- package/dist/regenerate/unit-review.js +3 -0
- package/dist/run-report.d.ts +1 -1
- package/dist/run-report.js +6 -0
- package/dist/self-invocation.d.ts +1 -1
- package/dist/self-invocation.js +3 -2
- package/dist/skill-progress.d.ts +1 -1
- package/dist/skill.d.ts +5 -0
- package/dist/skill.js +1 -0
- package/dist/skills.d.ts +14 -0
- package/dist/skills.js +19 -1
- package/dist/surface.d.ts +5 -0
- package/dist/surface.js +18 -0
- package/docs/authoring-plain.md +2 -2
- package/docs/config.md +4 -2
- package/docs/development.md +1 -0
- package/docs/headless.md +4 -0
- package/docs/hosting.md +45 -0
- package/docs/installation.md +1 -0
- package/docs/regenerating.md +5 -5
- package/docs/skills-and-gates.md +3 -1
- package/docs/using.md +33 -5
- package/genome/rules/func-specs.md +1 -0
- package/genome/rules/turri-regeneration.md +3 -0
- package/genome/skills/add-functional-spec/SKILL.md +12 -3
- package/genome/skills/add-functional-specs/SKILL.md +12 -2
- package/genome/skills/brood/SKILL.md +2 -0
- package/genome/skills/bud/SKILL.md +2 -0
- package/genome/skills/spawn/SKILL.md +47 -35
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,7 +1,5 @@
|
|
|
1
1
|
<p align="center"><img src="docs/images/turri-banner.gif" alt="turri, beside its mark - the first coding agent with the power of regenerative software: npm install -g @turri-dev/cli - and a pixel jellyfish regenerating, from a polyp on the sea floor"></p>
|
|
2
2
|
|
|
3
|
-
<h1 align="center">turri<br>the first coding agent with the power of<br>regenerative software<br> </h1>
|
|
4
|
-
|
|
5
3
|
<p align="center">
|
|
6
4
|
<a href="https://www.npmjs.com/package/@turri-dev/cli"><img src="https://img.shields.io/npm/v/@turri-dev/cli?label=npm" alt="npm version"></a>
|
|
7
5
|
<a href="LICENSE"><img src="https://img.shields.io/npm/l/@turri-dev/cli" alt="MIT licence"></a>
|
|
@@ -49,7 +47,7 @@ turri init-config --vendor anthropic
|
|
|
49
47
|
|
|
50
48
|
It then tells you which API key to set - or to type `/login` once **turri** is running instead. **turri** loads the `config.turri.yaml` in the folder you start it from. Below the two models, the file sets every other setting to its default - change any of them in the file, or from the chat with `/turri-config`. Any other model works too: [models](#models).
|
|
51
49
|
|
|
52
|
-
Once `turri` is running,
|
|
50
|
+
Once `turri` is running, type: `/spawn a small API where people can register and sign in` - or `/brood` for an interview that settles each feature with you first.
|
|
53
51
|
|
|
54
52
|
No project of your own yet? Build one of [**the examples**](examples/README.md) with turri, step by step.
|
|
55
53
|
|
|
@@ -146,11 +144,11 @@ cd ~/my-project
|
|
|
146
144
|
turri
|
|
147
145
|
```
|
|
148
146
|
|
|
149
|
-
Then
|
|
147
|
+
Then start the interview:
|
|
150
148
|
|
|
151
|
-
>
|
|
149
|
+
> /brood a small API where people can register and sign in
|
|
152
150
|
|
|
153
|
-
It
|
|
151
|
+
It asks you about the problem, the stack and the product, writes \*\*\*plain, and builds each functionality as it is written. While it works:
|
|
154
152
|
|
|
155
153
|
- the **panel** above the input shows every build - its stage, what its agents are doing, its model and cost - and the queue behind it;
|
|
156
154
|
- ask to try it at any time - "let me try it" - and **`release`** puts what is built so far in `dist/<module>` and **`activate`** starts it, installing what it needs - nothing to approve;
|
|
@@ -188,7 +186,7 @@ Start at the [documentation index](docs/README.md), or go straight to a page:
|
|
|
188
186
|
| | |
|
|
189
187
|
| --- | --- |
|
|
190
188
|
| [Installation](docs/installation.md) | doctor, what turri needs of this machine, **turri** and `pi`, environment variables |
|
|
191
|
-
| [Examples](examples/README.md) | projects to build with turri step by step:
|
|
189
|
+
| [Examples](examples/README.md) | projects to build with turri step by step: guestbook - a backend and a frontend in minutes - a CLI, and a backend service with conformance tests |
|
|
192
190
|
| [Using **turri**](docs/using.md) | the conversation, skills, tools, pulses and regen, choosing models, the command line |
|
|
193
191
|
| [The project config](docs/config.md) | `config.turri.yaml` and each stack's `config.turri.<stack>.yaml`: every key, models and their inheritance, conformance, the testing scripts, stacks and choosing one, the old `config.yaml` |
|
|
194
192
|
| [Deploy](docs/deploy.md) | trying what is built: the last commit in `dist/<module>`, started on your machine, nothing installed without asking |
|
package/dist/btw.d.ts
CHANGED
|
@@ -53,6 +53,8 @@ export interface SideQuestion {
|
|
|
53
53
|
projectDir: string;
|
|
54
54
|
/** turri's own folder: its source, skills, rules and docs. */
|
|
55
55
|
harnessDir: string;
|
|
56
|
+
/** A host's surface, in its own words (TurriHost.surface): where the person watches the work instead of the terminal. */
|
|
57
|
+
surface?: string;
|
|
56
58
|
}
|
|
57
59
|
export declare function sideQuestionPrompt(side: SideQuestion): string;
|
|
58
60
|
/** Answers a side question with an agent of its own, read only, on `model`. */
|
package/dist/btw.js
CHANGED
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
import { readFileSync } from "node:fs";
|
|
13
13
|
import { join } from "node:path";
|
|
14
14
|
import { confinedTools } from "./regenerate/confine.js";
|
|
15
|
-
import {
|
|
15
|
+
import { conversationIdentity } from "./identity.js";
|
|
16
16
|
import { ask, BEGIN, createExecutor } from "./pi.js";
|
|
17
17
|
/** How many tool calls the side agent may make before it is told to answer. */
|
|
18
18
|
export const SIDE_CALLS = 20;
|
|
@@ -85,7 +85,7 @@ export function skillSessionText(runDir, maxChars = SKILL_SESSION_CHARS) {
|
|
|
85
85
|
}
|
|
86
86
|
export function sideQuestionPrompt(side) {
|
|
87
87
|
return [
|
|
88
|
-
|
|
88
|
+
conversationIdentity(side.surface),
|
|
89
89
|
"",
|
|
90
90
|
"The person is in the middle of something with you - a skill, a pulse - and has asked you a question on the side",
|
|
91
91
|
"with /btw. Answer it as turri: in the first person, to them, in whole sentences - briefly and plainly, from what is",
|
package/dist/cli.js
CHANGED
|
@@ -81,6 +81,7 @@ const COMMANDS = {
|
|
|
81
81
|
"spec-diff": { module: "./spec-diff-cli.ts", blurb: "apply a change to the specs, given as a git diff, and regenerate it" },
|
|
82
82
|
validate: { module: "./validate-cli.ts", blurb: "validate a ***plain spec, regenerating nothing" },
|
|
83
83
|
lineage: { module: "./lineage-cli.ts", blurb: "a module's lineage: what was regenerated into it, and when" },
|
|
84
|
+
ledger: { module: "./ledger-cli.ts", blurb: "what turri did here: the last runs and their pulses, or one pulse, from the ledger" },
|
|
84
85
|
release: { module: "./release-cli.ts", blurb: "release what is regenerated to dist/<module> and activate it on this machine" },
|
|
85
86
|
cultivate: { module: "./cultivate-cli.ts", blurb: "run a gated skill" },
|
|
86
87
|
};
|
|
@@ -173,7 +174,7 @@ process.argv = [
|
|
|
173
174
|
// A headless run is stopped by a signal - SIGTERM, SIGHUP, Ctrl-C - whatever else listens for it: it exits, and what
|
|
174
175
|
// its agents left running goes with it (shell.ts kills every process group on exit). Left to the libraries' own
|
|
175
176
|
// handlers, one ran on for minutes after it was told to stop, building in the same codebase as the run after it.
|
|
176
|
-
if (!["validate", "check", "drift", "lineage"].includes(command)) {
|
|
177
|
+
if (!["validate", "check", "drift", "lineage", "ledger"].includes(command)) {
|
|
177
178
|
for (const [signal, code] of [["SIGTERM", 143], ["SIGHUP", 129], ["SIGINT", 130]]) {
|
|
178
179
|
process.on(signal, () => {
|
|
179
180
|
console.error(`\nstopped (${signal})`);
|
package/dist/config-edit.d.ts
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
* kept; `models:` and `agent-env:`, which nest, are written again whole, their own comments lost.
|
|
6
6
|
*/
|
|
7
7
|
/** When a change takes effect. Nothing turri reads needs a restart: what is fixed at its start is changed live. */
|
|
8
|
-
export type Effect = "now" | "next-message" | "next-pulse" | "next-plan" | "next-test-run" | "next-landing" | "next-regenerate";
|
|
8
|
+
export type Effect = "now" | "next-message" | "next-pulse" | "next-plan" | "next-test-run" | "next-landing" | "next-regenerate" | "next-start";
|
|
9
9
|
export type SettingKind = "boolean" | "choice" | "number" | "model" | "list" | "path" | "budget";
|
|
10
10
|
export interface Setting {
|
|
11
11
|
/** As written in the file - a nested one by its path: `models.coding`, `models.coding.turn-limit`, `agent-env.files`. */
|
package/dist/config-edit.js
CHANGED
|
@@ -11,6 +11,7 @@ export const EFFECT_TEXT = {
|
|
|
11
11
|
"next-test-run": "in effect from the next test run - the running pulse's too",
|
|
12
12
|
"next-landing": "in effect from the next pulse that lands - the running one too",
|
|
13
13
|
"next-regenerate": "in effect for the next regeneration that names no .plain file",
|
|
14
|
+
"next-start": "in effect the next time turri starts here",
|
|
14
15
|
};
|
|
15
16
|
const SECONDS = ["0", "60", "120", "180", "300"];
|
|
16
17
|
/** A role's line: which agents it is, for the list. */
|
|
@@ -75,6 +76,7 @@ export function settings(rules = []) {
|
|
|
75
76
|
{ key: "agent-env.files", about: "dotenv files whose variables the agents' commands get", kind: "list", unset: "none", effect: "next-pulse", projectOnly: true },
|
|
76
77
|
{ key: "agent-env.vars", about: "variables from turri's environment the agents' commands get, by name", kind: "list", unset: "none", effect: "next-pulse", projectOnly: true },
|
|
77
78
|
{ key: "toolchains", about: "folders of this machine an agent's commands may read beside the usual ones", kind: "list", unset: "none", effect: "next-pulse", projectOnly: true },
|
|
79
|
+
{ key: "ledger-keep", about: "megabytes of the ledger - what turri did, as facts - the project keeps; 0 keeps none", kind: "number", options: ["0", "50", "100", "500"], min: 0, unset: "100", effect: "next-start", projectOnly: true },
|
|
78
80
|
...roles,
|
|
79
81
|
];
|
|
80
82
|
}
|
package/dist/config.d.ts
CHANGED
|
@@ -260,6 +260,11 @@ export interface ProjectConfig {
|
|
|
260
260
|
* toolchain installed somewhere unusual. A module's own are added to the project's.
|
|
261
261
|
*/
|
|
262
262
|
toolchains?: string[];
|
|
263
|
+
/**
|
|
264
|
+
* Megabytes of the ledger - what turri did, as facts, in .turri/ledger - the project keeps: past it, the oldest runs'
|
|
265
|
+
* are pruned when a run first writes. 0 keeps none, and writes none. The project's alone. Unset: 100.
|
|
266
|
+
*/
|
|
267
|
+
ledgerKeep?: number;
|
|
263
268
|
/** Each agent role's own model, thinking and turn limit; `default` for every role not named. */
|
|
264
269
|
models?: Partial<Record<AgentRole | "default", RoleChoice>>;
|
|
265
270
|
/** Seconds each turn of an agent a render starts has, for every role without its own. Unset: 120. 0: none. */
|
|
@@ -288,6 +293,7 @@ export declare const CONFIG_KEYS: {
|
|
|
288
293
|
readonly maxLinesPerPulse: "max-lines-per-pulse";
|
|
289
294
|
readonly agentEnv: "agent-env";
|
|
290
295
|
readonly toolchains: "toolchains";
|
|
296
|
+
readonly ledgerKeep: "ledger-keep";
|
|
291
297
|
readonly models: "models";
|
|
292
298
|
readonly turnLimit: "turn-limit";
|
|
293
299
|
};
|
package/dist/config.js
CHANGED
|
@@ -379,6 +379,7 @@ export const CONFIG_KEYS = {
|
|
|
379
379
|
maxLinesPerPulse: "max-lines-per-pulse",
|
|
380
380
|
agentEnv: "agent-env",
|
|
381
381
|
toolchains: "toolchains",
|
|
382
|
+
ledgerKeep: "ledger-keep",
|
|
382
383
|
models: "models",
|
|
383
384
|
turnLimit: "turn-limit",
|
|
384
385
|
};
|
|
@@ -431,7 +432,7 @@ function readStackedConfig(root, stack) {
|
|
|
431
432
|
// The project's alone: one conversation, one module built when none is named.
|
|
432
433
|
const merged = { ...project };
|
|
433
434
|
for (const key of Object.keys(CONFIG_KEYS)) {
|
|
434
|
-
if (key === "module" || key === "harnessModel" || key === "parallelJobs" || key === "rulesOff" || key === "compactAt" || key === "toolchains" || key === "agentEnv" || own[key] === undefined)
|
|
435
|
+
if (key === "module" || key === "harnessModel" || key === "parallelJobs" || key === "rulesOff" || key === "compactAt" || key === "ledgerKeep" || key === "toolchains" || key === "agentEnv" || own[key] === undefined)
|
|
435
436
|
continue;
|
|
436
437
|
merged[key] = own[key];
|
|
437
438
|
}
|
|
@@ -542,6 +543,9 @@ function readConfigFile(path) {
|
|
|
542
543
|
: undefined,
|
|
543
544
|
maxSpecsPerPulse: positiveInteger(raw[CONFIG_KEYS.maxSpecsPerPulse]),
|
|
544
545
|
maxLinesPerPulse: positiveInteger(raw[CONFIG_KEYS.maxLinesPerPulse]),
|
|
546
|
+
ledgerKeep: typeof raw[CONFIG_KEYS.ledgerKeep] === "number" && Number.isInteger(raw[CONFIG_KEYS.ledgerKeep]) && raw[CONFIG_KEYS.ledgerKeep] >= 0
|
|
547
|
+
? raw[CONFIG_KEYS.ledgerKeep]
|
|
548
|
+
: undefined,
|
|
545
549
|
models: modelsSetting(raw[CONFIG_KEYS.models]),
|
|
546
550
|
turnLimit: turnLimitSetting(raw[CONFIG_KEYS.turnLimit]),
|
|
547
551
|
};
|
package/dist/extension.d.ts
CHANGED
|
@@ -41,6 +41,17 @@ export interface TurriHost {
|
|
|
41
41
|
onPlanChange?(): void;
|
|
42
42
|
/** The dialogs ask_user puts its questions in - the conversation's and a skill's interview - instead of the terminal's. */
|
|
43
43
|
dialogs?: AskUserIO;
|
|
44
|
+
/**
|
|
45
|
+
* The host's own UI, in its own words: the views it has in place of the terminal's, what its marks and buttons mean,
|
|
46
|
+
* where questions appear. Given, turri tells the model none of the terminal's - its keys, the side panel, regen,
|
|
47
|
+
* /pulses - and gives it this instead, as a `<surface>` block in turri's identity. Absent, the terminal is the surface.
|
|
48
|
+
*/
|
|
49
|
+
surface?: string;
|
|
50
|
+
/**
|
|
51
|
+
* The last word on the whole system prompt, as turri has built it for this turn - after its own before_agent_start
|
|
52
|
+
* work: what it returns is what is sent. For a host that needs to change anything else.
|
|
53
|
+
*/
|
|
54
|
+
systemPrompt?(prompt: string): string;
|
|
44
55
|
}
|
|
45
56
|
/** turri's conversation as an extension of pi's, for a host that runs it itself: see TurriHost. */
|
|
46
57
|
export declare function createTurriExtension(host?: TurriHost): (pi: ExtensionAPI) => void;
|
package/dist/extension.js
CHANGED
|
@@ -44,6 +44,8 @@ import { describeDiagnosis, diagnoseFailure } from "./regenerate/diagnose.js";
|
|
|
44
44
|
import { HISTORY_DIR, historyLog, historyShow } from "./regenerate/lineage.js";
|
|
45
45
|
import { defaultBuildDir } from "./regenerate/paths.js";
|
|
46
46
|
import { describeTurnIncident, describeUsage, onModelEvent } from "./regenerate/activity.js";
|
|
47
|
+
import { ledgerWanted, startLedger } from "./regenerate/ledger.js";
|
|
48
|
+
import { describePastPulse, describeRuns, earlierPulses, ledgerProjects } from "./regenerate/ledger-view.js";
|
|
47
49
|
import { describeSpecConflict } from "./regenerate/stop-report.js";
|
|
48
50
|
import { inheritProviders, inheritThinkingLevel } from "./pi.js";
|
|
49
51
|
import { beginCapture, captureConsole, describeCaptured, dropNoise } from "./quiet.js";
|
|
@@ -53,13 +55,14 @@ import { describeStdlib } from "./stdlib.js";
|
|
|
53
55
|
import { peekKeyListener, runAsk } from "./ask.js";
|
|
54
56
|
import { QuestionDialog } from "./question-dialog.js";
|
|
55
57
|
import { registerStacks } from "./stacks.js";
|
|
56
|
-
import { catalogueLine, discoverSkills, leftOutNote } from "./skills.js";
|
|
58
|
+
import { catalogueLine, createCommandGate, discoverSkills, leftOutNote, startedByCommandOnly } from "./skills.js";
|
|
57
59
|
import { supersededByTurri } from "./chat-skills.js";
|
|
58
|
-
import {
|
|
60
|
+
import { conversationIdentity } from "./identity.js";
|
|
61
|
+
import { setSurface, surfaceOf } from "./surface.js";
|
|
59
62
|
import { turriIdentity } from "./environment.js";
|
|
60
63
|
import { conformanceChooser } from "./regenerate/choose-conformance.js";
|
|
61
64
|
import { answerSideQuestion, askWithAsides, BTW_HOW, runningSkill, skillRunStarted, skillSessionText, transcriptOf } from "./btw.js";
|
|
62
|
-
import { createJobRegistry, defaultParallelJobs, describeJob, formatDuration, pulseName, shouldWait, STOPPED_ON_REQUEST, } from "./regenerate/jobs.js";
|
|
65
|
+
import { createJobRegistry, defaultParallelJobs, describeJob, firstRegeneration, formatDuration, formatElapsed, pulseName, shouldWait, STOPPED_ON_REQUEST, } from "./regenerate/jobs.js";
|
|
63
66
|
/**
|
|
64
67
|
* Makes gated skills reachable from a conversation.
|
|
65
68
|
*
|
|
@@ -180,6 +183,14 @@ export function chatAskUserTool(dialogs) {
|
|
|
180
183
|
},
|
|
181
184
|
};
|
|
182
185
|
}
|
|
186
|
+
/**
|
|
187
|
+
* The conversation's system prompt for a turn: pi's, turri's identity - with the host's surface in place of the
|
|
188
|
+
* terminal's, when it has one - and turri's blocks; then the host's last word on it.
|
|
189
|
+
*/
|
|
190
|
+
function conversationSystemPrompt(base, host, blocks) {
|
|
191
|
+
const prompt = [base, conversationIdentity(host.surface), ...blocks].filter((part) => part !== "").join("\n\n");
|
|
192
|
+
return host.systemPrompt !== undefined ? host.systemPrompt(prompt) : prompt;
|
|
193
|
+
}
|
|
183
194
|
/** The host each conversation's jobs report to, for the work loop made for them later. */
|
|
184
195
|
const hosts = new WeakMap();
|
|
185
196
|
/** turri's conversation as an extension of pi's, for a host that runs it itself: see TurriHost. */
|
|
@@ -260,6 +271,8 @@ function turriExtension(pi, host) {
|
|
|
260
271
|
},
|
|
261
272
|
});
|
|
262
273
|
hosts.set(jobs, host);
|
|
274
|
+
// What the model is told of where the person watches the work, deep in the tools too: the host's surface, or the terminal.
|
|
275
|
+
setSurface(jobs, host.surface);
|
|
263
276
|
host.onJobs?.(jobs);
|
|
264
277
|
// The ***plain rules go into the system prompt in full, every turn. The block is
|
|
265
278
|
// identical each time, so it sits inside the cached prefix rather than invalidating it.
|
|
@@ -298,7 +311,7 @@ function turriExtension(pi, host) {
|
|
|
298
311
|
}
|
|
299
312
|
}
|
|
300
313
|
return {
|
|
301
|
-
systemPrompt:
|
|
314
|
+
systemPrompt: conversationSystemPrompt(event.systemPrompt, host, [TOOLS_NOT_CLI, projectConfigBlock(cwd), machineBlock(cwd, machine.now()), rulesBlock()]),
|
|
302
315
|
};
|
|
303
316
|
});
|
|
304
317
|
// The conversation's agent does not run turri again through its shell, unless the person
|
|
@@ -314,7 +327,7 @@ function turriExtension(pi, host) {
|
|
|
314
327
|
if (event.toolName === "bash" && typeof command === "string") {
|
|
315
328
|
const what = selfInvocation(command);
|
|
316
329
|
if (what !== undefined && !askedForCommandLine(lastUserMessage))
|
|
317
|
-
return { block: true, reason: refusal(what) };
|
|
330
|
+
return { block: true, reason: refusal(what, surfaceOf(jobs)) };
|
|
318
331
|
}
|
|
319
332
|
markConversationWrite(event.toolCallId, event.toolName, event.input);
|
|
320
333
|
return undefined;
|
|
@@ -368,6 +381,24 @@ function turriExtension(pi, host) {
|
|
|
368
381
|
ctx.ui.notify(event.job !== undefined ? `${event.job}: ${line}` : line, "warning");
|
|
369
382
|
});
|
|
370
383
|
});
|
|
384
|
+
// What this session does, as facts, in the project's .turri/ledger - a pulse's own in its project. Said once, in the
|
|
385
|
+
// chat, when it cannot be written.
|
|
386
|
+
const ledgerTrouble = [];
|
|
387
|
+
let tellLedgerTrouble;
|
|
388
|
+
if (ledgerWanted()) {
|
|
389
|
+
startLedger({
|
|
390
|
+
mode: Object.keys(host).length > 0 ? "host" : "chat",
|
|
391
|
+
project: cwd,
|
|
392
|
+
warn: (message) => (tellLedgerTrouble !== undefined ? tellLedgerTrouble(message) : ledgerTrouble.push(message)),
|
|
393
|
+
});
|
|
394
|
+
}
|
|
395
|
+
pi.on("session_start", (_event, ctx) => {
|
|
396
|
+
if (!ctx.hasUI)
|
|
397
|
+
return;
|
|
398
|
+
tellLedgerTrouble = (message) => ctx.ui.notify(message, "warning");
|
|
399
|
+
for (const message of ledgerTrouble.splice(0))
|
|
400
|
+
tellLedgerTrouble(message);
|
|
401
|
+
});
|
|
371
402
|
// What a turri that was killed left running - its commands' process groups - stopped as this one starts.
|
|
372
403
|
const orphans = sweepOrphanedGroups();
|
|
373
404
|
let orphansSaid = orphans === 0;
|
|
@@ -416,6 +447,19 @@ function turriExtension(pi, host) {
|
|
|
416
447
|
return;
|
|
417
448
|
const catalogue = skills.map(catalogueLine).join("\n");
|
|
418
449
|
const pendingNote = leftOutNote(discoveredSkills);
|
|
450
|
+
// The skills the person starts themselves - brood, spawn, bud: each has its command, and cultivate starts it only
|
|
451
|
+
// once its command is typed. The command puts what was typed in the chat, as the person's message, for the model to
|
|
452
|
+
// run it with what follows as its context.
|
|
453
|
+
const gate = createCommandGate();
|
|
454
|
+
for (const skill of skills.filter((s) => s.commandOnly)) {
|
|
455
|
+
pi.registerCommand(skill.name, {
|
|
456
|
+
description: `Start ${skill.name}${skill.steps.length > 1 ? ` - ${skill.steps.map((step) => step.name).join(" → ")}` : ""}: /${skill.name} [what you want]`,
|
|
457
|
+
handler: async (args) => {
|
|
458
|
+
gate.typed(skill.name);
|
|
459
|
+
pi.sendUserMessage(`/${skill.name}${args.trim() !== "" ? ` ${args.trim()}` : ""}`, { deliverAs: "followUp" });
|
|
460
|
+
},
|
|
461
|
+
});
|
|
462
|
+
}
|
|
419
463
|
pi.registerTool({
|
|
420
464
|
name: "cultivate",
|
|
421
465
|
label: "Cultivate",
|
|
@@ -426,6 +470,7 @@ function turriExtension(pi, host) {
|
|
|
426
470
|
promptSnippet: "cultivate: run a gated skill - its steps and review gates enforced by turri",
|
|
427
471
|
promptGuidelines: [
|
|
428
472
|
"When a request matches a skill listed in cultivate's description, call cultivate instead of doing the work yourself. Doing it by hand skips the review gates.",
|
|
473
|
+
"A skill marked as started only by the person runs only from its command: a message that is its command - /spawn a small server - is the person starting it, so call cultivate for it at once, with what follows the command as context. Never call cultivate for one otherwise; tell them to type its command.",
|
|
429
474
|
// pi lists .agents/skills among its own skills too: read and followed by hand, a skill's gates would be skipped.
|
|
430
475
|
"A skill cultivate lists may also be among your own skills, from the project's .agents/skills: run it with cultivate all the same, never by reading its SKILL.md and following it.",
|
|
431
476
|
"cultivate returns the reviewed artifact and a trace of every gate decision. Report the artifact to the user; do not rewrite it.",
|
|
@@ -458,6 +503,8 @@ function turriExtension(pi, host) {
|
|
|
458
503
|
details: {},
|
|
459
504
|
};
|
|
460
505
|
}
|
|
506
|
+
if (!gate.mayStart(skill))
|
|
507
|
+
return { content: [{ type: "text", text: startedByCommandOnly(skill.name) }], details: { notStarted: true } };
|
|
461
508
|
// A new run clears the run folder. An interrupted one is not thrown away by accident:
|
|
462
509
|
// whether to pick it up or start over is the user's call.
|
|
463
510
|
const runDir = skillRunDir(cwd, skill.name);
|
|
@@ -475,6 +522,8 @@ function turriExtension(pi, host) {
|
|
|
475
522
|
details: { interrupted },
|
|
476
523
|
};
|
|
477
524
|
}
|
|
525
|
+
// Its command is used up: started again later, it is typed again.
|
|
526
|
+
gate.started(skill.name);
|
|
478
527
|
const trace = [`skill: ${skill.name}`, ...describeGates(skill).map((l) => ` ${l}`)];
|
|
479
528
|
// While it runs, what its agent is doing leads: pi shows a running tool's first lines, and the trace's first
|
|
480
529
|
// lines are the gates, which say nothing about now. The result keeps the trace alone.
|
|
@@ -560,6 +609,8 @@ function turriExtension(pi, host) {
|
|
|
560
609
|
const filesBefore = projectFiles(cwd);
|
|
561
610
|
// A side question asked while it runs is told what it has done so far.
|
|
562
611
|
const ended = skillRunStarted({ skill: skill.name, dir: skill.dir, step: () => live.step, trace: () => [...trace], runDir });
|
|
612
|
+
// From here the time to first regeneration is counted, for a skill that writes spec diffs: its first pulse started.
|
|
613
|
+
const began = Date.now();
|
|
563
614
|
const { result, output: captured } = await captureConsole(() => runSkill({
|
|
564
615
|
skillDir: skill.dir,
|
|
565
616
|
cwd,
|
|
@@ -620,11 +671,17 @@ function turriExtension(pi, host) {
|
|
|
620
671
|
? `${specsChanged.join(", ")} changed, and nothing is regenerated from that yet: call regenerate for it, and the code is brought up to the spec.`
|
|
621
672
|
: "";
|
|
622
673
|
const gated = skill.steps.some((step) => step.gate !== undefined);
|
|
674
|
+
const first = skill.allowedTools.includes("write_spec_diff") ? firstRegeneration(jobs.list(), began) : {};
|
|
675
|
+
const ttfr = first.started === undefined
|
|
676
|
+
? ""
|
|
677
|
+
: `Time to first regeneration: ${formatElapsed(first.started)} - its first pulse started that long after ${skill.name} began` +
|
|
678
|
+
(first.landed !== undefined ? `, and landed after ${formatElapsed(first.landed)}.` : ".");
|
|
623
679
|
const text = result.ok
|
|
624
680
|
? [
|
|
625
681
|
gated ? `${skill.name}: passed all gates.` : `${skill.name}: finished. It has no review gates, so nothing checked its work: read what it changed before you report it.`,
|
|
626
682
|
changed,
|
|
627
683
|
...(unrendered !== "" ? [unrendered] : []),
|
|
684
|
+
...(ttfr !== "" ? [ttfr] : []),
|
|
628
685
|
"",
|
|
629
686
|
"<artifact>",
|
|
630
687
|
result.output,
|
|
@@ -1411,20 +1468,27 @@ async function deliver(jobs, input) {
|
|
|
1411
1468
|
const panelOpeners = new WeakMap();
|
|
1412
1469
|
/** By registry: the last pulse that failed, stopped or did not pass, not yet looked at - ctrl+h opens on it. */
|
|
1413
1470
|
const needsLooking = new WeakMap();
|
|
1471
|
+
/** What heads a pulse told from the ledger: it is not this session's, and the facts are all there is of it. */
|
|
1472
|
+
const pastPulseNote = "A pulse of an earlier session, as the ledger recorded it - facts, not its transcript. Its whole log is the file it names, " +
|
|
1473
|
+
"for the read tool.";
|
|
1414
1474
|
function registerJobTools(pi, jobs, cwd) {
|
|
1415
1475
|
pi.registerTool({
|
|
1416
1476
|
name: "diagnose",
|
|
1417
1477
|
label: "Diagnose a pulse",
|
|
1418
1478
|
description: "Why a pulse did not land, from its whole transcript: how it ended and its diagnosis, the stages that went wrong, the " +
|
|
1419
1479
|
"last failing test run with its output, the last review that sent the work back, and the tool calls that failed - " +
|
|
1420
|
-
"and where the whole log is.",
|
|
1480
|
+
"and where the whole log is. A pulse of an earlier session is told from the ledger - what it recorded of it, as facts.",
|
|
1421
1481
|
promptSnippet: "diagnose: why a pulse did not land, from its transcript",
|
|
1422
1482
|
promptGuidelines: ["When a pulse failed or did not land, call diagnose before you explain it or change the spec: go by what happened, not by the report alone."],
|
|
1423
1483
|
parameters: Type.Object({ job_id: Type.String({ description: "The pulse's id" }) }),
|
|
1424
1484
|
async execute(_id, params) {
|
|
1425
1485
|
const job = jobs.get(params.job_id);
|
|
1426
|
-
if (job === undefined)
|
|
1427
|
-
|
|
1486
|
+
if (job === undefined) {
|
|
1487
|
+
const past = describePastPulse(ledgerProjects(cwd), params.job_id);
|
|
1488
|
+
if (past !== undefined)
|
|
1489
|
+
return { content: [{ type: "text", text: `${pastPulseNote}\n\n${past}` }], details: { status: "earlier" } };
|
|
1490
|
+
return { content: [{ type: "text", text: `No pulse ${params.job_id} has run in this session, and the ledger has none by that id.` }], details: { status: "unknown" } };
|
|
1491
|
+
}
|
|
1428
1492
|
return { content: [{ type: "text", text: whyFailed(job) }], details: { status: job.status } };
|
|
1429
1493
|
},
|
|
1430
1494
|
});
|
|
@@ -1432,7 +1496,8 @@ function registerJobTools(pi, jobs, cwd) {
|
|
|
1432
1496
|
name: "pulses",
|
|
1433
1497
|
label: "Pulses",
|
|
1434
1498
|
description: "What is regenerating: with no pulse, every pulse of this session - running, planned and finished - and what deploy started; " +
|
|
1435
|
-
"with a pulse's id, that pulse - whether it is still running, its latest output, and its report once it has finished."
|
|
1499
|
+
"with a pulse's id, that pulse - whether it is still running, its latest output, and its report once it has finished. " +
|
|
1500
|
+
"A pulse of an earlier session is told from the ledger.",
|
|
1436
1501
|
promptSnippet: "pulses: every pulse, or one - what is regenerating and how it is going",
|
|
1437
1502
|
parameters: Type.Object({
|
|
1438
1503
|
job_id: Type.Optional(Type.String({ description: "The id returned when the pulse started. Omitted: every pulse" })),
|
|
@@ -1440,7 +1505,7 @@ function registerJobTools(pi, jobs, cwd) {
|
|
|
1440
1505
|
}),
|
|
1441
1506
|
async execute(_id, params) {
|
|
1442
1507
|
if (params.job_id === undefined) {
|
|
1443
|
-
return { content: [{ type: "text", text: listJobs(jobs) }], details: { status: "all" } };
|
|
1508
|
+
return { content: [{ type: "text", text: listJobs(jobs, cwd) }], details: { status: "all" } };
|
|
1444
1509
|
}
|
|
1445
1510
|
const existing = jobs.get(params.job_id);
|
|
1446
1511
|
const planned = shownJobs(jobs).find((job) => job.id === params.job_id && jobs.get(job.id) === undefined);
|
|
@@ -1451,6 +1516,9 @@ function registerJobTools(pi, jobs, cwd) {
|
|
|
1451
1516
|
};
|
|
1452
1517
|
}
|
|
1453
1518
|
if (existing === undefined) {
|
|
1519
|
+
const past = describePastPulse(ledgerProjects(cwd), params.job_id);
|
|
1520
|
+
if (past !== undefined)
|
|
1521
|
+
return { content: [{ type: "text", text: `${pastPulseNote}\n\n${past}` }], details: { status: "earlier" } };
|
|
1454
1522
|
const known = shownJobs(jobs).map((job) => job.id);
|
|
1455
1523
|
return {
|
|
1456
1524
|
content: [
|
|
@@ -1564,6 +1632,21 @@ function registerJobTools(pi, jobs, cwd) {
|
|
|
1564
1632
|
const panelBeside = () => (panelDocked && panelTerminal !== undefined ? dockedWidth(paddedColumns(panelTerminal.columns)) : 0);
|
|
1565
1633
|
// What is open in the pulse list and the map, and where their cursors are: the conversation's, as left each time it opens.
|
|
1566
1634
|
const jobsTree = createJobsTreeState();
|
|
1635
|
+
// Earlier sessions' pulses, from the ledger: read as the panel first shows them - they do not change while this one runs.
|
|
1636
|
+
let earlier;
|
|
1637
|
+
const earlierJobs = () => {
|
|
1638
|
+
if (earlier !== undefined)
|
|
1639
|
+
return earlier;
|
|
1640
|
+
try {
|
|
1641
|
+
const found = earlierPulses(ledgerProjects(cwd), new Set(jobs.list().map((job) => job.id)));
|
|
1642
|
+
earlier = found.jobs;
|
|
1643
|
+
jobsTree.earlierMore = found.more;
|
|
1644
|
+
}
|
|
1645
|
+
catch {
|
|
1646
|
+
earlier = [];
|
|
1647
|
+
}
|
|
1648
|
+
return earlier;
|
|
1649
|
+
};
|
|
1567
1650
|
const mapState = createSpecMapState();
|
|
1568
1651
|
// The map from the files - the .plain files asked for, else the project's - with the work loop's plan laid over them.
|
|
1569
1652
|
const specMap = createSpecMapSource({
|
|
@@ -1657,6 +1740,7 @@ function registerJobTools(pi, jobs, cwd) {
|
|
|
1657
1740
|
specMap.refresh();
|
|
1658
1741
|
const view = createSidePanel({
|
|
1659
1742
|
jobs: () => shownJobs(jobs),
|
|
1743
|
+
earlier: earlierJobs,
|
|
1660
1744
|
tree: jobsTree,
|
|
1661
1745
|
map: () => specMap.read(),
|
|
1662
1746
|
mapState,
|
|
@@ -1930,13 +2014,27 @@ function deployedLabel(name, what) {
|
|
|
1930
2014
|
function shownJobs(jobs) {
|
|
1931
2015
|
return [...(workLoops.get(jobs)?.loop.planned() ?? []), ...jobs.list()];
|
|
1932
2016
|
}
|
|
1933
|
-
function listJobs(jobs) {
|
|
2017
|
+
function listJobs(jobs, cwd) {
|
|
1934
2018
|
const all = [...shownJobs(jobs)].reverse().filter((job) => job.replacedBy === undefined);
|
|
1935
2019
|
const deployed = runningDeployed().map((item) => `deploy ${item.name} running ${item.moduleName}: ${item.url ?? item.command} (pid ${item.pid}, output in ${relative(process.cwd(), item.log) || item.log})`);
|
|
1936
2020
|
const listed = deployed.length > 0 ? `\n\nActivated from dist - /halt activated <name> halts one:\n${deployed.join("\n")}` : "";
|
|
2021
|
+
const before = earlierRuns(jobs, cwd);
|
|
1937
2022
|
if (all.length === 0)
|
|
1938
|
-
return `No pulses have been started.${listed}`;
|
|
1939
|
-
return `${jobLines(all)}${listed}`;
|
|
2023
|
+
return `No pulses have been started in this session.${listed}${before}`;
|
|
2024
|
+
return `${jobLines(all)}${listed}${before}`;
|
|
2025
|
+
}
|
|
2026
|
+
/** Earlier sessions' last runs and their pulses, from the ledger - nothing when it has none. */
|
|
2027
|
+
function earlierRuns(jobs, cwd) {
|
|
2028
|
+
try {
|
|
2029
|
+
const found = earlierPulses(ledgerProjects(cwd), new Set(jobs.list().map((job) => job.id)));
|
|
2030
|
+
if (found.runs.length === 0)
|
|
2031
|
+
return "";
|
|
2032
|
+
const more = found.more > 0 ? `\n\n${found.more} earlier run${found.more === 1 ? "" : "s"} beyond these are in the ledger.` : "";
|
|
2033
|
+
return `\n\nEarlier sessions, from the ledger - pulses or diagnose with a pulse's id tells one:\n${describeRuns(found.runs)}${more}`;
|
|
2034
|
+
}
|
|
2035
|
+
catch {
|
|
2036
|
+
return "";
|
|
2037
|
+
}
|
|
1940
2038
|
}
|
|
1941
2039
|
function jobLines(all) {
|
|
1942
2040
|
return all
|
|
@@ -2083,6 +2181,7 @@ async function askOnTheSide(pi, ctx, cwd, jobs, question) {
|
|
|
2083
2181
|
pulses: jobs.list().map((job) => `${job.id} ${job.status}: ${pulseName(job)}`),
|
|
2084
2182
|
projectDir: cwd,
|
|
2085
2183
|
harnessDir: PACKAGE_ROOT,
|
|
2184
|
+
...(surfaceOf(jobs) !== undefined ? { surface: surfaceOf(jobs) } : {}),
|
|
2086
2185
|
}, chatModelOf(ctx));
|
|
2087
2186
|
try {
|
|
2088
2187
|
pi.appendEntry(SIDE_ENTRY, { question, answer });
|
package/dist/host.d.ts
CHANGED
|
@@ -19,3 +19,5 @@ export { createWorkLoop, describeModules, functionalityCompletion, promptOf, typ
|
|
|
19
19
|
export { createJobRegistry, defaultParallelJobs, type Job, type JobRegistry } from "./regenerate/jobs.ts";
|
|
20
20
|
export { describeTurnIncident, onModelEvent, type TranscriptEntry } from "./regenerate/activity.ts";
|
|
21
21
|
export { sweepOrphanedGroups } from "./regenerate/shell.ts";
|
|
22
|
+
export { ledgerDir, onLedgerEvent, readLedger, type AgentRef, type LedgerEntry, type LedgerEvent, type LedgerKind, type LedgerQuery } from "./regenerate/ledger.ts";
|
|
23
|
+
export { asJob, describePulseView, describeRuns, earlierPulses, ledgerProjects, pulseView, runViews, type PulseView, type RunView } from "./regenerate/ledger-view.ts";
|
package/dist/host.js
CHANGED
|
@@ -23,3 +23,6 @@ export { createWorkLoop, describeModules, functionalityCompletion, promptOf } fr
|
|
|
23
23
|
export { createJobRegistry, defaultParallelJobs } from "./regenerate/jobs.js";
|
|
24
24
|
export { describeTurnIncident, onModelEvent } from "./regenerate/activity.js";
|
|
25
25
|
export { sweepOrphanedGroups } from "./regenerate/shell.js";
|
|
26
|
+
// What turri did, as facts: the ledger, heard live and read back - this session's pulses and every earlier one's.
|
|
27
|
+
export { ledgerDir, onLedgerEvent, readLedger } from "./regenerate/ledger.js";
|
|
28
|
+
export { asJob, describePulseView, describeRuns, earlierPulses, ledgerProjects, pulseView, runViews } from "./regenerate/ledger-view.js";
|
package/dist/identity.d.ts
CHANGED
|
@@ -1,2 +1,7 @@
|
|
|
1
|
-
/**
|
|
1
|
+
/**
|
|
2
|
+
* Who the conversation's agent is: turri, and what it does - first, so it speaks as turri, in the first person. Where
|
|
3
|
+
* the person watches the work is the terminal's regen; given a host's surface, that is said in its place.
|
|
4
|
+
*/
|
|
5
|
+
export declare function conversationIdentity(surface?: string): string;
|
|
6
|
+
/** turri as the terminal runs it. */
|
|
2
7
|
export declare const TURRI_IDENTITY: string;
|
package/dist/identity.js
CHANGED
|
@@ -1,5 +1,31 @@
|
|
|
1
|
-
/**
|
|
2
|
-
|
|
1
|
+
/** In the terminal: regen, where the person watches the work - the side panel, its key, and the pulses behind it. */
|
|
2
|
+
const REGEN = [
|
|
3
|
+
"- regen is the side panel (ctrl+h): the spec with each functionality and item marked built, regenerating,",
|
|
4
|
+
" planned or waiting - and behind it the pulses (/pulses), every pulse regenerating, planned and finished. Say regen,",
|
|
5
|
+
" lowercase. It was the regeneration, the job queue, or the job panel.",
|
|
6
|
+
];
|
|
7
|
+
/** In a host: where the person watches the work is the host's to say - its surface, below. */
|
|
8
|
+
const HOST_SURFACE = [
|
|
9
|
+
"- What the person sees of your work - where they watch it, and what they press - is the app's you run in, and",
|
|
10
|
+
" <surface> below says it in its own words. Say only what it says, and speak of no key, panel or view it does not",
|
|
11
|
+
" name.",
|
|
12
|
+
];
|
|
13
|
+
/**
|
|
14
|
+
* Who the conversation's agent is: turri, and what it does - first, so it speaks as turri, in the first person. Where
|
|
15
|
+
* the person watches the work is the terminal's regen; given a host's surface, that is said in its place.
|
|
16
|
+
*/
|
|
17
|
+
export function conversationIdentity(surface) {
|
|
18
|
+
const text = surface?.trim() ?? "";
|
|
19
|
+
const hosted = text !== "";
|
|
20
|
+
return [
|
|
21
|
+
...IDENTITY_BEFORE,
|
|
22
|
+
...(hosted ? HOST_SURFACE : REGEN),
|
|
23
|
+
...IDENTITY_AFTER,
|
|
24
|
+
...(hosted ? ["", "<surface>", text, "</surface>"] : []),
|
|
25
|
+
"</turri>",
|
|
26
|
+
].join("\n");
|
|
27
|
+
}
|
|
28
|
+
const IDENTITY_BEFORE = [
|
|
3
29
|
"<turri>",
|
|
4
30
|
"You are turri - always lowercase - a coding agent with the power of regenerative software. The software is its",
|
|
5
31
|
"***plain; the code is regenerated from it. You help the person write ***plain, and you regenerate the code from it a",
|
|
@@ -25,11 +51,11 @@ export const TURRI_IDENTITY = [
|
|
|
25
51
|
"- A pulse is one unit of work regenerating the code - a spec diff regenerated in a codebase, or its conformance",
|
|
26
52
|
" tests. It was called a job, and still is in places: the job_id a tool takes is a pulse's id, and parallel-jobs and",
|
|
27
53
|
" allow-to-stop-jobs in config.turri.yaml are about pulses. \"Job\" in what you read is a pulse; say pulse.",
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
" lowercase. It was the regeneration, the job queue, or the job panel.",
|
|
54
|
+
];
|
|
55
|
+
const IDENTITY_AFTER = [
|
|
31
56
|
"- mutate, in a skill's activity, is write_spec_diff: a spec change written and regenerated in one step.",
|
|
32
57
|
"- Regenerate is the word for what you do to the code: never grow, grew or grown - older text, a tool's description",
|
|
33
58
|
" or a report may still say it, and means regenerate.",
|
|
34
|
-
|
|
35
|
-
|
|
59
|
+
];
|
|
60
|
+
/** turri as the terminal runs it. */
|
|
61
|
+
export const TURRI_IDENTITY = conversationIdentity();
|
package/dist/init-config.d.ts
CHANGED
|
@@ -66,6 +66,7 @@ export declare const DEFAULTS: {
|
|
|
66
66
|
readonly compactAt: 100000;
|
|
67
67
|
readonly maxSpecsPerPulse: 3;
|
|
68
68
|
readonly maxLinesPerPulse: 120;
|
|
69
|
+
readonly ledgerKeep: 100;
|
|
69
70
|
};
|
|
70
71
|
/** How many pulses run at once without a setting: half this machine's cores, one to four - as regenerate/jobs.ts has it. */
|
|
71
72
|
export declare function defaultParallel(cores?: number): number;
|
package/dist/init-config.js
CHANGED
|
@@ -118,6 +118,7 @@ export const DEFAULTS = {
|
|
|
118
118
|
compactAt: 100000,
|
|
119
119
|
maxSpecsPerPulse: 3,
|
|
120
120
|
maxLinesPerPulse: 120,
|
|
121
|
+
ledgerKeep: 100,
|
|
121
122
|
};
|
|
122
123
|
/** How many pulses run at once without a setting: half this machine's cores, one to four - as regenerate/jobs.ts has it. */
|
|
123
124
|
export function defaultParallel(cores = availableParallelism()) {
|
|
@@ -183,6 +184,10 @@ export function quickStartConfig(vendor = "other", bedrock) {
|
|
|
183
184
|
"# Folders of this machine an agent's commands may read beside the usual ones - a toolchain somewhere unusual: [/srv/zig].",
|
|
184
185
|
"toolchains: []",
|
|
185
186
|
"",
|
|
187
|
+
"# Megabytes of the ledger - what turri did, as facts, in .turri/ledger, never committed - the project keeps; past it,",
|
|
188
|
+
"# the oldest runs' are pruned. 0 keeps none.",
|
|
189
|
+
`ledger-keep: ${DEFAULTS.ledgerKeep}`,
|
|
190
|
+
"",
|
|
186
191
|
"# The testing scripts, by path from the project root - written here by the skill that writes them. One not named is",
|
|
187
192
|
"# looked for at the root, then in test_scripts/. Each runs the static checks of what it tests - the formatter, the",
|
|
188
193
|
"# linter, the type checker - with the tests, exiting 80 when the tests pass and only the checks do not. The prepare",
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|