@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.
Files changed (82) hide show
  1. package/.pi/themes/turri-dark.json +67 -66
  2. package/.pi/themes/turri-light.json +71 -73
  3. package/README.md +40 -128
  4. package/assets/jellyfish/dark-00.png +0 -0
  5. package/assets/jellyfish/dark-01.png +0 -0
  6. package/assets/jellyfish/dark-02.png +0 -0
  7. package/assets/jellyfish/dark-03.png +0 -0
  8. package/assets/jellyfish/dark-04.png +0 -0
  9. package/assets/jellyfish/dark-05.png +0 -0
  10. package/assets/jellyfish/dark-06.png +0 -0
  11. package/assets/jellyfish/dark-07.png +0 -0
  12. package/assets/jellyfish/dark-08.png +0 -0
  13. package/assets/jellyfish/dark-09.png +0 -0
  14. package/assets/jellyfish/dark-10.png +0 -0
  15. package/assets/jellyfish/dark-11.png +0 -0
  16. package/assets/jellyfish/dark-12.png +0 -0
  17. package/assets/jellyfish/dark-13.png +0 -0
  18. package/assets/jellyfish/dark-14.png +0 -0
  19. package/assets/jellyfish/light-00.png +0 -0
  20. package/assets/jellyfish/light-01.png +0 -0
  21. package/assets/jellyfish/light-02.png +0 -0
  22. package/assets/jellyfish/light-03.png +0 -0
  23. package/assets/jellyfish/light-04.png +0 -0
  24. package/assets/jellyfish/light-05.png +0 -0
  25. package/assets/jellyfish/light-06.png +0 -0
  26. package/assets/jellyfish/light-07.png +0 -0
  27. package/assets/jellyfish/light-08.png +0 -0
  28. package/assets/jellyfish/light-09.png +0 -0
  29. package/assets/jellyfish/light-10.png +0 -0
  30. package/assets/jellyfish/light-11.png +0 -0
  31. package/assets/jellyfish/light-12.png +0 -0
  32. package/assets/jellyfish/light-13.png +0 -0
  33. package/assets/jellyfish/light-14.png +0 -0
  34. package/dist/brand.js +7 -7
  35. package/dist/cultivate-cli.js +16 -23
  36. package/dist/extension.js +15 -21
  37. package/dist/loop-cli.js +8 -8
  38. package/dist/module-report.js +5 -4
  39. package/dist/regenerate/plain-palette.js +51 -36
  40. package/dist/regenerate/side-panel.js +1 -1
  41. package/dist/regenerate/spec-map-view.js +9 -5
  42. package/dist/release-cli.js +3 -8
  43. package/dist/validate-cli.js +3 -8
  44. package/docs/README.md +5 -12
  45. package/docs/authoring-plain.md +37 -119
  46. package/docs/config.md +50 -176
  47. package/docs/deploy.md +21 -80
  48. package/docs/development.md +13 -41
  49. package/docs/headless.md +30 -136
  50. package/docs/images/how-turri-works.svg +29 -31
  51. package/docs/images/turri-banner.png +0 -0
  52. package/docs/installation.md +13 -40
  53. package/docs/parser.md +15 -43
  54. package/docs/regenerating.md +231 -964
  55. package/docs/sandbox.md +38 -152
  56. package/docs/skills-and-gates.md +50 -173
  57. package/docs/theming.md +45 -46
  58. package/docs/using.md +66 -356
  59. package/genome/rules/bullet-continuation.md +5 -13
  60. package/genome/rules/concise-specs-examples.md +2 -4
  61. package/genome/rules/concise-specs-integration-examples.md +2 -6
  62. package/genome/rules/concise-specs.md +1 -2
  63. package/genome/rules/func-specs.md +1 -0
  64. package/genome/rules/integration-embedded-testing.md +1 -4
  65. package/genome/rules/integrations.md +1 -2
  66. package/genome/rules/project-model.md +14 -35
  67. package/genome/rules/stdlib.md +14 -49
  68. package/genome/rules/turri-regeneration.md +48 -0
  69. package/genome/skills/add-implementation-requirement/SKILL.md +1 -2
  70. package/genome/skills/analyze-if-func-spec-too-complex/SKILL.md +3 -8
  71. package/genome/skills/brood/SKILL.md +37 -127
  72. package/genome/skills/brood/references/intent.md +4 -12
  73. package/genome/skills/brood/references/product.md +24 -76
  74. package/genome/skills/brood/references/stack.md +7 -29
  75. package/genome/skills/brood/references/testing.md +11 -42
  76. package/genome/skills/bud/SKILL.md +32 -109
  77. package/genome/skills/bud/references/intent.md +4 -11
  78. package/genome/skills/implement-conformance-testing-script/SKILL.md +3 -6
  79. package/genome/skills/implement-unit-testing-script/SKILL.md +1 -2
  80. package/genome/skills/mature/SKILL.md +21 -69
  81. package/genome/skills/spawn/SKILL.md +28 -113
  82. 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 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;
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("#0a1fd4", 34) : ink("#e0ff6e", 93),
38
+ accent: light ? ink("#1e3442", 34) : ink("#5ce1e6", 96),
39
39
  /** Done, and what was written. */
40
- success: light ? ink("#223936", 32) : ink("#79fc96", 92),
40
+ success: light ? ink("#732553", 35) : ink("#ff5c8d", 95),
41
41
  /** What is wrong: what stops turri. */
42
- error: light ? ink("#d00505", 31) : ink("#dd5353", 91),
42
+ error: ink("#ff7a3d", 33),
43
43
  /** What works, with limits. */
44
- warning: ink("#dc6803", 33),
44
+ warning: ink("#ff7a3d", 33),
45
45
  /** What goes with it, and can be skimmed. */
46
- muted: ink("#808080", 90),
46
+ muted: light ? ink("#3e5a6b", 90) : ink("#85a3b2", 37),
47
47
  bold: (text) => (on ? `\x1b[1m${text}\x1b[22m` : text),
48
48
  };
49
49
  }
@@ -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
- 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
- };
10
+ import { c } from "./module-report.js";
18
11
  const reporter = {
19
- step: (name, attempt) => console.log(`\n${c.bold(c.cyan(`> step ${name}`))} ${c.dim(`attempt ${attempt}`)}`),
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.yellow(`> gate ${name}`))} ${c.dim("no artifact - failing without spending judges")}`
27
- : `${c.bold(c.yellow(`> gate ${name}`))} ${c.dim(`${judges} independent judges`)}`),
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.yellow(`review ${step}`)}: ${scope} ${c.dim(`- ${findings} to fix`)}`),
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.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`)}`),
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.green("pass") : c.red(verdict);
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.green,
41
- retry: c.yellow,
42
- escalate: c.yellow,
43
- abort: c.red,
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.cyan("? "))}${title}\n${list}\n> `)).trim();
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.cyan("? "))}${title}\n> `)).trim();
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.yellow("! human review required"))}`);
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.green(c.bold("run complete")) : c.red(c.bold("run aborted at a gate"))} ${c.dim(`artifacts in ${relative(cwd, skillRunDir(cwd, skill.name))}`)}`);
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 { brandBlue, plainPaint } from "./regenerate/plain-palette.js";
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, esc gives the chat its keys back and the panel stays, and the key gives them to the panel.
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: brand blue.
1649
- conforms: brandBlue(theme),
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
- // ctrl+c closes it too: whatever else goes wrong, a key always leads back to the chat.
1713
- if (matchesKey(data, PANEL_KEY) || matchesKey(data, "ctrl+c"))
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, PANEL_KEY))
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.fg("accent", "turri")} ${theme.fg("muted", `v${data.version}`)}` +
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.cyan(`▶ ${job.id}`)} ${pulseName(job)}${job.stage !== undefined ? c.dim(` - ${job.stage}`) : ""}`);
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.green("✓") : job.status === "stopped" ? c.dim("■") : c.red("✗");
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.yellow(` spec gap: ${gap.question}`), c.dim(`Meanwhile: ${gap.assumption}`));
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.yellow(` ${event.job !== undefined ? `${event.job}: ` : ""}${line}`));
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.red(message));
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.red(c.bold(`${failed.length} job${failed.length === 1 ? "" : "s"} failed`))
165
+ ? c.error(c.bold(`${failed.length} job${failed.length === 1 ? "" : "s"} failed`))
166
166
  : unfinished.length > 0
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"));
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}`);
@@ -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
- 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`,
6
+ success: brand.success,
7
+ error: brand.error,
8
+ warning: brand.warning,
9
+ accent: brand.accent,
9
10
  };
@@ -1,38 +1,46 @@
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. */
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: "#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" },
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: "#86efac",
14
- removed: "#fca5a5",
15
- addedFill: "#1f4d2e",
16
- removedFill: "#5c2323",
17
- muted: "#808080",
18
+ added: "#5ce1e6",
19
+ removed: "#ff7fa6",
20
+ addedFill: "#1e3442",
21
+ removedFill: "#732553",
22
+ muted: "#85a3b2",
18
23
  };
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. */
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: "#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" },
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: "#166534",
32
- removed: "#991b1b",
33
- addedFill: "#d3f5dc",
34
- removedFill: "#fcd9d9",
35
- muted: "#6b7280",
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
- * 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.
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 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;
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() ? ["s full screen", "esc or ⌥⌘← to the chat", `${options.key} close`] : ["s beside the chat", `esc or ${options.key} back to the chat`];
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.error(text) : style.muted(text);
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.error(LADDER.needsYou);
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.error))
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" || mark === "removed")
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.error(`${LADDER.needsYou} needs you`) : page.status;
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
  }
@@ -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.cyan(`[${item.name}]`)} ${line}`);
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.green(c.bold("\nRunning - Ctrl-C stops it.")) + c.dim(" Its output follows.\n"));
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
  }
@@ -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.green(line) : line.startsWith("FAIL") ? c.red(line) : c.dim(line))
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.green(c.bold("all specs valid")) : c.red(c.bold(`${failed} of ${results.length} failed`))}`);
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
- this machine.
12
- 2. [Using turri](using.md) - the conversation, the skills and tools, background jobs and
13
- their logs, choosing the model that renders, the command line.
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