@turri-dev/cli 0.2.1-next.bc56a0d → 0.2.2-next.368861b
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/extension.js
CHANGED
|
@@ -1237,7 +1237,7 @@ function registerRegenerate(pi, cwd, jobs) {
|
|
|
1237
1237
|
// Said at once, and kept in the chat: what follows takes a while - the planner is an agent - and the person sees it
|
|
1238
1238
|
// began, not a chat that stays empty until the first pulse starts. The plan is said under it once it is made.
|
|
1239
1239
|
say(scratch
|
|
1240
|
-
? `Deleting ${module}'s code${chain ? " and its whole chain's" : ""}
|
|
1240
|
+
? `Deleting ${module}'s code${chain ? " and its whole chain's" : ""} and then starting regeneration…`
|
|
1241
1241
|
: again.length > 0
|
|
1242
1242
|
? `Planning ${again.join(", ")} again…`
|
|
1243
1243
|
: `Planning ${module}'s pulses…`, "info");
|
|
@@ -160,10 +160,21 @@ export interface PlaceAnswer {
|
|
|
160
160
|
/** What of the person's wishes it did not follow, and why. */
|
|
161
161
|
notes?: string;
|
|
162
162
|
}
|
|
163
|
-
/**
|
|
163
|
+
/**
|
|
164
|
+
* A group to title: the number the agent answers with, and its functional specs' text - or, for a pulse that makes the
|
|
165
|
+
* whole of an edit, `changes`: what the edit does to the ***plain the code is built from.
|
|
166
|
+
*/
|
|
164
167
|
export interface TitleGroup {
|
|
165
168
|
number: number;
|
|
166
169
|
specs: string[];
|
|
170
|
+
changes?: TitleChange[];
|
|
171
|
+
}
|
|
172
|
+
/** A change a pulse makes, as the title agent is shown it: what it is, how it changes, its text now - and before, changed. */
|
|
173
|
+
export interface TitleChange {
|
|
174
|
+
kind: "changed" | "taken out" | "added";
|
|
175
|
+
what: string;
|
|
176
|
+
text: string;
|
|
177
|
+
before?: string;
|
|
167
178
|
}
|
|
168
179
|
export interface TitleAnswer {
|
|
169
180
|
titles: Array<{
|
|
@@ -175,7 +186,8 @@ export interface TitleAnswer {
|
|
|
175
186
|
* The judgements, each asked again with what was wrong with its answer - the last answer given, whether it holds
|
|
176
187
|
* or not: the plan checks it again. Injectable, so the plan is testable without a model. `title` titles the groups no
|
|
177
188
|
* agent titled - the conformance tests', and a grouping that fell back - for what each pulse does, rather than its
|
|
178
|
-
* first functional spec's first line; without it, they keep that.
|
|
189
|
+
* first functional spec's first line; without it, they keep that. It titles a pulse that makes the whole of an edit
|
|
190
|
+
* too, shown the edit's changes; without it, that pulse is titled by `editTitle`.
|
|
179
191
|
*/
|
|
180
192
|
export interface ChunkAgents {
|
|
181
193
|
group(specs: NewSpec[], check: (answer: GroupAnswer) => string[], steering?: Steering[], maxSpecs?: number, built?: BuiltChange[]): Promise<GroupAnswer | undefined>;
|
|
@@ -188,10 +200,15 @@ export interface ChunkAgents {
|
|
|
188
200
|
* is built as the new ones are. One taken out builds nothing, and does not.
|
|
189
201
|
*/
|
|
190
202
|
export declare function groupProblems(specs: NewSpec[], answer: GroupAnswer, maxSpecs?: number, built?: BuiltChange[]): string[];
|
|
191
|
-
/** What is wrong with a titling: every group titled once, by its number, none empty, none too long. */
|
|
203
|
+
/** What is wrong with a titling: every group titled once, by its number, none empty, none too long, none naming the act. */
|
|
192
204
|
export declare function titleProblems(groups: TitleGroup[], answer: TitleAnswer): string[];
|
|
193
205
|
/** What is wrong with a placement: every new req once, each where there is such a job. */
|
|
194
206
|
export declare function placeProblems(input: PlaceInput, answer: PlaceAnswer): string[];
|
|
207
|
+
/**
|
|
208
|
+
* The title of a pulse that makes the whole of an edit, when no agent gave one: the first line of the first functional
|
|
209
|
+
* spec it changes - else of its first change - and how many more it makes. Never the bare word.
|
|
210
|
+
*/
|
|
211
|
+
export declare function editTitle(changes: SpecChange[]): string;
|
|
195
212
|
export declare function planChunks(input: PlanInput, agents?: ChunkAgents, notes?: string[]): Promise<DraftJob[]>;
|
|
196
213
|
export declare function groupPrompt(specs: NewSpec[], steering?: Steering[], maxSpecs?: number, built?: BuiltChange[]): string;
|
|
197
214
|
export declare function placePrompt(input: PlaceInput): string;
|
|
@@ -79,6 +79,20 @@ export const TITLE_CHARS = 70;
|
|
|
79
79
|
function fitsTitle(title) {
|
|
80
80
|
return title.trim().split(/\s+/).length <= TITLE_WORDS && title.trim().length <= TITLE_CHARS;
|
|
81
81
|
}
|
|
82
|
+
/** Whether a title names the act of regenerating - "regenerate", "Regenerate the scenery" - rather than the change. */
|
|
83
|
+
function namesTheAct(title) {
|
|
84
|
+
return /^regenerat(e|es|ed|ing)\b/i.test(title.trim());
|
|
85
|
+
}
|
|
86
|
+
/** What is wrong with one title, or nothing: empty, too long, or naming the act rather than the change. */
|
|
87
|
+
function titleProblem(title, empty) {
|
|
88
|
+
if (title.trim() === "")
|
|
89
|
+
return empty;
|
|
90
|
+
if (!fitsTitle(title))
|
|
91
|
+
return `"${title}" is too long for a title: at most ${TITLE_WORDS} words and ${TITLE_CHARS} characters.`;
|
|
92
|
+
if (namesTheAct(title))
|
|
93
|
+
return `"${title}" names the act of regenerating: say what the pulse changes in the software.`;
|
|
94
|
+
return undefined;
|
|
95
|
+
}
|
|
82
96
|
/**
|
|
83
97
|
* A title held to `TITLE_WORDS` and `TITLE_CHARS` - or less, for room beside it - cut at a word, with an ellipsis. Text
|
|
84
98
|
* cut already is cut again without stacking a second: several first lines joined, each cut, end on one ellipsis.
|
|
@@ -101,7 +115,10 @@ const RETRIES = 2;
|
|
|
101
115
|
* its scenarios, each under its scenario-id, where the tree has them: the conformance track's.
|
|
102
116
|
*/
|
|
103
117
|
function textOf(change) {
|
|
104
|
-
|
|
118
|
+
return textOfLine(change.after ?? change.before ?? "");
|
|
119
|
+
}
|
|
120
|
+
/** An item's text from its line in a tree: its markdown, and its scenarios, if any. */
|
|
121
|
+
function textOfLine(line) {
|
|
105
122
|
try {
|
|
106
123
|
const item = JSON.parse(line);
|
|
107
124
|
const scenarios = [...(item[CONFORMANCE_TESTS] ?? []), ...(item[ACCEPTANCE_TESTS] ?? [])].map((scenario) => `# ${scenario.id ?? ""}\n${scenario.text ?? ""}`);
|
|
@@ -181,14 +198,13 @@ function newSpecProblems(specs, answer, maxSpecs, built) {
|
|
|
181
198
|
if (!nextTo(group.specs[i - 1], group.specs[i]))
|
|
182
199
|
problems.push(`"${group.title}" groups ${group.specs.join(", ")}: only functional specs next to each other in the same module go together.`);
|
|
183
200
|
}
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
problems.push(`"${group.title}" is too long for a title: at most ${TITLE_WORDS} words and ${TITLE_CHARS} characters.`);
|
|
201
|
+
const wrong = titleProblem(group.title, "Every group needs a title.");
|
|
202
|
+
if (wrong !== undefined)
|
|
203
|
+
problems.push(wrong);
|
|
188
204
|
});
|
|
189
205
|
return problems;
|
|
190
206
|
}
|
|
191
|
-
/** What is wrong with a titling: every group titled once, by its number, none empty, none too long. */
|
|
207
|
+
/** What is wrong with a titling: every group titled once, by its number, none empty, none too long, none naming the act. */
|
|
192
208
|
export function titleProblems(groups, answer) {
|
|
193
209
|
const problems = [];
|
|
194
210
|
const numbers = groups.map((group) => group.number);
|
|
@@ -197,10 +213,9 @@ export function titleProblems(groups, answer) {
|
|
|
197
213
|
if (!numbers.includes(entry.group))
|
|
198
214
|
problems.push(`There is no group ${entry.group}.`);
|
|
199
215
|
seen.set(entry.group, (seen.get(entry.group) ?? 0) + 1);
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
problems.push(`"${entry.title}" is too long for a title: at most ${TITLE_WORDS} words and ${TITLE_CHARS} characters.`);
|
|
216
|
+
const wrong = titleProblem(entry.title, `Group ${entry.group} needs a title.`);
|
|
217
|
+
if (wrong !== undefined)
|
|
218
|
+
problems.push(wrong);
|
|
204
219
|
}
|
|
205
220
|
for (const number of numbers) {
|
|
206
221
|
const times = seen.get(number) ?? 0;
|
|
@@ -323,7 +338,34 @@ function draftOf(chunks, net) {
|
|
|
323
338
|
}
|
|
324
339
|
/** A spec's first line, as a group's title when no agent gave one. */
|
|
325
340
|
function titleOf(change) {
|
|
326
|
-
return shortTitle(
|
|
341
|
+
return shortTitle(firstLineOf(change));
|
|
342
|
+
}
|
|
343
|
+
function firstLineOf(change) {
|
|
344
|
+
return textOf(change).split("\n")[0].replace(/^-\s*/, "");
|
|
345
|
+
}
|
|
346
|
+
const isFunctionalSpec = (change) => change.section === FUNCTIONAL_REQUIREMENTS || change.section === REQUIRED_FUNCTIONAL_SPECS;
|
|
347
|
+
/**
|
|
348
|
+
* The title of a pulse that makes the whole of an edit, when no agent gave one: the first line of the first functional
|
|
349
|
+
* spec it changes - else of its first change - and how many more it makes. Never the bare word.
|
|
350
|
+
*/
|
|
351
|
+
export function editTitle(changes) {
|
|
352
|
+
const [first, ...rest] = [...changes.filter(isFunctionalSpec), ...changes.filter((change) => !isFunctionalSpec(change))];
|
|
353
|
+
if (first === undefined)
|
|
354
|
+
return "No change";
|
|
355
|
+
const line = first.kind === "remove" ? `Takes out: ${firstLineOf(first)}` : firstLineOf(first);
|
|
356
|
+
if (rest.length === 0)
|
|
357
|
+
return shortTitle(line);
|
|
358
|
+
const more = `, and ${rest.length} more`;
|
|
359
|
+
return `${shortTitle(line, TITLE_CHARS - more.length, TITLE_WORDS - 3).replace(/[.,;:]+$/, "")}${more}`;
|
|
360
|
+
}
|
|
361
|
+
/** A change as the title agent is shown it: what it is, how it changes, its text now - and before, for a change. */
|
|
362
|
+
function titleChangeOf(change) {
|
|
363
|
+
return {
|
|
364
|
+
kind: change.kind === "change" ? "changed" : change.kind === "remove" ? "taken out" : "added",
|
|
365
|
+
what: WHAT[change.section] ?? change.section,
|
|
366
|
+
text: textOf(change),
|
|
367
|
+
...(change.kind === "change" && change.before !== undefined ? { before: textOfLine(change.before) } : {}),
|
|
368
|
+
};
|
|
327
369
|
}
|
|
328
370
|
/**
|
|
329
371
|
* Cut the queue's work - from `input.base` to `input.current` - into jobs, as the three steps say. Returns the draft
|
|
@@ -472,10 +514,29 @@ async function cut(input, agents, notes) {
|
|
|
472
514
|
const builtShown = builtChanges.map((change, index) => builtChangeOf(change, index + 1, input.moduleName));
|
|
473
515
|
/** Where each change to what is built goes: with the group holding a new functional spec - by that spec's number - or first. */
|
|
474
516
|
const builtTo = new Map();
|
|
475
|
-
|
|
476
|
-
|
|
517
|
+
/**
|
|
518
|
+
* The whole of the work in one job - an edit that only changes what is built, or one no plan of holds together -
|
|
519
|
+
* titled by what the edit does: by the title agent, shown every change, else by what it changes first.
|
|
520
|
+
*/
|
|
521
|
+
const wholeJob = async () => {
|
|
522
|
+
const whole = { title: "", reason: "the whole of the work, in one job", changes: net.map((change) => change.id) };
|
|
523
|
+
if (checkDraft(input, [whole]).length > 0)
|
|
524
|
+
return [];
|
|
525
|
+
const fallback = editTitle(net);
|
|
526
|
+
if (agents?.title === undefined)
|
|
527
|
+
return [{ ...whole, title: fallback }];
|
|
528
|
+
reportStage("planning the queue: a title for the pulse");
|
|
529
|
+
const toTitle = [{ number: 1, specs: [], changes: net.map(titleChangeOf) }];
|
|
530
|
+
const answer = (await agents.title(toTitle, (given) => titleProblems(toTitle, given), input.track === "conformance" ? "conformance" : "code").catch((error) => plannerFailed("title", error))) ?? undefined;
|
|
531
|
+
const problems = answer === undefined ? [] : titleProblems(toTitle, answer);
|
|
532
|
+
if (answer !== undefined && problems.length === 0)
|
|
533
|
+
return [{ ...whole, title: answer.titles[0].title.trim() }];
|
|
534
|
+
record({ kind: "planner.fellBack", step: "title", why: answer === undefined ? "no title was given" : firstLine(problems.join(" "), 300) });
|
|
535
|
+
say(answer === undefined ? `the title was not given - the pulse titled by what it changes first: "${fallback}"` : `the title did not hold (${problems.join(" ")}) - the pulse titled by what it changes first: "${fallback}"`);
|
|
536
|
+
return [{ ...whole, title: fallback }];
|
|
537
|
+
};
|
|
477
538
|
if (specs.length === 0)
|
|
478
|
-
return
|
|
539
|
+
return wholeJob();
|
|
479
540
|
// 1. Which functional specs go together.
|
|
480
541
|
reportStage(`planning the queue: which of the ${specs.length} functional spec${specs.length === 1 ? "" : "s"} to build go together`);
|
|
481
542
|
const numbered = specs.map((change, index) => {
|
|
@@ -661,7 +722,7 @@ async function cut(input, agents, notes) {
|
|
|
661
722
|
const simple = draftOf(chunks, net);
|
|
662
723
|
if (checkDraft(input, simple).length === 0)
|
|
663
724
|
return simple;
|
|
664
|
-
return
|
|
725
|
+
return wholeJob();
|
|
665
726
|
}
|
|
666
727
|
// ---------------------------------------------------------------------------------------------
|
|
667
728
|
// The agents.
|
|
@@ -752,8 +813,25 @@ export function titlePrompt(groups, track) {
|
|
|
752
813
|
? "Give each group a title that says what its pulse tests - the behaviour, not the spec's wording."
|
|
753
814
|
: "Give each group a title that says what its pulse builds - the feature, not the spec's wording.",
|
|
754
815
|
`At most ${TITLE_WORDS} words and ${TITLE_CHARS} characters: it heads the pulse where the person watches the work, and a longer one is cut short there.`,
|
|
816
|
+
...(groups.some((group) => group.changes !== undefined)
|
|
817
|
+
? [
|
|
818
|
+
"",
|
|
819
|
+
"A group of changes is one edit to software already built: its pulse makes the whole of it. Title it by what the",
|
|
820
|
+
"edit does to the software, briefly - \"Expired links answer 410 and the stats page goes\", \"Greeter greets by",
|
|
821
|
+
"name\". A definition or an implementation req changed is titled by what it changes in what is built - \"Links are",
|
|
822
|
+
"kept for 30 days\" - never by the section it is in.",
|
|
823
|
+
]
|
|
824
|
+
: []),
|
|
825
|
+
"Name the change, never the act: \"regenerate\", \"Regenerate the scenery\" and \"Changes\" are not titles.",
|
|
755
826
|
"",
|
|
756
|
-
...groups.map((group) => [
|
|
827
|
+
...groups.map((group) => [
|
|
828
|
+
`<group number="${group.number}">`,
|
|
829
|
+
...group.specs.map((spec) => `<functional spec>\n${spec.trim()}\n</functional spec>`),
|
|
830
|
+
...(group.changes ?? []).map((change) => change.before !== undefined
|
|
831
|
+
? `<change kind="${change.kind}" what="${change.what}">\n<before>\n${change.before.trim()}\n</before>\n<now>\n${change.text.trim()}\n</now>\n</change>`
|
|
832
|
+
: `<change kind="${change.kind}" what="${change.what}">\n${change.text.trim()}\n</change>`),
|
|
833
|
+
"</group>",
|
|
834
|
+
].join("\n")),
|
|
757
835
|
"",
|
|
758
836
|
"Call submit_titles once, with a title for every group, by its number.",
|
|
759
837
|
].join("\n");
|
package/docs/regenerating.md
CHANGED
|
@@ -184,7 +184,7 @@ A job that regenerates the whole of a new module's \*\*\*plain is too big to bui
|
|
|
184
184
|
2. **The definitions each group uses**, in code: the concepts its functional specs name, and the concepts those definitions name in turn. A definition goes with the first group that uses it; later groups have it built. One nothing uses goes with the last.
|
|
185
185
|
3. **Where each new implementation req goes.** An agent places each with the group it is about - or with the first, when it is about everything (the language, the framework).
|
|
186
186
|
|
|
187
|
-
Each pulse's title says what it does - at most 10 words and 70 characters, as it heads the pulse where the person watches the work. The grouping agent titles its groups; groups no agent made - the conformance tests', and a grouping that fell back - are titled by a third agent, told what each pulse does: what it builds, or, for the conformance tests, what they test. Without it, or when it fails, a pulse is titled by its functional specs' first lines, cut short.
|
|
187
|
+
Each pulse's title says what it does - at most 10 words and 70 characters, as it heads the pulse where the person watches the work. The grouping agent titles its groups; groups no agent made - the conformance tests', and a grouping that fell back - are titled by a third agent, told what each pulse does: what it builds, or, for the conformance tests, what they test. Without it, or when it fails, a pulse is titled by its functional specs' first lines, cut short. A pulse that makes the whole of an edit in one go - an edit that only changes what is built (a functional spec reworded, a definition or an implementation req changed, none new and none marked to regenerate again), or one no plan of holds together - is titled by the same agent, shown every change, before and after: by what the edit does (`Expired links answer 410 and the stats page goes`), and an edit to definitions or implementation reqs alone by what it changes in what is built (`Links are kept for 30 days`), never by its section. Without it, or when it fails, it is titled by the first line of the first functional spec it changes - else of its first change - and how many more (`Follow-link answers 410 for expired links, and 2 more`). A title names the change, never the act: one that starts with "regenerate" does not hold. Pulses already in the ledger keep the titles they were recorded with.
|
|
188
188
|
|
|
189
189
|
Changes to what is built - an item edited or taken out, a functional spec built worded differently - go where the same agent places each: with the group of new functional specs it is about, by default - so one edit that adds a functional spec, changes another and takes out a third is one pulse - or first, in a pulse of its own, when it reshapes what the new work is built on (a concept many functional specs use redefined, one others rely on taken out, a large rewrite) or has nothing to do with it. A functional spec changed counts toward its group's `max-specs-per-pulse`; one taken out does not. Every answer is checked: every new functional spec once, in order, next to each other, at most `max-specs-per-pulse`; every change to what is built placed once; every req placed once; every group titled once, within the limit. One that does not hold goes back to its agent, twice at most, and then the plan is a functional spec a job with every req in the first. A group whose functional specs and the definitions they bring pass `max-lines-per-pulse` (120 unset) is split into as few pulses as fit - its functional specs kept together in runs, in order, as long as each run stays within it, `Comments (1 of 2)`, `Comments (2 of 2)` - not a functional spec a job; its reqs do not count, as a split leaves them with its first job whatever happens. The planner runs on the chat's model in a conversation, and on the harness model under `turri regenerate`. No agent reviews its plan: the harness checks it - every change in exactly one job, \*\*\*plain holding together after each, the last ending on \*\*\*plain as it is.
|
|
190
190
|
|