@turri-dev/cli 0.1.4-next.afc43a8 → 0.1.4-next.d6e59a8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.pi/themes/turri-dark.json +67 -66
- package/.pi/themes/turri-light.json +71 -73
- package/README.md +41 -127
- package/assets/jellyfish/dark-00.png +0 -0
- package/assets/jellyfish/dark-01.png +0 -0
- package/assets/jellyfish/dark-02.png +0 -0
- package/assets/jellyfish/dark-03.png +0 -0
- package/assets/jellyfish/dark-04.png +0 -0
- package/assets/jellyfish/dark-05.png +0 -0
- package/assets/jellyfish/dark-06.png +0 -0
- package/assets/jellyfish/dark-07.png +0 -0
- package/assets/jellyfish/dark-08.png +0 -0
- package/assets/jellyfish/dark-09.png +0 -0
- package/assets/jellyfish/dark-10.png +0 -0
- package/assets/jellyfish/dark-11.png +0 -0
- package/assets/jellyfish/light-00.png +0 -0
- package/assets/jellyfish/light-01.png +0 -0
- package/assets/jellyfish/light-02.png +0 -0
- package/assets/jellyfish/light-03.png +0 -0
- package/assets/jellyfish/light-04.png +0 -0
- package/assets/jellyfish/light-05.png +0 -0
- package/assets/jellyfish/light-06.png +0 -0
- package/assets/jellyfish/light-07.png +0 -0
- package/assets/jellyfish/light-08.png +0 -0
- package/assets/jellyfish/light-09.png +0 -0
- package/assets/jellyfish/light-10.png +0 -0
- package/assets/jellyfish/light-11.png +0 -0
- package/dist/brand.js +7 -7
- package/dist/cultivate-cli.js +16 -23
- package/dist/extension.js +33 -59
- package/dist/jellyfish.js +130 -0
- package/dist/loop-cli.js +8 -8
- package/dist/module-report.js +5 -4
- package/dist/regenerate/plain-palette.js +52 -37
- package/dist/regenerate/side-panel.js +1 -1
- package/dist/regenerate/spec-map-view.js +9 -5
- package/dist/release-cli.js +3 -8
- package/dist/validate-cli.js +3 -8
- package/docs/README.md +5 -12
- package/docs/authoring-plain.md +37 -121
- package/docs/config.md +50 -176
- package/docs/deploy.md +21 -80
- package/docs/development.md +13 -41
- package/docs/headless.md +30 -136
- package/docs/images/how-turri-works.svg +29 -31
- package/docs/images/turri-banner.png +0 -0
- package/docs/images/turri-screenshot.png +0 -0
- package/docs/installation.md +13 -40
- package/docs/parser.md +15 -43
- package/docs/regenerating.md +231 -964
- package/docs/sandbox.md +38 -152
- package/docs/skills-and-gates.md +50 -173
- package/docs/theming.md +45 -46
- package/docs/using.md +66 -356
- package/genome/rules/bullet-continuation.md +5 -13
- package/genome/rules/concise-specs-examples.md +2 -4
- package/genome/rules/concise-specs-integration-examples.md +2 -6
- package/genome/rules/concise-specs.md +1 -2
- package/genome/rules/integration-embedded-testing.md +1 -4
- package/genome/rules/integrations.md +1 -2
- package/genome/rules/project-model.md +14 -37
- package/genome/rules/stdlib.md +14 -49
- package/genome/rules/turri-regeneration.md +35 -23
- package/genome/skills/add-implementation-requirement/SKILL.md +1 -2
- package/genome/skills/analyze-if-func-spec-too-complex/SKILL.md +3 -8
- package/genome/skills/brood/SKILL.md +37 -127
- package/genome/skills/brood/references/intent.md +4 -12
- package/genome/skills/brood/references/product.md +24 -76
- package/genome/skills/brood/references/stack.md +7 -29
- package/genome/skills/brood/references/testing.md +11 -42
- package/genome/skills/bud/SKILL.md +32 -109
- package/genome/skills/bud/references/intent.md +4 -11
- package/genome/skills/implement-conformance-testing-script/SKILL.md +3 -6
- package/genome/skills/implement-unit-testing-script/SKILL.md +1 -2
- package/genome/skills/mature/SKILL.md +21 -69
- package/genome/skills/spawn/SKILL.md +28 -113
- package/package.json +1 -1
- package/assets/jellyfish/dark-12.png +0 -0
- package/assets/jellyfish/dark-13.png +0 -0
- package/assets/jellyfish/dark-14.png +0 -0
- package/assets/jellyfish/fallback.txt +0 -252
- package/assets/jellyfish/light-12.png +0 -0
- package/assets/jellyfish/light-13.png +0 -0
- package/assets/jellyfish/light-14.png +0 -0
|
@@ -1,45 +1,53 @@
|
|
|
1
|
-
/**
|
|
1
|
+
/**
|
|
2
|
+
* On a dark surface - turri-dark's abyss, #0a0f1a, pi's dark ones - in the design system's colours alone: a section's
|
|
3
|
+
* header in pink, a concept in neon, text in quotes or backticks in siesta tan. Every colour at least 7:1 on abyss and 4.8:1 on the blue whale fill
|
|
4
|
+
* of an added line; on the eggplant of a removed one, the design system's only fill of that warmth, its pinks and
|
|
5
|
+
* grauzone drop to 3.7:1.
|
|
6
|
+
*/
|
|
2
7
|
const DARK = {
|
|
3
8
|
roles: {
|
|
4
|
-
section: { colour: "#
|
|
5
|
-
concept: { colour: "#
|
|
6
|
-
link: { colour: "#
|
|
7
|
-
literal: { colour: "#
|
|
8
|
-
keyword: { colour: "#
|
|
9
|
-
module: { colour: "#
|
|
10
|
-
comment: { colour: "#
|
|
11
|
-
bullet: { colour: "#
|
|
9
|
+
section: { colour: "#ff7fa6", bold: true },
|
|
10
|
+
concept: { colour: "#5ce1e6" },
|
|
11
|
+
link: { colour: "#f4f7f9", underline: true },
|
|
12
|
+
literal: { colour: "#e9d8c8" },
|
|
13
|
+
keyword: { colour: "#f4f7f9", bold: true },
|
|
14
|
+
module: { colour: "#f4f7f9" },
|
|
15
|
+
comment: { colour: "#85a3b2", italic: true },
|
|
16
|
+
bullet: { colour: "#85a3b2" },
|
|
12
17
|
},
|
|
13
|
-
added: "#
|
|
14
|
-
removed: "#
|
|
15
|
-
addedFill: "#
|
|
16
|
-
removedFill: "#
|
|
17
|
-
muted: "#
|
|
18
|
+
added: "#5ce1e6",
|
|
19
|
+
removed: "#ff7fa6",
|
|
20
|
+
addedFill: "#1e3442",
|
|
21
|
+
removedFill: "#732553",
|
|
22
|
+
muted: "#85a3b2",
|
|
18
23
|
};
|
|
19
|
-
/**
|
|
24
|
+
/**
|
|
25
|
+
* On a light surface - turri-light's white, pi's light ones - in the design system's deep colours, the only ones it has
|
|
26
|
+
* that read on white. Every colour at least 7:1 on white, and 5:1 on the neon hover and siesta tan fills.
|
|
27
|
+
*/
|
|
20
28
|
const LIGHT = {
|
|
21
29
|
roles: {
|
|
22
|
-
section: { colour: "#
|
|
23
|
-
concept: { colour: "#
|
|
24
|
-
link: { colour: "#
|
|
25
|
-
literal: { colour: "#
|
|
26
|
-
keyword: { colour: "#
|
|
27
|
-
module: { colour: "#
|
|
28
|
-
comment: { colour: "#
|
|
29
|
-
bullet: { colour: "#
|
|
30
|
+
section: { colour: "#3e5a6b", bold: true },
|
|
31
|
+
concept: { colour: "#732553" },
|
|
32
|
+
link: { colour: "#1e3442", underline: true },
|
|
33
|
+
literal: { colour: "#1e3442" },
|
|
34
|
+
keyword: { colour: "#0a0f1a", bold: true },
|
|
35
|
+
module: { colour: "#1e3442" },
|
|
36
|
+
comment: { colour: "#3e5a6b", italic: true },
|
|
37
|
+
bullet: { colour: "#3e5a6b" },
|
|
30
38
|
},
|
|
31
|
-
added: "#
|
|
32
|
-
removed: "#
|
|
33
|
-
addedFill: "#
|
|
34
|
-
removedFill: "#
|
|
35
|
-
muted: "#
|
|
39
|
+
added: "#1e3442",
|
|
40
|
+
removed: "#732553",
|
|
41
|
+
addedFill: "#8eebee",
|
|
42
|
+
removedFill: "#e9d8c8",
|
|
43
|
+
muted: "#3e5a6b",
|
|
36
44
|
};
|
|
37
45
|
const rgb = (hex) => {
|
|
38
46
|
const value = Number.parseInt(hex.slice(1), 16);
|
|
39
47
|
return [(value >> 16) & 255, (value >> 8) & 255, value & 255];
|
|
40
48
|
};
|
|
41
49
|
/** A colour's SGR parameters: 24-bit, or the nearest of the 256 where the terminal has no more. */
|
|
42
|
-
function sgr(hex, background, truecolor) {
|
|
50
|
+
export function sgr(hex, background, truecolor) {
|
|
43
51
|
const [r, g, b] = rgb(hex);
|
|
44
52
|
if (truecolor)
|
|
45
53
|
return `${background ? 48 : 38};2;${r};${g};${b}`;
|
|
@@ -57,17 +65,24 @@ const fillWith = (hex, text, truecolor) => {
|
|
|
57
65
|
return `${on}${text.replace(/\x1b\[(?:0|49)?m/g, (reset) => `${reset}${on}`)}\x1b[49m`;
|
|
58
66
|
};
|
|
59
67
|
/**
|
|
60
|
-
*
|
|
61
|
-
* on
|
|
62
|
-
* to white, 7.34:1 on brand black.
|
|
68
|
+
* Neon, for what is shown to do what its spec says - regen's ◆: the design system's colour for what can be followed,
|
|
69
|
+
* 12.2:1 on abyss. On a light surface, where neon is illegible (1.6:1 on white), blue whale, 12.9:1 on white.
|
|
63
70
|
*/
|
|
64
|
-
export const
|
|
65
|
-
/**
|
|
66
|
-
|
|
67
|
-
|
|
71
|
+
export const BRAND_NEON = { light: "#1e3442", dark: "#5ce1e6" };
|
|
72
|
+
/**
|
|
73
|
+
* Pink, the one pink thing in a view - the banner's wordmark: 6.54:1 on abyss. On a light surface, where pink is
|
|
74
|
+
* too pale (2.9:1 on white), eggplant, 9.9:1 on white.
|
|
75
|
+
*/
|
|
76
|
+
export const BRAND_PINK = { light: "#732553", dark: "#ff5c8d" };
|
|
77
|
+
const brandInk = (colours) => (theme) => {
|
|
78
|
+
const colour = theme.appearance === "light" ? colours.light : colours.dark;
|
|
68
79
|
const truecolor = (theme.getColorMode?.() ?? "truecolor") === "truecolor";
|
|
69
80
|
return (text) => paintWith({ colour, bold: true }, text, truecolor);
|
|
70
|
-
}
|
|
81
|
+
};
|
|
82
|
+
/** Text in neon on a theme - blue whale on a light one - in as many colours as the terminal has. */
|
|
83
|
+
export const brandNeon = brandInk(BRAND_NEON);
|
|
84
|
+
/** Text in pink on a theme - eggplant on a light one - in as many colours as the terminal has. */
|
|
85
|
+
export const brandPink = brandInk(BRAND_PINK);
|
|
71
86
|
/** How a spec diff is painted on a theme: its palette by whether the theme is dark or light, in as many colours as the terminal has. */
|
|
72
87
|
export function plainPaint(theme) {
|
|
73
88
|
const palette = theme.appearance === "light" ? LIGHT : DARK;
|
|
@@ -16,7 +16,7 @@ export function createSidePanel(options) {
|
|
|
16
16
|
const backKeys = () => {
|
|
17
17
|
if (options.dock === undefined)
|
|
18
18
|
return [`esc or ${options.key} back to the chat`];
|
|
19
|
-
return options.dock.docked() ? [
|
|
19
|
+
return options.dock.docked() ? [`${options.key} or esc to the chat`, "ctrl+c close", "s full screen"] : ["s beside the chat", `esc or ${options.key} back to the chat`];
|
|
20
20
|
};
|
|
21
21
|
const waitingNote = () =>
|
|
22
22
|
// A question asked while it showed waits in the chat, unanswered: the person is told it is there.
|
|
@@ -52,7 +52,7 @@ export function tabBar(pages, shown, width, style, now) {
|
|
|
52
52
|
const text = `${mark}${tabName(page)}`;
|
|
53
53
|
if (page === shown)
|
|
54
54
|
return style.heading(`[${text}]`);
|
|
55
|
-
return regenerating ? style.accent(text) : page.stuck ? style
|
|
55
|
+
return regenerating ? style.accent(text) : page.stuck ? needs(style)(text) : style.muted(text);
|
|
56
56
|
};
|
|
57
57
|
const tabs = pages.map(label);
|
|
58
58
|
const at = Math.max(0, shown === undefined ? 0 : pages.indexOf(shown));
|
|
@@ -69,6 +69,8 @@ export function tabBar(pages, shown, width, style, now) {
|
|
|
69
69
|
}
|
|
70
70
|
return truncateToWidth(lineOf(from, to), width, "…");
|
|
71
71
|
}
|
|
72
|
+
/** What needs the person - a stuck page, its `!` - in the warning colour: it waits on them, nothing is broken. */
|
|
73
|
+
const needs = (style) => style.warning ?? style.error;
|
|
72
74
|
/** Strike text through. */
|
|
73
75
|
const strike = (text) => `\x1b[9m${text}\x1b[29m`;
|
|
74
76
|
/** How far a group, and what is in it, is indented under its module. */
|
|
@@ -121,7 +123,7 @@ function rungGlyph(rung, style, now) {
|
|
|
121
123
|
if (rung === "conform")
|
|
122
124
|
return green(LADDER.conform);
|
|
123
125
|
if (rung === "needsYou")
|
|
124
|
-
return style
|
|
126
|
+
return needs(style)(LADDER.needsYou);
|
|
125
127
|
if (rung === "takenOut")
|
|
126
128
|
return style.error(LADDER.takenOut);
|
|
127
129
|
if (rung === "toRegenerate")
|
|
@@ -486,7 +488,7 @@ export function specMapLines(pages, state, width, style, _jobs = [], now = Date.
|
|
|
486
488
|
// The first item that needs the person says what for, and how to go about it - opened or closed.
|
|
487
489
|
if (page.stuck && !toldWhy && rungOf(item) === "needsYou") {
|
|
488
490
|
toldWhy = true;
|
|
489
|
-
for (const line of wrapped(needsLine(page), width, style
|
|
491
|
+
for (const line of wrapped(needsLine(page), width, needs(style)))
|
|
490
492
|
add(line, mark);
|
|
491
493
|
}
|
|
492
494
|
if (state.open.has(row.key)) {
|
|
@@ -515,7 +517,9 @@ function paintOf(mark, style) {
|
|
|
515
517
|
return style.success ?? style.added;
|
|
516
518
|
if (mark === "regenerating" || mark === "planning")
|
|
517
519
|
return style.accent;
|
|
518
|
-
if (mark === "stuck"
|
|
520
|
+
if (mark === "stuck")
|
|
521
|
+
return needs(style);
|
|
522
|
+
if (mark === "removed")
|
|
519
523
|
return style.error;
|
|
520
524
|
return style.muted;
|
|
521
525
|
}
|
|
@@ -548,7 +552,7 @@ export function specMapHeader(page, width, style, waiting = "") {
|
|
|
548
552
|
const strip = progressStrip(page, style);
|
|
549
553
|
const count = total > 0 ? `${built} of ${total} ${total === 1 ? "functionality" : "functionalities"} built · ` : "";
|
|
550
554
|
// Short, never cut off: what for, and how, is under the first `!`.
|
|
551
|
-
const status = page.stuck ? style
|
|
555
|
+
const status = page.stuck ? needs(style)(`${LADDER.needsYou} needs you`) : page.status;
|
|
552
556
|
// The module's file heads its column, under this line.
|
|
553
557
|
return truncateToWidth(`${style.heading("regen")}${strip !== "" ? ` ${strip}` : ""} ${count}${status}${waiting}`, width);
|
|
554
558
|
}
|
package/dist/release-cli.js
CHANGED
|
@@ -4,6 +4,7 @@ import { configForSpec, modelSpec, projectModels, readProjectConfig } from "./co
|
|
|
4
4
|
import { createDeployState } from "./release-tools.js";
|
|
5
5
|
import { runReleaseAgent } from "./release-agent.js";
|
|
6
6
|
import { listDeployed, runningDeployed, stopDeployed } from "./regenerate/release.js";
|
|
7
|
+
import { c } from "./module-report.js";
|
|
7
8
|
const USAGE = `usage: turri release [file.plain] [options]
|
|
8
9
|
|
|
9
10
|
Releases the module on top of the spec's requires chain, which holds the code of the ones below, and
|
|
@@ -20,12 +21,6 @@ harness-model (the agent's model).
|
|
|
20
21
|
--in-module <module> the codebase to take, when modules built on this one branch
|
|
21
22
|
--guidance <text> how this project releases it: the scripts to run, in order
|
|
22
23
|
--model <provider/id> the agent's model (default: harness-model)`;
|
|
23
|
-
const c = {
|
|
24
|
-
dim: (s) => `\x1b[2m${s}\x1b[0m`,
|
|
25
|
-
bold: (s) => `\x1b[1m${s}\x1b[0m`,
|
|
26
|
-
green: (s) => `\x1b[32m${s}\x1b[0m`,
|
|
27
|
-
cyan: (s) => `\x1b[36m${s}\x1b[0m`,
|
|
28
|
-
};
|
|
29
24
|
const PROBLEMS = "If you are having problems, use chat mode to deploy: run turri and ask to try it.";
|
|
30
25
|
function parseArgs(argv) {
|
|
31
26
|
const positional = [];
|
|
@@ -76,7 +71,7 @@ function followLogs() {
|
|
|
76
71
|
closeSync(fd);
|
|
77
72
|
offsets.set(item.log, size);
|
|
78
73
|
for (const line of buffer.toString("utf8").split("\n").filter((l) => l !== ""))
|
|
79
|
-
console.log(`${c.
|
|
74
|
+
console.log(`${c.accent(`[${item.name}]`)} ${line}`);
|
|
80
75
|
}
|
|
81
76
|
if (running.length === 0) {
|
|
82
77
|
clearInterval(timer);
|
|
@@ -119,7 +114,7 @@ async function main() {
|
|
|
119
114
|
console.log(`\n\n${c.dim(PROBLEMS)}`);
|
|
120
115
|
if (runningDeployed().length === 0)
|
|
121
116
|
process.exit(0);
|
|
122
|
-
console.log(c.
|
|
117
|
+
console.log(c.success(c.bold("\nRunning - Ctrl-C stops it.")) + c.dim(" Its output follows.\n"));
|
|
123
118
|
await followLogs();
|
|
124
119
|
process.exit(0);
|
|
125
120
|
}
|
package/dist/validate-cli.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { checkPlainTargets, formatCheckResults } from "./plain/check.js";
|
|
2
|
+
import { c } from "./module-report.js";
|
|
2
3
|
const USAGE = `usage: turri validate <file.plain | directory> [options]
|
|
3
4
|
|
|
4
5
|
Validates a ***plain specification statically: parses it, resolves its imports and
|
|
@@ -8,12 +9,6 @@ required modules, and reports every error without rendering anything.
|
|
|
8
9
|
--json machine-readable output
|
|
9
10
|
|
|
10
11
|
PLAIN_TEMPLATE_DIRS colon-separated template directories`;
|
|
11
|
-
const c = {
|
|
12
|
-
dim: (s) => `\x1b[2m${s}\x1b[0m`,
|
|
13
|
-
bold: (s) => `\x1b[1m${s}\x1b[0m`,
|
|
14
|
-
green: (s) => `\x1b[32m${s}\x1b[0m`,
|
|
15
|
-
red: (s) => `\x1b[31m${s}\x1b[0m`,
|
|
16
|
-
};
|
|
17
12
|
const argv = process.argv.slice(2);
|
|
18
13
|
const templateDirs = [];
|
|
19
14
|
const positional = [];
|
|
@@ -43,10 +38,10 @@ if (json) {
|
|
|
43
38
|
else {
|
|
44
39
|
const output = formatCheckResults(results)
|
|
45
40
|
.split("\n")
|
|
46
|
-
.map((line) => line.startsWith("PASS") ? c.
|
|
41
|
+
.map((line) => line.startsWith("PASS") ? c.success(line) : line.startsWith("FAIL") ? c.error(line) : c.dim(line))
|
|
47
42
|
.join("\n");
|
|
48
43
|
console.log(output);
|
|
49
44
|
const failed = results.filter((r) => !r.ok).length;
|
|
50
|
-
console.log(`\n${failed === 0 ? c.
|
|
45
|
+
console.log(`\n${failed === 0 ? c.success(c.bold("all specs valid")) : c.error(c.bold(`${failed} of ${results.length} failed`))}`);
|
|
51
46
|
}
|
|
52
47
|
process.exit(results.every((r) => r.ok) ? 0 : 1);
|
package/docs/README.md
CHANGED
|
@@ -1,20 +1,13 @@
|
|
|
1
1
|
# turri documentation
|
|
2
2
|
|
|
3
|
-
turri implements regenerative software: the \*\*\*plain specs are the durable definition of
|
|
4
|
-
what a system does, and the code is generated from them, validated against them, and regenerated
|
|
5
|
-
as they change. The [project README](../README.md) says why, and how to install and start; these
|
|
6
|
-
pages go into each part.
|
|
3
|
+
turri implements regenerative software: the \*\*\*plain specs are the durable definition of what a system does, and the code is generated from them, validated against them, and regenerated as they change. The [project README](../README.md) says why, and how to install and start; these pages go into each part.
|
|
7
4
|
|
|
8
5
|
## Start here
|
|
9
6
|
|
|
10
|
-
1. [Installation](installation.md) - get a machine ready: `turri doctor`, and what turri needs of
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
3. [Authoring \*\*\*plain](authoring-plain.md) - writing the specs: what a spec is made of, the
|
|
15
|
-
authoring rules, the skills that write and fix specs.
|
|
16
|
-
4. [Rendering](regenerating.md) - turning the specs into code: what each build does, in which order,
|
|
17
|
-
and what has to pass before it moves on.
|
|
7
|
+
1. [Installation](installation.md) - get a machine ready: `turri doctor`, and what turri needs of this machine.
|
|
8
|
+
2. [Using turri](using.md) - the conversation, the skills and tools, background jobs and their logs, choosing the model that renders, the command line.
|
|
9
|
+
3. [Authoring \*\*\*plain](authoring-plain.md) - writing the specs: what a spec is made of, the authoring rules, the skills that write and fix specs.
|
|
10
|
+
4. [Rendering](regenerating.md) - turning the specs into code: what each build does, in which order, and what has to pass before it moves on.
|
|
18
11
|
|
|
19
12
|
## By question
|
|
20
13
|
|
package/docs/authoring-plain.md
CHANGED
|
@@ -1,14 +1,8 @@
|
|
|
1
1
|
# Authoring \*\*\*plain
|
|
2
2
|
|
|
3
|
-
Rendering turns a spec into code; authoring is the half that decides what gets built. The code is
|
|
4
|
-
only ever as good as the spec - a fact in the wrong section is silently ignored, a functional spec
|
|
5
|
-
that is too big comes out half-built, two specs that contradict each other break each other's
|
|
6
|
-
tests. So this repo carries as much for writing specs as for rendering them: the authoring rules,
|
|
7
|
-
a library of skills that apply them, and independent review of what they write.
|
|
3
|
+
Rendering turns a spec into code; authoring is the half that decides what gets built. The code is only ever as good as the spec - a fact in the wrong section is silently ignored, a functional spec that is too big comes out half-built, two specs that contradict each other break each other's tests. So this repo carries as much for writing specs as for rendering them: the authoring rules, a library of skills that apply them, and independent review of what they write.
|
|
8
4
|
|
|
9
|
-
The rule underneath all of it: **the specs are the source of truth**. The generated code in
|
|
10
|
-
`plain_modules/` is never edited by hand. A bug is fixed in the spec, and a change that is wanted
|
|
11
|
-
goes into the spec - that is what persists and what the next render builds from.
|
|
5
|
+
The rule underneath all of it: **the specs are the source of truth**. The generated code in `plain_modules/` is never edited by hand. A bug is fixed in the spec, and a change that is wanted goes into the spec - that is what persists and what the next render builds from.
|
|
12
6
|
|
|
13
7
|
## A \*\*\*plain project
|
|
14
8
|
|
|
@@ -33,8 +27,7 @@ A `.plain` file is one module: optional YAML frontmatter, then up to four sectio
|
|
|
33
27
|
| `***test reqs***` | everything about the black-box conformance tests: framework, how they run, mocking | `- :ConformanceTests: use pytest and call the CLI as a subprocess.` |
|
|
34
28
|
| `***functional specs***` | WHAT it does - observable, language-agnostic behaviour, one functionality per item, in the order they are built | `- A :User: can add a :Task:.` |
|
|
35
29
|
|
|
36
|
-
Under a functional spec, a nested `***acceptance tests***` list states how that one
|
|
37
|
-
functionality is checked end to end.
|
|
30
|
+
Under a functional spec, a nested `***acceptance tests***` list states how that one functionality is checked end to end.
|
|
38
31
|
|
|
39
32
|
The frontmatter connects modules:
|
|
40
33
|
|
|
@@ -46,20 +39,13 @@ The frontmatter connects modules:
|
|
|
46
39
|
| `exported_concepts` | which concepts a module that `requires` this one may use |
|
|
47
40
|
| `required_concepts` | concepts an import module expects the importing module to define |
|
|
48
41
|
|
|
49
|
-
Beyond that: a spec links to files under `resources/` with markdown link syntax, and the file's
|
|
50
|
-
contents go to the renderer with the spec; lines starting with `>` are comments for people; and
|
|
51
|
-
files are processed with Liquid first, so a template can be included with parameters.
|
|
42
|
+
Beyond that: a spec links to files under `resources/` with markdown link syntax, and the file's contents go to the renderer with the spec; lines starting with `>` are comments for people; and files are processed with Liquid first, so a template can be included with parameters.
|
|
52
43
|
|
|
53
|
-
**Section ownership is the rule that bites.** The renderer reads each kind of fact only from the
|
|
54
|
-
section that owns it. A tech choice written into a functional spec, or a behaviour written into
|
|
55
|
-
the implementation reqs, is not flagged - it is simply not read.
|
|
44
|
+
**Section ownership is the rule that bites.** The renderer reads each kind of fact only from the section that owns it. A tech choice written into a functional spec, or a behaviour written into the implementation reqs, is not flagged - it is simply not read.
|
|
56
45
|
|
|
57
46
|
## The standard library
|
|
58
47
|
|
|
59
|
-
turri ships a standard library of import modules in `stdlib/` that settle everything about a
|
|
60
|
-
generic piece of software on one stack except what it does: its language and framework, layout,
|
|
61
|
-
configuration, error handling, logging, shutdown, and how it is unit and conformance tested. A spec
|
|
62
|
-
imports one and writes only its own concepts and functional specs:
|
|
48
|
+
turri ships a standard library of import modules in `stdlib/` that settle everything about a generic piece of software on one stack except what it does: its language and framework, layout, configuration, error handling, logging, shutdown, and how it is unit and conformance tested. A spec imports one and writes only its own concepts and functional specs:
|
|
63
49
|
|
|
64
50
|
```plain
|
|
65
51
|
---
|
|
@@ -82,13 +68,7 @@ import:
|
|
|
82
68
|
|
|
83
69
|
It is built in three layers, and a stack imports the other two:
|
|
84
70
|
|
|
85
|
-
- **The language** - `stdlib/lang/python`, `typescript`, `java`, `go`, `rust`: the version, the
|
|
86
|
-
dependency manager, formatting, linting and type checking (ruff and mypy for Python; Prettier, ESLint
|
|
87
|
-
and `tsc --noEmit` for TypeScript), how a failure is reported (an exception, a returned error, a
|
|
88
|
-
`Result`), the logging library, and the unit test framework and layout. A module on a stack the library does not have can import its language alone.
|
|
89
|
-
The formatter, linter and type checker a language names are enforced by the testing scripts, which
|
|
90
|
-
run them with the tests - the unit testing script on the code, the conformance testing script on the
|
|
91
|
-
tests - and count them as the tests (see [the static checks](regenerating.md#the-static-checks)).
|
|
71
|
+
- **The language** - `stdlib/lang/python`, `typescript`, `java`, `go`, `rust`: the version, the dependency manager, formatting, linting and type checking (ruff and mypy for Python; Prettier, ESLint and `tsc --noEmit` for TypeScript), how a failure is reported (an exception, a returned error, a `Result`), the logging library, and the unit test framework and layout. A module on a stack the library does not have can import its language alone. The formatter, linter and type checker a language names are enforced by the testing scripts, which run them with the tests - the unit testing script on the code, the conformance testing script on the tests - and count them as the tests (see [the static checks](regenerating.md#the-static-checks)).
|
|
92
72
|
- **The kind's `common`** - what every stack of a kind shares:
|
|
93
73
|
|
|
94
74
|
| kind | settles |
|
|
@@ -98,52 +78,23 @@ It is built in three layers, and a stack imports the other two:
|
|
|
98
78
|
| cli | settings from flags, then `<TOOL>_<SETTING>`, then defaults; the work apart from parsing; errors as `Error: <message>` on standard error, exit 0, 1, 2 for a usage error, 130 on Ctrl-C, a stack trace only with `--debug`; logs on standard error, warnings by default, `--verbose`, `--debug` |
|
|
99
79
|
| library | one public entry point; one base error type, with a dependency's errors wrapped; logs through the language's logging with no output set up; no environment variables, output or global state |
|
|
100
80
|
|
|
101
|
-
- **The stack** - the framework and how it does each of those: for `python-flask`, an application
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
-
|
|
114
|
-
conformance tests are written or run for it. Turning them on later is adding that one import.
|
|
115
|
-
- **A reserved name.** An import that starts with `stdlib/` is always turri's template, never
|
|
116
|
-
a file of the project's. A project with a module at such a path - `stdlib/backend/python-fastapi.plain`
|
|
117
|
-
beside the spec, in its `template/` folder or in `PLAIN_TEMPLATE_DIRS` - gets an error saying the
|
|
118
|
-
name is reserved, so which one a spec builds on never depends on what is on disk beside it.
|
|
119
|
-
- **No concepts.** Its requirements are on the predefined `:Implementation:`, `:UnitTests:` and
|
|
120
|
-
`:ConformanceTests:` only, so it never collides with a spec's own definitions.
|
|
121
|
-
- **Pinned to a toolchain.** It names its stack's toolchain - Python 3.12, Node.js 24, Java 21, Go 1.25, Rust -
|
|
122
|
-
which the agents use from this machine; `turri doctor` says when the machine lacks it.
|
|
123
|
-
- **Not storage, not the testing scripts.** A module with a database says which on top of its
|
|
124
|
-
stack. It still needs its unit testing script, and a conformance testing script when it has
|
|
125
|
-
conformance tests.
|
|
126
|
-
- **Known to the agents.** The `stdlib` rule, in every spec-writing agent's system prompt, says when
|
|
127
|
-
to import which, and lists every template with what it is and the folder it is in, read from
|
|
128
|
-
`stdlib/` when the rules load. `spawn` and `brood` import a stack when the one chosen
|
|
129
|
-
is in it, and its conformance template when conformance tests are on.
|
|
130
|
-
- **Only what this machine can build.** The agents use this machine's toolchains and install none, so
|
|
131
|
-
a Go stack on a laptop without Go stops at its first build. Every spec-writing agent and the
|
|
132
|
-
conversation are told which of the standard library's languages the machine can build now - Python
|
|
133
|
-
needs `uv`, TypeScript and every frontend `node` and `npm`, Go `go`, Rust `cargo`, Java a JDK
|
|
134
|
-
(`src/stack.ts`, `toolchainsBlock`) - and `spawn` and `brood` offer, and choose, only stacks in those.
|
|
135
|
-
A stack the person names in a language the machine lacks is not refused: they are told what to install
|
|
136
|
-
first, and that `turri doctor` checks it.
|
|
137
|
-
|
|
138
|
-
A requirement beyond the stack - storage, an outside service - is added on top of it. A module that
|
|
139
|
-
differs from its stack on something the stack settles - another framework, another layout - writes
|
|
140
|
-
its own instead: an import cannot be taken back in part.
|
|
81
|
+
- **The stack** - the framework and how it does each of those: for `python-flask`, an application factory, one Blueprint per resource, Pydantic for bodies, `@app.errorhandler` for the error body, structlog JSON logs with the request id bound in a hook, Gunicorn. For `go-cobra`, `RunE` commands with Cobra's own error printing off, so `main` alone writes the error and picks the exit code, and slog on standard error.
|
|
82
|
+
|
|
83
|
+
Each conformance template imports its kind's `conformance/common`: a backend's tests start the built service on a free port and reach it over HTTP only, checking a failure by status and `error.code`; a frontend's run the served build in Chromium through Playwright with the backend stood in; a command-line tool's run the built tool as a process; a library's go through its public entry point.
|
|
84
|
+
|
|
85
|
+
- **Conformance tests apart.** A spec without the conformance template has no test reqs, so no conformance tests are written or run for it. Turning them on later is adding that one import.
|
|
86
|
+
- **A reserved name.** An import that starts with `stdlib/` is always turri's template, never a file of the project's. A project with a module at such a path - `stdlib/backend/python-fastapi.plain` beside the spec, in its `template/` folder or in `PLAIN_TEMPLATE_DIRS` - gets an error saying the name is reserved, so which one a spec builds on never depends on what is on disk beside it.
|
|
87
|
+
- **No concepts.** Its requirements are on the predefined `:Implementation:`, `:UnitTests:` and `:ConformanceTests:` only, so it never collides with a spec's own definitions.
|
|
88
|
+
- **Pinned to a toolchain.** It names its stack's toolchain - Python 3.12, Node.js 24, Java 21, Go 1.25, Rust - which the agents use from this machine; `turri doctor` says when the machine lacks it.
|
|
89
|
+
- **Not storage, not the testing scripts.** A module with a database says which on top of its stack. It still needs its unit testing script, and a conformance testing script when it has conformance tests.
|
|
90
|
+
- **Known to the agents.** The `stdlib` rule, in every spec-writing agent's system prompt, says when to import which, and lists every template with what it is and the folder it is in, read from `stdlib/` when the rules load. `spawn` and `brood` import a stack when the one chosen is in it, and its conformance template when conformance tests are on.
|
|
91
|
+
- **Only what this machine can build.** The agents use this machine's toolchains and install none, so a Go stack on a laptop without Go stops at its first build. Every spec-writing agent and the conversation are told which of the standard library's languages the machine can build now - Python needs `uv`, TypeScript and every frontend `node` and `npm`, Go `go`, Rust `cargo`, Java a JDK (`src/stack.ts`, `toolchainsBlock`) - and `spawn` and `brood` offer, and choose, only stacks in those. A stack the person names in a language the machine lacks is not refused: they are told what to install first, and that `turri doctor` checks it.
|
|
92
|
+
|
|
93
|
+
A requirement beyond the stack - storage, an outside service - is added on top of it. A module that differs from its stack on something the stack settles - another framework, another layout - writes its own instead: an import cannot be taken back in part.
|
|
141
94
|
|
|
142
95
|
## The rules
|
|
143
96
|
|
|
144
|
-
`genome/rules/` holds the authoring rules. They are in the system prompt of every agent that
|
|
145
|
-
writes specs - the conversation, the interview skills, the reviewers - so they are not something
|
|
146
|
-
an agent has to decide to go and read.
|
|
97
|
+
`genome/rules/` holds the authoring rules. They are in the system prompt of every agent that writes specs - the conversation, the interview skills, the reviewers - so they are not something an agent has to decide to go and read.
|
|
147
98
|
|
|
148
99
|
| rule | covers |
|
|
149
100
|
| --- | --- |
|
|
@@ -158,68 +109,39 @@ an agent has to decide to go and read.
|
|
|
158
109
|
| `import-modules.md`, `requires-modules.md` | the two ways modules build on each other |
|
|
159
110
|
| `exported-concepts.md`, `required-concepts.md` | which concepts cross module boundaries |
|
|
160
111
|
| `integrations.md` (+ standalone, embedded, embedded testing) | specs for REST API integrations, deployed standalone or embedded in an existing codebase |
|
|
161
|
-
| `turri-regeneration.md` |
|
|
112
|
+
| `turri-regeneration.md` | turri's model of itself: a spec change regenerated as a spec diff, wherever it sits, in the chain's tip; what each kind of change sets off; what a coding agent sees; how bad code is fixed; where the cost is - so a spec is placed by what it means, never to spare regeneration |
|
|
162
113
|
| `stdlib.md` | the standard library: when to import which template, and every template with what it is and where it is, read from `stdlib/` |
|
|
163
114
|
|
|
164
115
|
The ones that shape a spec most:
|
|
165
116
|
|
|
166
|
-
- **One functionality is at most ~200 lines of code.** A bigger one is split before it is written
|
|
167
|
-
|
|
168
|
-
- **
|
|
169
|
-
|
|
170
|
-
- **
|
|
171
|
-
adding tasks", not "The system should…".
|
|
172
|
-
- **Nothing a test cannot check.** No `robust`, `seamless`, `as appropriate`; state the checkable
|
|
173
|
-
fact or cut the word.
|
|
174
|
-
- **No two specs contradict each other.** A conflict is found and resolved when a spec is added,
|
|
175
|
-
not when its tests fail.
|
|
117
|
+
- **One functionality is at most ~200 lines of code.** A bigger one is split before it is written (`analyze-if-func-spec-too-complex`, `break-down-func-spec`).
|
|
118
|
+
- **Functional specs are in build order.** Each is rendered on top of the ones before it - which is what a spec may assume, not what a change costs: a spec inserted anywhere is regenerated alone.
|
|
119
|
+
- **They state facts, with a concrete subject.** "A :User: can add a :Task:" - not "Implement adding tasks", not "The system should…".
|
|
120
|
+
- **Nothing a test cannot check.** No `robust`, `seamless`, `as appropriate`; state the checkable fact or cut the word.
|
|
121
|
+
- **No two specs contradict each other.** A conflict is found and resolved when a spec is added, not when its tests fail.
|
|
176
122
|
|
|
177
123
|
## Writing specs with turri
|
|
178
124
|
|
|
179
|
-
In a conversation (`turri`), say what you want and the matching skill runs. Two build a
|
|
180
|
-
project from scratch, and write each spec change as a git diff that is built in the background the
|
|
181
|
-
moment it is written. `brood` interviews you one question at a time and writes the specs as
|
|
182
|
-
you answer; `spawn`, its quick twin, asks only what you want to build and which stack - or leaves the stack
|
|
183
|
-
to it - and writes the rest on its own:
|
|
125
|
+
In a conversation (`turri`), say what you want and the matching skill runs. Two build a project from scratch, and write each spec change as a git diff that is built in the background the moment it is written. `brood` interviews you one question at a time and writes the specs as you answer; `spawn`, its quick twin, asks only what you want to build and which stack - or leaves the stack to it - and writes the rest on its own:
|
|
184
126
|
|
|
185
127
|
| skill | phases | how a spec change is written |
|
|
186
128
|
| --- | --- | --- |
|
|
187
129
|
| `brood` | intent → stack → testing → product, feature by feature | first, how many questions the stack and product phases may ask - light 3, medium 5, thorough 10 or best judgement, broader with fewer, covering the same ground - and what they leave open turri decides itself, each decision a spec change of its own; every product answer is still a spec change, built before the next question; in testing, whether there are conformance tests, their `***test reqs***` and script - written as files, building nothing; then one spec change per answer, across whatever sections it needs, as a git diff with `write_spec_diff`, rendered as a render spec diff job and conformance tested as it is built |
|
|
188
130
|
| `spawn` | intent → groundwork → product | only the intent and the stack are asked - the stack one question, `You choose` first, skipped when the user named one; the stack and the unit tests are written as files, with conformance tests off unless the user asks for them; then one spec change per feature, in build order, as a git diff with `write_spec_diff`, one after another with no answer between them. Changes after it are asked for in the conversation, as spec diffs, and what is left to build is worked out again from the spec |
|
|
189
131
|
|
|
190
|
-
`mature` starts from a spec you already have, rough or hand-written, and runs on its own until
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
spec leaves open or unclear - sent back again and again, the judge going back and forth, a spec
|
|
194
|
-
conflict - it writes what the spec should say as a spec diff, stops that build, and renders it again.
|
|
195
|
-
What the builds had to decide themselves - their assumptions, pitfalls, a failed build's diagnosis -
|
|
196
|
-
goes into the spec the same way, when a user, a caller or a test can see it. It never asks: it takes
|
|
197
|
-
the reading that fits the rest of the spec, and marks each such decision in its report. It leaves
|
|
198
|
-
code, environment and turri problems to the builds, and stops when a functionality keeps
|
|
199
|
-
struggling after the spec was clarified, when two cycles change nothing, or after twenty.
|
|
200
|
-
|
|
201
|
-
For an existing project, `bud` works the same way, scoped to one feature: intent → scope → product.
|
|
202
|
-
It first asks how many questions the product phase may ask - light, medium, thorough or best
|
|
203
|
-
judgement, as `brood` does - then a short feature-intent interview, then product questions - a
|
|
204
|
-
trigger and its outcome, an edge case, a rule - and every answer is a spec change, a git diff
|
|
205
|
-
written with `write_spec_diff` and built the moment it is written, before the next question. What
|
|
206
|
-
the questions leave open it decides itself, a spec change each. A change to something already
|
|
207
|
-
specified, built or still to build, is put to the user first and is a spec diff too: what is left to
|
|
208
|
-
build is worked out again from the spec. It ends on the person's confirmation of a short summary of
|
|
209
|
-
the feature's changes.
|
|
132
|
+
`mature` starts from a spec you already have, rough or hand-written, and runs on its own until it is fully rendered - every functionality built from the spec as it stands, its tests passing. It renders what is not built and watches the agents building it; when one struggles on something the spec leaves open or unclear - sent back again and again, the judge going back and forth, a spec conflict - it writes what the spec should say as a spec diff, stops that build, and renders it again. What the builds had to decide themselves - their assumptions, pitfalls, a failed build's diagnosis - goes into the spec the same way, when a user, a caller or a test can see it. It never asks: it takes the reading that fits the rest of the spec, and marks each such decision in its report. It leaves code, environment and turri problems to the builds, and stops when a functionality keeps struggling after the spec was clarified, when two cycles change nothing, or after twenty.
|
|
133
|
+
|
|
134
|
+
For an existing project, `bud` works the same way, scoped to one feature: intent → scope → product. It first asks how many questions the product phase may ask - light, medium, thorough or best judgement, as `brood` does - then a short feature-intent interview, then product questions - a trigger and its outcome, an edge case, a rule - and every answer is a spec change, a git diff written with `write_spec_diff` and built the moment it is written, before the next question. What the questions leave open it decides itself, a spec change each. A change to something already specified, built or still to build, is put to the user first and is a spec diff too: what is left to build is worked out again from the spec. It ends on the person's confirmation of a short summary of the feature's changes.
|
|
210
135
|
|
|
211
136
|
What keeps the interview honest is the harness, not the interviewer:
|
|
212
137
|
|
|
213
|
-
- **Every question offers choices** (`ask_user` refuses a question without them), and each
|
|
214
|
-
|
|
215
|
-
- **Every answer is written before the next question.** In the product phase of `brood` and `bud`, `ask_user` refuses
|
|
216
|
-
a new question until the last answer is a spec change - see [skills and gates](skills-and-gates.md).
|
|
138
|
+
- **Every question offers choices** (`ask_user` refuses a question without them), and each answer maps to one spec change.
|
|
139
|
+
- **Every answer is written before the next question.** In the product phase of `brood` and `bud`, `ask_user` refuses a new question until the last answer is a spec change - see [skills and gates](skills-and-gates.md).
|
|
217
140
|
- **Each phase ends on the person's confirmation** of a short summary, not on the interviewer's word.
|
|
218
141
|
|
|
219
142
|
## The skills
|
|
220
143
|
|
|
221
|
-
Every skill in `genome/skills/` can be run from a conversation; most are also used by the bigger ones. A project's own,
|
|
222
|
-
in `.turri/skills/` or `.agents/skills/`, can be too (see [skills and gates](skills-and-gates.md#a-projects-own-skills)).
|
|
144
|
+
Every skill in `genome/skills/` can be run from a conversation; most are also used by the bigger ones. A project's own, in `.turri/skills/` or `.agents/skills/`, can be too (see [skills and gates](skills-and-gates.md#a-projects-own-skills)).
|
|
223
145
|
|
|
224
146
|
| to… | skills |
|
|
225
147
|
| --- | --- |
|
|
@@ -231,8 +153,7 @@ in `.turri/skills/` or `.agents/skills/`, can be too (see [skills and gates](ski
|
|
|
231
153
|
| restructure modules | `create-import-module`, `create-requires-module`, `refactor-module`, `consolidate-concepts` |
|
|
232
154
|
| set up the project to build | `implement-unit-testing-script`, `implement-conformance-testing-script`, `check-plain-env` |
|
|
233
155
|
|
|
234
|
-
`debug-specs` is the one for a running application that misbehaves: it reads the generated code
|
|
235
|
-
to find the cause, traces it back to the spec, and fixes only the `.plain` files.
|
|
156
|
+
`debug-specs` is the one for a running application that misbehaves: it reads the generated code to find the cause, traces it back to the spec, and fixes only the `.plain` files.
|
|
236
157
|
|
|
237
158
|
## Checking a spec
|
|
238
159
|
|
|
@@ -240,11 +161,6 @@ to find the cause, traces it back to the spec, and fixes only the `.plain` files
|
|
|
240
161
|
turri validate app.plain # or validate in a conversation
|
|
241
162
|
```
|
|
242
163
|
|
|
243
|
-
It parses the spec with this repo's own parser, resolves its imports and required modules, and
|
|
244
|
-
reports every syntax, concept and structure error with its line - no model, no API key. The
|
|
245
|
-
interview skills run it before a phase ends, and `write_spec_diff`
|
|
246
|
-
refuses a change that leaves a file that does not parse. Pass the project directory to check every
|
|
247
|
-
module at once.
|
|
164
|
+
It parses the spec with this repo's own parser, resolves its imports and required modules, and reports every syntax, concept and structure error with its line - no model, no API key. The interview skills run it before a phase ends, and `write_spec_diff` refuses a change that leaves a file that does not parse. Pass the project directory to check every module at once.
|
|
248
165
|
|
|
249
|
-
What `check` cannot see - a fact in the wrong section, a spec that is too big, two that conflict,
|
|
250
|
-
wording no test can check - is what the rules, the skills and the reviewers are for.
|
|
166
|
+
What `check` cannot see - a fact in the wrong section, a spec that is too big, two that conflict, wording no test can check - is what the rules, the skills and the reviewers are for.
|