@turri-dev/cli 0.1.4-next.5a94e68 → 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 +40 -128
- 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 +15 -21
- 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/side-panel.js +1 -1
- package/dist/regenerate/spec-map-view.js +9 -5
- package/dist/release-cli.js +3 -8
- package/dist/validate-cli.js +3 -8
- package/docs/README.md +5 -12
- package/docs/authoring-plain.md +37 -119
- package/docs/config.md +50 -176
- package/docs/deploy.md +21 -80
- package/docs/development.md +13 -41
- package/docs/headless.md +30 -136
- package/docs/images/how-turri-works.svg +29 -31
- package/docs/images/turri-banner.png +0 -0
- package/docs/installation.md +13 -40
- package/docs/parser.md +15 -43
- package/docs/regenerating.md +231 -964
- package/docs/sandbox.md +38 -152
- package/docs/skills-and-gates.md +50 -173
- package/docs/theming.md +45 -46
- package/docs/using.md +66 -356
- package/genome/rules/bullet-continuation.md +5 -13
- package/genome/rules/concise-specs-examples.md +2 -4
- package/genome/rules/concise-specs-integration-examples.md +2 -6
- package/genome/rules/concise-specs.md +1 -2
- package/genome/rules/func-specs.md +1 -0
- package/genome/rules/integration-embedded-testing.md +1 -4
- package/genome/rules/integrations.md +1 -2
- package/genome/rules/project-model.md +14 -35
- package/genome/rules/stdlib.md +14 -49
- package/genome/rules/turri-regeneration.md +48 -0
- package/genome/skills/add-implementation-requirement/SKILL.md +1 -2
- package/genome/skills/analyze-if-func-spec-too-complex/SKILL.md +3 -8
- package/genome/skills/brood/SKILL.md +37 -127
- package/genome/skills/brood/references/intent.md +4 -12
- package/genome/skills/brood/references/product.md +24 -76
- package/genome/skills/brood/references/stack.md +7 -29
- package/genome/skills/brood/references/testing.md +11 -42
- package/genome/skills/bud/SKILL.md +32 -109
- package/genome/skills/bud/references/intent.md +4 -11
- package/genome/skills/implement-conformance-testing-script/SKILL.md +3 -6
- package/genome/skills/implement-unit-testing-script/SKILL.md +1 -2
- package/genome/skills/mature/SKILL.md +21 -69
- package/genome/skills/spawn/SKILL.md +28 -113
- package/package.json +1 -1
|
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";
|
|
@@ -1517,7 +1517,8 @@ function registerJobTools(pi, jobs, cwd) {
|
|
|
1517
1517
|
});
|
|
1518
1518
|
// The side panel: the regeneration - and the pulses behind p - over the whole screen, or beside the chat (s, from one to
|
|
1519
1519
|
// the other). Its key (PANEL_KEY) opens it; the key again closes it and goes back to the chat, as esc does over the
|
|
1520
|
-
// screen - beside the chat,
|
|
1520
|
+
// screen - beside the chat, the key switches sides, giving the keys to the chat or the panel, as esc gives them to the
|
|
1521
|
+
// chat, the panel staying; ctrl+c closes it.
|
|
1521
1522
|
// While the panel has the keys, every key is its own.
|
|
1522
1523
|
let panel;
|
|
1523
1524
|
// Beside the chat or over it: as the person last left it, for the conversation.
|
|
@@ -1645,8 +1646,8 @@ function registerJobTools(pi, jobs, cwd) {
|
|
|
1645
1646
|
removed: (text) => theme.fg("toolDiffRemoved", text),
|
|
1646
1647
|
// A spec diff's ***plain highlighted as plyn highlights it, and its added and removed lines filled.
|
|
1647
1648
|
diff: plainPaint(theme),
|
|
1648
|
-
// What conformance tests show does what its spec says:
|
|
1649
|
-
conforms:
|
|
1649
|
+
// What conformance tests show does what its spec says: neon.
|
|
1650
|
+
conforms: brandNeon(theme),
|
|
1650
1651
|
},
|
|
1651
1652
|
back,
|
|
1652
1653
|
close,
|
|
@@ -1688,17 +1689,6 @@ function registerJobTools(pi, jobs, cwd) {
|
|
|
1688
1689
|
}
|
|
1689
1690
|
if (!state)
|
|
1690
1691
|
return undefined;
|
|
1691
|
-
// Beside the chat, ⌥⌘← and ⌥⌘→ go to the side they point at, whichever has the keys - on a terminal that tells
|
|
1692
|
-
// turri of ⌘ (the Kitty keyboard protocol). Its release goes nowhere.
|
|
1693
|
-
if (panelBeside() > 0 && (matchesKey(data, SIDE_KEYS.chat) || matchesKey(data, SIDE_KEYS.panel))) {
|
|
1694
|
-
if (!isKeyRelease(data)) {
|
|
1695
|
-
if (matchesKey(data, SIDE_KEYS.chat))
|
|
1696
|
-
toChat();
|
|
1697
|
-
else
|
|
1698
|
-
toPanel();
|
|
1699
|
-
}
|
|
1700
|
-
return { consume: true };
|
|
1701
|
-
}
|
|
1702
1692
|
// Beside the chat, with the chat's keys: they are the chat's - PANEL_KEY among them, for the shortcut that gives
|
|
1703
1693
|
// them to the panel. A click on the panel gives it the focus, and the keys with it.
|
|
1704
1694
|
if (!entry.keys && entry.handle?.isFocused() !== true)
|
|
@@ -1709,9 +1699,13 @@ function registerJobTools(pi, jobs, cwd) {
|
|
|
1709
1699
|
// presses count - and ctrl+h held down, repeating, does not open and close it over and over.
|
|
1710
1700
|
if (isKeyRelease(data) || (isKeyRepeat(data) && matchesKey(data, PANEL_KEY)))
|
|
1711
1701
|
return { consume: true };
|
|
1712
|
-
//
|
|
1713
|
-
|
|
1702
|
+
// PANEL_KEY goes back: beside the chat, to the chat, the panel staying - the switch between the sides, as it is
|
|
1703
|
+
// from the chat to the panel - and over the screen, closing it. ctrl+c closes it either way: whatever else goes
|
|
1704
|
+
// wrong, a key always leads back to the chat.
|
|
1705
|
+
if (matchesKey(data, "ctrl+c"))
|
|
1714
1706
|
close();
|
|
1707
|
+
else if (matchesKey(data, PANEL_KEY))
|
|
1708
|
+
back();
|
|
1715
1709
|
else {
|
|
1716
1710
|
try {
|
|
1717
1711
|
view.handleInput(data);
|
|
@@ -1741,8 +1735,10 @@ function registerJobTools(pi, jobs, cwd) {
|
|
|
1741
1735
|
handleInput: (data) => {
|
|
1742
1736
|
if (isKeyRepeat(data) && matchesKey(data, PANEL_KEY))
|
|
1743
1737
|
return;
|
|
1744
|
-
if (matchesKey(data,
|
|
1738
|
+
if (matchesKey(data, "ctrl+c"))
|
|
1745
1739
|
close();
|
|
1740
|
+
else if (matchesKey(data, PANEL_KEY))
|
|
1741
|
+
back();
|
|
1746
1742
|
else
|
|
1747
1743
|
view.handleInput(data);
|
|
1748
1744
|
tui.requestRender();
|
|
@@ -1877,8 +1873,6 @@ export const JOB_FINISHED_REACTION = [
|
|
|
1877
1873
|
" to do, and that \"resume\" picks it up once it is in.",
|
|
1878
1874
|
"- a credential `agent-env:` names that is not there: never make one up - tell the person where it is read from.",
|
|
1879
1875
|
].join("\n");
|
|
1880
|
-
/** The keys that go to the chat and to the panel, side by side: ⌥⌘← and ⌥⌘→. */
|
|
1881
|
-
const SIDE_KEYS = { chat: "alt+super+left", panel: "alt+super+right" };
|
|
1882
1876
|
/** Opens the side panel, and goes back to it from the conversation. */
|
|
1883
1877
|
/** The modules in this project that have a history, by name. */
|
|
1884
1878
|
function modulesWithHistory(cwd) {
|
|
@@ -2314,7 +2308,7 @@ function registerBranding(pi, ruleCount, skills) {
|
|
|
2314
2308
|
if (data === undefined)
|
|
2315
2309
|
return undefined;
|
|
2316
2310
|
const lines = [
|
|
2317
|
-
`${theme
|
|
2311
|
+
`${brandPink(theme)("turri")} ${theme.fg("muted", `v${data.version}`)}` +
|
|
2318
2312
|
(data.commit !== undefined ? theme.fg("dim", ` from a clone, ${data.commit}${data.changed ? " with changes" : ""}`) : ""),
|
|
2319
2313
|
theme.fg("text", "regenerative software - the code is regenerated from the spec"),
|
|
2320
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;
|
|
@@ -16,7 +16,7 @@ export function createSidePanel(options) {
|
|
|
16
16
|
const backKeys = () => {
|
|
17
17
|
if (options.dock === undefined)
|
|
18
18
|
return [`esc or ${options.key} back to the chat`];
|
|
19
|
-
return options.dock.docked() ? [
|
|
19
|
+
return options.dock.docked() ? [`${options.key} or esc to the chat`, "ctrl+c close", "s full screen"] : ["s beside the chat", `esc or ${options.key} back to the chat`];
|
|
20
20
|
};
|
|
21
21
|
const waitingNote = () =>
|
|
22
22
|
// A question asked while it showed waits in the chat, unanswered: the person is told it is there.
|
|
@@ -52,7 +52,7 @@ export function tabBar(pages, shown, width, style, now) {
|
|
|
52
52
|
const text = `${mark}${tabName(page)}`;
|
|
53
53
|
if (page === shown)
|
|
54
54
|
return style.heading(`[${text}]`);
|
|
55
|
-
return regenerating ? style.accent(text) : page.stuck ? style
|
|
55
|
+
return regenerating ? style.accent(text) : page.stuck ? needs(style)(text) : style.muted(text);
|
|
56
56
|
};
|
|
57
57
|
const tabs = pages.map(label);
|
|
58
58
|
const at = Math.max(0, shown === undefined ? 0 : pages.indexOf(shown));
|
|
@@ -69,6 +69,8 @@ export function tabBar(pages, shown, width, style, now) {
|
|
|
69
69
|
}
|
|
70
70
|
return truncateToWidth(lineOf(from, to), width, "…");
|
|
71
71
|
}
|
|
72
|
+
/** What needs the person - a stuck page, its `!` - in the warning colour: it waits on them, nothing is broken. */
|
|
73
|
+
const needs = (style) => style.warning ?? style.error;
|
|
72
74
|
/** Strike text through. */
|
|
73
75
|
const strike = (text) => `\x1b[9m${text}\x1b[29m`;
|
|
74
76
|
/** How far a group, and what is in it, is indented under its module. */
|
|
@@ -121,7 +123,7 @@ function rungGlyph(rung, style, now) {
|
|
|
121
123
|
if (rung === "conform")
|
|
122
124
|
return green(LADDER.conform);
|
|
123
125
|
if (rung === "needsYou")
|
|
124
|
-
return style
|
|
126
|
+
return needs(style)(LADDER.needsYou);
|
|
125
127
|
if (rung === "takenOut")
|
|
126
128
|
return style.error(LADDER.takenOut);
|
|
127
129
|
if (rung === "toRegenerate")
|
|
@@ -486,7 +488,7 @@ export function specMapLines(pages, state, width, style, _jobs = [], now = Date.
|
|
|
486
488
|
// The first item that needs the person says what for, and how to go about it - opened or closed.
|
|
487
489
|
if (page.stuck && !toldWhy && rungOf(item) === "needsYou") {
|
|
488
490
|
toldWhy = true;
|
|
489
|
-
for (const line of wrapped(needsLine(page), width, style
|
|
491
|
+
for (const line of wrapped(needsLine(page), width, needs(style)))
|
|
490
492
|
add(line, mark);
|
|
491
493
|
}
|
|
492
494
|
if (state.open.has(row.key)) {
|
|
@@ -515,7 +517,9 @@ function paintOf(mark, style) {
|
|
|
515
517
|
return style.success ?? style.added;
|
|
516
518
|
if (mark === "regenerating" || mark === "planning")
|
|
517
519
|
return style.accent;
|
|
518
|
-
if (mark === "stuck"
|
|
520
|
+
if (mark === "stuck")
|
|
521
|
+
return needs(style);
|
|
522
|
+
if (mark === "removed")
|
|
519
523
|
return style.error;
|
|
520
524
|
return style.muted;
|
|
521
525
|
}
|
|
@@ -548,7 +552,7 @@ export function specMapHeader(page, width, style, waiting = "") {
|
|
|
548
552
|
const strip = progressStrip(page, style);
|
|
549
553
|
const count = total > 0 ? `${built} of ${total} ${total === 1 ? "functionality" : "functionalities"} built · ` : "";
|
|
550
554
|
// Short, never cut off: what for, and how, is under the first `!`.
|
|
551
|
-
const status = page.stuck ? style
|
|
555
|
+
const status = page.stuck ? needs(style)(`${LADDER.needsYou} needs you`) : page.status;
|
|
552
556
|
// The module's file heads its column, under this line.
|
|
553
557
|
return truncateToWidth(`${style.heading("regen")}${strip !== "" ? ` ${strip}` : ""} ${count}${status}${waiting}`, width);
|
|
554
558
|
}
|
package/dist/release-cli.js
CHANGED
|
@@ -4,6 +4,7 @@ import { configForSpec, modelSpec, projectModels, readProjectConfig } from "./co
|
|
|
4
4
|
import { createDeployState } from "./release-tools.js";
|
|
5
5
|
import { runReleaseAgent } from "./release-agent.js";
|
|
6
6
|
import { listDeployed, runningDeployed, stopDeployed } from "./regenerate/release.js";
|
|
7
|
+
import { c } from "./module-report.js";
|
|
7
8
|
const USAGE = `usage: turri release [file.plain] [options]
|
|
8
9
|
|
|
9
10
|
Releases the module on top of the spec's requires chain, which holds the code of the ones below, and
|
|
@@ -20,12 +21,6 @@ harness-model (the agent's model).
|
|
|
20
21
|
--in-module <module> the codebase to take, when modules built on this one branch
|
|
21
22
|
--guidance <text> how this project releases it: the scripts to run, in order
|
|
22
23
|
--model <provider/id> the agent's model (default: harness-model)`;
|
|
23
|
-
const c = {
|
|
24
|
-
dim: (s) => `\x1b[2m${s}\x1b[0m`,
|
|
25
|
-
bold: (s) => `\x1b[1m${s}\x1b[0m`,
|
|
26
|
-
green: (s) => `\x1b[32m${s}\x1b[0m`,
|
|
27
|
-
cyan: (s) => `\x1b[36m${s}\x1b[0m`,
|
|
28
|
-
};
|
|
29
24
|
const PROBLEMS = "If you are having problems, use chat mode to deploy: run turri and ask to try it.";
|
|
30
25
|
function parseArgs(argv) {
|
|
31
26
|
const positional = [];
|
|
@@ -76,7 +71,7 @@ function followLogs() {
|
|
|
76
71
|
closeSync(fd);
|
|
77
72
|
offsets.set(item.log, size);
|
|
78
73
|
for (const line of buffer.toString("utf8").split("\n").filter((l) => l !== ""))
|
|
79
|
-
console.log(`${c.
|
|
74
|
+
console.log(`${c.accent(`[${item.name}]`)} ${line}`);
|
|
80
75
|
}
|
|
81
76
|
if (running.length === 0) {
|
|
82
77
|
clearInterval(timer);
|
|
@@ -119,7 +114,7 @@ async function main() {
|
|
|
119
114
|
console.log(`\n\n${c.dim(PROBLEMS)}`);
|
|
120
115
|
if (runningDeployed().length === 0)
|
|
121
116
|
process.exit(0);
|
|
122
|
-
console.log(c.
|
|
117
|
+
console.log(c.success(c.bold("\nRunning - Ctrl-C stops it.")) + c.dim(" Its output follows.\n"));
|
|
123
118
|
await followLogs();
|
|
124
119
|
process.exit(0);
|
|
125
120
|
}
|
package/dist/validate-cli.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { checkPlainTargets, formatCheckResults } from "./plain/check.js";
|
|
2
|
+
import { c } from "./module-report.js";
|
|
2
3
|
const USAGE = `usage: turri validate <file.plain | directory> [options]
|
|
3
4
|
|
|
4
5
|
Validates a ***plain specification statically: parses it, resolves its imports and
|
|
@@ -8,12 +9,6 @@ required modules, and reports every error without rendering anything.
|
|
|
8
9
|
--json machine-readable output
|
|
9
10
|
|
|
10
11
|
PLAIN_TEMPLATE_DIRS colon-separated template directories`;
|
|
11
|
-
const c = {
|
|
12
|
-
dim: (s) => `\x1b[2m${s}\x1b[0m`,
|
|
13
|
-
bold: (s) => `\x1b[1m${s}\x1b[0m`,
|
|
14
|
-
green: (s) => `\x1b[32m${s}\x1b[0m`,
|
|
15
|
-
red: (s) => `\x1b[31m${s}\x1b[0m`,
|
|
16
|
-
};
|
|
17
12
|
const argv = process.argv.slice(2);
|
|
18
13
|
const templateDirs = [];
|
|
19
14
|
const positional = [];
|
|
@@ -43,10 +38,10 @@ if (json) {
|
|
|
43
38
|
else {
|
|
44
39
|
const output = formatCheckResults(results)
|
|
45
40
|
.split("\n")
|
|
46
|
-
.map((line) => line.startsWith("PASS") ? c.
|
|
41
|
+
.map((line) => line.startsWith("PASS") ? c.success(line) : line.startsWith("FAIL") ? c.error(line) : c.dim(line))
|
|
47
42
|
.join("\n");
|
|
48
43
|
console.log(output);
|
|
49
44
|
const failed = results.filter((r) => !r.ok).length;
|
|
50
|
-
console.log(`\n${failed === 0 ? c.
|
|
45
|
+
console.log(`\n${failed === 0 ? c.success(c.bold("all specs valid")) : c.error(c.bold(`${failed} of ${results.length} failed`))}`);
|
|
51
46
|
}
|
|
52
47
|
process.exit(results.every((r) => r.ok) ? 0 : 1);
|
package/docs/README.md
CHANGED
|
@@ -1,20 +1,13 @@
|
|
|
1
1
|
# turri documentation
|
|
2
2
|
|
|
3
|
-
turri implements regenerative software: the \*\*\*plain specs are the durable definition of
|
|
4
|
-
what a system does, and the code is generated from them, validated against them, and regenerated
|
|
5
|
-
as they change. The [project README](../README.md) says why, and how to install and start; these
|
|
6
|
-
pages go into each part.
|
|
3
|
+
turri implements regenerative software: the \*\*\*plain specs are the durable definition of what a system does, and the code is generated from them, validated against them, and regenerated as they change. The [project README](../README.md) says why, and how to install and start; these pages go into each part.
|
|
7
4
|
|
|
8
5
|
## Start here
|
|
9
6
|
|
|
10
|
-
1. [Installation](installation.md) - get a machine ready: `turri doctor`, and what turri needs of
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
3. [Authoring \*\*\*plain](authoring-plain.md) - writing the specs: what a spec is made of, the
|
|
15
|
-
authoring rules, the skills that write and fix specs.
|
|
16
|
-
4. [Rendering](regenerating.md) - turning the specs into code: what each build does, in which order,
|
|
17
|
-
and what has to pass before it moves on.
|
|
7
|
+
1. [Installation](installation.md) - get a machine ready: `turri doctor`, and what turri needs of this machine.
|
|
8
|
+
2. [Using turri](using.md) - the conversation, the skills and tools, background jobs and their logs, choosing the model that renders, the command line.
|
|
9
|
+
3. [Authoring \*\*\*plain](authoring-plain.md) - writing the specs: what a spec is made of, the authoring rules, the skills that write and fix specs.
|
|
10
|
+
4. [Rendering](regenerating.md) - turning the specs into code: what each build does, in which order, and what has to pass before it moves on.
|
|
18
11
|
|
|
19
12
|
## By question
|
|
20
13
|
|