@turri-dev/cli 0.1.4-next.67734c0 → 0.1.4-next.8254fbc
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 +1 -1
- 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/dark-12.png +0 -0
- package/assets/jellyfish/dark-13.png +0 -0
- package/assets/jellyfish/dark-14.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/assets/jellyfish/light-12.png +0 -0
- package/assets/jellyfish/light-13.png +0 -0
- package/assets/jellyfish/light-14.png +0 -0
- package/dist/brand.js +7 -7
- package/dist/cultivate-cli.js +16 -23
- package/dist/extension.js +4 -4
- package/dist/loop-cli.js +8 -8
- package/dist/module-report.js +5 -4
- package/dist/regenerate/plain-palette.js +51 -36
- 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/images/how-turri-works.svg +29 -31
- package/docs/images/turri-banner.png +0 -0
- package/package.json +1 -1
|
@@ -2,79 +2,80 @@
|
|
|
2
2
|
"$schema": "https://raw.githubusercontent.com/earendil-works/pi/main/packages/coding-agent/src/modes/interactive/theme/theme-schema.json",
|
|
3
3
|
"name": "turri-dark",
|
|
4
4
|
"vars": {
|
|
5
|
-
"
|
|
6
|
-
"
|
|
7
|
-
"
|
|
8
|
-
"
|
|
9
|
-
"
|
|
10
|
-
"
|
|
11
|
-
"
|
|
12
|
-
"
|
|
13
|
-
"
|
|
14
|
-
"
|
|
15
|
-
"
|
|
5
|
+
"abyss": "#0a0f1a",
|
|
6
|
+
"blueWhale": "#1e3442",
|
|
7
|
+
"lines": "#22344a",
|
|
8
|
+
"path": "#3e5a6b",
|
|
9
|
+
"grauzone": "#85a3b2",
|
|
10
|
+
"white": "#f4f7f9",
|
|
11
|
+
"siestaTan": "#e9d8c8",
|
|
12
|
+
"pink": "#ff5c8d",
|
|
13
|
+
"pinkHover": "#ff7fa6",
|
|
14
|
+
"neon": "#5ce1e6",
|
|
15
|
+
"neonHover": "#8eebee",
|
|
16
|
+
"orange": "#ff7a3d"
|
|
16
17
|
},
|
|
17
18
|
"colors": {
|
|
18
|
-
"accent": "
|
|
19
|
-
"border": "
|
|
20
|
-
"borderAccent": "
|
|
21
|
-
"borderMuted": "
|
|
22
|
-
"success": "
|
|
23
|
-
"error": "
|
|
24
|
-
"warning": "
|
|
25
|
-
"muted": "
|
|
26
|
-
"dim": "
|
|
27
|
-
"text": "
|
|
28
|
-
"thinkingText": "
|
|
29
|
-
"selectedBg": "
|
|
30
|
-
"scrollbarTrack": "
|
|
31
|
-
"scrollbarThumb": "
|
|
32
|
-
"searchMatchBg": "
|
|
33
|
-
"searchMatchText": "
|
|
19
|
+
"accent": "neon",
|
|
20
|
+
"border": "path",
|
|
21
|
+
"borderAccent": "neon",
|
|
22
|
+
"borderMuted": "lines",
|
|
23
|
+
"success": "pink",
|
|
24
|
+
"error": "orange",
|
|
25
|
+
"warning": "orange",
|
|
26
|
+
"muted": "grauzone",
|
|
27
|
+
"dim": "path",
|
|
28
|
+
"text": "grauzone",
|
|
29
|
+
"thinkingText": "grauzone",
|
|
30
|
+
"selectedBg": "blueWhale",
|
|
31
|
+
"scrollbarTrack": "lines",
|
|
32
|
+
"scrollbarThumb": "path",
|
|
33
|
+
"searchMatchBg": "neon",
|
|
34
|
+
"searchMatchText": "abyss",
|
|
34
35
|
"userMessageBg": "",
|
|
35
|
-
"userMessageText": "
|
|
36
|
+
"userMessageText": "white",
|
|
36
37
|
"customMessageBg": "",
|
|
37
|
-
"customMessageText": "
|
|
38
|
-
"customMessageLabel": "
|
|
38
|
+
"customMessageText": "grauzone",
|
|
39
|
+
"customMessageLabel": "neon",
|
|
39
40
|
"toolPendingBg": "",
|
|
40
41
|
"toolSuccessBg": "",
|
|
41
42
|
"toolErrorBg": "",
|
|
42
|
-
"toolTitle": "
|
|
43
|
-
"toolOutput": "
|
|
44
|
-
"mdHeading": "
|
|
45
|
-
"mdLink": "
|
|
46
|
-
"mdLinkUrl": "
|
|
47
|
-
"mdCode": "
|
|
48
|
-
"mdCodeBlock": "
|
|
49
|
-
"mdCodeBlockBorder": "
|
|
50
|
-
"mdQuote": "
|
|
51
|
-
"mdQuoteBorder": "
|
|
52
|
-
"mdHr": "
|
|
53
|
-
"mdListBullet": "
|
|
54
|
-
"toolDiffAdded": "
|
|
55
|
-
"toolDiffRemoved": "
|
|
56
|
-
"toolDiffContext": "
|
|
57
|
-
"syntaxComment": "
|
|
58
|
-
"syntaxKeyword": "
|
|
59
|
-
"syntaxFunction": "
|
|
60
|
-
"syntaxVariable": "
|
|
61
|
-
"syntaxString": "
|
|
62
|
-
"syntaxNumber": "
|
|
63
|
-
"syntaxType": "
|
|
64
|
-
"syntaxOperator": "
|
|
65
|
-
"syntaxPunctuation": "
|
|
66
|
-
"thinkingOff": "
|
|
67
|
-
"thinkingMinimal": "
|
|
68
|
-
"thinkingLow": "
|
|
69
|
-
"thinkingMedium": "
|
|
70
|
-
"thinkingHigh": "
|
|
71
|
-
"thinkingXhigh": "
|
|
72
|
-
"thinkingMax": "
|
|
73
|
-
"bashMode": "
|
|
43
|
+
"toolTitle": "white",
|
|
44
|
+
"toolOutput": "grauzone",
|
|
45
|
+
"mdHeading": "white",
|
|
46
|
+
"mdLink": "neon",
|
|
47
|
+
"mdLinkUrl": "path",
|
|
48
|
+
"mdCode": "siestaTan",
|
|
49
|
+
"mdCodeBlock": "white",
|
|
50
|
+
"mdCodeBlockBorder": "lines",
|
|
51
|
+
"mdQuote": "grauzone",
|
|
52
|
+
"mdQuoteBorder": "path",
|
|
53
|
+
"mdHr": "lines",
|
|
54
|
+
"mdListBullet": "neon",
|
|
55
|
+
"toolDiffAdded": "neon",
|
|
56
|
+
"toolDiffRemoved": "pink",
|
|
57
|
+
"toolDiffContext": "grauzone",
|
|
58
|
+
"syntaxComment": "path",
|
|
59
|
+
"syntaxKeyword": "pinkHover",
|
|
60
|
+
"syntaxFunction": "neon",
|
|
61
|
+
"syntaxVariable": "white",
|
|
62
|
+
"syntaxString": "siestaTan",
|
|
63
|
+
"syntaxNumber": "pinkHover",
|
|
64
|
+
"syntaxType": "neonHover",
|
|
65
|
+
"syntaxOperator": "grauzone",
|
|
66
|
+
"syntaxPunctuation": "path",
|
|
67
|
+
"thinkingOff": "path",
|
|
68
|
+
"thinkingMinimal": "grauzone",
|
|
69
|
+
"thinkingLow": "siestaTan",
|
|
70
|
+
"thinkingMedium": "neonHover",
|
|
71
|
+
"thinkingHigh": "neon",
|
|
72
|
+
"thinkingXhigh": "pinkHover",
|
|
73
|
+
"thinkingMax": "pink",
|
|
74
|
+
"bashMode": "orange"
|
|
74
75
|
},
|
|
75
76
|
"export": {
|
|
76
|
-
"pageBg": "#
|
|
77
|
-
"cardBg": "#
|
|
78
|
-
"infoBg": "#
|
|
77
|
+
"pageBg": "#0a0f1a",
|
|
78
|
+
"cardBg": "#0d1420",
|
|
79
|
+
"infoBg": "#142030"
|
|
79
80
|
}
|
|
80
|
-
}
|
|
81
|
+
}
|
|
@@ -2,81 +2,79 @@
|
|
|
2
2
|
"$schema": "https://raw.githubusercontent.com/earendil-works/pi/main/packages/coding-agent/src/modes/interactive/theme/theme-schema.json",
|
|
3
3
|
"name": "turri-light",
|
|
4
4
|
"vars": {
|
|
5
|
-
"
|
|
6
|
-
"
|
|
7
|
-
"
|
|
8
|
-
"
|
|
9
|
-
"
|
|
10
|
-
"
|
|
11
|
-
"
|
|
12
|
-
"
|
|
13
|
-
"
|
|
14
|
-
"
|
|
15
|
-
"
|
|
16
|
-
"paleGreen": "#eef7f5",
|
|
17
|
-
"paleGray": "#f4f4f2"
|
|
5
|
+
"abyss": "#0a0f1a",
|
|
6
|
+
"blueWhale": "#1e3442",
|
|
7
|
+
"lines": "#22344a",
|
|
8
|
+
"path": "#3e5a6b",
|
|
9
|
+
"grauzone": "#85a3b2",
|
|
10
|
+
"white": "#f4f7f9",
|
|
11
|
+
"siestaTan": "#e9d8c8",
|
|
12
|
+
"eggplant": "#732553",
|
|
13
|
+
"pink": "#ff5c8d",
|
|
14
|
+
"neon": "#5ce1e6",
|
|
15
|
+
"orange": "#ff7a3d"
|
|
18
16
|
},
|
|
19
17
|
"colors": {
|
|
20
|
-
"accent": "
|
|
21
|
-
"border": "
|
|
22
|
-
"borderAccent": "
|
|
23
|
-
"borderMuted": "
|
|
24
|
-
"success": "
|
|
25
|
-
"error": "
|
|
26
|
-
"warning": "
|
|
27
|
-
"muted": "
|
|
28
|
-
"dim": "
|
|
29
|
-
"text": "
|
|
30
|
-
"thinkingText": "
|
|
31
|
-
"selectedBg": "
|
|
32
|
-
"scrollbarTrack": "
|
|
33
|
-
"scrollbarThumb": "
|
|
34
|
-
"searchMatchBg": "
|
|
35
|
-
"searchMatchText": "
|
|
36
|
-
"userMessageBg": "
|
|
37
|
-
"userMessageText": "
|
|
38
|
-
"customMessageBg": "
|
|
39
|
-
"customMessageText": "
|
|
40
|
-
"customMessageLabel": "
|
|
41
|
-
"toolPendingBg": "
|
|
42
|
-
"toolSuccessBg": "
|
|
43
|
-
"toolErrorBg": "
|
|
44
|
-
"toolTitle": "
|
|
45
|
-
"toolOutput": "
|
|
46
|
-
"mdHeading": "
|
|
47
|
-
"mdLink": "
|
|
48
|
-
"mdLinkUrl": "
|
|
49
|
-
"mdCode": "
|
|
50
|
-
"mdCodeBlock": "
|
|
51
|
-
"mdCodeBlockBorder": "
|
|
52
|
-
"mdQuote": "
|
|
53
|
-
"mdQuoteBorder": "
|
|
54
|
-
"mdHr": "
|
|
55
|
-
"mdListBullet": "
|
|
56
|
-
"toolDiffAdded": "
|
|
57
|
-
"toolDiffRemoved": "
|
|
58
|
-
"toolDiffContext": "
|
|
59
|
-
"syntaxComment": "
|
|
60
|
-
"syntaxKeyword": "
|
|
61
|
-
"syntaxFunction": "
|
|
62
|
-
"syntaxVariable": "
|
|
63
|
-
"syntaxString": "
|
|
64
|
-
"syntaxNumber": "
|
|
65
|
-
"syntaxType": "
|
|
66
|
-
"syntaxOperator": "
|
|
67
|
-
"syntaxPunctuation": "
|
|
68
|
-
"thinkingOff": "
|
|
69
|
-
"thinkingMinimal": "
|
|
70
|
-
"thinkingLow": "
|
|
71
|
-
"thinkingMedium": "
|
|
72
|
-
"thinkingHigh": "
|
|
73
|
-
"thinkingXhigh": "
|
|
74
|
-
"thinkingMax": "
|
|
75
|
-
"bashMode": "
|
|
18
|
+
"accent": "eggplant",
|
|
19
|
+
"border": "path",
|
|
20
|
+
"borderAccent": "eggplant",
|
|
21
|
+
"borderMuted": "grauzone",
|
|
22
|
+
"success": "eggplant",
|
|
23
|
+
"error": "orange",
|
|
24
|
+
"warning": "orange",
|
|
25
|
+
"muted": "path",
|
|
26
|
+
"dim": "grauzone",
|
|
27
|
+
"text": "abyss",
|
|
28
|
+
"thinkingText": "path",
|
|
29
|
+
"selectedBg": "siestaTan",
|
|
30
|
+
"scrollbarTrack": "siestaTan",
|
|
31
|
+
"scrollbarThumb": "path",
|
|
32
|
+
"searchMatchBg": "neon",
|
|
33
|
+
"searchMatchText": "abyss",
|
|
34
|
+
"userMessageBg": "white",
|
|
35
|
+
"userMessageText": "abyss",
|
|
36
|
+
"customMessageBg": "",
|
|
37
|
+
"customMessageText": "abyss",
|
|
38
|
+
"customMessageLabel": "eggplant",
|
|
39
|
+
"toolPendingBg": "",
|
|
40
|
+
"toolSuccessBg": "",
|
|
41
|
+
"toolErrorBg": "",
|
|
42
|
+
"toolTitle": "abyss",
|
|
43
|
+
"toolOutput": "path",
|
|
44
|
+
"mdHeading": "eggplant",
|
|
45
|
+
"mdLink": "blueWhale",
|
|
46
|
+
"mdLinkUrl": "path",
|
|
47
|
+
"mdCode": "eggplant",
|
|
48
|
+
"mdCodeBlock": "abyss",
|
|
49
|
+
"mdCodeBlockBorder": "grauzone",
|
|
50
|
+
"mdQuote": "path",
|
|
51
|
+
"mdQuoteBorder": "grauzone",
|
|
52
|
+
"mdHr": "grauzone",
|
|
53
|
+
"mdListBullet": "eggplant",
|
|
54
|
+
"toolDiffAdded": "blueWhale",
|
|
55
|
+
"toolDiffRemoved": "eggplant",
|
|
56
|
+
"toolDiffContext": "path",
|
|
57
|
+
"syntaxComment": "path",
|
|
58
|
+
"syntaxKeyword": "eggplant",
|
|
59
|
+
"syntaxFunction": "blueWhale",
|
|
60
|
+
"syntaxVariable": "abyss",
|
|
61
|
+
"syntaxString": "lines",
|
|
62
|
+
"syntaxNumber": "eggplant",
|
|
63
|
+
"syntaxType": "blueWhale",
|
|
64
|
+
"syntaxOperator": "path",
|
|
65
|
+
"syntaxPunctuation": "path",
|
|
66
|
+
"thinkingOff": "grauzone",
|
|
67
|
+
"thinkingMinimal": "path",
|
|
68
|
+
"thinkingLow": "lines",
|
|
69
|
+
"thinkingMedium": "blueWhale",
|
|
70
|
+
"thinkingHigh": "eggplant",
|
|
71
|
+
"thinkingXhigh": "pink",
|
|
72
|
+
"thinkingMax": "orange",
|
|
73
|
+
"bashMode": "orange"
|
|
76
74
|
},
|
|
77
75
|
"export": {
|
|
78
|
-
"pageBg": "#
|
|
79
|
-
"cardBg": "#
|
|
80
|
-
"infoBg": "#
|
|
76
|
+
"pageBg": "#f4f7f9",
|
|
77
|
+
"cardBg": "#f4f7f9",
|
|
78
|
+
"infoBg": "#e9d8c8"
|
|
81
79
|
}
|
|
82
|
-
}
|
|
80
|
+
}
|
package/README.md
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
<p align="center"><img src="docs/images/turri-banner.png" alt="turri - coding agent with the power of regenerative software:
|
|
1
|
+
<p align="center"><img src="docs/images/turri-banner.png" alt="turri - the first coding agent with the power of regenerative software, beside a pixel jellyfish: npm install -g @turri-dev/cli"></p>
|
|
2
2
|
|
|
3
3
|
<h1 align="center">turri<br>the first coding agent with the power of<br>regenerative software<br> </h1>
|
|
4
4
|
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
package/dist/brand.js
CHANGED
|
@@ -2,8 +2,8 @@ import { existsSync, readFileSync } from "node:fs";
|
|
|
2
2
|
import { homedir } from "node:os";
|
|
3
3
|
import { join } from "node:path";
|
|
4
4
|
/**
|
|
5
|
-
* turri's colours for what its command line prints - the
|
|
6
|
-
*
|
|
5
|
+
* turri's colours for what its command line prints - the design system's, as its themes use them (.pi/themes): on a
|
|
6
|
+
* dark terminal neon and pink, on a light one blue whale and eggplant. Nothing is coloured when the output is not a terminal, or NO_COLOR is set;
|
|
7
7
|
* a terminal without 24-bit colour gets the nearest of its sixteen.
|
|
8
8
|
*/
|
|
9
9
|
/** Whether the terminal is light: COLORFGBG says so (a light background, 7 or 15), or pi's theme is a light one. */
|
|
@@ -35,15 +35,15 @@ function paint(stream = process.stdout) {
|
|
|
35
35
|
};
|
|
36
36
|
return {
|
|
37
37
|
/** turri's accent: what to look at - a heading, a choice's number, the command to run. */
|
|
38
|
-
accent: light ? ink("#
|
|
38
|
+
accent: light ? ink("#1e3442", 34) : ink("#5ce1e6", 96),
|
|
39
39
|
/** Done, and what was written. */
|
|
40
|
-
success: light ? ink("#
|
|
40
|
+
success: light ? ink("#732553", 35) : ink("#ff5c8d", 95),
|
|
41
41
|
/** What is wrong: what stops turri. */
|
|
42
|
-
error:
|
|
42
|
+
error: ink("#ff7a3d", 33),
|
|
43
43
|
/** What works, with limits. */
|
|
44
|
-
warning: ink("#
|
|
44
|
+
warning: ink("#ff7a3d", 33),
|
|
45
45
|
/** What goes with it, and can be skimmed. */
|
|
46
|
-
muted: ink("#
|
|
46
|
+
muted: light ? ink("#3e5a6b", 90) : ink("#85a3b2", 37),
|
|
47
47
|
bold: (text) => (on ? `\x1b[1m${text}\x1b[22m` : text),
|
|
48
48
|
};
|
|
49
49
|
}
|
package/dist/cultivate-cli.js
CHANGED
|
@@ -7,40 +7,33 @@ import { skillRunDir } from "./regenerate/paths.js";
|
|
|
7
7
|
import { loadSkill } from "./skill.js";
|
|
8
8
|
import { discoverSkills } from "./skills.js";
|
|
9
9
|
import { PACKAGE_ROOT } from "./environment.js";
|
|
10
|
-
|
|
11
|
-
dim: (s) => `\x1b[2m${s}\x1b[0m`,
|
|
12
|
-
bold: (s) => `\x1b[1m${s}\x1b[0m`,
|
|
13
|
-
green: (s) => `\x1b[32m${s}\x1b[0m`,
|
|
14
|
-
red: (s) => `\x1b[31m${s}\x1b[0m`,
|
|
15
|
-
yellow: (s) => `\x1b[33m${s}\x1b[0m`,
|
|
16
|
-
cyan: (s) => `\x1b[36m${s}\x1b[0m`,
|
|
17
|
-
};
|
|
10
|
+
import { c } from "./module-report.js";
|
|
18
11
|
const reporter = {
|
|
19
|
-
step: (name, attempt) => console.log(`\n${c.bold(c.
|
|
12
|
+
step: (name, attempt) => console.log(`\n${c.bold(c.accent(`> step ${name}`))} ${c.dim(`attempt ${attempt}`)}`),
|
|
20
13
|
artifact: (path, body) => {
|
|
21
14
|
console.log(c.dim(` ${path}:`));
|
|
22
15
|
for (const line of body.split("\n"))
|
|
23
16
|
console.log(c.dim(` | ${line}`));
|
|
24
17
|
},
|
|
25
18
|
gate: (name, judges) => console.log(judges === 0
|
|
26
|
-
? `${c.bold(c.
|
|
27
|
-
: `${c.bold(c.
|
|
19
|
+
? `${c.bold(c.warning(`> gate ${name}`))} ${c.dim("no artifact - failing without spending judges")}`
|
|
20
|
+
: `${c.bold(c.warning(`> gate ${name}`))} ${c.dim(`${judges} independent judges`)}`),
|
|
28
21
|
livePass: (step, scope, findings) => console.log(findings === 0
|
|
29
22
|
? c.dim(` review ${step}: ${scope} - clean`)
|
|
30
|
-
: ` ${c.
|
|
23
|
+
: ` ${c.warning(`review ${step}`)}: ${scope} ${c.dim(`- ${findings} to fix`)}`),
|
|
31
24
|
liveReview: (name, passes, findings) => console.log(findings === 0
|
|
32
|
-
? `${c.bold(c.
|
|
33
|
-
: `${c.bold(c.
|
|
25
|
+
? `${c.bold(c.warning(`> gate ${name}`))} ${c.dim(`reviewer read along in ${passes} passes, nothing outstanding`)}`
|
|
26
|
+
: `${c.bold(c.warning(`> gate ${name}`))} ${c.dim(`${findings} unresolved from the reviewer - failing without spending judges`)}`),
|
|
34
27
|
judge: (index, model, verdict, violations) => {
|
|
35
|
-
const mark = verdict === "pass" ? c.
|
|
28
|
+
const mark = verdict === "pass" ? c.success("pass") : c.error(verdict);
|
|
36
29
|
console.log(` judge ${index + 1} ${c.dim(`(${model})`)}: ${mark}${violations ? c.dim(` (${violations})`) : ""}`);
|
|
37
30
|
},
|
|
38
31
|
outcome: (name, decision, critique) => {
|
|
39
32
|
const colour = {
|
|
40
|
-
advance: c.
|
|
41
|
-
retry: c.
|
|
42
|
-
escalate: c.
|
|
43
|
-
abort: c.
|
|
33
|
+
advance: c.success,
|
|
34
|
+
retry: c.warning,
|
|
35
|
+
escalate: c.warning,
|
|
36
|
+
abort: c.error,
|
|
44
37
|
};
|
|
45
38
|
console.log(` ${c.bold(colour[decision](decision))} ${c.dim(`<- gate ${name}`)}`);
|
|
46
39
|
if (critique)
|
|
@@ -78,7 +71,7 @@ async function main() {
|
|
|
78
71
|
select: async (title, options, context) => {
|
|
79
72
|
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
80
73
|
const list = options.map((option, i) => ` ${c.bold(String(i + 1))}. ${option}`).join("\n");
|
|
81
|
-
const reply = (await rl.question(`\n${context !== undefined ? `${context}\n\n` : ""}${c.bold(c.
|
|
74
|
+
const reply = (await rl.question(`\n${context !== undefined ? `${context}\n\n` : ""}${c.bold(c.accent("? "))}${title}\n${list}\n> `)).trim();
|
|
82
75
|
rl.close();
|
|
83
76
|
const index = Number.parseInt(reply, 10);
|
|
84
77
|
if (Number.isInteger(index) && index >= 1 && index <= options.length)
|
|
@@ -87,13 +80,13 @@ async function main() {
|
|
|
87
80
|
},
|
|
88
81
|
input: async (title, context) => {
|
|
89
82
|
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
90
|
-
const reply = (await rl.question(`\n${context !== undefined ? `${context}\n\n` : ""}${c.bold(c.
|
|
83
|
+
const reply = (await rl.question(`\n${context !== undefined ? `${context}\n\n` : ""}${c.bold(c.accent("? "))}${title}\n> `)).trim();
|
|
91
84
|
rl.close();
|
|
92
85
|
return reply === "" ? undefined : reply;
|
|
93
86
|
},
|
|
94
87
|
}),
|
|
95
88
|
escalate: async ({ question, critique }) => {
|
|
96
|
-
console.log(`\n${c.bold(c.
|
|
89
|
+
console.log(`\n${c.bold(c.warning("! human review required"))}`);
|
|
97
90
|
console.log(c.dim(critique));
|
|
98
91
|
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
99
92
|
const answer = (await rl.question(`${question} [y/N] `)).trim().toLowerCase();
|
|
@@ -101,7 +94,7 @@ async function main() {
|
|
|
101
94
|
return answer === "y";
|
|
102
95
|
},
|
|
103
96
|
});
|
|
104
|
-
console.log(`\n${result.ok ? c.
|
|
97
|
+
console.log(`\n${result.ok ? c.success(c.bold("run complete")) : c.error(c.bold("run aborted at a gate"))} ${c.dim(`artifacts in ${relative(cwd, skillRunDir(cwd, skill.name))}`)}`);
|
|
105
98
|
process.exit(result.ok ? 0 : 1);
|
|
106
99
|
}
|
|
107
100
|
main().catch((err) => {
|
package/dist/extension.js
CHANGED
|
@@ -29,7 +29,7 @@ import { describeChoices, describeResolution, modelFor, resolveModel } from "./r
|
|
|
29
29
|
import { checkPlainFile, checkPlainTargets, formatCheckResults } from "./plain/check.js";
|
|
30
30
|
import { whyFailed } from "./regenerate/diagnose-job.js";
|
|
31
31
|
import { createWorkLoop, describeModules, isUpToDate, previewJobs } from "./regenerate/loop.js";
|
|
32
|
-
import {
|
|
32
|
+
import { brandNeon, brandPink, plainPaint } from "./regenerate/plain-palette.js";
|
|
33
33
|
import { createJobPanel, didNotPass, keyPressed, PANEL_KEY, spinner } from "./regenerate/job-panel.js";
|
|
34
34
|
import { clickToFocus, dockedWidth, narrowChat, shade, shadeChat } from "./regenerate/panel-dock.js";
|
|
35
35
|
import { overPictureRow, PICTURE_AT } from "./picture-row.js";
|
|
@@ -1646,8 +1646,8 @@ function registerJobTools(pi, jobs, cwd) {
|
|
|
1646
1646
|
removed: (text) => theme.fg("toolDiffRemoved", text),
|
|
1647
1647
|
// A spec diff's ***plain highlighted as plyn highlights it, and its added and removed lines filled.
|
|
1648
1648
|
diff: plainPaint(theme),
|
|
1649
|
-
// What conformance tests show does what its spec says:
|
|
1650
|
-
conforms:
|
|
1649
|
+
// What conformance tests show does what its spec says: neon.
|
|
1650
|
+
conforms: brandNeon(theme),
|
|
1651
1651
|
},
|
|
1652
1652
|
back,
|
|
1653
1653
|
close,
|
|
@@ -2308,7 +2308,7 @@ function registerBranding(pi, ruleCount, skills) {
|
|
|
2308
2308
|
if (data === undefined)
|
|
2309
2309
|
return undefined;
|
|
2310
2310
|
const lines = [
|
|
2311
|
-
`${theme
|
|
2311
|
+
`${brandPink(theme)("turri")} ${theme.fg("muted", `v${data.version}`)}` +
|
|
2312
2312
|
(data.commit !== undefined ? theme.fg("dim", ` from a clone, ${data.commit}${data.changed ? " with changes" : ""}`) : ""),
|
|
2313
2313
|
theme.fg("text", "regenerative software - the code is regenerated from the spec"),
|
|
2314
2314
|
theme.fg("muted", `${data.rules} rules · ${data.skills} skill${data.skills === 1 ? "" : "s"}` +
|
package/dist/loop-cli.js
CHANGED
|
@@ -80,17 +80,17 @@ async function runLoopReporting(options) {
|
|
|
80
80
|
return;
|
|
81
81
|
seen.set(job.id, line);
|
|
82
82
|
if (job.status === "running")
|
|
83
|
-
console.log(`${c.
|
|
83
|
+
console.log(`${c.accent(`▶ ${job.id}`)} ${pulseName(job)}${job.stage !== undefined ? c.dim(` - ${job.stage}`) : ""}`);
|
|
84
84
|
},
|
|
85
85
|
onFinish: (job) => {
|
|
86
|
-
const mark = job.status === "succeeded" ? c.
|
|
86
|
+
const mark = job.status === "succeeded" ? c.success("✓") : job.status === "stopped" ? c.dim("■") : c.error("✗");
|
|
87
87
|
console.log(`${mark} ${job.id} ${pulseName(job)} - ${job.status}`);
|
|
88
88
|
if (job.status !== "succeeded")
|
|
89
89
|
console.log(c.dim((job.announce ?? job.report ?? job.error ?? "").trim()));
|
|
90
90
|
// Its report is not printed when it succeeds, and nobody is asked here: the spec gaps its agents met are said.
|
|
91
91
|
else
|
|
92
92
|
for (const gap of job.details?.specGaps ?? [])
|
|
93
|
-
console.log(c.
|
|
93
|
+
console.log(c.warning(` spec gap: ${gap.question}`), c.dim(`Meanwhile: ${gap.assumption}`));
|
|
94
94
|
report?.write();
|
|
95
95
|
},
|
|
96
96
|
});
|
|
@@ -98,7 +98,7 @@ async function runLoopReporting(options) {
|
|
|
98
98
|
onModelEvent((event) => {
|
|
99
99
|
const line = describeTurnIncident(event);
|
|
100
100
|
if (line !== undefined)
|
|
101
|
-
console.log(c.
|
|
101
|
+
console.log(c.warning(` ${event.job !== undefined ? `${event.job}: ` : ""}${line}`));
|
|
102
102
|
});
|
|
103
103
|
const planner = modelSpec(projectModels(project).harness);
|
|
104
104
|
const loop = createWorkLoop({
|
|
@@ -131,7 +131,7 @@ async function runLoopReporting(options) {
|
|
|
131
131
|
// No terminal and no --start-over: no one to ask, so the run stops here rather than build nothing and say it is done.
|
|
132
132
|
if (options.startOver !== true && process.stdin.isTTY !== true) {
|
|
133
133
|
const message = `${why}\nThere is no terminal to ask whether to start over: run it again with --start-over to start over without asking.`;
|
|
134
|
-
console.error(c.
|
|
134
|
+
console.error(c.error(message));
|
|
135
135
|
report?.end("incomplete", message);
|
|
136
136
|
process.exit(1);
|
|
137
137
|
}
|
|
@@ -162,10 +162,10 @@ async function runLoopReporting(options) {
|
|
|
162
162
|
// waiting for a start over, nothing the planner left out.
|
|
163
163
|
const unfinished = moduleStates(loop.drift(plainFile)).filter((module) => module.state !== "complete");
|
|
164
164
|
const verdict = failed.length > 0
|
|
165
|
-
? c.
|
|
165
|
+
? c.error(c.bold(`${failed.length} job${failed.length === 1 ? "" : "s"} failed`))
|
|
166
166
|
: unfinished.length > 0
|
|
167
|
-
? c.
|
|
168
|
-
: c.
|
|
167
|
+
? c.error(c.bold(`not complete: ${unfinished.map((module) => `${module.module}${module.why !== undefined ? ` - ${module.why}` : ""}`).join("; ")}`))
|
|
168
|
+
: c.success(c.bold("all jobs done"));
|
|
169
169
|
console.log(`\n${verdict}`);
|
|
170
170
|
for (const job of [...ended].reverse())
|
|
171
171
|
console.log(` ${job.id} ${pulseName(job)}: ${job.status}`);
|
package/dist/module-report.js
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
|
+
import { brand } from "./brand.js";
|
|
1
2
|
/** Colours for what the command line prints. */
|
|
2
3
|
export const c = {
|
|
3
4
|
dim: (s) => `\x1b[2m${s}\x1b[0m`,
|
|
4
5
|
bold: (s) => `\x1b[1m${s}\x1b[0m`,
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
6
|
+
success: brand.success,
|
|
7
|
+
error: brand.error,
|
|
8
|
+
warning: brand.warning,
|
|
9
|
+
accent: brand.accent,
|
|
9
10
|
};
|
|
@@ -1,38 +1,46 @@
|
|
|
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);
|
|
@@ -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;
|
|
@@ -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);
|
|
@@ -2,49 +2,47 @@
|
|
|
2
2
|
<title id="d-title">How turri works</title>
|
|
3
3
|
<desc id="d-desc">You describe changes to turri; turri asks and reports back. turri writes the ***plain specs, which you read and review with turri. Then turri loops without end: it reads the specs, regenerates the code from them, and tests the code against them.</desc>
|
|
4
4
|
<style>
|
|
5
|
-
@font-face { font-family: "Archivo"; font-weight: 900; src: url(data:font/woff2;base64,d09GMgABAAAAAAN0ABAAAAAABuQAAAMZAAIAQgAAAAAAAAAAAAAAAAAAAAAAAAAAGhYbehwqBmA/U1RBVFoARBEICoIAgV8BNgIkAxQLDAAEIAWESgcgDAcbnQUgrgN4MoaXUIIRo4bSsCiga71j9UcNccO+yjyC+P1os19OzJJmreeIVRLJ+4WIehOXTKRJJRSJ8Pyncf+de2ce5wNCgxqZAC0ct6/blrChgUdrbfZNUdmKmpz5LBKSlaKpfEIsfsMjiZDIAa9FmMiRBO5xu8oTYkgzRXzs+9+lkO3UTW0sDfC/Eg80wzWI0sVrASYYaPgX/QUUUZwVWGASpe6a4qnU8vN++BgB6gBkCJEIgRo+ovDC4Pg8xPbtoA4xEkgQEHs5W4cACBAkpkf0mAcJRkVgEgAAgTGBCIQQJjCRXOsngM9xiaHI0vMFJWTvskM4oi4XP9X6lXQAfsTmjZMjkCBh+cB/UGwghwgga+BnGCIQoidAGwEm8do+ioSAtZ0iEiB2445KnDAtK24IZGGvBwgEUSMWkGMczoAAZBA8JHWfFAGq6DHYzcYSIRCVLqQT6bgAkGIzEQeUgBqc3whsB6D43F9FhbTpcmwPhZZSSaVRteGjj59qv27LeOjVk2PYcSj5hTCSqg9DMZq5r2qx/exN7XkyKJ65eHCjDr99TrmSxrrQ/ddvbRP77/bfbnTXBClNV3Wb/V8OzhdlfN+zmyYuvQ78yq+N29b1NhUoOpktvThzPnRiPpgdaR7/8x/4q21F2Fy8K2X997rzFrMa3zXdVG/2h9xezABAgEzWr9dWPOVvgPgG+P6P4MdMl+X/1lRRbBQToAIDAqr30kH43z8jcK3MVqyLdCnxtj+kNzsyh0CpnxcQsFXvUo4KA+AWpkJC3CvMzFdFyPA9lG6KhlN4yVpBkZIhoC76oBCMMUIa2QnfHuyS7wR203cG/ql5BtMxm0QgQlSUiOkoDzVEHCqTbY9IcsBAVbAZZJw96CIyCAw2JwwIhkImR6tw4fpJ462YXZW6GTRVyml0kl2Mo0KNYyZOVdLpkkNE1TjaAtmLq9pHZVHt2KOYxREwuYutVpgIESIlKzdh0oi2kNVNXUEsmhfLSQw6qnQVhlhynlg3gWQfui5Ikww2zQ7VCFSTkj2zjI2LdG6ANOUYfxXpj+RRAAA=) format("woff2"); }
|
|
6
|
-
@font-face { font-family: "Archivo"; font-weight: 800; src: url(data:font/woff2;base64,d09GMgABAAAAAARoABAAAAAACJwAAAQNAAIAQgAAAAAAAAAAAAAAAAAAAAAAAAAAGhYbgRAcKgZgP1NUQVRaAEwRCAqEcIQUATYCJAMcCxAABCAFhGoHIAwHGxgHKB6FccNdgjLFEIv53uLyOp7///3+N9e+57sm0yTgybwE8f4rdCIhkRhdNGRC4VMilffzedb7TYcZyRkQmwJOF4CK41InIf9hXSYupz+WPr61fmGAXUCBJToX/m9+bNFHYwImlGY9dw/0QE2Pto0FGJB3TbabIke9urYiGAWUiH4RDNuQg3atvrUXPF6zdQrw0ODXaHrF1yjAmY3SFD8GlZ0L4lAlTLPL0UnJ/fIYmVhii1q6qEK3JOFBTgBoVdFFcaB+0WYB77Aof+fwXxCQP/HwfnsTXAAuqlP8D7R7aBUQqHHaPmAFJmJQh1kYDtBOeDoRQuDkmUiHQF7o2DIUgqbftAgIlUA8IEKGsCigUyqhAAGF/VwQSXc+N1xZ2/KEkh7VAzxCmfp/naUWmJuTGJHdgwkwAMYxhN8AGQCgAaKUEtmc9w139ZoOjmDPstnQRLsdn221WsH17b5TrrNOHABvDt7EZ5/ej/AJFoulj7zk6slgHOF0137XPZb+2ecPIzRxk+LQSZsywKgp5fju1U3FMD5x3a5jGsN7hCMCUv/s2/bKh24R+AZxs8+EMJw2YeXOrcHkMM/CjjsWtnQtADFmhTLwvqUbNwbjy9fvuxc4gA/usTywFOADFFFpJskqM0X0FzGC6/Pju4o3xkiPRid8ITPi0smIxJgrY2JWFcZ31+UzQ+DvSA2byKWYZJ6JSVRFzr371fH3Fss0vNFpV08yjN+40JocFHFQyw2Nv1ucGptSfAcqRobHllTrLa6KGk0M9BuKy4zMWHMKFZ4SPLLsid/6rJjmmhxW8IG2RyHXZqYFxn1fC5dHBscQ1WaSrKbGEINDFJACzdaG5ExZeny6PCM5KUPe/wZZJnzE+0aR7FFb6vz357/zCT+1Lixvi+d0l2an4GfVfrDwStzwZlFdmIlTdd9jf9tr5vOXtY5l75dtLqt+s/noT/ekh+bWKAAIypVR2eOTTM/Sf84Y9hbw52/YQcBpUYX3/7hedptMjwL6FQifI/1JhvhvHAOxWw3ULLhbPIgvagCl4Gh7PRBVjiYY8IyDzTa73ZgP0ElzAT4CCh3HdtBl3CndueX9YJOk+ywnx9fvMK0NCIxmHKQRDGcW7QLTWZIJhOi2TAhEDzkL5ZpzcJWSCkVDTEhEJ0qOrIsYpZcO8o0qGlyKIWagnMFOs5IEH5dOitTTuZU0tLkke/N0VLRKZMokJF6+lR5HBi4luUwd5BTEOPS00vWiqPBl+h2dHIfRg09OA6tFGd5C1jHpGmyOfzc+IT3Zp2jkyJAlSzZChzbtmhDWW7fB0sH+aK6TxJQUolCvLQIDyepHKC4xHl076Vx7fblybDJNsUetGFc3Je9KChjQk0mzV39D/6dOnwsA) format("woff2"); }
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
.
|
|
11
|
-
.
|
|
12
|
-
.
|
|
13
|
-
.flow { fill: none; stroke: #0f0f0f; stroke-width: 2.25; stroke-linecap: round; }
|
|
14
|
-
.review { stroke-dasharray: 9 7; }
|
|
5
|
+
@font-face { font-family: "Geist Mono"; font-weight: 400; src: url(data:font/woff2;base64,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) format("woff2"); }
|
|
6
|
+
@font-face { font-family: "Geist Mono"; font-weight: 500; src: url(data:font/woff2;base64,d09GMgABAAAAACeEABAAAAAAUmgAACckAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGnQbiUgchCoGYD9TVEFURACFEBEICvJg2nALhBIAATYCJAOIEAQgBYRsB4hDDAcbUUOzERVsHIAwSBfI4DgPGwf4bZYpispJN/uvE7gxROsDfYXGdpfBgxLwbp0ZETs40EBQUEBky3KEOP4rU15Eroz+znwAzigryw2mi+z60j1ail1eLkx7hMY+yYX+Ia/+69ykMXA/kCsQ+FppJzCNATM8v80eBnYjJSrRIlH1ESkJwZiVi3Sbt3OVV3MVF9vNq5RFxlUt+q5yWt3SyNCtSZxFxAFHCjlOVlvluheh/ZUPIbSEHKB/+nv07H0zEQ5FAxirQK1TKFBArVrt5/n/vzf3eTwFvC8fwPS6vBWDUCIaiw1YACMw0fz799t8mIW+kIZ41ARpR1rC/A88x/2k5y/LVGiDQr8018+Tgtp/fs1P+07m3wWeArmUwS8JVVldoare3DezkzsvbzP50N0JLGKyhMlSk5TZAWDK5NEBCVMhKyukrvCu8Dy/n8t3Z2ZvfxuESiJULh5vixO/8wZeRUMUT/QRO/5/+6V2NswuGwdsDLrEqbs8/2/SvvKUOOHZqNlGmNZ4Yln3UjXl2bLcE1tEaeorjAAU0tbV99ey/gRw75bS6ETKU0QP5/HVb1mfvcjTxciyLDc3TXMp8Rfgai6PvPELMUgOtdV2Qhjp0JJB9tdNRjQBoxkVGH1FwJijDYxFpoLxtatg/OAWGB5/E0uM/4gln+fEIhygPEAmQHkwRBEwgngWHfynPPt+IJn65GJ6fCQppC/4OhZIxwXwfQ4g7EwsgPzXKWySE0PdAeWXdO1UjeSSCZIJRjKGbE84MOsrFYeIdMJI+D0BJljM9WCy72awdEvPXEqpS+UgCpa+qHCRiFczcYAII7P7E9uWlsqaXHMhmJY7VWVH/AgeZl/7VDS5ffvGZlrNe2yz0QZrlwxCvvJVYohsiVHRIZk7TCfFGNMD5Ad0GSi0lvuMqpGPk3UU5/J+ekpiXExkVcOPQa1F+6Q0RtxXvkzJsDhQpji4AkGHQqv0HbV/dS2YmGvn1CPcbnJdcqmCjM2JOkSU5cJFl+zEXIu32iBXDrnOig7dUEsNcEzILeaqZDtb91IHpZR6eQ2c9FyuHD9L29sT/lmB2LLMDjpF8mpExLM8trSJkxhCV38ABJQ00sLA00FwoLVI00n9rdS8qQwmQlpOWgLDLsQXbw79q9m6tolktGHVqqyair1VzYz4Sa0GKf6b8DcWqvwdjxYCFnaGQMN+LnR4IW3MZpcKg++0ajNQ+55L3RaWPcSlqP1pratYpvpwKBAaA54OCFBgBOLVCi2wAxRCgoyEEnDwhTuyIo+Qj6ojaOtCpho5QZroM+n3gx0bRIrv0vgu7MhlviVH3wHgXT+jF7DXrGn09yTfKYf6sFJ1Lgn8c2ts7uYGvPvjEhjA0bkHCc1fPTj9e/6B+mZBM0jvtrwE9ktnOQF9uRHxrX/eIM8ohnNcwrI6RJikfQRF+PYpQoCDAj63wzJzdVgGpxDsariYTvXYKLZlk+/a89lgNIf8ZEx2X7BARtVps/2jNkCd5yNWg/4v4dQv3S4iWQ1SxKzfaAEg6emiVcVQjQvcF8m2bkKsiJPEFQz3cC6mqRg7184Qjx4AOESQo2CoNpsZ16Zm5sjbNXZCboyE2pS9n4e59xNDxZnTQ+LB9i1LAvRvvYUmRY8oYLpQjgG2YPbIPJOfZNRpkMC3kKPs2D/+vwS4XSy6A3D65wLAIosGqJkwl8ZSMoF2jln90x0YgLd+jsUq8hFxOL4JV6IUq1EVDwMYHBuxcOXH2fS/WgdIcVRFwTHTHv9rMBrbvlx0V3btwGLjYpNiMfdcZDESgUQjcUgGkouUI0+i0KjlqJXo7P//B9RExbXR+87R1jxwnNjEE3KQhcjSi6cjOd/dQADA11UrUNa/rENX3VX9/1uaAJR+/sNtAPzw/sPoD1vHf7nuh9Y393/v/X7Cdx8AowPAGLc8BYBONfF1oEuhfZb0v9o7PnDchOv+cMoJX/naAbft9pldPrHHXj+574GPnAQjmRTSyCAiT74CpRDKIaFgEJCQUVDRsNRi4+D5zqe+95svA+hATkFFw8jEzMLJzcPLx69ZqynadOjUp9+AQVN947Jv/WKnD1110zW3XHHG/3wwzWG/Opd48JmfjdsUGPjHafsDB1dMd8Q73vaufeAYLmEChEgilRyZsmQrUahIsXRqq4RVoRrOXVVq0DEwcRG1ExMQkhKRkFEy0NLRq2NjB1Fr0CggKCLknrBeXbr1GNJiGF6Tv3nGQYf8YFLUj2DI91UCKAwAywwoDZA+LRAJwN0TcHoEAMP8I77zZC9Lk2B0k433jTrbDffhH8vldk+zx9Ze2IYwMXgdA44xtrci7/5r9UTUulFsyNv21JzNu504EQ4gjNpR9u3+CsAtptBoBnfuoilTa6nbU3LOsuJiYnhBnDgScJ3bDiughxatPB2N8bb9YBamwWuZkGRSZESvdHoYp30en/GrxC++XuY54ct0H1HQq/qnZ6Z6pVouTeP9fgM52b9i2NtFcJU/IOmLkbK/rPR9MX5AZlIVmeRQ1D+jBk6fzGl9k9KvorHccZkMi1HT2R07quIkUkQaJF+rhNjUaMREdoeviuUAmezAargYVruhl7jq3KvXuHGpVDITz0usqxxFFZlFlZnM0D6ZinRqH5Try2F55Mmb2cmJSKZvff9nP0WyY3Ai1S8JdZQQr8iJUhWr6UGijxgALjopE40y6XM911JdjbVSt0nZp8J3G32Pcp6BPS5bKJrONahAMaRkfho9lhwRQmmljsnFrrvse6pSraaU4soUii6oSa1mw83QyqbzJRJ8uDNv5eWO73SE7BPeomc0ITtkxndI6+Msk0tcFuQuPZcyjikhLSLBwD5kLHmKbjh10ViMeG+TkIFtZt1ml4R80F9Qt7CW6hlZ3yzb2DHebevGC/XG3oIJDLEZZR3v8cnsGXJI3c/Nt7mnvfi1ej2PHPf8cosvJfztpSuPRS7gaqO5B4Y4Femcfi7V2t2uLXouOHQ92QcpPpMVolVXdzjzbctriuth10bjOWqoDIKJVcv1ENdFx9U8mTdNQfr2YcDVIWerKciye3hbhzHyUUKTBG3+ztaWFMVkiFG7LUBxxQx/nLsN/s9X5xYR04YHiOri0M4Zc6cKAJndEA/Xnf6xSIjKKBgClQ59acqai0E1tBz1NBd//+awwYo8dgb9jyLHZ9rlSNerrFZGqv3htODq3NG6o671j8q9gQPKasgMNDHh/MXXhJvUm7zOdheXbq4DWfp9KONTfDIu5fwcHwpkJpvZK3lmeKwGEaDE7HHDbCu9D540xDk41WZCX1lW9vaAEYz+kX7tXtYcchVyFZTdpb7RYSDSyClgqvxFomiI/G1DVtVkFJReaSq9U4NoQAlc1f0OtK6lRyNPZXPuHS/qbEjSrTXe9vcu1Oz+Q7D/vPKphTaJoXDaJwFwRpkz0apKFyh09qTu/bD+jk3oDehkui2hIDiQaCi89znxZO8+Irdvi4hoOrOP784mdhEjL4ncEtb9etJyht+zOMtYjVKyAWPp+Xydc/f379M1VP0XPaeYQPHxbO7a3mumovtLYDU16NG4HCKEzgU4V05W3IuPzLAHJegKZPyV+3Y+uL5szaO55JZT4RhtOZOc4yWvwdOJRFqmuA1VqN0YvV5Fl3QfYA5sX53Gvv+QVWUIYw7eJz0ibdzVNPjmVYHi20a6SXfFTLrEq/oLyEKz8LEF9gY/M5Wz5/vXUYZrlXp5dwDazZAK+tKDqAzhO3AdMYR0WCHHBhJ9jBjVS6CR3XwYzih7+vjuoQRfjfufv+CK2zmq96SNpNBZvJF2jVJjvrGXDZfOpwPO29mBj32+kTWD8e3CbK4VUJQFqQZnxVHVfNseO7bwrKZ3HLqRqIzZFWqY/fcxrUaj/2goBbpGDWp8DvQnsH0qnFZGllWTtCax3cqN+8yyMyNUQ2EuO+F+w4PFm4+8/m/Vc6QZUhxvXRQLFhwMgCGK1ShNi3ssdbmRV5Upo2L7vjLMeYT3DSMktAx5OcYwRiGLAlN0QttLSP1d2xLaFfgVcqj671laNo/4v7dSgZoKzfFyy8hv0bOcUMLdTrhegOYfnjhSOmbMJabO9ez+SGst4yJuZmTQluIOKt/muhE5cuUhSqsRt44h1p8aJmUXbb4B7ckLSzSC5uTtgqNBCxyPUFDJPyxGdkPRbLg6tMDJtc32nfXAYz7+P18svBjKf+fOVZ6GRrH+nFpbrRioj5H1XNNopWpQBG+rv+YvcvQVW5Rwl1WChsst1XXLnauNmqaeMplvuAvTFltqMyzjC8IpMjhAowAsdBN9xaSYyzdB96N+z7+NSZ8F/2xMXM35yVVTUW+fBhMFW2to6TH6sEveGtaQ+vtd8mTQsrL7lVHAoLKMs9FnGajcVZTH7bLLW4qXYf1bBrTydxu2pwb0sdzhXSFMSnEXiTZ6G+PFpvy60FM6MuTDqWZavv14dh7Tc5U+4XfQHWaKpmVHN0nv1t9cnaEowxtwpiCWnU6xO7J4mzdMomugNa1P+EFmOvkD2tHi68u6AG1pcdQPUT3Ms3nBtcjTt8DLMm0nzabjaj1bJXbav5YNVW3TWiOBpU/lw33rJrLXLuYh4/q4WzBGkyEwlfcFKAq4mkLO+i47ATJUq7D0jmyZDRHoM4aWlU5yIqyRLD9yXc81di3UOy9J+D80bxwTMg36vFK77M6c2a0DRcOjyiU5Ybad9Jz0yhClapCrof47vBW+8Fja9yPkNpJ4kHP5NhTToQxUUdxQQ9Ios5tWpPyp2uDyE88vL/XANqt8PHUS3PEjFIfgLbwG8kPaMF065qHAHKDGWWQ+C93ksiP40vm4m3b8+RfFCVAJxWrisUeMjIRNefQFXCzkago4q70pHlnPHQ4bBOHiejrZAu/N/Wz/eE8TRs1NHouPodqDzXVIoXpkYAnU9ykY1vWWFWkymyrEbhS3tlzuh3OHaXYoLGl3vISiWNUfsbWzG/fX1TYOoQN7wXbsnxcOUxv1KKFAJ+JjYr/p3Ae7thjXBLoPbmLDNo/1iJBd01bzlO6/IHiqC7w3dN5cdY0M64gsz/yz+uWyn7yXRrQSQeHJydG3at/mdJPwT8v9wDS+0ZJOPQqHxk+u8a85vSOUHsn6MIz5tNi/PgBs0RP9w2E3sVKurxLDJU48Sc/wzY70TQ/MVEhYNKacI50ZaJKeo9LOSZsAOcMxUC/Y2tMr2DrgcgRGZuX2tMena7R6BlMhwgw76zBDChGz1mBLNbfFZQ3PXwhs0b2+RVbtuz092ncWWXyyoCTQPHP27ObZAQlwRj8O6U6K/KLTthAIRr8MzLmeKPFLCACfYei3ybYM94q2DrhssjBH3pKubIOni5UGDl0pxA476yqG1EImWULG2gvedr9qheI2bdxEtRjIgB1tcdzvu+9oiba4eLELGEO7wK7Ql62sg+wMVTOf2w0zNCXArBKTVCo2ms+3nB+aMQrWnF+d45r1z2S0JZp/26tS5W4FQbO/+3xlCMRCwy822sLRsXnFVTwxFg3bNv4cCOTR2c9dOQ1NKzbXDwgZUB8k2DowJFADgvoh/tahAQA2e4Au52GGXS7MVDmfTgcQMjmcO4OzGYHmLyYmmr8KUFQsSmPBAoulYD4lBAsxgNudPeT2ZA/vxmG/iORxT1/ovlhhicJCG0+u8q86vSMEHNFr2o4aflAu5wc7mNretK5mvUrA46sE+mbQtSL4g1otEPDUXPUPwR8tnQxeg1zOC3UyLTdWRFuioGOgz1nf69wQSfdCuPdw0pywqvn+ezBDci6QRa93z+4OMogGNWEuTxOj5sfLsg63ZBxAD2cRp+cN9kgVAjZHwZf2AGaGoUcg64jXTIEHqxUyNIk48QKJvG7AaXPVOW56tbZWwPa0MnTaZg4/ApdPiVeT9uF58loK4lMF4rY9P8QnWLhcQX0HFTwNnQolig0QMnpmvTntZMtZo8Iqk8mE8WzLyWmZAf48M9n5UReyl50zB5p1ky3czlRNuQcyFbJlveMNZUIhmYD0eeMn/sH5mc6h/5PA56HB0dw3csFT7JnxvhU1t//QfafHsYDaOPg6ErrROPB6VgjcTaGbMGQJUW7V2TJ2Y4ReFn3GuhZ4uq7X64609kbOZL/OQNjM42nYgrn5mdYCQ3m5lSpBpeGrV5Y9hcpR58F4is25bP585zIbQ8Ou6ncR/4vRVhpRxT9MDyRkObV6CPM+SsaS6uzSZHOq9iayfKnDG59q10NSna6zE/9tklitrbTi0Spl7q81T5DYe4f/WxKjEZaiyABKISrKnuz/Nm6mLxrGrxo7rSwnyEPSmukOR830kNTuESmCjjpFQEB6gbbwOGjr76A2Rdsl50+32/nTuhRaQQNRAL0Vge+T1FBk+qDDoQ/KqHL29kj82yKnnwZgKSf1ocqJivLTY6vw4agvbua3mxMUZcUe4u9oK4eHtrwgCRSBOociCB7DCyTU3kJJpBdMZ783aPEmFNm8SXMM5mmY97z/pmrbBPweo4nf3SbQsJwkiQZtq4B4g+0itVuihhwqtVUiVdtMRrUdUFN0HTL+jNMp2n6glW1S/nRn6bQ4ndYoolzb3wzP1/W77qhT42QqCYutlIFTVK1GZ9wDbULcKaf8/TV0CAJV43xvBV/xVQt8Yz9LpqytNVaZKTXSJgmnR2dk94YlEradTgDWwAMZx9NK0RYIdXXAGI/W/oSXKJ1mk7oOeKNh3MoNj2VF1Ww9Hefi1YUFarqUzlGolFygh06t++MRRPxmH7QvAlpWQ5uqQ9QrfxRB5gST1eYE5mj4679S5py8fYk2uOUPb/BNkFCeUY4unwrNNxiNhmRmOfQzBLC5ztjBTW9zZSQqVRYvTVkqUlJFbEewKegQj5dmH/y32u53COeV5XwSU20H5lxvzKLty8gaBJ5vYFa6OHVhgYKpqKn4QEjmAUKK9j+mIP509bQGEGRPAV3jkakDdXUyLsM9gB0pVteaWXNcq6xMLQ/X7yRcKkvH/BrX5Fw+MkJOJrpOWj3dYameoVNSPfk/Z1gFAtF+yc//NdOyAA/0yze/LIlVYKUIBFoKVKt3+WFDBwQusdAQ0FOOSx2gNkO1ojhm8/HkRQ/Eqob6erxOYvwltEUoRFl+BurVnqaqLe/+LS/FG/0G0gytjjyjQa/HK4r++f67lPkPaGdKbHRmif0MjX6uzE6nldnvgIzV4eTjB2IUxdX64D8A2fUil5h0ADbk/+t7wiWURSi8r7uEr+srAtihA9ej1w9AFxNXLoArpQp+t7Mdx9+Bz7t+DgKMFFWYUmumReKn/surfQeZyw1qjOnOKh+JTNa48XKVm8owtEfi6y/ko2qKEIXTe56aU+uqeEQqS+7HAuUvKlZTuFYR6QxDMsihigf7XFSajeWd1zosbhCrDLXcSqaimEnyEdgXacRGDluqEP0GhixGrfFp+L8KDOYN6OrX0DUIvEoxuJsbnGGhmiZh1Eo1cm44twEsOp8mr9eBP77JaV++4yXbmQHDkZxW4QaFyTGg11rDOT0SFn+6gPJd5icxPXO+mvlV30TcN+m9o0ffOApmr4YkJR8G01HfwdrWbBrb1HbW9L+kmg2MDZIGNrSrQdqgg8DfgywxmUCe7WrLQ94qKb6k9MXON7Olep1SPpi+KClZjKSl5pJoyNCso2nYn7G5+sy+SGYWHRAGzV6fee7eYDL7fEYFbg2dthZXOUqlLQTTWjIem+Kkd0Ofsbuet7WnV+t2GU1dLqXeHnBd9cYASShGD0N1AtpIIoqsqA4C/oaArOcT9a7Kxxa8MyoN/RwCVBNpWl9bEys6/B4a89F0vFyvSpK2wZObF07v1zczarzCWozJJlPjvjULRwumhUBFRv+0zjhXK9yjp3MqZQXziASVtJhQ66tF96ZhmXe5/O+mTIEnW4fCLcoIuRbiS61mY3oOlBSKGOXihj46GFhjdSvP9Z2EAtAa2+PfZKA6Q9VM57u0bfCIgsUWafQqMlK2D18px53ugJGWLwwRSx8aX1XeQyoigtp2lUrQ2SEApAxrj4a7JOwsWy/pnSIxSZ14luH0lMRV+hqWXkCYAQus3KQzYZmqCJfTpjaJujoFuqBUb9LIVUYRSY7BSUhEtEKCA5+2c891HtQ7B0L3PGKNdxlvZclGFyBkaHooEq/C4AkbMnP+MhikbHZdiC6RNNJr69hSveH/HEOWp9GgkHi7KZqAyMIVctVytUoj9yG6EsQ4yyRPMpmTZOZZUH3+dA67OKK26Jm8WoBH8Qp3ubKptkugbZapTUIhzjZxkcLsdgmRITWXIzj1gRIuu1dacRH93gRSoJfKJTaVWIQdQqOhinwgKoAN1fbj8Qn75ML/obec9xWv2deAwNXWTfH56lq1PtDKTdFI8HiZXEqQV+u/tEQhJGv++7f84B+QKH0YBNxi4vVq/gjYLCWniTdNFBdJFmQVPi9EZUhUMS61TCyUCRmU/InVJ+ZlfSoFeK7xKloTk/cCgxKUFIsqigXFRZcK8neXlh7J0cb5pDy+TMwlY2oTk61olkkEGFLoAPfj/wx/MCVMaP/qpgWgkitQssTaWMWjEqQMg/i5vOi/jaqC2/fE4ynuPdMLhDwNR2RQAjxXbjn7JfK7o208UP6O5WVS4LBR8JuEV9xJaxJOSJNZ/ppwaY8cELg6LY+J3dZ+Ob+CImFKZWImpSL/su8Alqnh6UQYV2mJHYNuLCltRe0vvlSUHy0pieYXXQI/j4yOgN/Ga8e5IyPDgj0g6cLIBG98sOpe5F4VT8G9tPAF9bgkvZwGl4IJXsTT0WUFT45p8XVCyDpZSvmKq5DLE6St8fESYbiNo5OHGSw3H/+OdcOHd7/G9B6LQGXjoyOZBXGI79nXrz9ICarwNqHDeghB3HcSfiUcqxN/MJqEr5G+R0jUQ59YjQ9RyEdGcLhAQmiYu868zmOeC4wFx8S7AMYJgJs1Cb2Dxi9TrUcXr/9y9qR95qTvo34qaGzPvuvxyybwb3vX5gf9D71tXhB45FnlATlOUJt0bs+3TH27Tm5fglym0RAUZkbUSstS1BK1mqSlCarAq/YIllgglxYOiVg/HLBi2G0KGhvxNBnDz5BT8Y0mU9Bd7zWT9MQ6jcCuhCco4wWQ1kHWkcwe8ONwvcdM0pEdWgGkjE9QwIV2TR1RTzJ7692moKkRT5Uz/AwZDd9oNAXB2Xazx+z4/cPfwbz2lXSskSevXmz7hgR7x463g+4rF+WObN3Og68dPWPbxf4g0WYC7Nb1LA4g2ZPBInp0nwF1zti21s6CVw/f1Nn6ZwCYM+3/hV3t09c9pzQr9Nk4Ta9a8vbMXspee383V2bpsHFWBLsxI8+I6GedHwUQnxQx2C6zHNclkROKarAJu5LcOl9yK9yKQGelZ+5OD0Gmin6TSEyHKJIgZkUwQppp49SW/5M1a23GHnuKvIq8pDAvI9dAYbwJfs1o6g3WU4pVAgndfTe33OR3ORx1NjVP4TbQF0/EZ8nJgeTiqva7UEsf/jM/kuMjTf7fcvCv9/CkCW6kYnVlAUqKqujHZM+LgNcMpo1KcwmEdJeDynwZ9bXL2wFLrIqQeWZZC3y6mMGr+suXW/arMUOaZqtyVSUev/DDDjSNqQxUiIXeKq6+qTl+ioTMLD25OAeRKc3UZXnwb1OqFsxe2UoisWQNuGoBUY1CTMqyMMWbl+XmbX8K8CnOOR71tvYO9VZtcjrneaijPQVsdRJFUFezwOOpWRjUKuRCzIWWNeaCoE7upekZBK9QSPDoa2gEFZYsNZjjlJ9iCES1gE/UELDHXWxNVPkgQNzDDeF1OnyIyyXSaYm4HKzVarlIq2MWiht4NY18fk1INbHYz2OFmFiNeJYoSkIrV0IjIF5eVE9Vi7qresAWUsn93Jy7JSQwEun1CSNiXLNY2Odr8cl8SxHim+ISRIOkYaJB2gAIYyQtgaChkAlaLZ4EiQeRKSlyhmhD/Y7F/oFCvoqZvgR3b+LvR2rugxftHr8HvFwU8oW8T50G7qYIb6vD2gTni5AWG39Gl7wjSWB/OxL/kQo46iQKB/hbono/FRDGeQEOJ8Djcfzq87YBvwjwXDN2v76iQrcfW7GfB5O//4MFgyPzRzGiRkbnt4RdSLRgNRXNH7gmiK+fZphWpjP08GXt8ZqWl6MOZMqFF+XIYyaMIUeZ7WbidYxbtbOF2orFD8FlLb1LviEJFQxy+Sdp4iwov5FNMHpLXQcFJMzT9yTrIuv0vmginkPd9eytPb082+2CgvNeF2mr0TPpElE5lQ/pejobYsHnXu1ZyvPykrKLuvX+CxlSBKe2PsAQ6QJUljU4AN97rQrN348qTDmwS/8eMBMje5aGgWrUcthyaJX1sLWw7BmrwX3kueNBfH8nbgK6R7dfS1jf7OoWS7q6xF3AcnUxIOCQjjIEhERCiDJHefcqiTxcOmncdc4FjM31n9d/dnHUgbIk9ObegpAaOnxoGGSWdEA0WEaggE5AuQNRTzT3gPuEG8z7HfXph/n2h8mFy2hOq7qRjz26ZMnsrMI51D0L2CPZA9l2/A+ivlHzADX523KyHw6wr94VZqnpGgZPapSxwrkBW4qCTkD7jx0C69oFtnpHnc0pEFqddQ5rvUCKqKypqUR0JzQfbpm0gPgJO6EDLVanUGAbYavnG4BDTU9LTR0UJi2Ns7rJ2QQ4a1ZwyNApPNHZ7wDr/K1PzyoO4Im/pbkFkGz18zvF88FZm1hF/cR6m3dRc99k1jZmWwHBITJoiFLpEiRqCV1KNBYQRNXh6qpIdXWkal4wkt2UnR0h4FkHTb7czNSX3PSslmwOQo8vQpb9Xp+STcBwyphisGmKJdsCdHoldmV2+u0+mR/PFc/NKWBEK1EvwdO1/og/h4H8S/ty33vH90VFm7NztpP/5my/sDuP8l9rgW7Y+dwBDhbOXeoviL7ue288wKxCvx7s9yuMVV8/LPsfoD4YECIOwb49mwuwCd+3HsDG/C9sHiCRLylXQYodEEgLpXvSJxm1XvDsJ5sgw3pkRjH/j+8FybbRT08X6IJJqYdj31H4jomnMRqjMS26DlIGaD2Ppmmapmma9p1Dwn8IEq9iW0r8b/C8tdgbfM2TPB8ossVCsxZv1h1K2FYvG/sK61q0urV4X74msNqrwZqN5Y/rMZnK7QzDGIAvm+/j/xVvgq8JLnkYYl+slRCTzWfcSV/qUilRaPCKsrGPGCzFm5Z9XzwDhLgMlL2sdIWu0jW6TjfoJm7hFXubq3Aux7NXlt5V75p33bvh3fRu4fbqBjZWIJPqW516McTC1A2HX9d0xKYaGuhhhk7G7sLLVOvLUbUKzn2AF5UYQAPlvrXVAf+hUH8c/GirHlSDBbVuUmidoHlsPdflcUc/s7fv4bEXv3XMbY3z8BJenpfvFaCY/erxT/qK//5rseRX3uE3jM/k79f0/+b3SM+seAlmA6hzACoC1uWMVbSMlveaoZU+tJwXW8iRd6HMfNtFc9YjZzHRitJMCcZ6cuwYaHlpBlbi6u+ZYeVWjq1TMEOm5T1mSBN2IagFF4iQaHlpBq2sAKwNzsqoh6vaFbrZ6yjoddONxFVzr3Jc1BG7xHSjdBhWXwJ3hOgK3ex9VFvRWvP0fcpUtkyImc8bjrkRqx4OHRLd7Dwq4V9C61oFjfU6JLpZOkrrydVqeGh8sUX11Alvtg3QKCNtAfRCKm8DX9pZ8Of1nnymWC3fEbbnnxppjXxPH7woPCd2om2RcexgW0gOQN5mfGlnwY/2nnzGWC3fUX57pD7CGvmedk8Aa7mDwQm50onUzwu0xP8YXz6ZAL9lI/jT7+5X6UhlLuUujMnGYxftHpr7K+lbtmu9k1u+xOde3wK+2dE2NLuvd7NjTWxsr5HUyT7HjTa3Ngf2VROFtKVkLmQbNTY/HwzQc+8Q/RffkMH/S4yN/TkAv17fqwPAbwv2bgD3a858PgwQxAAQvDuhwcYEL32nYLh10iSUgE5Xp3COJIDZCMtbZF6ZEy6og31lRhDOsW2gOjlmFotkcjBDBgKqrowLlKAUJKoAdWYZyISVxMDODBdysOCdWf3UrTCDDSyZZ5aFZKihARl26KAEdUYQSMgGZWoyjJiKmaCCTdRZFxXInVw7FmEbtuBtzMJ7WI3tWHNNrbUAB3xOAPyayPUAAOBn92vF886qLQQkwATMILowMIAgcqGnddRGI/h16rn4G/dISXuwAQ+RjUzIoUYiCpHqRn14By/xCnyUO4IN1agAD3rYIIXJqcgp9uFEs4M4QdCxAGdRQJ/gHC7gHRzAdXyNQryIAdjDF7cm+fYlG+p0kM2Ff/IWGCgMRoIzUDwCts5+JZ1H693cJAC3Urg9SdT2WKxMf5zk0N6+qd2TAlS27qQgNQ1Jei1JT8qUi3xillRql2GAqDxKqDYqKFlw4YNgmE7tAjpo9IXztmkRUiecNWnOxcDS2Eh/jVVJ0hsECem8YDv1rE8X57koKNokvGkjmuuLxoH0RiHzbXo0RYlb3aRa4ECPbRlsi0RDVouqre3z8jhYyegPKhas0oSHbxPQk0M30mplqA/rKQGbTktHRccUe4ZwW4zMrCdIK9lQSyTs92nmHDT1ripStUmLunG/ILLQhVChGMjaFMu26bOil0jFdGTX4moxTC6bfyE6gPiNFQZQ1+HEgNcF7RXyvmUq4DSq9IsqYYcccVQ1PAKiY4474SRSYt/z9c0j6E45rclZy33gQwx/YKp5N1j/c85rdgEHFy9kvxFMtoWltfnodhspKHVS+ZVaVzHq3TTvtXrji/oMGEwcWMnAc2Dwd5E1D7EaNm38LaYaN8NHbP5kB3F4Qx2nmWabY5Z6Lm4ev/uc1zvelfNetvCdYP+FS5QmAZYIS4Ilw1JgqbA0WDosA5YpS7YcufLkK1CoSHFfesgjHqtEqTII5ZBQ0DDFCluWDKpB2C2O69A2+cFCaQTJJpJpn/2kUkhlEj8hsR9N+tgBEz6x0y5f+0Y8ydSRxB4LvGmRMfNSAVeDn434SoIqm2+NdP73N59CQim3UsAWklSVqGp4BEQkZBRPKlQancFksTnclpA56JLrLrviRrg3r9jqqxbOpVcUw1N0ReX694TKKf3EjFDTdFsZsPgXK/HbdpewaKmPh487sPxnQZh2wn1IRLQe6S/LEXv/mI/ECPvmEy7C/hFLv4Tx+S3ADHPMMAhhhgamZ2DAML0IZmhgYGB6nNcJmmIrzIjvDkhzuuYMkEkW2UXHWvMU+oNAivSnYSTk1y1FJ6PPpfm2WnyfP67iT6owYl1XHDTZXLvl16l0LsrV8jzWEUhz1uBJPO7op2qwqyF1CI15gdyLxQodn6vR0p2g2TXdETkx/2JDtOM54cW8ox/8+fMT8iAA) format("woff2"); }
|
|
7
|
+
text { font-family: "Geist Mono", ui-monospace, Menlo, monospace; font-variant-ligatures: none; font-feature-settings: "calt" 0, "liga" 0; }
|
|
8
|
+
.lbl { font-family: "Geist Mono", ui-monospace, Menlo, monospace; font-size: 15px; font-weight: 400; fill: #85a3b2; }
|
|
9
|
+
.node { font-family: "Geist Mono", ui-monospace, Menlo, monospace; font-weight: 500; font-size: 22px; letter-spacing: -0.01em; }
|
|
10
|
+
.sub { font-family: "Geist Mono", ui-monospace, Menlo, monospace; font-weight: 400; font-size: 12px; letter-spacing: 0.04em; fill: #85a3b2; }
|
|
11
|
+
.flow { fill: none; stroke: #85a3b2; stroke-width: 2; }
|
|
12
|
+
.review { stroke: #3e5a6b; stroke-width: 4; stroke-dasharray: 8 8; }
|
|
15
13
|
@media (prefers-reduced-motion: no-preference) {
|
|
16
|
-
.spin { transform-box: fill-box; transform-origin: 50% 58%; animation: turn 6s
|
|
14
|
+
.spin { transform-box: fill-box; transform-origin: 50% 58%; animation: turn 6s steps(8) infinite; }
|
|
17
15
|
@keyframes turn { to { transform: rotate(360deg); } }
|
|
18
16
|
}
|
|
19
17
|
</style>
|
|
20
|
-
<rect x="16" y="-5" width="1100" height="490"
|
|
18
|
+
<rect x="16" y="-5" width="1100" height="490" fill="#0a0f1a"/>
|
|
21
19
|
<defs>
|
|
22
20
|
<marker id="head" viewBox="0 0 10 10" refX="10" refY="5" markerWidth="7" markerHeight="7" orient="auto-start-reverse">
|
|
23
|
-
<path d="M0 0 L10 5 L0 10 z" fill="#
|
|
21
|
+
<path d="M0 0 L10 5 L0 10 z" fill="#85a3b2"/>
|
|
24
22
|
</marker>
|
|
25
23
|
</defs>
|
|
26
|
-
<path class="flow review" d="M120
|
|
27
|
-
<text class="lbl" x="552" y="
|
|
28
|
-
<path class="flow" d="
|
|
24
|
+
<path class="flow review" d="M120 140 V 40 H 985 V 155"/>
|
|
25
|
+
<text class="lbl" x="552" y="24" text-anchor="middle">review loop</text>
|
|
26
|
+
<path class="flow" d="M180 180 L 466 180" marker-end="url(#head)"/>
|
|
29
27
|
<text class="lbl" x="324" y="166" text-anchor="middle">describe changes</text>
|
|
30
|
-
<path class="flow" d="
|
|
28
|
+
<path class="flow" d="M466 220 L 180 220" marker-end="url(#head)"/>
|
|
31
29
|
<text class="lbl" x="324" y="246" text-anchor="middle">asks · reports</text>
|
|
32
|
-
<path class="flow" d="
|
|
30
|
+
<path class="flow" d="M650 180 L 896 180" marker-end="url(#head)"/>
|
|
33
31
|
<text class="lbl" x="771" y="166" text-anchor="middle">writes</text>
|
|
34
|
-
<path class="flow" d="
|
|
32
|
+
<path class="flow" d="M896 220 L 650 220" marker-end="url(#head)"/>
|
|
35
33
|
<text class="lbl" x="771" y="246" text-anchor="middle">reads</text>
|
|
36
|
-
<path class="flow" d="M538
|
|
34
|
+
<path class="flow" d="M538 247 L 538 376" marker-end="url(#head)"/>
|
|
37
35
|
<text class="lbl" x="524" y="318" text-anchor="end">regenerates</text>
|
|
38
|
-
<path class="flow" d="M578
|
|
36
|
+
<path class="flow" d="M578 376 L 578 247" marker-end="url(#head)"/>
|
|
39
37
|
<text class="lbl" x="592" y="318" text-anchor="start">tests against ***plain</text>
|
|
40
|
-
<text class="spin" x="880" y="372" text-anchor="middle" font-
|
|
41
|
-
<
|
|
42
|
-
<text class="node" x="120" y="208" text-anchor="middle" fill="#
|
|
43
|
-
<rect x="470" y="156" width="176" height="88"
|
|
44
|
-
<text x="558" y="
|
|
45
|
-
<rect x="900" y="158" width="170" height="84"
|
|
46
|
-
<text x="985" y="208" text-anchor="middle" font-
|
|
47
|
-
<rect x="473" y="380" width="170" height="84"
|
|
48
|
-
<text class="node" x="558" y="420" text-anchor="middle" fill="#
|
|
49
|
-
<text class="sub" x="558" y="444" text-anchor="middle"
|
|
38
|
+
<text class="spin" x="880" y="372" text-anchor="middle" font-weight="500" font-size="44" fill="#5ce1e6">↻</text>
|
|
39
|
+
<rect x="66" y="146" width="108" height="108" fill="#142030"/><rect x="66" y="143" width="108" height="3" fill="#85a3b2"/><rect x="66" y="254" width="108" height="3" fill="#85a3b2"/><rect x="63" y="146" width="3" height="108" fill="#85a3b2"/><rect x="174" y="146" width="3" height="108" fill="#85a3b2"/>
|
|
40
|
+
<text class="node" x="120" y="208" text-anchor="middle" fill="#f4f7f9">you</text>
|
|
41
|
+
<rect x="470" y="156" width="176" height="88" fill="#142030"/><rect x="470" y="153" width="176" height="3" fill="#85a3b2"/><rect x="470" y="244" width="176" height="3" fill="#85a3b2"/><rect x="467" y="156" width="3" height="88" fill="#85a3b2"/><rect x="646" y="156" width="3" height="88" fill="#85a3b2"/>
|
|
42
|
+
<text x="558" y="211" text-anchor="middle" font-weight="500" font-size="34" letter-spacing="-0.02em" fill="#ff5c8d">turri</text>
|
|
43
|
+
<rect x="900" y="158" width="170" height="84" fill="#142030"/><rect x="900" y="155" width="170" height="3" fill="#85a3b2"/><rect x="900" y="242" width="170" height="3" fill="#85a3b2"/><rect x="897" y="158" width="3" height="84" fill="#85a3b2"/><rect x="1070" y="158" width="3" height="84" fill="#85a3b2"/>
|
|
44
|
+
<text x="985" y="208" text-anchor="middle" font-weight="500" font-size="24" fill="#e9d8c8">***plain</text>
|
|
45
|
+
<rect x="473" y="380" width="170" height="84" fill="none" stroke="#3e5a6b" stroke-width="4" stroke-dasharray="8 8"/>
|
|
46
|
+
<text class="node" x="558" y="420" text-anchor="middle" fill="#f4f7f9">code</text>
|
|
47
|
+
<text class="sub" x="558" y="444" text-anchor="middle">disposable</text>
|
|
50
48
|
</svg>
|
|
Binary file
|