@turri-dev/cli 0.1.8 → 0.1.9
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.
|
@@ -82,7 +82,10 @@ export declare const MAX_SPECS_PER_JOB = 3;
|
|
|
82
82
|
/** The most a pulse's title is - in words, and in characters - however it was made: a host shows it as its heading. */
|
|
83
83
|
export declare const TITLE_WORDS = 10;
|
|
84
84
|
export declare const TITLE_CHARS = 70;
|
|
85
|
-
/**
|
|
85
|
+
/**
|
|
86
|
+
* A title held to `TITLE_WORDS` and `TITLE_CHARS` - or less, for room beside it - cut at a word, with an ellipsis. Text
|
|
87
|
+
* cut already is cut again without stacking a second: several first lines joined, each cut, end on one ellipsis.
|
|
88
|
+
*/
|
|
86
89
|
export declare function shortTitle(text: string, chars?: number, words?: number): string;
|
|
87
90
|
export declare const MAX_LINES_PER_JOB = 120;
|
|
88
91
|
/**
|
|
@@ -140,22 +143,39 @@ export interface PlaceAnswer {
|
|
|
140
143
|
/** What of the person's wishes it did not follow, and why. */
|
|
141
144
|
notes?: string;
|
|
142
145
|
}
|
|
146
|
+
/** A group to title: the number the agent answers with, and its functional specs' text. */
|
|
147
|
+
export interface TitleGroup {
|
|
148
|
+
number: number;
|
|
149
|
+
specs: string[];
|
|
150
|
+
}
|
|
151
|
+
export interface TitleAnswer {
|
|
152
|
+
titles: Array<{
|
|
153
|
+
group: number;
|
|
154
|
+
title: string;
|
|
155
|
+
}>;
|
|
156
|
+
}
|
|
143
157
|
/**
|
|
144
|
-
* The
|
|
145
|
-
* or not: the plan checks it again. Injectable, so the plan is testable without a model.
|
|
158
|
+
* The judgements, each asked again with what was wrong with its answer - the last answer given, whether it holds
|
|
159
|
+
* or not: the plan checks it again. Injectable, so the plan is testable without a model. `title` titles the groups no
|
|
160
|
+
* agent titled - the conformance tests', and a grouping that fell back - for what each pulse does, rather than its
|
|
161
|
+
* first functional spec's first line; without it, they keep that.
|
|
146
162
|
*/
|
|
147
163
|
export interface ChunkAgents {
|
|
148
164
|
group(specs: NewSpec[], check: (answer: GroupAnswer) => string[], steering?: Steering[], maxSpecs?: number): Promise<GroupAnswer | undefined>;
|
|
149
165
|
place(input: PlaceInput, check: (answer: PlaceAnswer) => string[]): Promise<PlaceAnswer | undefined>;
|
|
166
|
+
title?(groups: TitleGroup[], check: (answer: TitleAnswer) => string[], track: "code" | "conformance"): Promise<TitleAnswer | undefined>;
|
|
150
167
|
}
|
|
151
168
|
/** What is wrong with a grouping: every new functional spec once, in order, next to each other, none too big. */
|
|
152
169
|
export declare function groupProblems(specs: NewSpec[], answer: GroupAnswer, maxSpecs?: number): string[];
|
|
170
|
+
/** What is wrong with a titling: every group titled once, by its number, none empty, none too long. */
|
|
171
|
+
export declare function titleProblems(groups: TitleGroup[], answer: TitleAnswer): string[];
|
|
153
172
|
/** What is wrong with a placement: every new req once, each where there is such a job. */
|
|
154
173
|
export declare function placeProblems(input: PlaceInput, answer: PlaceAnswer): string[];
|
|
155
174
|
export declare function planChunks(input: PlanInput, agents?: ChunkAgents, notes?: string[]): Promise<DraftJob[]>;
|
|
156
175
|
export declare function groupPrompt(specs: NewSpec[], steering?: Steering[], maxSpecs?: number): string;
|
|
157
176
|
export declare function placePrompt(input: PlaceInput): string;
|
|
158
|
-
|
|
177
|
+
export declare function titlePrompt(groups: TitleGroup[], track: "code" | "conformance"): string;
|
|
178
|
+
/** The chunking's agents, on a model - fresh for every plan. */
|
|
159
179
|
export declare function llmChunkAgents(options: {
|
|
160
180
|
cwd: string;
|
|
161
181
|
model?: string;
|
|
@@ -74,15 +74,23 @@ export const MAX_SPECS_PER_JOB = 3;
|
|
|
74
74
|
/** The most a pulse's title is - in words, and in characters - however it was made: a host shows it as its heading. */
|
|
75
75
|
export const TITLE_WORDS = 10;
|
|
76
76
|
export const TITLE_CHARS = 70;
|
|
77
|
-
/**
|
|
77
|
+
/** Whether a title is within `TITLE_WORDS` and `TITLE_CHARS`. */
|
|
78
|
+
function fitsTitle(title) {
|
|
79
|
+
return title.trim().split(/\s+/).length <= TITLE_WORDS && title.trim().length <= TITLE_CHARS;
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* A title held to `TITLE_WORDS` and `TITLE_CHARS` - or less, for room beside it - cut at a word, with an ellipsis. Text
|
|
83
|
+
* cut already is cut again without stacking a second: several first lines joined, each cut, end on one ellipsis.
|
|
84
|
+
*/
|
|
78
85
|
export function shortTitle(text, chars = TITLE_CHARS, words = TITLE_WORDS) {
|
|
79
86
|
const all = text.trim().split(/\s+/).filter((word) => word !== "");
|
|
80
87
|
let cut = all.slice(0, words).join(" ");
|
|
81
88
|
if (all.length <= words && cut.length <= chars)
|
|
82
89
|
return cut;
|
|
90
|
+
cut = cut.replace(/[\s,.;:…-]+$/, "");
|
|
83
91
|
if (cut.length > chars - 1)
|
|
84
92
|
cut = cut.slice(0, chars - 1).replace(/\s+\S*$/, "") || cut.slice(0, chars - 1);
|
|
85
|
-
return `${cut.replace(/[\s
|
|
93
|
+
return `${cut.replace(/[\s,.;:…-]+$/, "")}…`;
|
|
86
94
|
}
|
|
87
95
|
export const MAX_LINES_PER_JOB = 120;
|
|
88
96
|
/** How often an answer that does not hold goes back to its agent before the plan falls back. */
|
|
@@ -137,9 +145,31 @@ export function groupProblems(specs, answer, maxSpecs = MAX_SPECS_PER_JOB) {
|
|
|
137
145
|
}
|
|
138
146
|
if (group.title.trim() === "")
|
|
139
147
|
problems.push("Every group needs a title.");
|
|
140
|
-
else if (
|
|
148
|
+
else if (!fitsTitle(group.title))
|
|
141
149
|
problems.push(`"${group.title}" is too long for a title: at most ${TITLE_WORDS} words and ${TITLE_CHARS} characters.`);
|
|
142
|
-
|
|
150
|
+
}
|
|
151
|
+
return problems;
|
|
152
|
+
}
|
|
153
|
+
/** What is wrong with a titling: every group titled once, by its number, none empty, none too long. */
|
|
154
|
+
export function titleProblems(groups, answer) {
|
|
155
|
+
const problems = [];
|
|
156
|
+
const numbers = groups.map((group) => group.number);
|
|
157
|
+
const seen = new Map();
|
|
158
|
+
for (const entry of answer.titles) {
|
|
159
|
+
if (!numbers.includes(entry.group))
|
|
160
|
+
problems.push(`There is no group ${entry.group}.`);
|
|
161
|
+
seen.set(entry.group, (seen.get(entry.group) ?? 0) + 1);
|
|
162
|
+
if (entry.title.trim() === "")
|
|
163
|
+
problems.push(`Group ${entry.group} needs a title.`);
|
|
164
|
+
else if (!fitsTitle(entry.title))
|
|
165
|
+
problems.push(`"${entry.title}" is too long for a title: at most ${TITLE_WORDS} words and ${TITLE_CHARS} characters.`);
|
|
166
|
+
}
|
|
167
|
+
for (const number of numbers) {
|
|
168
|
+
const times = seen.get(number) ?? 0;
|
|
169
|
+
if (times === 0)
|
|
170
|
+
problems.push(`Group ${number} has no title: every group is titled.`);
|
|
171
|
+
if (times > 1)
|
|
172
|
+
problems.push(`Group ${number} is titled ${times} times: once.`);
|
|
143
173
|
}
|
|
144
174
|
return problems;
|
|
145
175
|
}
|
|
@@ -271,8 +301,8 @@ function plannerFailed(step, error) {
|
|
|
271
301
|
export async function planChunks(input, agents, notes) {
|
|
272
302
|
if (input.track !== "conformance")
|
|
273
303
|
return cut(input, agents, notes);
|
|
274
|
-
// The conformance tests are grouped by the harness, as one job as far as the guardrail lets:
|
|
275
|
-
return anchored(await cut(input), netChanges(input.base, input.current));
|
|
304
|
+
// The conformance tests are grouped by the harness, as one job as far as the guardrail lets: an agent only titles them.
|
|
305
|
+
return anchored(await cut(input, agents, notes), netChanges(input.base, input.current));
|
|
276
306
|
}
|
|
277
307
|
/**
|
|
278
308
|
* A conformance plan, every job of it testing a functional spec: a job that touches none - reqs or definitions alone -
|
|
@@ -393,8 +423,29 @@ async function cut(input, agents, notes) {
|
|
|
393
423
|
...list.map((group) => ({ title: group.title, specs: group.specs.map((number) => bySpec.get(number)), definitions: [], reqs: [] })),
|
|
394
424
|
];
|
|
395
425
|
const conformance = input.track === "conformance";
|
|
426
|
+
// The conformance tests are grouped and their reqs placed by the harness: their agents are only asked for titles.
|
|
427
|
+
const planners = conformance ? undefined : agents;
|
|
428
|
+
/**
|
|
429
|
+
* Groups titled for what their pulses do, by the title agent: those no agent titled - the conformance tests', a
|
|
430
|
+
* grouping that fell back. When it fails, or its answer does not hold, they keep their first lines.
|
|
431
|
+
*/
|
|
432
|
+
const titled = async (list) => {
|
|
433
|
+
if (agents?.title === undefined || list.length === 0)
|
|
434
|
+
return list;
|
|
435
|
+
const track = conformance ? "conformance" : "code";
|
|
436
|
+
reportStage(`planning the queue: a title for each of the ${list.length} pulse${list.length === 1 ? "" : "s"}`);
|
|
437
|
+
const toTitle = list.map((group, index) => ({ number: index + 1, specs: group.specs.map((number) => textOf(bySpec.get(number))) }));
|
|
438
|
+
const answer = (await agents.title(toTitle, (given) => titleProblems(toTitle, given), track).catch((error) => plannerFailed("title", error))) ?? undefined;
|
|
439
|
+
const problems = answer === undefined ? [] : titleProblems(toTitle, answer);
|
|
440
|
+
if (answer !== undefined && problems.length === 0)
|
|
441
|
+
return list.map((group, index) => ({ ...group, title: answer.titles.find((entry) => entry.group === index + 1).title.trim() }));
|
|
442
|
+
record({ kind: "planner.fellBack", step: "title", why: answer === undefined ? "no titles were given" : firstLine(problems.join(" "), 300) });
|
|
443
|
+
say(answer === undefined ? "the titles were not given - each pulse titled by its functional specs' first lines" : `the titles did not hold (${problems.join(" ")}) - each pulse titled by its functional specs' first lines`);
|
|
444
|
+
return list;
|
|
445
|
+
};
|
|
396
446
|
const grouped = conformance ? undefined : ((await agents?.group(numbered, (answer) => groupProblems(numbered, answer, maxSpecs), input.steering, maxSpecs).catch((error) => plannerFailed("group", error))) ?? undefined);
|
|
397
|
-
const
|
|
447
|
+
const holds = grouped !== undefined && groupProblems(numbered, grouped, maxSpecs).length === 0;
|
|
448
|
+
const made = conformance
|
|
398
449
|
? conformanceGroups(numbered, input.separately === true, maxLines, (list) => {
|
|
399
450
|
const trial = chunksFor(list);
|
|
400
451
|
placeDefinitions(trial, definitions);
|
|
@@ -403,7 +454,7 @@ async function cut(input, agents, notes) {
|
|
|
403
454
|
const frid = fridOf(bySpec.get(spec.number));
|
|
404
455
|
return frid === undefined ? -1 : (input.groups ?? []).findIndex((group) => group.includes(frid));
|
|
405
456
|
})
|
|
406
|
-
: grouped !== undefined &&
|
|
457
|
+
: grouped !== undefined && holds
|
|
407
458
|
? grouped.groups
|
|
408
459
|
: numbered.map((spec, index) => ({ specs: [spec.number], title: titleOf(specs[index]) }));
|
|
409
460
|
if (grouped?.notes !== undefined && grouped.notes.trim() !== "")
|
|
@@ -418,6 +469,8 @@ async function cut(input, agents, notes) {
|
|
|
418
469
|
say(`the grouping did not hold (${groupProblems(numbered, grouped, maxSpecs).join(" ")}) - a functional spec a pulse`);
|
|
419
470
|
}
|
|
420
471
|
}
|
|
472
|
+
// Titled before the chunks are made of them: a group split for its size keeps its title, numbered.
|
|
473
|
+
const groups = holds ? made : await titled(made);
|
|
421
474
|
let chunks = chunksFor(groups);
|
|
422
475
|
// 2. The definitions each group uses.
|
|
423
476
|
placeDefinitions(chunks, definitions);
|
|
@@ -434,13 +487,13 @@ async function cut(input, agents, notes) {
|
|
|
434
487
|
implementationReqs: implementationReqs.map((change, index) => ({ number: index + 1, text: textOf(change) })),
|
|
435
488
|
testReqs: testReqs.map((change, index) => ({ number: index + 1, text: textOf(change) })),
|
|
436
489
|
};
|
|
437
|
-
const answer = (await
|
|
490
|
+
const answer = (await planners?.place(placeInput, (given) => placeProblems(placeInput, given)).catch((error) => plannerFailed("place", error))) ?? undefined;
|
|
438
491
|
if (answer?.notes !== undefined && answer.notes.trim() !== "")
|
|
439
492
|
say(`the placement: ${answer.notes.trim()}`);
|
|
440
493
|
placement = answer !== undefined && placeProblems(placeInput, answer).length === 0 ? answer : undefined;
|
|
441
|
-
if (placement === undefined &&
|
|
494
|
+
if (placement === undefined && planners !== undefined)
|
|
442
495
|
record({ kind: "planner.fellBack", step: "place", why: answer === undefined ? "no placement was given" : firstLine(placeProblems(placeInput, answer).join(" "), 300) });
|
|
443
|
-
if (placement === undefined &&
|
|
496
|
+
if (placement === undefined && planners !== undefined)
|
|
444
497
|
say("the placement did not hold - every implementation req with the first pulse, every test req with the first conformance pulse");
|
|
445
498
|
}
|
|
446
499
|
/** A req's chunk: its group, the built job, or the first job of the new work for "all". */
|
|
@@ -489,7 +542,7 @@ async function cut(input, agents, notes) {
|
|
|
489
542
|
say(`"${group.title}" is past ${maxLines} lines with its definitions and reqs - split into ${runs.length} pulses`);
|
|
490
543
|
runs.forEach((specs, index) => split.push({
|
|
491
544
|
specs,
|
|
492
|
-
title:
|
|
545
|
+
title: `${shortTitle(group.title, TITLE_CHARS - 9, TITLE_WORDS - 3)} (${index + 1} of ${runs.length})`,
|
|
493
546
|
}));
|
|
494
547
|
});
|
|
495
548
|
const before = placement;
|
|
@@ -517,7 +570,7 @@ async function cut(input, agents, notes) {
|
|
|
517
570
|
// It does not hold together: a functional spec a job, every req with the first.
|
|
518
571
|
say(`the plan did not hold together (${checkDraft(input, draft)[0]?.slice(0, 120)}) - a functional spec a pulse`);
|
|
519
572
|
placement = undefined;
|
|
520
|
-
groups.splice(0, groups.length, ...numbered.map((spec, index) => ({ specs: [spec.number], title: titleOf(specs[index]) })));
|
|
573
|
+
groups.splice(0, groups.length, ...(await titled(numbered.map((spec, index) => ({ specs: [spec.number], title: titleOf(specs[index]) })))));
|
|
521
574
|
chunks = chunksFor(groups);
|
|
522
575
|
place();
|
|
523
576
|
const simple = draftOf(chunks, net);
|
|
@@ -586,6 +639,22 @@ export function placePrompt(input) {
|
|
|
586
639
|
"Call submit_placement once, with every one of them placed once.",
|
|
587
640
|
].join("\n");
|
|
588
641
|
}
|
|
642
|
+
export function titlePrompt(groups, track) {
|
|
643
|
+
return [
|
|
644
|
+
track === "conformance"
|
|
645
|
+
? "You title the pulses that test a codebase. Each group below is a pulse: it writes and runs the conformance tests for its functional specs - black-box tests of the code as built."
|
|
646
|
+
: "You title the pulses that build a codebase. Each group below is a pulse: a coding agent builds its functional specs into the code.",
|
|
647
|
+
"",
|
|
648
|
+
track === "conformance"
|
|
649
|
+
? "Give each group a title that says what its pulse tests - the behaviour, not the spec's wording."
|
|
650
|
+
: "Give each group a title that says what its pulse builds - the feature, not the spec's wording.",
|
|
651
|
+
`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.`,
|
|
652
|
+
"",
|
|
653
|
+
...groups.map((group) => [`<group number="${group.number}">`, ...group.specs.map((spec) => `<functional spec>\n${spec.trim()}\n</functional spec>`), "</group>"].join("\n")),
|
|
654
|
+
"",
|
|
655
|
+
"Call submit_titles once, with a title for every group, by its number.",
|
|
656
|
+
].join("\n");
|
|
657
|
+
}
|
|
589
658
|
const TargetSchema = Type.Union([Type.Number(), Type.Literal("all"), Type.Literal("built")]);
|
|
590
659
|
const NOTES = "Each of the person's wishes for how the queue is planned that you did not follow, and why - a limit it would break, say. Leave it out when you followed them all, or there are none.";
|
|
591
660
|
/** Ask one step of an agent, and ask again with what was wrong, until it holds or the retries are spent. */
|
|
@@ -608,7 +677,7 @@ async function askStep(options) {
|
|
|
608
677
|
session.dispose();
|
|
609
678
|
}
|
|
610
679
|
}
|
|
611
|
-
/** The chunking's
|
|
680
|
+
/** The chunking's agents, on a model - fresh for every plan. */
|
|
612
681
|
export function llmChunkAgents(options) {
|
|
613
682
|
const label = options.label ?? "queue planner";
|
|
614
683
|
return {
|
|
@@ -654,5 +723,27 @@ export function llmChunkAgents(options) {
|
|
|
654
723
|
},
|
|
655
724
|
}),
|
|
656
725
|
}),
|
|
726
|
+
title: (groups, check, track) => askStep({
|
|
727
|
+
cwd: options.cwd,
|
|
728
|
+
model: options.model,
|
|
729
|
+
label: `${label} titles`,
|
|
730
|
+
task: titlePrompt(groups, track),
|
|
731
|
+
check,
|
|
732
|
+
tool: (take) => defineTool({
|
|
733
|
+
name: "submit_titles",
|
|
734
|
+
label: "Submit the titles",
|
|
735
|
+
description: `A title for every group, by its number: at most ${TITLE_WORDS} words and ${TITLE_CHARS} characters each.`,
|
|
736
|
+
parameters: Type.Object({
|
|
737
|
+
titles: Type.Array(Type.Object({
|
|
738
|
+
group: Type.Number(),
|
|
739
|
+
title: Type.String({ description: `What the group's pulse ${track === "conformance" ? "tests" : "builds"}: at most ${TITLE_WORDS} words and ${TITLE_CHARS} characters.` }),
|
|
740
|
+
})),
|
|
741
|
+
}),
|
|
742
|
+
async execute(_id, params) {
|
|
743
|
+
take(params);
|
|
744
|
+
return { content: [{ type: "text", text: "Recorded." }], details: {}, terminate: true };
|
|
745
|
+
},
|
|
746
|
+
}),
|
|
747
|
+
}),
|
|
657
748
|
};
|
|
658
749
|
}
|
|
@@ -88,11 +88,11 @@ export type LedgerEvent = {
|
|
|
88
88
|
why: string;
|
|
89
89
|
} | {
|
|
90
90
|
kind: "planner.failed";
|
|
91
|
-
step: "group" | "place";
|
|
91
|
+
step: "group" | "place" | "title";
|
|
92
92
|
error: string;
|
|
93
93
|
} | {
|
|
94
94
|
kind: "planner.fellBack";
|
|
95
|
-
step: "group" | "place";
|
|
95
|
+
step: "group" | "place" | "title";
|
|
96
96
|
why: string;
|
|
97
97
|
} | {
|
|
98
98
|
kind: "model.call";
|
package/docs/regenerating.md
CHANGED
|
@@ -174,7 +174,9 @@ A job that renders the whole of a new spec is too big to build well, and a rende
|
|
|
174
174
|
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.
|
|
175
175
|
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).
|
|
176
176
|
|
|
177
|
-
|
|
177
|
+
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.
|
|
178
|
+
|
|
179
|
+
Changes to what is built - an item edited or taken out, a functional spec built worded differently - go first, in a job of their own. Every answer is checked: every new functional spec once, in order, next to each other, at most `max-specs-per-pulse`; 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, the spec holding together after each, the last ending on the spec as it is.
|
|
178
180
|
|
|
179
181
|
Each planned job knows its cases - which functional specs it adds, changes (their text, or a concept they use) and takes out, or none: reqs and definitions only - and the loop says them, with each job's title, where the person looks (`drift`, `pulses`, and what `regenerate` returns).
|
|
180
182
|
|
|
@@ -312,7 +314,7 @@ They follow the code: a functional spec gets conformance tests once its code is
|
|
|
312
314
|
|
|
313
315
|
### Conformance tests in the work loop
|
|
314
316
|
|
|
315
|
-
In the work loop, conformance is a track beside the code's. What the conformance tests are written for is recorded apart - `tests/.turri.json`: the functional specs they test, as built, with their acceptance tests, the definitions and implementation reqs they were written against, and the test reqs they follow - and the work is from that record to what they are to be: of the functional specs the code is built from, those chosen - every one, or those `conform` names - as the spec has them, with its test reqs. A functional spec whose code is behind the spec - built from other text, or not in the code record at all, as while `regenerate` regenerates it again by name - keeps its tests until the code lands; they are taken out only once the spec and the code have both dropped it. The harness cuts the work into jobs
|
|
317
|
+
In the work loop, conformance is a track beside the code's. What the conformance tests are written for is recorded apart - `tests/.turri.json`: the functional specs they test, as built, with their acceptance tests, the definitions and implementation reqs they were written against, and the test reqs they follow - and the work is from that record to what they are to be: of the functional specs the code is built from, those chosen - every one, or those `conform` names - as the spec has them, with its test reqs. A functional spec whose code is behind the spec - built from other text, or not in the code record at all, as while `regenerate` regenerates it again by name - keeps its tests until the code lands; they are taken out only once the spec and the code have both dropped it. The harness cuts the work into jobs - an agent only titles them: every functional spec tested - added or changed, asked for by `conform`, chosen, or left by the code jobs - is in one conformance job, a module's own together, as far as a guardrail lets: a new job begins only where the next functional spec would take the job's functional specs and the definitions they bring past 120 lines. The reqs they are written against go with the first job, and do not count. When the person asks for it - one at a time, separately - `conform`'s `one_by_one` (`turri conform --one-by-one`) makes it a job a functional spec instead, until conformance tests are asked for again without it. And when the person asks for them in pulses of their own grouping - 1-3 and 4-6 - `conform`'s `groups` gives each group a job of its own: a job never takes functional specs of two groups, and within a group the guardrail still cuts. Only work that touches no functional spec at all, the test reqs changed say, is a job without one. A module's conformance jobs run on its lane once its code is built - every code job of it landed - so the module is tested once, as built: one explorer, one run of the whole suite, rather than a conformance cycle after each code job; and they pass before anything is built on it. Until then they are not planned, but what is planned lists them last - `conformance tests - planned once the code is built` - wherever they are on for the whole module, so asking for them is seen to have worked; `conform` asked again for a module they are on for already says so, and changes nothing.
|
|
316
318
|
|
|
317
319
|
A conformance job is a spec diff's conformance run: the tests of its change written, changed or taken out, a failure judged - the test or the code wrong - and fixed, then the whole suite with the unit tests, the tests the module inherits with them. The run that passed already was of the whole suite - the module's tests folder holds the chain's, copied up - so the suite is run once more only when the code or the tests changed after it passed with the unit tests; otherwise it is not run again. Passing, the tests record is written and committed with the tests; otherwise all of it - code fixes included - is rolled back, and the module halts until the spec or the code changes, or `conform` asks again: its conformance tests, its code - nothing more is built on code whose conformance tests did not pass - and every module built on it, which waits.
|
|
318
320
|
|