@turri-dev/cli 0.1.3-next.2babf06 → 0.1.3-next.420ce7e
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/README.md +1 -1
- package/dist/config.js +3 -2
- package/dist/extension.js +86 -35
- package/dist/identity.js +1 -1
- package/dist/loop-cli.js +1 -1
- package/dist/loop-tools.js +1 -1
- package/dist/pi-launch.js +5 -0
- package/dist/question-dialog.js +8 -3
- package/dist/questioning.js +1 -1
- package/dist/regenerate/activity.js +2 -0
- package/dist/regenerate/confine.js +1 -0
- package/dist/regenerate/conform.js +5 -5
- package/dist/regenerate/conformance-agents.js +220 -11
- package/dist/regenerate/conformance.js +91 -35
- package/dist/regenerate/debugger.js +27 -3
- package/dist/regenerate/environment-error.js +63 -9
- package/dist/regenerate/loop.js +22 -4
- package/dist/regenerate/notes.js +66 -17
- package/dist/regenerate/panel-dock.js +31 -2
- package/dist/regenerate/prompt.js +3 -1
- package/dist/regenerate/regenerate.js +35 -14
- package/dist/regenerate/regression.js +2 -1
- package/dist/regenerate/requires.js +55 -2
- package/dist/regenerate/side-panel.js +1 -1
- package/dist/regenerate/stop-report.js +13 -0
- package/dist/regenerate/test-scripts.js +22 -1
- package/dist/regenerate/unit-review.js +11 -0
- package/dist/regenerate-cli.js +1 -1
- package/dist/resume-line.js +4 -0
- package/dist/spec-tools.js +8 -44
- package/docs/authoring-plain.md +9 -6
- package/docs/config.md +3 -2
- package/docs/headless.md +2 -2
- package/docs/regenerating.md +75 -18
- package/docs/skills-and-gates.md +2 -2
- package/docs/using.md +5 -3
- package/genome/skills/bud/SKILL.md +67 -36
- package/genome/skills/bud/references/intent.md +4 -0
- package/package.json +3 -3
- package/genome/skills/bud/references/checklist.md +0 -44
package/README.md
CHANGED
|
@@ -212,7 +212,7 @@ It runs the interview skill, asks you about the problem, the stack and the produ
|
|
|
212
212
|
model and cost - and the queue behind it;
|
|
213
213
|
- ask to try it at any time - "let me try it" - and **`release`** puts what is built so far in
|
|
214
214
|
`dist/<module>` and **`activate`** starts it, installing what it needs - nothing to approve;
|
|
215
|
-
- the **side panel** (`ctrl+h`) takes the screen and opens on **regen**
|
|
215
|
+
- the **side panel** (`ctrl+h`) takes the screen and opens on **regen** - \*\*\*plain, every item marked planned, working, regenerated, validating, conform or needs you - and `p` turns it to the **pulses** (`/pulses`) - every pulse, running, queued and finished, and what each regenerates; what its agents did is in each pulse's log in `.turri/jobs/`;
|
|
216
216
|
- **`/halt`** halts a pulse, **`/lineage`** shows every generation of a
|
|
217
217
|
module, and the **`drift`** tool says what is left to regenerate;
|
|
218
218
|
- **`/btw <question>`** asks turri something on the side - answered at once, even in the middle of a skill's interview
|
package/dist/config.js
CHANGED
|
@@ -189,8 +189,9 @@ export const ROLE_THINKING = {
|
|
|
189
189
|
diagnosis: "medium",
|
|
190
190
|
};
|
|
191
191
|
/** The roles on the conversation's side: without their own entry they take the chat's model, not `default`. */
|
|
192
|
-
// The release is not: it follows steps - a script, a start command - and runs on the coding model, thinking low.
|
|
193
|
-
|
|
192
|
+
// The release is not: it follows steps - a script, a start command - and runs on the coding model, thinking low. Nor
|
|
193
|
+
// is the diagnosis: a post-mortem of a job that failed, read by the person - it runs on the coding model, thinking medium.
|
|
194
|
+
const CHAT_SIDE = ["chat", "chooser", "planner"];
|
|
194
195
|
function isThinkingLevel(value) {
|
|
195
196
|
return typeof value === "string" && THINKING_LEVELS.includes(value);
|
|
196
197
|
}
|
package/dist/extension.js
CHANGED
|
@@ -9,7 +9,7 @@ import { allocateImageId, Box, getCapabilities, getPngDimensions, isKeyRelease,
|
|
|
9
9
|
import { defineTool } from "@earendil-works/pi-coding-agent";
|
|
10
10
|
import { Type } from "typebox";
|
|
11
11
|
import { CODING_MODEL_ENV } from "./chat-models.js";
|
|
12
|
-
import { AGENT_ROLES, CONFIG_FILE, configForSpec, hasProjectConfig, LEGACY_CONFIG_FILE, projectConfigFile, stackConfigFileIn, stackOf, stacksOf, withStack, modelSpec, projectModels, readProjectConfig, roleModel, splitModelSpec } from "./config.js";
|
|
12
|
+
import { AGENT_ROLES, CONFIG_FILE, configForSpec, hasProjectConfig, LEGACY_CONFIG_FILE, projectConfigFile, stackConfigFileIn, stackOf, stacksOf, withStack, modelOnly, modelSpec, projectModels, readProjectConfig, roleModel, splitModelSpec } from "./config.js";
|
|
13
13
|
import { skillRunDir } from "./regenerate/paths.js";
|
|
14
14
|
import { sweepOrphanedGroups } from "./regenerate/shell.js";
|
|
15
15
|
import { readScript, scriptAgainstMachine, stackOnMachine, toolchainsBlock } from "./stack.js";
|
|
@@ -31,7 +31,7 @@ import { whyFailed } from "./regenerate/diagnose-job.js";
|
|
|
31
31
|
import { createWorkLoop, describeModules, isUpToDate, previewJobs } from "./regenerate/loop.js";
|
|
32
32
|
import { brandBlue, plainPaint } from "./regenerate/plain-palette.js";
|
|
33
33
|
import { createJobPanel, didNotPass, keyPressed, PANEL_KEY, spinner } from "./regenerate/job-panel.js";
|
|
34
|
-
import { clickToFocus, dockedWidth, narrowChat } from "./regenerate/panel-dock.js";
|
|
34
|
+
import { clickToFocus, dockedWidth, narrowChat, shade, shadeChat } from "./regenerate/panel-dock.js";
|
|
35
35
|
import { overPictureRow, PICTURE_AT } from "./picture-row.js";
|
|
36
36
|
import { skillAboutLine, skillProgressLine } from "./skill-progress.js";
|
|
37
37
|
import { createSidePanel } from "./regenerate/side-panel.js";
|
|
@@ -90,15 +90,23 @@ aside) {
|
|
|
90
90
|
// The question, with what the asker gives to read for it - a brief to confirm - above it; what it said of its work
|
|
91
91
|
// before asking is kept in its record, never put in the dialog.
|
|
92
92
|
// A dialog of turri's own: a long brief scrolls above the question, and the options always show (question-dialog.ts).
|
|
93
|
-
const dialog = (question, context, options) => ctx.ui.custom((tui, theme, _keybindings, done) =>
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
93
|
+
const dialog = (question, context, options) => ctx.ui.custom((tui, theme, _keybindings, done) => {
|
|
94
|
+
const shown = new QuestionDialog({
|
|
95
|
+
question,
|
|
96
|
+
...(context !== undefined ? { context } : {}),
|
|
97
|
+
...(options !== undefined ? { options } : {}),
|
|
98
|
+
rows: () => tui.terminal.rows,
|
|
99
|
+
style: { accent: (text) => theme.fg("accent", text), muted: (text) => theme.fg("muted", text), bold: (text) => theme.bold(text) },
|
|
100
|
+
done: (answer) => {
|
|
101
|
+
if (questionShown === shown)
|
|
102
|
+
questionShown = undefined;
|
|
103
|
+
done(answer);
|
|
104
|
+
},
|
|
105
|
+
requestRender: () => tui.requestRender(),
|
|
106
|
+
});
|
|
107
|
+
questionShown = shown;
|
|
108
|
+
return shown;
|
|
109
|
+
});
|
|
102
110
|
const asking = (asked) => runAsk(asked, {
|
|
103
111
|
select: (title, options, context) => dialog(title, context, options),
|
|
104
112
|
input: (title, context) => dialog(title, context),
|
|
@@ -117,6 +125,12 @@ aside) {
|
|
|
117
125
|
}
|
|
118
126
|
/** The side panel, as a question open in the chat reaches it: set once the extension has its panel. */
|
|
119
127
|
let questionPeek;
|
|
128
|
+
/**
|
|
129
|
+
* The question open in the chat now, if any. The side panel gives the keys back to it: pi would give them to what had the
|
|
130
|
+
* focus when the panel first opened, and a panel kept beside the chat outlives that question - the keys would go to a
|
|
131
|
+
* dialog already answered, and nothing would take them.
|
|
132
|
+
*/
|
|
133
|
+
let questionShown;
|
|
120
134
|
/** A widget with nothing to draw: below the editor, it takes no line. */
|
|
121
135
|
const PEEK_KEY_WIDGET = "turri-peek-key";
|
|
122
136
|
/**
|
|
@@ -186,7 +200,8 @@ export default function (pi) {
|
|
|
186
200
|
projectDir: cwd,
|
|
187
201
|
harnessDir: PACKAGE_ROOT,
|
|
188
202
|
jobLog,
|
|
189
|
-
model:
|
|
203
|
+
// The coding model the job ran on - its model alone: the diagnosis thinks as its role has it.
|
|
204
|
+
model: modelSpec(modelOnly(job.model ?? modelSpec(projectModels(readProjectConfig(cwd)).coding))),
|
|
190
205
|
});
|
|
191
206
|
return diagnosis !== undefined ? describeDiagnosis(diagnosis) : undefined;
|
|
192
207
|
},
|
|
@@ -1507,6 +1522,7 @@ function registerJobTools(pi, jobs, cwd) {
|
|
|
1507
1522
|
}
|
|
1508
1523
|
needsLooking.delete(jobs);
|
|
1509
1524
|
if (panel !== undefined) {
|
|
1525
|
+
panel.keepChatFocus();
|
|
1510
1526
|
panel.handle?.setHidden(false);
|
|
1511
1527
|
panel.keys = true;
|
|
1512
1528
|
panel.handle?.focus();
|
|
@@ -1514,7 +1530,21 @@ function registerJobTools(pi, jobs, cwd) {
|
|
|
1514
1530
|
}
|
|
1515
1531
|
// Not awaited: the panel lives on while the jobs it shows run.
|
|
1516
1532
|
void ctx.ui.custom((tui, theme, _keybindings, done) => {
|
|
1517
|
-
|
|
1533
|
+
// What the chat shows when the panel takes the keys - its editor, or a pi dialog - for when it gives them back.
|
|
1534
|
+
const focusedNow = () => tui.getFocusedComponent?.() ?? null;
|
|
1535
|
+
let chatFocus = focusedNow();
|
|
1536
|
+
const keepChatFocus = () => {
|
|
1537
|
+
const focused = focusedNow();
|
|
1538
|
+
if (focused !== null && focused !== component && !(focused instanceof QuestionDialog))
|
|
1539
|
+
chatFocus = focused;
|
|
1540
|
+
};
|
|
1541
|
+
// The keys back to the chat: to the question open there now, else to what it showed - never to a question answered
|
|
1542
|
+
// while the panel was open.
|
|
1543
|
+
const giveBack = () => entry.handle?.unfocus({ target: questionShown ?? chatFocus });
|
|
1544
|
+
const close = () => {
|
|
1545
|
+
giveBack();
|
|
1546
|
+
done(undefined);
|
|
1547
|
+
};
|
|
1518
1548
|
panelTerminal = tui.terminal;
|
|
1519
1549
|
let component;
|
|
1520
1550
|
// Beside the chat: the chat drawn narrower, on the left, and a click on either side giving it the keys - undone
|
|
@@ -1531,7 +1561,10 @@ function registerJobTools(pi, jobs, cwd) {
|
|
|
1531
1561
|
else
|
|
1532
1562
|
toPanel();
|
|
1533
1563
|
});
|
|
1564
|
+
// The side without the keys in the shade: the chat while the panel has them.
|
|
1565
|
+
const unshade = shadeChat(tui, () => panelBeside() > 0 && entry.keys);
|
|
1534
1566
|
undock = () => {
|
|
1567
|
+
unshade();
|
|
1535
1568
|
unwatch();
|
|
1536
1569
|
widen();
|
|
1537
1570
|
};
|
|
@@ -1544,10 +1577,11 @@ function registerJobTools(pi, jobs, cwd) {
|
|
|
1544
1577
|
// Beside the chat, back to it: the chat has the keys, the panel stays.
|
|
1545
1578
|
const toChat = () => {
|
|
1546
1579
|
entry.keys = false;
|
|
1547
|
-
|
|
1580
|
+
giveBack();
|
|
1548
1581
|
tui.requestRender();
|
|
1549
1582
|
};
|
|
1550
1583
|
const toPanel = () => {
|
|
1584
|
+
keepChatFocus();
|
|
1551
1585
|
entry.keys = true;
|
|
1552
1586
|
entry.handle?.focus();
|
|
1553
1587
|
tui.requestRender();
|
|
@@ -1600,7 +1634,7 @@ function registerJobTools(pi, jobs, cwd) {
|
|
|
1600
1634
|
return focused !== null && focused !== component;
|
|
1601
1635
|
},
|
|
1602
1636
|
});
|
|
1603
|
-
const entry = { view, close, keys: true };
|
|
1637
|
+
const entry = { view, close, keepChatFocus, keys: true };
|
|
1604
1638
|
panel = entry;
|
|
1605
1639
|
layOut();
|
|
1606
1640
|
// Every key goes to the panel while it shows, whatever has the focus: a question the chat model asks in the
|
|
@@ -1624,6 +1658,17 @@ function registerJobTools(pi, jobs, cwd) {
|
|
|
1624
1658
|
}
|
|
1625
1659
|
if (!state)
|
|
1626
1660
|
return undefined;
|
|
1661
|
+
// Beside the chat, ⌥⌘← and ⌥⌘→ go to the side they point at, whichever has the keys - on a terminal that tells
|
|
1662
|
+
// turri of ⌘ (the Kitty keyboard protocol). Its release goes nowhere.
|
|
1663
|
+
if (panelBeside() > 0 && (matchesKey(data, SIDE_KEYS.chat) || matchesKey(data, SIDE_KEYS.panel))) {
|
|
1664
|
+
if (!isKeyRelease(data)) {
|
|
1665
|
+
if (matchesKey(data, SIDE_KEYS.chat))
|
|
1666
|
+
toChat();
|
|
1667
|
+
else
|
|
1668
|
+
toPanel();
|
|
1669
|
+
}
|
|
1670
|
+
return { consume: true };
|
|
1671
|
+
}
|
|
1627
1672
|
// Beside the chat, with the chat's keys: they are the chat's - PANEL_KEY among them, for the shortcut that gives
|
|
1628
1673
|
// them to the panel. A click on the panel gives it the focus, and the keys with it.
|
|
1629
1674
|
if (!entry.keys && entry.handle?.isFocused() !== true)
|
|
@@ -1655,8 +1700,13 @@ function registerJobTools(pi, jobs, cwd) {
|
|
|
1655
1700
|
}, WATCH_REDRAW_MS);
|
|
1656
1701
|
timer.unref?.();
|
|
1657
1702
|
component = {
|
|
1658
|
-
// Beside the chat, a rule down its left edge between them.
|
|
1659
|
-
render: (width) =>
|
|
1703
|
+
// Beside the chat, a rule down its left edge between them - and the panel in the shade while the chat has the keys.
|
|
1704
|
+
render: (width) => {
|
|
1705
|
+
if (panelBeside() === 0)
|
|
1706
|
+
return view.render(width);
|
|
1707
|
+
const lines = view.render(width - 2).map((line) => `${theme.fg("borderMuted", "│")} ${line}`);
|
|
1708
|
+
return entry.keys ? lines : lines.map(shade);
|
|
1709
|
+
},
|
|
1660
1710
|
// Reached only if the listener above is gone: the same, so the panel never passes a key on.
|
|
1661
1711
|
handleInput: (data) => {
|
|
1662
1712
|
if (isKeyRepeat(data) && matchesKey(data, PANEL_KEY))
|
|
@@ -1695,22 +1745,17 @@ function registerJobTools(pi, jobs, cwd) {
|
|
|
1695
1745
|
},
|
|
1696
1746
|
});
|
|
1697
1747
|
};
|
|
1698
|
-
//
|
|
1699
|
-
//
|
|
1700
|
-
// in each pulse's log.
|
|
1701
|
-
|
|
1702
|
-
|
|
1703
|
-
|
|
1704
|
-
|
|
1705
|
-
|
|
1706
|
-
|
|
1707
|
-
|
|
1708
|
-
|
|
1709
|
-
return;
|
|
1710
|
-
openPanel(ctx, view);
|
|
1711
|
-
},
|
|
1712
|
-
});
|
|
1713
|
-
}
|
|
1748
|
+
// /pulses opens the side panel on every pulse, regenerating, planned and finished, and what each regenerates; regen -
|
|
1749
|
+
// the spec, with how far the code has been regenerated into it - is PANEL_KEY's, no command of its own beside
|
|
1750
|
+
// /regenerate. What the agents did is in each pulse's log.
|
|
1751
|
+
pi.registerCommand("pulses", {
|
|
1752
|
+
description: `The pulses, in the side panel: every pulse, regenerating, planned and finished, and what each regenerates. ${PANEL_KEY} opens the panel too.`,
|
|
1753
|
+
handler: async (_args, ctx) => {
|
|
1754
|
+
if (!ctx.hasUI)
|
|
1755
|
+
return;
|
|
1756
|
+
openPanel(ctx, "pulses");
|
|
1757
|
+
},
|
|
1758
|
+
});
|
|
1714
1759
|
panelOpeners.set(jobs, (ctx) => openPanel(ctx));
|
|
1715
1760
|
questionPeek = {
|
|
1716
1761
|
open: (ctx) => openPanel(ctx),
|
|
@@ -1785,8 +1830,12 @@ export const JOB_FINISHED_REACTION = [
|
|
|
1785
1830
|
"A pulse that stopped on its environment - \"stopped: turri's sandbox kept its agents from…\" or \"stopped: the",
|
|
1786
1831
|
"environment lacks…\" - stops the same way again until that is fixed: never regenerate it before. Heal it, then regenerate the",
|
|
1787
1832
|
"module to resume:",
|
|
1788
|
-
"- turri's sandbox kept the agents from a folder on this machine: nothing is missing. Add the folder its report
|
|
1789
|
-
" `toolchains:` in config.turri.yaml - a list, keeping what is there - say so in a line, and resume.",
|
|
1833
|
+
"- turri's sandbox kept the agents from reading a folder on this machine: nothing is missing. Add the folder its report",
|
|
1834
|
+
" names to `toolchains:` in config.turri.yaml - a list, keeping what is there - say so in a line, and resume.",
|
|
1835
|
+
"- turri's sandbox kept the agents from writing a file - a lock a build tool takes, say: no setting lets them, and",
|
|
1836
|
+
" `toolchains:` does not - never add it there, and never resume before the fix. Do what the report says if it is the",
|
|
1837
|
+
" project's to change - a tool told to use `$HOME` in the build's own files - say so, and resume; otherwise tell the",
|
|
1838
|
+
" person, in a line or two, what the report says and that \"resume\" picks it up once it is done.",
|
|
1790
1839
|
"- the machine lacks a toolchain or a tool - a JDK, Maven, Gradle, Go, uv, Node, a system library: first find out, in your",
|
|
1791
1840
|
" shell, whether it is installed somewhere off the PATH (`command -v`, Homebrew's opt folders, /Library/Java/",
|
|
1792
1841
|
" JavaVirtualMachines, a version manager the person uses - mise, asdf, sdkman, nvm). Then ask the person with ask_user",
|
|
@@ -1798,6 +1847,8 @@ export const JOB_FINISHED_REACTION = [
|
|
|
1798
1847
|
" to do, and that \"resume\" picks it up once it is in.",
|
|
1799
1848
|
"- a credential `agent-env:` names that is not there: never make one up - tell the person where it is read from.",
|
|
1800
1849
|
].join("\n");
|
|
1850
|
+
/** The keys that go to the chat and to the panel, side by side: ⌥⌘← and ⌥⌘→. */
|
|
1851
|
+
const SIDE_KEYS = { chat: "alt+super+left", panel: "alt+super+right" };
|
|
1801
1852
|
/** Opens the side panel, and goes back to it from the conversation. */
|
|
1802
1853
|
/** The modules in this project that have a history, by name. */
|
|
1803
1854
|
function modulesWithHistory(cwd) {
|
package/dist/identity.js
CHANGED
|
@@ -25,7 +25,7 @@ export const TURRI_IDENTITY = [
|
|
|
25
25
|
"- A pulse is one unit of work regenerating the code - a spec diff regenerated in a codebase, or its conformance",
|
|
26
26
|
" tests. It was called a job, and still is in places: the job_id a tool takes is a pulse's id, and parallel-jobs and",
|
|
27
27
|
" allow-to-stop-jobs in config.turri.yaml are about pulses. \"Job\" in what you read is a pulse; say pulse.",
|
|
28
|
-
"- regen is the side panel (
|
|
28
|
+
"- regen is the side panel (ctrl+h): the spec with each functionality and item marked built, regenerating,",
|
|
29
29
|
" planned or waiting - and behind it the pulses (/pulses), every pulse regenerating, planned and finished. Say regen,",
|
|
30
30
|
" lowercase. It was the regeneration, the job queue, or the job panel.",
|
|
31
31
|
"- mutate, in a skill's activity, is write_spec_diff: a spec change written and regenerated in one step.",
|
package/dist/loop-cli.js
CHANGED
|
@@ -69,7 +69,7 @@ async function runLoopReporting(options) {
|
|
|
69
69
|
const registry = createJobRegistry({
|
|
70
70
|
retries: 1,
|
|
71
71
|
diagnose: async (job, failures, jobLog) => {
|
|
72
|
-
const diagnosis = await diagnoseFailure({ job, failures, projectDir, harnessDir: PACKAGE_ROOT, jobLog, model: modelSpec(
|
|
72
|
+
const diagnosis = await diagnoseFailure({ job, failures, projectDir, harnessDir: PACKAGE_ROOT, jobLog, model: modelSpec(modelOnly(job.model ?? model)) });
|
|
73
73
|
return diagnosis !== undefined ? describeDiagnosis(diagnosis) : undefined;
|
|
74
74
|
},
|
|
75
75
|
onChange: (job) => {
|
package/dist/loop-tools.js
CHANGED
|
@@ -73,7 +73,7 @@ export async function loopReply(loop, statuses, wait) {
|
|
|
73
73
|
export function stopJob(jobs, named, reason) {
|
|
74
74
|
const matches = jobs.list().filter((job) => job.id.startsWith(named.trim()));
|
|
75
75
|
if (matches.length === 0)
|
|
76
|
-
return `No pulse ${named}. /
|
|
76
|
+
return `No pulse ${named}. /pulses shows them.`;
|
|
77
77
|
if (matches.length > 1)
|
|
78
78
|
return `'${named}' matches ${matches.map((job) => job.id).join(", ")}. Give more of the id.`;
|
|
79
79
|
const target = matches[0];
|
package/dist/pi-launch.js
CHANGED
|
@@ -11,6 +11,7 @@ import { fileURLToPath, pathToFileURL } from "node:url";
|
|
|
11
11
|
import { main, SettingsManager } from "@earendil-works/pi-coding-agent";
|
|
12
12
|
import { ProcessTerminal } from "@earendil-works/pi-tui";
|
|
13
13
|
import "./terminal-images.js";
|
|
14
|
+
import { resumeLine } from "./resume-line.js";
|
|
14
15
|
/**
|
|
15
16
|
* pi, started the way its own CLI starts it, for `turri`.
|
|
16
17
|
*
|
|
@@ -31,6 +32,10 @@ const setTitle = ProcessTerminal.prototype.setTitle;
|
|
|
31
32
|
ProcessTerminal.prototype.setTitle = function (title) {
|
|
32
33
|
setTitle.call(this, title.replace(/^π(?= |$)/, "🪼 turri"));
|
|
33
34
|
};
|
|
35
|
+
// What pi prints on the way out to resume the session names pi - its APP_NAME, a constant - which may not be on the
|
|
36
|
+
// PATH at all: turri takes the same flags, so it is turri the line names.
|
|
37
|
+
const write = process.stdout.write.bind(process.stdout);
|
|
38
|
+
process.stdout.write = ((chunk, ...rest) => write(typeof chunk === "string" ? resumeLine(chunk) : chunk, ...rest));
|
|
34
39
|
// pi's own keys stand: a new line is shift+enter or ctrl+j. turri's side panel is on ctrl+h (job-panel.ts PANEL_KEY).
|
|
35
40
|
// What pi's CLI does before main: the process title, pi's environment markers, the HTTP
|
|
36
41
|
// dispatcher. Not exported, so loaded from the file - beside pi's entry point, wherever npm put it.
|
package/dist/question-dialog.js
CHANGED
|
@@ -58,18 +58,23 @@ export class QuestionDialog {
|
|
|
58
58
|
const choices = this.options.options ?? [];
|
|
59
59
|
const body = this.input !== undefined
|
|
60
60
|
? this.input.render(inner)
|
|
61
|
-
:
|
|
61
|
+
: // A long option wraps, its lines under its first, rather than cut off where the person cannot read it.
|
|
62
|
+
choices.flatMap((choice, index) => wrapTextWithAnsi(choice, Math.max(1, inner - 2)).map((line, at) => {
|
|
63
|
+
const shown = `${at === 0 && index === this.selected ? "›" : " "} ${line}`;
|
|
64
|
+
return index === this.selected ? style.accent(shown) : shown;
|
|
65
|
+
}));
|
|
62
66
|
const hint = this.input !== undefined
|
|
63
67
|
? "enter answers · esc dismisses"
|
|
64
68
|
: "↑↓ choose · enter picks · esc dismisses";
|
|
65
69
|
// The brief gets what the question, the options and the hints leave of the screen.
|
|
66
|
-
const fixed = question.length + body.length +
|
|
70
|
+
const fixed = question.length + body.length + 5;
|
|
67
71
|
const room = Math.max(3, this.options.rows() - KEPT_ABOVE - fixed);
|
|
68
72
|
const scrolls = brief.length > room;
|
|
69
73
|
this.window = scrolls ? room - 1 : brief.length;
|
|
70
74
|
this.maxScroll = scrolls ? brief.length - this.window : 0;
|
|
71
75
|
this.scroll = Math.min(this.scroll, this.maxScroll);
|
|
72
|
-
|
|
76
|
+
// Set apart from what is drawn above it - the job panel's hints.
|
|
77
|
+
const lines = [""];
|
|
73
78
|
if (brief.length > 0) {
|
|
74
79
|
lines.push(...brief.slice(this.scroll, this.scroll + this.window).map(pad));
|
|
75
80
|
if (scrolls) {
|
package/dist/questioning.js
CHANGED
|
@@ -43,7 +43,7 @@ export function questioningQuestion(phases) {
|
|
|
43
43
|
return {
|
|
44
44
|
question: `How many questions should I ask you in ${named}?`,
|
|
45
45
|
options: QUESTIONING_LEVELS.map((level) => level.option),
|
|
46
|
-
context: `Light, medium and thorough cap each of ${named} at 3, 5 and 10 questions - broader with fewer, so every level ` +
|
|
46
|
+
context: `Light, medium and thorough cap ${phases.length === 1 ? named : `each of ${named}`} at 3, 5 and 10 questions - broader with fewer, so every level ` +
|
|
47
47
|
"covers the same ground, only in less or more depth. What the questions leave open, turri decides itself and " +
|
|
48
48
|
"says what it chose. With best judgement, turri asks as much as it judges each phase needs. Each phase still " +
|
|
49
49
|
"ends with your confirmation of its summary.",
|
|
@@ -183,6 +183,8 @@ export function describeToolCall(name, args) {
|
|
|
183
183
|
return `± my_diff`;
|
|
184
184
|
case "update_notes":
|
|
185
185
|
return `✐ update_notes`;
|
|
186
|
+
case "report_spec_gap":
|
|
187
|
+
return `? spec gap: ${text("question") ?? ""}`.trimEnd();
|
|
186
188
|
case "copy_resource":
|
|
187
189
|
return `⎘ copy_resource ${path ?? text("to") ?? ""}`.trimEnd();
|
|
188
190
|
case "can_it_fail":
|
|
@@ -188,6 +188,7 @@ export function sandboxNote(fence) {
|
|
|
188
188
|
const report = fence.environmentTool !== undefined
|
|
189
189
|
? `report it as an environment error with ${fence.environmentTool}`
|
|
190
190
|
: "say so plainly - what is missing and what needs it - and end your turn";
|
|
191
|
+
lines.push("- A command refused a file outside your folder - `Operation not permitted`, `Permission denied`, a lock it could not", ` take - is these walls, not the code, and nothing you change gets past them: ${report} at once, naming the file`, " and the command that wanted it.");
|
|
191
192
|
lines.push(fence.write.length > 0
|
|
192
193
|
? "- The project's dependencies are yours: declare them in the build manifest, and let the build tools fetch them."
|
|
193
194
|
: "- You install nothing.", `${fence.write.length > 0 ? " Installing anything else is not" : " Nor is installing anything on the machine yours"}: no toolchains, system packages, drivers, global packages or \`sudo\`. If the`, ` machine lacks something the code needs, ${report}, name what is missing, and go no further with what depends on it.`, " The person decides.", "- Ports and processes: you may stop only what you started. If a port is taken by something else, use another port.", " `ps` may show nothing but what you started: check a server you started with `kill -0 $!`, its log, or a request to it.", "- Temporary files go in `$TMPDIR`: `/tmp` is outside your walls.");
|
|
@@ -7,7 +7,7 @@ import { abandonWork, startWork } from "./lineage.js";
|
|
|
7
7
|
import { ensureRegressionPasses } from "./regression.js";
|
|
8
8
|
import { modulesRoot, projectDirOf } from "./paths.js";
|
|
9
9
|
import { applySpecDiff, snapshotSpec, specGapsText } from "./regenerate.js";
|
|
10
|
-
import {
|
|
10
|
+
import { gapMark, gapsSince } from "./notes.js";
|
|
11
11
|
import { sectionsOf } from "./state.js";
|
|
12
12
|
import { describeStop } from "./stop-report.js";
|
|
13
13
|
import { missingTestLabels } from "./test-kinds.js";
|
|
@@ -41,8 +41,8 @@ export async function renderSpecDiffConformance(options) {
|
|
|
41
41
|
// state, not to what an interrupted run left half-written. The tests and the whole suite are
|
|
42
42
|
// one unit, landed whole or undone whole.
|
|
43
43
|
const unit = startWork([buildDir, conformanceDir], moduleName);
|
|
44
|
-
// The
|
|
45
|
-
const
|
|
44
|
+
// The spec gaps its agents report from here on are told with the result.
|
|
45
|
+
const gapsFrom = gapMark();
|
|
46
46
|
const conformance = await renderConformanceTests({
|
|
47
47
|
plainFile: tip.plainFile,
|
|
48
48
|
frid: lastFrid,
|
|
@@ -58,8 +58,8 @@ export async function renderSpecDiffConformance(options) {
|
|
|
58
58
|
});
|
|
59
59
|
// What the codebase now has conformance tests for, as their labels say.
|
|
60
60
|
const tested = () => frids.filter((frid) => missingTestLabels(conformanceDir, frid, []).length === 0);
|
|
61
|
-
//
|
|
62
|
-
const base = { moduleName, buildDir, conformanceDir, diff: applied, conformance, specGaps:
|
|
61
|
+
// A gap is the person's to decide, whether the tests passed or not.
|
|
62
|
+
const base = { moduleName, buildDir, conformanceDir, diff: applied, conformance, specGaps: gapsSince(gapsFrom, [buildDir, conformanceDir]) };
|
|
63
63
|
if (!conformance.passed) {
|
|
64
64
|
abandonWork(unit, moduleName, notLandedWhy("for the spec change", conformance.unlabelled));
|
|
65
65
|
return { ...base, tested: tested(), passed: false, stoppedAt: "conformance" };
|