@turri-dev/cli 0.1.4-next.318a98a → 0.1.4-next.fa9223b
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/dist/ask.js +34 -15
- package/dist/extension.js +3 -2
- package/dist/loop-cli.js +4 -0
- package/dist/question-dialog.js +1 -0
- package/dist/regenerate/loop.js +12 -3
- package/dist/run-report.js +13 -0
- package/docs/headless.md +14 -1
- package/docs/regenerating.md +3 -1
- package/docs/skills-and-gates.md +3 -1
- package/docs/using.md +1 -1
- package/package.json +1 -1
package/dist/ask.js
CHANGED
|
@@ -9,6 +9,9 @@ import { PANEL_KEY } from "./regenerate/job-panel.js";
|
|
|
9
9
|
export const OTHER_OPTION = "Something else…";
|
|
10
10
|
export const CHAT_OPTION = "Chat about this";
|
|
11
11
|
export const DONE_OPTION = "Done — that's all of them";
|
|
12
|
+
/** An option's box in a question of several, ticked and not. */
|
|
13
|
+
export const TICKED = "☑";
|
|
14
|
+
export const UNTICKED = "☐";
|
|
12
15
|
export const DISMISSED = "The user dismissed the question without answering.";
|
|
13
16
|
/** How an answer that is no answer starts: the person chose to talk about the question first. */
|
|
14
17
|
export const CHAT_PREFIX = "The user has not answered this question yet: they chose to talk about it first.";
|
|
@@ -107,32 +110,48 @@ export async function runAsk(asked, io) {
|
|
|
107
110
|
return choice;
|
|
108
111
|
}
|
|
109
112
|
}
|
|
110
|
-
// Pick several:
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
113
|
+
// Pick several: every option a box, ticked and unticked as it is chosen, until they say they are done. What they type
|
|
114
|
+
// is one more box, ticked - and can be unticked too. Something else, Chat about this and Done are no boxes.
|
|
115
|
+
const listed = [...options];
|
|
116
|
+
const ticked = new Set();
|
|
117
|
+
const picked = () => listed.filter((option) => ticked.has(option));
|
|
118
|
+
let at = 0;
|
|
119
|
+
for (;;) {
|
|
120
|
+
const rows = listed.map((option) => `${ticked.has(option) ? TICKED : UNTICKED} ${option}`);
|
|
121
|
+
const choice = await io.select(request.question, [...rows, ...extras, DONE_OPTION], context, at);
|
|
122
|
+
if (choice === undefined || choice === DONE_OPTION)
|
|
119
123
|
break;
|
|
120
124
|
if (choice === OTHER_OPTION) {
|
|
125
|
+
at = listed.length;
|
|
121
126
|
const typed = await typeInstead();
|
|
122
|
-
if (
|
|
123
|
-
|
|
127
|
+
if (typedNothing(typed))
|
|
128
|
+
continue;
|
|
129
|
+
const option = typed.trim();
|
|
130
|
+
if (!listed.includes(option))
|
|
131
|
+
listed.push(option);
|
|
132
|
+
ticked.add(option);
|
|
133
|
+
at = listed.indexOf(option);
|
|
124
134
|
continue;
|
|
125
135
|
}
|
|
126
136
|
if (choice === CHAT_OPTION) {
|
|
137
|
+
at = listed.length + extras.indexOf(CHAT_OPTION);
|
|
127
138
|
const said = await chatAbout();
|
|
128
139
|
if (typedNothing(said))
|
|
129
140
|
continue;
|
|
130
|
-
return chatAnswer(said,
|
|
141
|
+
return chatAnswer(said, picked());
|
|
131
142
|
}
|
|
132
|
-
|
|
133
|
-
|
|
143
|
+
// A row, or - where the answer is typed, on the command line - an option by its text, or one more of their own.
|
|
144
|
+
const row = rows.indexOf(choice);
|
|
145
|
+
const option = row !== -1 ? listed[row] : choice.trim();
|
|
146
|
+
if (!listed.includes(option))
|
|
147
|
+
listed.push(option);
|
|
148
|
+
if (ticked.has(option))
|
|
149
|
+
ticked.delete(option);
|
|
150
|
+
else
|
|
151
|
+
ticked.add(option);
|
|
152
|
+
at = listed.indexOf(option);
|
|
134
153
|
}
|
|
135
|
-
return
|
|
154
|
+
return ticked.size > 0 ? picked().join(", ") : DISMISSED;
|
|
136
155
|
}
|
|
137
156
|
/**
|
|
138
157
|
* While a question is open, what catches ctrl+h on the terminal's input: pi gives an extension's shortcut only the keys
|
package/dist/extension.js
CHANGED
|
@@ -90,11 +90,12 @@ 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) => {
|
|
93
|
+
const dialog = (question, context, options, selected) => ctx.ui.custom((tui, theme, _keybindings, done) => {
|
|
94
94
|
const shown = new QuestionDialog({
|
|
95
95
|
question,
|
|
96
96
|
...(context !== undefined ? { context } : {}),
|
|
97
97
|
...(options !== undefined ? { options } : {}),
|
|
98
|
+
...(selected !== undefined ? { selected } : {}),
|
|
98
99
|
rows: () => tui.terminal.rows,
|
|
99
100
|
style: { accent: (text) => theme.fg("accent", text), muted: (text) => theme.fg("muted", text), bold: (text) => theme.bold(text) },
|
|
100
101
|
done: (answer) => {
|
|
@@ -108,7 +109,7 @@ aside) {
|
|
|
108
109
|
return shown;
|
|
109
110
|
});
|
|
110
111
|
const asking = (asked) => runAsk(asked, {
|
|
111
|
-
select: (title, options, context) => dialog(title, context, options),
|
|
112
|
+
select: (title, options, context, at) => dialog(title, context, options, at),
|
|
112
113
|
input: (title, context) => dialog(title, context),
|
|
113
114
|
});
|
|
114
115
|
const bare = { ...request, said: undefined };
|
package/dist/loop-cli.js
CHANGED
|
@@ -85,6 +85,10 @@ async function runLoopReporting(options) {
|
|
|
85
85
|
console.log(`${mark} ${job.id} ${pulseName(job)} - ${job.status}`);
|
|
86
86
|
if (job.status !== "succeeded")
|
|
87
87
|
console.log(c.dim((job.announce ?? job.report ?? job.error ?? "").trim()));
|
|
88
|
+
// Its report is not printed when it succeeds, and nobody is asked here: the spec gaps its agents met are said.
|
|
89
|
+
else
|
|
90
|
+
for (const gap of job.details?.specGaps ?? [])
|
|
91
|
+
console.log(c.yellow(` spec gap: ${gap.question}`), c.dim(`Meanwhile: ${gap.assumption}`));
|
|
88
92
|
report?.write();
|
|
89
93
|
},
|
|
90
94
|
});
|
package/dist/question-dialog.js
CHANGED
package/dist/regenerate/loop.js
CHANGED
|
@@ -597,7 +597,14 @@ export function createWorkLoop(options) {
|
|
|
597
597
|
}
|
|
598
598
|
return {
|
|
599
599
|
report: released !== undefined ? `${report}\n\n${released}` : report,
|
|
600
|
-
details: {
|
|
600
|
+
details: {
|
|
601
|
+
passed: result.landed,
|
|
602
|
+
landed: result.landed,
|
|
603
|
+
...(released !== undefined ? { released: true } : {}),
|
|
604
|
+
...(result.story !== undefined ? { story: result.story } : {}),
|
|
605
|
+
// The spec gaps its agents met, for a headless run to say - in the chat the report puts them to the person.
|
|
606
|
+
...(result.specGaps !== undefined && result.specGaps.length > 0 ? { specGaps: result.specGaps } : {}),
|
|
607
|
+
},
|
|
601
608
|
};
|
|
602
609
|
},
|
|
603
610
|
});
|
|
@@ -1565,7 +1572,7 @@ const defaultConformanceRunner = async (job, onDelta) => {
|
|
|
1565
1572
|
writeTestsRecord(result.conformanceDir, job.planned.step.tree);
|
|
1566
1573
|
recordHistory(job.buildDir, `conformance tests: ${job.planned.title}`);
|
|
1567
1574
|
}
|
|
1568
|
-
return { landed: result.passed, report: describeSpecDiffConformance(result) };
|
|
1575
|
+
return { landed: result.passed, report: describeSpecDiffConformance(result), ...(result.specGaps !== undefined ? { specGaps: result.specGaps } : {}) };
|
|
1569
1576
|
};
|
|
1570
1577
|
/** A job as a spec diff render, landed on its unit tests: conformance is a track of its own. */
|
|
1571
1578
|
const defaultRunner = async (job, onDelta) => {
|
|
@@ -1581,7 +1588,9 @@ const defaultRunner = async (job, onDelta) => {
|
|
|
1581
1588
|
onDelta,
|
|
1582
1589
|
});
|
|
1583
1590
|
// Landed code can be tried.
|
|
1584
|
-
return { landed: result.passed, report: withTryItLine(describeSpecDiff(result), result.passed && result.restructured !== true), story: codeStory(result)
|
|
1591
|
+
return { landed: result.passed, report: withTryItLine(describeSpecDiff(result), result.passed && result.restructured !== true), story: codeStory(result),
|
|
1592
|
+
...(result.specGaps !== undefined ? { specGaps: result.specGaps } : {}),
|
|
1593
|
+
};
|
|
1585
1594
|
};
|
|
1586
1595
|
/** How a code pulse went, for its line in the notes: the rounds it took, its test runs, what the debugger found. */
|
|
1587
1596
|
export function codeStory(result) {
|
package/dist/run-report.js
CHANGED
|
@@ -181,6 +181,18 @@ export function startRunReport(options) {
|
|
|
181
181
|
list,
|
|
182
182
|
};
|
|
183
183
|
};
|
|
184
|
+
/** The spec gaps the pulses' agents met, in the order the pulses started: questions a person would want to decide. */
|
|
185
|
+
const specGapsOf = (jobs) => jobs.flatMap((job) => {
|
|
186
|
+
const details = job.details;
|
|
187
|
+
return (details?.specGaps ?? []).map((gap) => ({
|
|
188
|
+
module: job.module ?? null,
|
|
189
|
+
pulse: job.pulse ?? null,
|
|
190
|
+
pulse_id: job.id,
|
|
191
|
+
landed: job.status === "succeeded" && details?.landed === true,
|
|
192
|
+
question: gap.question,
|
|
193
|
+
assumption: gap.assumption,
|
|
194
|
+
}));
|
|
195
|
+
});
|
|
184
196
|
const functionalitiesOf = (counts) => {
|
|
185
197
|
if (counts === undefined)
|
|
186
198
|
return null;
|
|
@@ -245,6 +257,7 @@ export function startRunReport(options) {
|
|
|
245
257
|
usage: usageOf(jobs),
|
|
246
258
|
pulses: pulsesOf(jobs, now),
|
|
247
259
|
functionalities: functionalitiesOf(attempt(sources.completion)),
|
|
260
|
+
spec_gaps: specGapsOf(jobs),
|
|
248
261
|
...(exitParts ?? {}),
|
|
249
262
|
logs: {
|
|
250
263
|
jobs_dir: fromProject(join(projectRoot, ".turri", "jobs")),
|
package/docs/headless.md
CHANGED
|
@@ -104,7 +104,8 @@ Conformance tests follow `conformance-tests:` in `config.turri.yaml`, or `--conf
|
|
|
104
104
|
The command exits `0` only when every module is complete: its code regenerated from the spec as it is
|
|
105
105
|
now, and its conformance tests written for what is built when they are on - no pulse failed, nothing
|
|
106
106
|
halted, nothing waiting for a start over. Anything else exits `1`, and the last lines say which
|
|
107
|
-
module is not complete, and why.
|
|
107
|
+
module is not complete, and why. A behaviour the spec leaves open that an agent had to decide is printed
|
|
108
|
+
as a `spec gap` under its pulse, with what was decided meanwhile. The log says, per functionality, what was built and whether its
|
|
108
109
|
tests passed.
|
|
109
110
|
|
|
110
111
|
| | |
|
|
@@ -191,6 +192,11 @@ changes meaning or goes; new fields may appear in the same version. Paths in `pu
|
|
|
191
192
|
],
|
|
192
193
|
"codebases": ["plain_modules/app/code"]
|
|
193
194
|
},
|
|
195
|
+
"spec_gaps": [{ // [] when no agent met one
|
|
196
|
+
"module": "app", "pulse": 3, "pulse_id": "2a058729", "landed": true,
|
|
197
|
+
"question": "What happens to a contact archived in HubSpot after it was synced?",
|
|
198
|
+
"assumption": "An archived contact is left as synced - asserted in sync.test.ts, archived contacts"
|
|
199
|
+
}],
|
|
194
200
|
"build_dir_export": { "exported": true, "dir": "/work/out", "module": "app", "commit": "8036b3b", "files": 23 },
|
|
195
201
|
"logs": { "jobs_dir": ".turri/jobs", "debug_dir": ".turri/debug", "sessions_dir": ".turri/sessions" }
|
|
196
202
|
}
|
|
@@ -209,6 +215,13 @@ calls; a role or model turri could not tell is `unknown`.
|
|
|
209
215
|
stopped, or did not pass - and everything it wrote was undone. A pulse that failed is tried once more
|
|
210
216
|
before it counts as failed; both tries are the one pulse.
|
|
211
217
|
|
|
218
|
+
**Spec gaps.** A behaviour the spec leaves open that an agent met and decided on its own - reported with
|
|
219
|
+
`report_spec_gap` (see [regenerating](regenerating.md)). In the chat they are put to you as the pulse ends; a
|
|
220
|
+
headless run has no one to ask, so they are listed here, in the order the pulses started, and printed under the
|
|
221
|
+
pulse that met them. `assumption` is what the agent decided meanwhile, also in the codebase's notes'
|
|
222
|
+
assumptions while its pulse is `landed`; a pulse rolled back takes it out of the notes, but the question still
|
|
223
|
+
stands. Settle the ones you care about in the spec and run again.
|
|
224
|
+
|
|
212
225
|
**Completed functional specs** - the pass rate, the figure to compare runs by.
|
|
213
226
|
A functional spec is completed when the code record of the codebase the run delivers - the
|
|
214
227
|
furthest-built of the chain, the one `--build-dir` exports - as last committed, holds it as the spec has
|
package/docs/regenerating.md
CHANGED
|
@@ -1111,7 +1111,9 @@ are the next agent's, and a gap is yours. An agent reports one with `report_spec
|
|
|
1111
1111
|
and the assumption it made meanwhile; turri writes that assumption into the notes' assumptions, so the code built on
|
|
1112
1112
|
it is known for what it is. The gaps a pulse's agents report are said in its report - "Spec gaps its agents met ... Put
|
|
1113
1113
|
each to the user", each with its assumption - so the conversation asks you, and writes the ones you decide into the
|
|
1114
|
-
spec; the pulse that builds the answer finds the assumption it replaces in the notes, and the tests it names.
|
|
1114
|
+
spec; the pulse that builds the answer finds the assumption it replaces in the notes, and the tests it names.
|
|
1115
|
+
A headless run has no one to ask: it prints each gap under its pulse, and lists them in its run report's
|
|
1116
|
+
`spec_gaps` ([running unattended](headless.md#the-run-report)). However many gaps a pulse's agents report, they
|
|
1115
1117
|
are put to you - never a reason to stop the pulse; the tool is guarded like any other. A notes
|
|
1116
1118
|
file from before keeps no spec gaps section: its lines are dropped as it is read.
|
|
1117
1119
|
|
package/docs/skills-and-gates.md
CHANGED
|
@@ -133,7 +133,9 @@ change the step makes and puts its findings on the executor's next tool result.
|
|
|
133
133
|
- **Interview the user.** `ask_user` puts one question to the person. In the TUI it is a dialog;
|
|
134
134
|
from the CLI the options are numbered. Offering choices is required, not optional - a call with
|
|
135
135
|
neither `options` nor `free_form: true` is refused - and every list carries "Something else…".
|
|
136
|
-
`multi_select: true`
|
|
136
|
+
`multi_select: true` gives every option a box, ticked and unticked as it is chosen - a pick made by mistake is
|
|
137
|
+
undone by choosing it again - until they choose Done; what they type is one more box, ticked. "Something
|
|
138
|
+
else…", "Chat about this" and Done are no boxes. The skill's agent talks in a
|
|
137
139
|
session of its own that the person never sees, so what the person must read to answer - the brief
|
|
138
140
|
to confirm, the plan to approve - it passes as `ask_user`'s `context`, shown above the question in
|
|
139
141
|
the dialog; `brood`, `spawn` and `bud` pass their briefs so. The agents are told to keep it to what can be taken in at
|
package/docs/using.md
CHANGED
|
@@ -63,7 +63,7 @@ where it stopped. A project can have skills of its own, in `.turri/skills/` or `
|
|
|
63
63
|
| `lineage` | a module's lineage: what each regeneration and conformance run changed in it, and what it was regenerated from |
|
|
64
64
|
| `cultivate` | run a gated skill, its steps and review gates enforced |
|
|
65
65
|
| `release`, `activate` | try what is regenerated: release it to `dist/<module>` and activate it on this machine, installing what it needs - see [deploy](deploy.md) |
|
|
66
|
-
| `ask_user` | put a question to you as a dialog to pick from - several at once, "Something else…" to type, or "Chat about this" to talk it over first; Esc in either goes back to the options |
|
|
66
|
+
| `ask_user` | put a question to you as a dialog to pick from - several at once, each a box you tick and untick, "Something else…" to type, or "Chat about this" to talk it over first; Esc in either goes back to the options |
|
|
67
67
|
| `web_search`, `web_fetch` | search the web and read a page as text - documentation, an API, an error; `curl` and the like run in its `bash` |
|
|
68
68
|
|
|
69
69
|
What each render does, in order, is in [regenerating the code](regenerating.md).
|