@turri-dev/cli 0.1.4-next.4342332 → 0.1.4-next.67734c0
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 +66 -67
- package/.pi/themes/turri-light.json +73 -71
- 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 +23 -16
- package/dist/extension.js +4 -4
- package/dist/loop-cli.js +8 -8
- package/dist/module-report.js +4 -5
- package/dist/regenerate/plain-palette.js +36 -51
- package/dist/regenerate/spec-map-view.js +5 -9
- package/dist/release-cli.js +8 -3
- package/dist/validate-cli.js +8 -3
- package/docs/images/how-turri-works.svg +31 -29
- package/package.json +1 -1
|
@@ -2,80 +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-dark",
|
|
4
4
|
"vars": {
|
|
5
|
-
"
|
|
6
|
-
"
|
|
7
|
-
"
|
|
8
|
-
"
|
|
9
|
-
"
|
|
10
|
-
"
|
|
11
|
-
"
|
|
12
|
-
"
|
|
13
|
-
"
|
|
14
|
-
"
|
|
15
|
-
"
|
|
16
|
-
"orange": "#ff7a3d"
|
|
5
|
+
"yellow": "#e0ff6e",
|
|
6
|
+
"yellowMuted": "#b9cb69",
|
|
7
|
+
"green": "#79fc96",
|
|
8
|
+
"greenLight": "#c5dcd9",
|
|
9
|
+
"greenDark": "#223936",
|
|
10
|
+
"black": "#0f0f0f",
|
|
11
|
+
"white": "#ffffff",
|
|
12
|
+
"gray": "#808080",
|
|
13
|
+
"grayLight": "#666666",
|
|
14
|
+
"statusError": "#dd5353",
|
|
15
|
+
"statusWarning": "#dc6803"
|
|
17
16
|
},
|
|
18
17
|
"colors": {
|
|
19
|
-
"accent": "
|
|
20
|
-
"border": "
|
|
21
|
-
"borderAccent": "
|
|
22
|
-
"borderMuted": "
|
|
23
|
-
"success": "
|
|
24
|
-
"error": "
|
|
25
|
-
"warning": "
|
|
26
|
-
"muted": "
|
|
27
|
-
"dim": "
|
|
28
|
-
"text": "
|
|
29
|
-
"thinkingText": "
|
|
30
|
-
"selectedBg": "
|
|
31
|
-
"scrollbarTrack": "
|
|
32
|
-
"scrollbarThumb": "
|
|
33
|
-
"searchMatchBg": "
|
|
34
|
-
"searchMatchText": "
|
|
18
|
+
"accent": "yellow",
|
|
19
|
+
"border": "green",
|
|
20
|
+
"borderAccent": "green",
|
|
21
|
+
"borderMuted": "greenDark",
|
|
22
|
+
"success": "green",
|
|
23
|
+
"error": "statusError",
|
|
24
|
+
"warning": "statusWarning",
|
|
25
|
+
"muted": "gray",
|
|
26
|
+
"dim": "grayLight",
|
|
27
|
+
"text": "greenLight",
|
|
28
|
+
"thinkingText": "gray",
|
|
29
|
+
"selectedBg": "greenDark",
|
|
30
|
+
"scrollbarTrack": "greenDark",
|
|
31
|
+
"scrollbarThumb": "green",
|
|
32
|
+
"searchMatchBg": "yellow",
|
|
33
|
+
"searchMatchText": "black",
|
|
35
34
|
"userMessageBg": "",
|
|
36
|
-
"userMessageText": "
|
|
35
|
+
"userMessageText": "yellow",
|
|
37
36
|
"customMessageBg": "",
|
|
38
|
-
"customMessageText": "
|
|
39
|
-
"customMessageLabel": "
|
|
37
|
+
"customMessageText": "greenLight",
|
|
38
|
+
"customMessageLabel": "yellow",
|
|
40
39
|
"toolPendingBg": "",
|
|
41
40
|
"toolSuccessBg": "",
|
|
42
41
|
"toolErrorBg": "",
|
|
43
|
-
"toolTitle": "
|
|
44
|
-
"toolOutput": "
|
|
45
|
-
"mdHeading": "
|
|
46
|
-
"mdLink": "
|
|
47
|
-
"mdLinkUrl": "
|
|
48
|
-
"mdCode": "
|
|
49
|
-
"mdCodeBlock": "
|
|
50
|
-
"mdCodeBlockBorder": "
|
|
51
|
-
"mdQuote": "
|
|
52
|
-
"mdQuoteBorder": "
|
|
53
|
-
"mdHr": "
|
|
54
|
-
"mdListBullet": "
|
|
55
|
-
"toolDiffAdded": "
|
|
56
|
-
"toolDiffRemoved": "
|
|
57
|
-
"toolDiffContext": "
|
|
58
|
-
"syntaxComment": "
|
|
59
|
-
"syntaxKeyword": "
|
|
60
|
-
"syntaxFunction": "
|
|
61
|
-
"syntaxVariable": "
|
|
62
|
-
"syntaxString": "
|
|
63
|
-
"syntaxNumber": "
|
|
64
|
-
"syntaxType": "
|
|
65
|
-
"syntaxOperator": "
|
|
66
|
-
"syntaxPunctuation": "
|
|
67
|
-
"thinkingOff": "
|
|
68
|
-
"thinkingMinimal": "
|
|
69
|
-
"thinkingLow": "
|
|
70
|
-
"thinkingMedium": "
|
|
71
|
-
"thinkingHigh": "
|
|
72
|
-
"thinkingXhigh": "
|
|
73
|
-
"thinkingMax": "
|
|
74
|
-
"bashMode": "
|
|
42
|
+
"toolTitle": "yellow",
|
|
43
|
+
"toolOutput": "gray",
|
|
44
|
+
"mdHeading": "yellow",
|
|
45
|
+
"mdLink": "green",
|
|
46
|
+
"mdLinkUrl": "grayLight",
|
|
47
|
+
"mdCode": "yellowMuted",
|
|
48
|
+
"mdCodeBlock": "greenLight",
|
|
49
|
+
"mdCodeBlockBorder": "greenDark",
|
|
50
|
+
"mdQuote": "gray",
|
|
51
|
+
"mdQuoteBorder": "greenDark",
|
|
52
|
+
"mdHr": "grayLight",
|
|
53
|
+
"mdListBullet": "green",
|
|
54
|
+
"toolDiffAdded": "green",
|
|
55
|
+
"toolDiffRemoved": "statusError",
|
|
56
|
+
"toolDiffContext": "gray",
|
|
57
|
+
"syntaxComment": "grayLight",
|
|
58
|
+
"syntaxKeyword": "yellow",
|
|
59
|
+
"syntaxFunction": "green",
|
|
60
|
+
"syntaxVariable": "greenLight",
|
|
61
|
+
"syntaxString": "yellowMuted",
|
|
62
|
+
"syntaxNumber": "statusWarning",
|
|
63
|
+
"syntaxType": "white",
|
|
64
|
+
"syntaxOperator": "gray",
|
|
65
|
+
"syntaxPunctuation": "grayLight",
|
|
66
|
+
"thinkingOff": "grayLight",
|
|
67
|
+
"thinkingMinimal": "gray",
|
|
68
|
+
"thinkingLow": "greenLight",
|
|
69
|
+
"thinkingMedium": "green",
|
|
70
|
+
"thinkingHigh": "yellowMuted",
|
|
71
|
+
"thinkingXhigh": "yellow",
|
|
72
|
+
"thinkingMax": "statusWarning",
|
|
73
|
+
"bashMode": "yellow"
|
|
75
74
|
},
|
|
76
75
|
"export": {
|
|
77
|
-
"pageBg": "#
|
|
78
|
-
"cardBg": "#
|
|
79
|
-
"infoBg": "#
|
|
76
|
+
"pageBg": "#0f0f0f",
|
|
77
|
+
"cardBg": "#121212",
|
|
78
|
+
"infoBg": "#223936"
|
|
80
79
|
}
|
|
81
|
-
}
|
|
80
|
+
}
|
|
@@ -2,79 +2,81 @@
|
|
|
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
|
-
"
|
|
5
|
+
"blue": "#0a1fd4",
|
|
6
|
+
"greenLight": "#c5dcd9",
|
|
7
|
+
"greenDark": "#223936",
|
|
8
|
+
"diffGreen": "#1a7f37",
|
|
9
|
+
"black": "#0f0f0f",
|
|
10
|
+
"gray": "#808080",
|
|
11
|
+
"grayLight": "#666666",
|
|
12
|
+
"red": "#d00505",
|
|
13
|
+
"statusErrorLight": "#fee4e2",
|
|
14
|
+
"statusWarningLight": "#fef0c7",
|
|
15
|
+
"statusWarning": "#dc6803",
|
|
16
|
+
"paleGreen": "#eef7f5",
|
|
17
|
+
"paleGray": "#f4f4f2"
|
|
16
18
|
},
|
|
17
19
|
"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": "
|
|
34
|
-
"userMessageBg": "
|
|
35
|
-
"userMessageText": "
|
|
36
|
-
"customMessageBg": "",
|
|
37
|
-
"customMessageText": "
|
|
38
|
-
"customMessageLabel": "
|
|
39
|
-
"toolPendingBg": "",
|
|
40
|
-
"toolSuccessBg": "",
|
|
41
|
-
"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": "
|
|
20
|
+
"accent": "blue",
|
|
21
|
+
"border": "gray",
|
|
22
|
+
"borderAccent": "blue",
|
|
23
|
+
"borderMuted": "greenLight",
|
|
24
|
+
"success": "greenDark",
|
|
25
|
+
"error": "red",
|
|
26
|
+
"warning": "statusWarning",
|
|
27
|
+
"muted": "grayLight",
|
|
28
|
+
"dim": "gray",
|
|
29
|
+
"text": "black",
|
|
30
|
+
"thinkingText": "grayLight",
|
|
31
|
+
"selectedBg": "greenLight",
|
|
32
|
+
"scrollbarTrack": "greenLight",
|
|
33
|
+
"scrollbarThumb": "grayLight",
|
|
34
|
+
"searchMatchBg": "statusWarningLight",
|
|
35
|
+
"searchMatchText": "black",
|
|
36
|
+
"userMessageBg": "paleGray",
|
|
37
|
+
"userMessageText": "black",
|
|
38
|
+
"customMessageBg": "paleGreen",
|
|
39
|
+
"customMessageText": "black",
|
|
40
|
+
"customMessageLabel": "blue",
|
|
41
|
+
"toolPendingBg": "paleGray",
|
|
42
|
+
"toolSuccessBg": "paleGreen",
|
|
43
|
+
"toolErrorBg": "statusErrorLight",
|
|
44
|
+
"toolTitle": "blue",
|
|
45
|
+
"toolOutput": "grayLight",
|
|
46
|
+
"mdHeading": "blue",
|
|
47
|
+
"mdLink": "blue",
|
|
48
|
+
"mdLinkUrl": "grayLight",
|
|
49
|
+
"mdCode": "greenDark",
|
|
50
|
+
"mdCodeBlock": "black",
|
|
51
|
+
"mdCodeBlockBorder": "greenLight",
|
|
52
|
+
"mdQuote": "grayLight",
|
|
53
|
+
"mdQuoteBorder": "greenLight",
|
|
54
|
+
"mdHr": "gray",
|
|
55
|
+
"mdListBullet": "blue",
|
|
56
|
+
"toolDiffAdded": "diffGreen",
|
|
57
|
+
"toolDiffRemoved": "red",
|
|
58
|
+
"toolDiffContext": "grayLight",
|
|
59
|
+
"syntaxComment": "grayLight",
|
|
60
|
+
"syntaxKeyword": "blue",
|
|
61
|
+
"syntaxFunction": "greenDark",
|
|
62
|
+
"syntaxVariable": "black",
|
|
63
|
+
"syntaxString": "greenDark",
|
|
64
|
+
"syntaxNumber": "statusWarning",
|
|
65
|
+
"syntaxType": "blue",
|
|
66
|
+
"syntaxOperator": "gray",
|
|
67
|
+
"syntaxPunctuation": "grayLight",
|
|
68
|
+
"thinkingOff": "gray",
|
|
69
|
+
"thinkingMinimal": "grayLight",
|
|
70
|
+
"thinkingLow": "greenDark",
|
|
71
|
+
"thinkingMedium": "blue",
|
|
72
|
+
"thinkingHigh": "blue",
|
|
73
|
+
"thinkingXhigh": "statusWarning",
|
|
74
|
+
"thinkingMax": "red",
|
|
75
|
+
"bashMode": "blue"
|
|
74
76
|
},
|
|
75
77
|
"export": {
|
|
76
|
-
"pageBg": "#
|
|
77
|
-
"cardBg": "#
|
|
78
|
-
"infoBg": "#
|
|
78
|
+
"pageBg": "#ffffff",
|
|
79
|
+
"cardBg": "#f4f4f2",
|
|
80
|
+
"infoBg": "#eef7f5"
|
|
79
81
|
}
|
|
80
|
-
}
|
|
82
|
+
}
|
|
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 ones its themes use (.pi/themes): on a dark terminal its lime
|
|
6
|
+
* accent and green, on a light one its blue. 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("#0a1fd4", 34) : ink("#e0ff6e", 93),
|
|
39
39
|
/** Done, and what was written. */
|
|
40
|
-
success: light ? ink("#
|
|
40
|
+
success: light ? ink("#223936", 32) : ink("#79fc96", 92),
|
|
41
41
|
/** What is wrong: what stops turri. */
|
|
42
|
-
error: ink("#
|
|
42
|
+
error: light ? ink("#d00505", 31) : ink("#dd5353", 91),
|
|
43
43
|
/** What works, with limits. */
|
|
44
|
-
warning: ink("#
|
|
44
|
+
warning: ink("#dc6803", 33),
|
|
45
45
|
/** What goes with it, and can be skimmed. */
|
|
46
|
-
muted:
|
|
46
|
+
muted: ink("#808080", 90),
|
|
47
47
|
bold: (text) => (on ? `\x1b[1m${text}\x1b[22m` : text),
|
|
48
48
|
};
|
|
49
49
|
}
|
package/dist/cultivate-cli.js
CHANGED
|
@@ -7,33 +7,40 @@ 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
|
-
|
|
10
|
+
const c = {
|
|
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
|
+
};
|
|
11
18
|
const reporter = {
|
|
12
|
-
step: (name, attempt) => console.log(`\n${c.bold(c.
|
|
19
|
+
step: (name, attempt) => console.log(`\n${c.bold(c.cyan(`> step ${name}`))} ${c.dim(`attempt ${attempt}`)}`),
|
|
13
20
|
artifact: (path, body) => {
|
|
14
21
|
console.log(c.dim(` ${path}:`));
|
|
15
22
|
for (const line of body.split("\n"))
|
|
16
23
|
console.log(c.dim(` | ${line}`));
|
|
17
24
|
},
|
|
18
25
|
gate: (name, judges) => console.log(judges === 0
|
|
19
|
-
? `${c.bold(c.
|
|
20
|
-
: `${c.bold(c.
|
|
26
|
+
? `${c.bold(c.yellow(`> gate ${name}`))} ${c.dim("no artifact - failing without spending judges")}`
|
|
27
|
+
: `${c.bold(c.yellow(`> gate ${name}`))} ${c.dim(`${judges} independent judges`)}`),
|
|
21
28
|
livePass: (step, scope, findings) => console.log(findings === 0
|
|
22
29
|
? c.dim(` review ${step}: ${scope} - clean`)
|
|
23
|
-
: ` ${c.
|
|
30
|
+
: ` ${c.yellow(`review ${step}`)}: ${scope} ${c.dim(`- ${findings} to fix`)}`),
|
|
24
31
|
liveReview: (name, passes, findings) => console.log(findings === 0
|
|
25
|
-
? `${c.bold(c.
|
|
26
|
-
: `${c.bold(c.
|
|
32
|
+
? `${c.bold(c.yellow(`> gate ${name}`))} ${c.dim(`reviewer read along in ${passes} passes, nothing outstanding`)}`
|
|
33
|
+
: `${c.bold(c.yellow(`> gate ${name}`))} ${c.dim(`${findings} unresolved from the reviewer - failing without spending judges`)}`),
|
|
27
34
|
judge: (index, model, verdict, violations) => {
|
|
28
|
-
const mark = verdict === "pass" ? c.
|
|
35
|
+
const mark = verdict === "pass" ? c.green("pass") : c.red(verdict);
|
|
29
36
|
console.log(` judge ${index + 1} ${c.dim(`(${model})`)}: ${mark}${violations ? c.dim(` (${violations})`) : ""}`);
|
|
30
37
|
},
|
|
31
38
|
outcome: (name, decision, critique) => {
|
|
32
39
|
const colour = {
|
|
33
|
-
advance: c.
|
|
34
|
-
retry: c.
|
|
35
|
-
escalate: c.
|
|
36
|
-
abort: c.
|
|
40
|
+
advance: c.green,
|
|
41
|
+
retry: c.yellow,
|
|
42
|
+
escalate: c.yellow,
|
|
43
|
+
abort: c.red,
|
|
37
44
|
};
|
|
38
45
|
console.log(` ${c.bold(colour[decision](decision))} ${c.dim(`<- gate ${name}`)}`);
|
|
39
46
|
if (critique)
|
|
@@ -71,7 +78,7 @@ async function main() {
|
|
|
71
78
|
select: async (title, options, context) => {
|
|
72
79
|
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
73
80
|
const list = options.map((option, i) => ` ${c.bold(String(i + 1))}. ${option}`).join("\n");
|
|
74
|
-
const reply = (await rl.question(`\n${context !== undefined ? `${context}\n\n` : ""}${c.bold(c.
|
|
81
|
+
const reply = (await rl.question(`\n${context !== undefined ? `${context}\n\n` : ""}${c.bold(c.cyan("? "))}${title}\n${list}\n> `)).trim();
|
|
75
82
|
rl.close();
|
|
76
83
|
const index = Number.parseInt(reply, 10);
|
|
77
84
|
if (Number.isInteger(index) && index >= 1 && index <= options.length)
|
|
@@ -80,13 +87,13 @@ async function main() {
|
|
|
80
87
|
},
|
|
81
88
|
input: async (title, context) => {
|
|
82
89
|
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
83
|
-
const reply = (await rl.question(`\n${context !== undefined ? `${context}\n\n` : ""}${c.bold(c.
|
|
90
|
+
const reply = (await rl.question(`\n${context !== undefined ? `${context}\n\n` : ""}${c.bold(c.cyan("? "))}${title}\n> `)).trim();
|
|
84
91
|
rl.close();
|
|
85
92
|
return reply === "" ? undefined : reply;
|
|
86
93
|
},
|
|
87
94
|
}),
|
|
88
95
|
escalate: async ({ question, critique }) => {
|
|
89
|
-
console.log(`\n${c.bold(c.
|
|
96
|
+
console.log(`\n${c.bold(c.yellow("! human review required"))}`);
|
|
90
97
|
console.log(c.dim(critique));
|
|
91
98
|
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
92
99
|
const answer = (await rl.question(`${question} [y/N] `)).trim().toLowerCase();
|
|
@@ -94,7 +101,7 @@ async function main() {
|
|
|
94
101
|
return answer === "y";
|
|
95
102
|
},
|
|
96
103
|
});
|
|
97
|
-
console.log(`\n${result.ok ? c.
|
|
104
|
+
console.log(`\n${result.ok ? c.green(c.bold("run complete")) : c.red(c.bold("run aborted at a gate"))} ${c.dim(`artifacts in ${relative(cwd, skillRunDir(cwd, skill.name))}`)}`);
|
|
98
105
|
process.exit(result.ok ? 0 : 1);
|
|
99
106
|
}
|
|
100
107
|
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 { brandBlue, 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: brand blue.
|
|
1650
|
+
conforms: brandBlue(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
|
-
`${
|
|
2311
|
+
`${theme.fg("accent", "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.cyan(`▶ ${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.green("✓") : job.status === "stopped" ? c.dim("■") : c.red("✗");
|
|
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.yellow(` 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.yellow(` ${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.red(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.red(c.bold(`${failed.length} job${failed.length === 1 ? "" : "s"} failed`))
|
|
166
166
|
: unfinished.length > 0
|
|
167
|
-
? c.
|
|
168
|
-
: c.
|
|
167
|
+
? c.red(c.bold(`not complete: ${unfinished.map((module) => `${module.module}${module.why !== undefined ? ` - ${module.why}` : ""}`).join("; ")}`))
|
|
168
|
+
: c.green(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,10 +1,9 @@
|
|
|
1
|
-
import { brand } from "./brand.js";
|
|
2
1
|
/** Colours for what the command line prints. */
|
|
3
2
|
export const c = {
|
|
4
3
|
dim: (s) => `\x1b[2m${s}\x1b[0m`,
|
|
5
4
|
bold: (s) => `\x1b[1m${s}\x1b[0m`,
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
5
|
+
green: (s) => `\x1b[32m${s}\x1b[0m`,
|
|
6
|
+
red: (s) => `\x1b[31m${s}\x1b[0m`,
|
|
7
|
+
yellow: (s) => `\x1b[33m${s}\x1b[0m`,
|
|
8
|
+
cyan: (s) => `\x1b[36m${s}\x1b[0m`,
|
|
10
9
|
};
|
|
@@ -1,46 +1,38 @@
|
|
|
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
|
-
*/
|
|
1
|
+
/** On a dark surface - turri-dark's #0f0f0f, pi's dark ones. Every colour at least 7:1 on it, and 4.5:1 on the fills. */
|
|
7
2
|
const DARK = {
|
|
8
3
|
roles: {
|
|
9
|
-
section: { colour: "#
|
|
10
|
-
concept: { colour: "#
|
|
11
|
-
link: { colour: "#
|
|
12
|
-
literal: { colour: "#
|
|
13
|
-
keyword: { colour: "#
|
|
14
|
-
module: { colour: "#
|
|
15
|
-
comment: { colour: "#
|
|
16
|
-
bullet: { colour: "#
|
|
4
|
+
section: { colour: "#fcd34d", bold: true },
|
|
5
|
+
concept: { colour: "#7dd3fc" },
|
|
6
|
+
link: { colour: "#c4b5fd", underline: true },
|
|
7
|
+
literal: { colour: "#fdba74" },
|
|
8
|
+
keyword: { colour: "#f9a8d4", bold: true },
|
|
9
|
+
module: { colour: "#5eead4" },
|
|
10
|
+
comment: { colour: "#b4bfcf", italic: true },
|
|
11
|
+
bullet: { colour: "#b4bfcf" },
|
|
17
12
|
},
|
|
18
|
-
added: "#
|
|
19
|
-
removed: "#
|
|
20
|
-
addedFill: "#
|
|
21
|
-
removedFill: "#
|
|
22
|
-
muted: "#
|
|
13
|
+
added: "#86efac",
|
|
14
|
+
removed: "#fca5a5",
|
|
15
|
+
addedFill: "#1f4d2e",
|
|
16
|
+
removedFill: "#5c2323",
|
|
17
|
+
muted: "#808080",
|
|
23
18
|
};
|
|
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
|
-
*/
|
|
19
|
+
/** On a light surface - turri-light's white, pi's light ones. Every colour at least 7:1 on it, and 5:1 on the fills. */
|
|
28
20
|
const LIGHT = {
|
|
29
21
|
roles: {
|
|
30
|
-
section: { colour: "#
|
|
31
|
-
concept: { colour: "#
|
|
32
|
-
link: { colour: "#
|
|
33
|
-
literal: { colour: "#
|
|
34
|
-
keyword: { colour: "#
|
|
35
|
-
module: { colour: "#
|
|
36
|
-
comment: { colour: "#
|
|
37
|
-
bullet: { colour: "#
|
|
22
|
+
section: { colour: "#0a1fd4", bold: true },
|
|
23
|
+
concept: { colour: "#155e75" },
|
|
24
|
+
link: { colour: "#6d28d9", underline: true },
|
|
25
|
+
literal: { colour: "#92400e" },
|
|
26
|
+
keyword: { colour: "#9d174d", bold: true },
|
|
27
|
+
module: { colour: "#065f46" },
|
|
28
|
+
comment: { colour: "#475569", italic: true },
|
|
29
|
+
bullet: { colour: "#475569" },
|
|
38
30
|
},
|
|
39
|
-
added: "#
|
|
40
|
-
removed: "#
|
|
41
|
-
addedFill: "#
|
|
42
|
-
removedFill: "#
|
|
43
|
-
muted: "#
|
|
31
|
+
added: "#166534",
|
|
32
|
+
removed: "#991b1b",
|
|
33
|
+
addedFill: "#d3f5dc",
|
|
34
|
+
removedFill: "#fcd9d9",
|
|
35
|
+
muted: "#6b7280",
|
|
44
36
|
};
|
|
45
37
|
const rgb = (hex) => {
|
|
46
38
|
const value = Number.parseInt(hex.slice(1), 16);
|
|
@@ -65,24 +57,17 @@ const fillWith = (hex, text, truecolor) => {
|
|
|
65
57
|
return `${on}${text.replace(/\x1b\[(?:0|49)?m/g, (reset) => `${reset}${on}`)}\x1b[49m`;
|
|
66
58
|
};
|
|
67
59
|
/**
|
|
68
|
-
*
|
|
69
|
-
*
|
|
60
|
+
* Brand blue, for what is shown to do what its spec says - regen's ◆. Brand blue itself on a light surface, 9.72:1
|
|
61
|
+
* on white; on a dark one, where it is illegible (1.97:1 on brand black - see docs/theming.md), its tint 55% of the way
|
|
62
|
+
* to white, 7.34:1 on brand black.
|
|
70
63
|
*/
|
|
71
|
-
export const
|
|
72
|
-
/**
|
|
73
|
-
|
|
74
|
-
|
|
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;
|
|
64
|
+
export const BRAND_BLUE = { light: "#0a1fd4", dark: "#919aec" };
|
|
65
|
+
/** Text in brand blue on a theme - its tint on a dark one - in as many colours as the terminal has. */
|
|
66
|
+
export function brandBlue(theme) {
|
|
67
|
+
const colour = theme.appearance === "light" ? BRAND_BLUE.light : BRAND_BLUE.dark;
|
|
79
68
|
const truecolor = (theme.getColorMode?.() ?? "truecolor") === "truecolor";
|
|
80
69
|
return (text) => paintWith({ colour, bold: true }, text, truecolor);
|
|
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);
|
|
70
|
+
}
|
|
86
71
|
/** 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. */
|
|
87
72
|
export function plainPaint(theme) {
|
|
88
73
|
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 ?
|
|
55
|
+
return regenerating ? style.accent(text) : page.stuck ? style.error(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,8 +69,6 @@ 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;
|
|
74
72
|
/** Strike text through. */
|
|
75
73
|
const strike = (text) => `\x1b[9m${text}\x1b[29m`;
|
|
76
74
|
/** How far a group, and what is in it, is indented under its module. */
|
|
@@ -123,7 +121,7 @@ function rungGlyph(rung, style, now) {
|
|
|
123
121
|
if (rung === "conform")
|
|
124
122
|
return green(LADDER.conform);
|
|
125
123
|
if (rung === "needsYou")
|
|
126
|
-
return
|
|
124
|
+
return style.error(LADDER.needsYou);
|
|
127
125
|
if (rung === "takenOut")
|
|
128
126
|
return style.error(LADDER.takenOut);
|
|
129
127
|
if (rung === "toRegenerate")
|
|
@@ -488,7 +486,7 @@ export function specMapLines(pages, state, width, style, _jobs = [], now = Date.
|
|
|
488
486
|
// The first item that needs the person says what for, and how to go about it - opened or closed.
|
|
489
487
|
if (page.stuck && !toldWhy && rungOf(item) === "needsYou") {
|
|
490
488
|
toldWhy = true;
|
|
491
|
-
for (const line of wrapped(needsLine(page), width,
|
|
489
|
+
for (const line of wrapped(needsLine(page), width, style.error))
|
|
492
490
|
add(line, mark);
|
|
493
491
|
}
|
|
494
492
|
if (state.open.has(row.key)) {
|
|
@@ -517,9 +515,7 @@ function paintOf(mark, style) {
|
|
|
517
515
|
return style.success ?? style.added;
|
|
518
516
|
if (mark === "regenerating" || mark === "planning")
|
|
519
517
|
return style.accent;
|
|
520
|
-
if (mark === "stuck")
|
|
521
|
-
return needs(style);
|
|
522
|
-
if (mark === "removed")
|
|
518
|
+
if (mark === "stuck" || mark === "removed")
|
|
523
519
|
return style.error;
|
|
524
520
|
return style.muted;
|
|
525
521
|
}
|
|
@@ -552,7 +548,7 @@ export function specMapHeader(page, width, style, waiting = "") {
|
|
|
552
548
|
const strip = progressStrip(page, style);
|
|
553
549
|
const count = total > 0 ? `${built} of ${total} ${total === 1 ? "functionality" : "functionalities"} built · ` : "";
|
|
554
550
|
// Short, never cut off: what for, and how, is under the first `!`.
|
|
555
|
-
const status = page.stuck ?
|
|
551
|
+
const status = page.stuck ? style.error(`${LADDER.needsYou} needs you`) : page.status;
|
|
556
552
|
// The module's file heads its column, under this line.
|
|
557
553
|
return truncateToWidth(`${style.heading("regen")}${strip !== "" ? ` ${strip}` : ""} ${count}${status}${waiting}`, width);
|
|
558
554
|
}
|
package/dist/release-cli.js
CHANGED
|
@@ -4,7 +4,6 @@ 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";
|
|
8
7
|
const USAGE = `usage: turri release [file.plain] [options]
|
|
9
8
|
|
|
10
9
|
Releases the module on top of the spec's requires chain, which holds the code of the ones below, and
|
|
@@ -21,6 +20,12 @@ harness-model (the agent's model).
|
|
|
21
20
|
--in-module <module> the codebase to take, when modules built on this one branch
|
|
22
21
|
--guidance <text> how this project releases it: the scripts to run, in order
|
|
23
22
|
--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
|
+
};
|
|
24
29
|
const PROBLEMS = "If you are having problems, use chat mode to deploy: run turri and ask to try it.";
|
|
25
30
|
function parseArgs(argv) {
|
|
26
31
|
const positional = [];
|
|
@@ -71,7 +76,7 @@ function followLogs() {
|
|
|
71
76
|
closeSync(fd);
|
|
72
77
|
offsets.set(item.log, size);
|
|
73
78
|
for (const line of buffer.toString("utf8").split("\n").filter((l) => l !== ""))
|
|
74
|
-
console.log(`${c.
|
|
79
|
+
console.log(`${c.cyan(`[${item.name}]`)} ${line}`);
|
|
75
80
|
}
|
|
76
81
|
if (running.length === 0) {
|
|
77
82
|
clearInterval(timer);
|
|
@@ -114,7 +119,7 @@ async function main() {
|
|
|
114
119
|
console.log(`\n\n${c.dim(PROBLEMS)}`);
|
|
115
120
|
if (runningDeployed().length === 0)
|
|
116
121
|
process.exit(0);
|
|
117
|
-
console.log(c.
|
|
122
|
+
console.log(c.green(c.bold("\nRunning - Ctrl-C stops it.")) + c.dim(" Its output follows.\n"));
|
|
118
123
|
await followLogs();
|
|
119
124
|
process.exit(0);
|
|
120
125
|
}
|
package/dist/validate-cli.js
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { checkPlainTargets, formatCheckResults } from "./plain/check.js";
|
|
2
|
-
import { c } from "./module-report.js";
|
|
3
2
|
const USAGE = `usage: turri validate <file.plain | directory> [options]
|
|
4
3
|
|
|
5
4
|
Validates a ***plain specification statically: parses it, resolves its imports and
|
|
@@ -9,6 +8,12 @@ required modules, and reports every error without rendering anything.
|
|
|
9
8
|
--json machine-readable output
|
|
10
9
|
|
|
11
10
|
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
|
+
};
|
|
12
17
|
const argv = process.argv.slice(2);
|
|
13
18
|
const templateDirs = [];
|
|
14
19
|
const positional = [];
|
|
@@ -38,10 +43,10 @@ if (json) {
|
|
|
38
43
|
else {
|
|
39
44
|
const output = formatCheckResults(results)
|
|
40
45
|
.split("\n")
|
|
41
|
-
.map((line) => line.startsWith("PASS") ? c.
|
|
46
|
+
.map((line) => line.startsWith("PASS") ? c.green(line) : line.startsWith("FAIL") ? c.red(line) : c.dim(line))
|
|
42
47
|
.join("\n");
|
|
43
48
|
console.log(output);
|
|
44
49
|
const failed = results.filter((r) => !r.ok).length;
|
|
45
|
-
console.log(`\n${failed === 0 ? c.
|
|
50
|
+
console.log(`\n${failed === 0 ? c.green(c.bold("all specs valid")) : c.red(c.bold(`${failed} of ${results.length} failed`))}`);
|
|
46
51
|
}
|
|
47
52
|
process.exit(results.every((r) => r.ok) ? 0 : 1);
|
|
@@ -2,47 +2,49 @@
|
|
|
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: "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,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) format("woff2"); }
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
.
|
|
11
|
-
.
|
|
12
|
-
.
|
|
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
|
+
@font-face { font-family: "JetBrains Mono"; font-weight: 500; src: url(data:font/woff2;base64,d09GMgABAAAAAAcgABAAAAAADXAAAAbHAAI2BAAAAAAAAAAAAAAAAAAAAAAAAAAAGjgbIBwqBmA/U1RBVEwAdBEICo5Ui00BNgIkA2QLNAAEIAWEKAcgDAcbowpRlDBSDcHPBJvO00IjNaLCrKQKldp0x/T4tsjzu+CfvL3/13VbGw7aXRAElGBRYBYEiQcR7ufx9P/ctFMyo3Mxtk6f2nRyOVVK8QB6+AJ4Hru9f6slbSV0FDdvzcMooDyQKMVII4gx4MBows0HoPP538/Vd2dI9SjedgiJSIxvJpoQtUqpRNL2J5pIopUqlhcaIXIIqWGpTnMQT/YbglvUuZMRoMiXCpIAAS5dAEB7wdwPmGTKhtbqRjWEYwLoAADWXYQNO1/0aR7jh1QYVB+KHv0CIcBP0QNwivw6gJ8RAAZJb2RQ9FgAeIHlPOOf//pAbUUliWdwxd4CYBXCJobDAJWTbcHldI1BAPwjY0AxmIspNcuMWvQLgF4A72G7ESyxKkmNk1FVRDhtYjLdJhYDEwuXnRstaIzxzv//P9hkMnPy8htlHJvz/+++scP2QQ232WqLXogaIugHg8Ym8w5YgDcAfymIh0Bh2YRgQntNODAekgAE4reURaREZ1fPlY7nRKfgUTHJ8dGp0cmlCVE5kUMiNYsG0MxBgkAzX+GLnqTig4PE4n7AcITzZvRpFw8i1AZgxksznomDAx5CgJpgzzRKAMlroQvzC+gUA/MiKq8G37YuJ2Qwh+jrc6VGBJO4WYsIprdNCkGrinTT8DxD3pXzDeCL+gB0muTi6wjpsr24c6oEG3s72z1E4m4MZ+xIhxGIYOAoCiNgJxNG6Ai9AdfiEBWHUISYM64SxQFAsTZV4W2sp3UaazGkwu/wrAa6YBbBVZ/GUYiObGLjU2LyczTzmlg78E7Cix2w6RKOL7oy6qJI098PpQuPWhS1gUB5EHBsRuIZewnDxpNpm09ndBHds2QndstmnqAb79y0K33DjlS9edE1NPM2sfgmzLii0+o2X7+ekrzv3r2ibJRm78WLSSn7R+9CiIAZO2yKJ62/fLlYp1HvuXOnIHHt1atpgkm76dSpdHzDiROpXjpyHVeL/BKhteibml367o2C0pH9f+Bciye8bpyuyzLSopngtscnDOmq5rWT9WRZ7XpuFJu7AtojVC31CleK2/sWvRWbnuZStNZTHdaAabPRGhjfI7pCriCviATHpCukx6A8qRx8sUmxQik2Ovgq63gnZqDLTgsk/0hXSv+VCE6X0ZhhvBOYER3WgHGzyRroCMKfhk3fSr/ldafQeXN2D6laECI18BJlE4qMI1YoRL0zfkplFaqXpLCoy4Y+J8tXBLxE1fj2LCpviFxByoYohqb9DDaf7jnInV8sQfaUjuHNNZTGHDAGjeaAhmquHa5OBpe3osXaacjgC45J7R6vs4S0y5aC+i36DLjSNyib6qrazS6Dn7aIN6PFyC5os9y0HLDE3TlpAV32O6w0r3xn0rGwk1lTVn1t/gYmCxrY3Ab+yzMWloAlzDlkAXWR101HNuJISl5UWQUio2KZUtQ7o1TNDuhVLlOIjJegeiVXZMvIyxLxY3KZ7Al0nL4Y4bW3qzos/iUjSrpb+VKq7drS+cNER8gV5BGR4Jr5ytfAUeRyp7NGGJo6m/QO2lgYyJQfE4mOk12QGuX6Qr5yKE3GcniVKbmsO2ercV1Ue21to6XdXKR1NIcqaMnT4qLyXXnZPWNrie5IbWNdi0PnAFdDR1sdZ4Nlw2Z686BlkCOTrLPAp2cL/UHjsehKRfQyv9G3+A1gpLqwi++R0gkJi2VST5fEI1uSkLhEKvDodYWjRA75tvi8ZXKJA3iJ/R+b8wOb8wGH/R1cNLTJyqjX42f9XVihMugaM3J7GzOcfz/7hqI+2ZDWYetI29Bnpf659rezaQ8dXr3GoKoo+nvWuNvlVJusQiX91ui89jAlf/Qr4N35a3JYa/KzV8PKWQNYhQjAKHUErINEoB5mMAEu4IkRDX0IQ3NyCgJ4Zn5h65pqjBnya2QwvgCA79W+TVmmt1789KdcIaZnfEarxb4i8ZdHtylU/1ko1/YUbUvROcQQqIc3a1q5po1DtzsLh1rjWEIlypGp6RPI/UkA/IAAzUoIdwZCU1GFlB3ZooWlqIkA4AHIehGuK72YaJ/1MpB+2c1Ee0Okp6wnFDu1UxBASZAjhDsFMdwFt4sFPGtQAoImGsXNyWUMFr6KoYNFvQG7o54dUCjhwWZufqMdTQILCGRqNUqAh52VJpPCWGO4BIyyULHpqVAEFxeSypVzcoO1GcuijFWAT3mwjbek4Y/CSe4v28TOxm0sn3Z2TmPRzEbhK8NXqZJcPSW53FtKB63wbCq32MUi3B4i6KQ3vmY7c50xzGhuVu334jXz/9ZdrAcfIL3xN/q/Yy4F) format("woff2"); }
|
|
8
|
+
@font-face { font-family: "JetBrains Mono"; font-weight: 700; src: url(data:font/woff2;base64,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) format("woff2"); }
|
|
9
|
+
text { font-variant-ligatures: none; font-feature-settings: "calt" 0, "liga" 0; }
|
|
10
|
+
.lbl { font-family: "JetBrains Mono", ui-monospace, Menlo, monospace; font-size: 15px; font-weight: 500; fill: #0f0f0f; }
|
|
11
|
+
.node { font-family: "Archivo", "Helvetica Neue", Arial, sans-serif; font-weight: 800; font-size: 22px; }
|
|
12
|
+
.sub { font-family: "JetBrains Mono", ui-monospace, Menlo, monospace; font-weight: 700; font-size: 12px; letter-spacing: 0.06em; }
|
|
13
|
+
.flow { fill: none; stroke: #0f0f0f; stroke-width: 2.25; stroke-linecap: round; }
|
|
14
|
+
.review { stroke-dasharray: 9 7; }
|
|
13
15
|
@media (prefers-reduced-motion: no-preference) {
|
|
14
|
-
.spin { transform-box: fill-box; transform-origin: 50% 58%; animation: turn 6s
|
|
16
|
+
.spin { transform-box: fill-box; transform-origin: 50% 58%; animation: turn 6s linear infinite; }
|
|
15
17
|
@keyframes turn { to { transform: rotate(360deg); } }
|
|
16
18
|
}
|
|
17
19
|
</style>
|
|
18
|
-
<rect x="16" y="-5" width="1100" height="490" fill="#
|
|
20
|
+
<rect x="16" y="-5" width="1100" height="490" rx="18" fill="#e0ff6e"/>
|
|
19
21
|
<defs>
|
|
20
22
|
<marker id="head" viewBox="0 0 10 10" refX="10" refY="5" markerWidth="7" markerHeight="7" orient="auto-start-reverse">
|
|
21
|
-
<path d="M0 0 L10 5 L0 10 z" fill="#
|
|
23
|
+
<path d="M0 0 L10 5 L0 10 z" fill="#0f0f0f"/>
|
|
22
24
|
</marker>
|
|
23
25
|
</defs>
|
|
24
|
-
<path class="flow review" d="M120
|
|
25
|
-
<text class="lbl" x="552" y="
|
|
26
|
-
<path class="flow" d="
|
|
26
|
+
<path class="flow review" d="M120 142 V 70 Q 120 40 150 40 H 955 Q 985 40 985 70 V 158"/>
|
|
27
|
+
<text class="lbl" x="552" y="28" text-anchor="middle">review loop</text>
|
|
28
|
+
<path class="flow" d="M174.4 180 L 474.8 180" marker-end="url(#head)"/>
|
|
27
29
|
<text class="lbl" x="324" y="166" text-anchor="middle">describe changes</text>
|
|
28
|
-
<path class="flow" d="
|
|
30
|
+
<path class="flow" d="M474.8 220 L 174.4 220" marker-end="url(#head)"/>
|
|
29
31
|
<text class="lbl" x="324" y="246" text-anchor="middle">asks · reports</text>
|
|
30
|
-
<path class="flow" d="
|
|
32
|
+
<path class="flow" d="M641.2 180 L 900 180" marker-end="url(#head)"/>
|
|
31
33
|
<text class="lbl" x="771" y="166" text-anchor="middle">writes</text>
|
|
32
|
-
<path class="flow" d="
|
|
34
|
+
<path class="flow" d="M900 220 L 641.2 220" marker-end="url(#head)"/>
|
|
33
35
|
<text class="lbl" x="771" y="246" text-anchor="middle">reads</text>
|
|
34
|
-
<path class="flow" d="M538
|
|
36
|
+
<path class="flow" d="M538 244 L 538 380" marker-end="url(#head)"/>
|
|
35
37
|
<text class="lbl" x="524" y="318" text-anchor="end">regenerates</text>
|
|
36
|
-
<path class="flow" d="M578
|
|
38
|
+
<path class="flow" d="M578 380 L 578 244" marker-end="url(#head)"/>
|
|
37
39
|
<text class="lbl" x="592" y="318" text-anchor="start">tests against ***plain</text>
|
|
38
|
-
<text class="spin" x="880" y="372" text-anchor="middle" font-weight="
|
|
39
|
-
<
|
|
40
|
-
<text class="node" x="120" y="208" text-anchor="middle" fill="#
|
|
41
|
-
<rect x="470" y="156" width="176" height="88"
|
|
42
|
-
<text x="558" y="
|
|
43
|
-
<rect x="900" y="158" width="170" height="84"
|
|
44
|
-
<text x="985" y="208" text-anchor="middle" font-weight="
|
|
45
|
-
<rect x="473" y="380" width="170" height="84" fill="
|
|
46
|
-
<text class="node" x="558" y="420" text-anchor="middle" fill="#
|
|
47
|
-
<text class="sub" x="558" y="444" text-anchor="middle">
|
|
40
|
+
<text class="spin" x="880" y="372" text-anchor="middle" font-family="Archivo, sans-serif" font-weight="900" font-size="44" fill="#0f0f0f">↻</text>
|
|
41
|
+
<circle cx="120" cy="200" r="58" fill="none" stroke="#0f0f0f" stroke-width="2.5"/>
|
|
42
|
+
<text class="node" x="120" y="208" text-anchor="middle" fill="#0f0f0f">you</text>
|
|
43
|
+
<rect x="470" y="156" width="176" height="88" rx="44" fill="#0f0f0f"/>
|
|
44
|
+
<text x="558" y="212" text-anchor="middle" font-family="Archivo, sans-serif" font-weight="900" font-size="34" letter-spacing="-1" fill="#e0ff6e">turri</text>
|
|
45
|
+
<rect x="900" y="158" width="170" height="84" rx="6" fill="#223936"/>
|
|
46
|
+
<text x="985" y="208" text-anchor="middle" font-family="JetBrains Mono, ui-monospace, monospace" font-weight="700" font-size="24" fill="#c5dcd9">***plain</text>
|
|
47
|
+
<rect x="473" y="380" width="170" height="84" rx="6" fill="#e0ff6e" stroke="#0f0f0f" stroke-width="2.5" stroke-dasharray="7 6"/>
|
|
48
|
+
<text class="node" x="558" y="420" text-anchor="middle" fill="#0f0f0f">code</text>
|
|
49
|
+
<text class="sub" x="558" y="444" text-anchor="middle" fill="#0f0f0f">DISPOSABLE</text>
|
|
48
50
|
</svg>
|