@turri-dev/cli 0.1.27 → 0.2.0
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 +6 -6
- package/dist/advisor-chat.d.ts +63 -0
- package/dist/advisor-chat.js +290 -0
- package/dist/background-skills.d.ts +53 -0
- package/dist/background-skills.js +121 -0
- package/dist/bedrock-region.d.ts +66 -0
- package/dist/bedrock-region.js +144 -0
- package/dist/btw.d.ts +1 -1
- package/dist/btw.js +1 -1
- package/dist/change-plain-cli.js +20 -13
- package/dist/chat-idle.d.ts +7 -0
- package/dist/chat-idle.js +20 -0
- package/dist/chats.d.ts +10 -0
- package/dist/chats.js +23 -0
- package/dist/cli-options.d.ts +1 -2
- package/dist/cli-options.js +2 -7
- package/dist/cli.js +11 -3
- package/dist/config-command.d.ts +14 -0
- package/dist/config-command.js +35 -15
- package/dist/config-edit.d.ts +3 -3
- package/dist/config-edit.js +16 -12
- package/dist/config.d.ts +54 -34
- package/dist/config.js +40 -44
- package/dist/conform-cli.js +12 -10
- package/dist/conformance-switch.d.ts +34 -0
- package/dist/conformance-switch.js +108 -0
- package/dist/cultivate-cli.js +15 -5
- package/dist/drift-cli.js +2 -0
- package/dist/extension.d.ts +15 -0
- package/dist/extension.js +260 -83
- package/dist/host-session.d.ts +26 -0
- package/dist/host-session.js +30 -2
- package/dist/host.d.ts +5 -2
- package/dist/host.js +7 -1
- package/dist/init-config.d.ts +1 -1
- package/dist/init-config.js +20 -11
- package/dist/loop-cli.d.ts +3 -3
- package/dist/loop-cli.js +13 -16
- package/dist/loop-tools.js +18 -10
- package/dist/model-check.d.ts +8 -3
- package/dist/model-check.js +13 -8
- package/dist/new-project.js +12 -8
- package/dist/{validate-cli.js → parse-cli.js} +11 -5
- package/dist/parse-tool.d.ts +9 -0
- package/dist/parse-tool.js +33 -0
- package/dist/pi-launch.js +1 -1
- package/dist/plain/check.d.ts +53 -6
- package/dist/plain/check.js +217 -24
- package/dist/plain/concepts.d.ts +8 -0
- package/dist/plain/concepts.js +66 -23
- package/dist/plain/cycles.d.ts +21 -0
- package/dist/plain/cycles.js +190 -0
- package/dist/plain/errors.d.ts +4 -0
- package/dist/plain/errors.js +7 -0
- package/dist/plain/files.d.ts +18 -1
- package/dist/plain/files.js +39 -2
- package/dist/plain/graph.d.ts +33 -4
- package/dist/plain/graph.js +60 -12
- package/dist/plain/highlight.d.ts +3 -2
- package/dist/plain/highlight.js +4 -3
- package/dist/plain/index.d.ts +8 -5
- package/dist/plain/index.js +8 -5
- package/dist/plain/liquid.js +1 -6
- package/dist/plain/markdown.d.ts +32 -3
- package/dist/plain/markdown.js +146 -24
- package/dist/plain/module-concepts.js +2 -2
- package/dist/plain/parser.d.ts +83 -28
- package/dist/plain/parser.js +381 -260
- package/dist/plain/plain.tmLanguage.json +29 -3
- package/dist/plain/project.d.ts +48 -0
- package/dist/plain/project.js +155 -0
- package/dist/plain/spec.d.ts +90 -28
- package/dist/plain/spec.js +107 -133
- package/dist/plain/test-plain-file.d.ts +59 -0
- package/dist/plain/test-plain-file.js +232 -0
- package/dist/regenerate/activity.d.ts +20 -9
- package/dist/regenerate/activity.js +11 -4
- package/dist/regenerate/advisor-records.d.ts +93 -0
- package/dist/regenerate/advisor-records.js +110 -0
- package/dist/regenerate/advisor-runs.d.ts +87 -0
- package/dist/regenerate/advisor-runs.js +327 -0
- package/dist/regenerate/base-folder.d.ts +68 -0
- package/dist/regenerate/base-folder.js +168 -0
- package/dist/regenerate/cases.d.ts +9 -6
- package/dist/regenerate/cases.js +70 -14
- package/dist/regenerate/change-view.d.ts +8 -3
- package/dist/regenerate/change-view.js +71 -22
- package/dist/regenerate/chunk-plan.d.ts +36 -15
- package/dist/regenerate/chunk-plan.js +165 -59
- package/dist/regenerate/conform.d.ts +3 -0
- package/dist/regenerate/conform.js +6 -5
- package/dist/regenerate/conformance-agents.d.ts +18 -39
- package/dist/regenerate/conformance-agents.js +60 -88
- package/dist/regenerate/conformance.d.ts +23 -13
- package/dist/regenerate/conformance.js +119 -82
- package/dist/regenerate/diff-view.d.ts +1 -1
- package/dist/regenerate/diff-view.js +20 -4
- package/dist/regenerate/drift.d.ts +47 -15
- package/dist/regenerate/drift.js +101 -30
- package/dist/regenerate/jobs.d.ts +2 -2
- package/dist/regenerate/jobs.js +1 -1
- package/dist/regenerate/ledger.d.ts +19 -0
- package/dist/regenerate/loop.d.ts +62 -43
- package/dist/regenerate/loop.js +472 -341
- package/dist/regenerate/paths.d.ts +4 -0
- package/dist/regenerate/paths.js +7 -1
- package/dist/regenerate/plain-palette.js +2 -0
- package/dist/regenerate/prompt.d.ts +57 -19
- package/dist/regenerate/prompt.js +157 -122
- package/dist/regenerate/records.d.ts +43 -12
- package/dist/regenerate/records.js +102 -88
- package/dist/regenerate/regenerate.js +29 -11
- package/dist/regenerate/regression.d.ts +4 -4
- package/dist/regenerate/regression.js +14 -10
- package/dist/regenerate/renames.d.ts +42 -0
- package/dist/regenerate/renames.js +185 -0
- package/dist/regenerate/requires.d.ts +1 -1
- package/dist/regenerate/requires.js +10 -3
- package/dist/regenerate/resources.d.ts +5 -3
- package/dist/regenerate/resources.js +4 -4
- package/dist/regenerate/scripts-being-written.d.ts +19 -0
- package/dist/regenerate/scripts-being-written.js +30 -0
- package/dist/regenerate/spec-map-view.d.ts +2 -2
- package/dist/regenerate/spec-map-view.js +46 -15
- package/dist/regenerate/spec-map.d.ts +27 -5
- package/dist/regenerate/spec-map.js +92 -60
- package/dist/regenerate/spec-steps.d.ts +48 -5
- package/dist/regenerate/spec-steps.js +213 -28
- package/dist/regenerate/state.d.ts +4 -3
- package/dist/regenerate/stop-report.d.ts +1 -1
- package/dist/regenerate/stop-report.js +10 -10
- package/dist/regenerate/test-advisor.d.ts +101 -0
- package/dist/regenerate/test-advisor.js +379 -0
- package/dist/regenerate/test-kinds.d.ts +71 -28
- package/dist/regenerate/test-kinds.js +110 -63
- package/dist/regenerate/test-scripts.d.ts +2 -0
- package/dist/regenerate/test-scripts.js +4 -0
- package/dist/regenerate/testing-steps.d.ts +41 -0
- package/dist/regenerate/testing-steps.js +110 -0
- package/dist/regenerate/testing.d.ts +1 -1
- package/dist/regenerate/testing.js +5 -4
- package/dist/regenerate/unit-review.d.ts +1 -1
- package/dist/regenerate/unit-review.js +3 -3
- package/dist/regenerate-cli.js +11 -12
- package/dist/release-agent.js +4 -0
- package/dist/release-cli.js +2 -0
- package/dist/release-tools.js +2 -0
- package/dist/run-report.d.ts +2 -2
- package/dist/run-report.js +4 -3
- package/dist/self-invocation.d.ts +1 -1
- package/dist/self-invocation.js +1 -1
- package/dist/sentinel.js +1 -1
- package/dist/setup.d.ts +10 -0
- package/dist/setup.js +52 -4
- package/dist/skill.d.ts +10 -0
- package/dist/skill.js +6 -0
- package/dist/skills.js +3 -1
- package/dist/spec-tools.js +21 -31
- package/dist/test-plain-files.d.ts +41 -0
- package/dist/test-plain-files.js +177 -0
- package/docs/README.md +3 -1
- package/docs/authoring-plain.md +46 -13
- package/docs/chats.md +3 -1
- package/docs/config.md +20 -15
- package/docs/deploy.md +1 -1
- package/docs/development.md +2 -1
- package/docs/headless.md +15 -18
- package/docs/hosting.md +28 -12
- package/docs/installation.md +1 -0
- package/docs/parser.md +88 -27
- package/docs/regenerating.md +87 -52
- package/docs/sentinel.md +1 -1
- package/docs/skills-and-gates.md +3 -1
- package/docs/test-advisor.md +110 -0
- package/docs/theming.md +1 -1
- package/docs/using.md +17 -15
- package/genome/rules/bullet-continuation.md +8 -8
- package/genome/rules/concise-specs-examples.md +111 -80
- package/genome/rules/concise-specs-integration-examples.md +56 -41
- package/genome/rules/concise-specs.md +17 -16
- package/genome/rules/definitions.md +5 -5
- package/genome/rules/examples/integration-embedded-testing/run_conformance_tests_java.sh +1 -1
- package/genome/rules/func-specs.md +69 -74
- package/genome/rules/impl-reqs.md +4 -4
- package/genome/rules/integration-embedded-testing.md +20 -20
- package/genome/rules/integration-embedded.md +31 -31
- package/genome/rules/integration-standalone.md +9 -9
- package/genome/rules/integrations.md +18 -18
- package/genome/rules/linked-resources.md +19 -16
- package/genome/rules/module-structure.md +21 -14
- package/genome/rules/project-model.md +18 -17
- package/genome/rules/required-concepts.md +1 -1
- package/genome/rules/requires-modules.md +6 -5
- package/genome/rules/semantic-ids.md +49 -0
- package/genome/rules/stdlib.md +4 -5
- package/genome/rules/test-plain-files.md +130 -0
- package/genome/rules/test-reqs.md +8 -8
- package/genome/rules/turri-regeneration.md +23 -8
- package/genome/skills/analyze-2-func-specs/SKILL.md +36 -21
- package/genome/skills/analyze-func-specs/SKILL.md +46 -32
- package/genome/skills/break-down-func-spec/SKILL.md +91 -67
- package/genome/skills/consolidate-concepts/SKILL.md +10 -10
- package/genome/skills/convert-old-syntax/SKILL.md +68 -0
- package/genome/skills/convert-old-syntax/references/rubric.md +11 -0
- package/genome/skills/debug-specs/SKILL.md +62 -56
- package/genome/skills/implement-conformance-testing-script/SKILL.md +19 -15
- package/genome/skills/implement-conformance-testing-script/assets/run_conformance_tests_java.sh +2 -2
- package/genome/skills/implement-conformance-testing-script/assets/run_conformance_tests_python.sh +2 -2
- package/genome/skills/implement-unit-testing-script/SKILL.md +14 -10
- package/genome/skills/refactor-module/SKILL.md +35 -35
- package/genome/skills/resolve-section-ownership/SKILL.md +9 -9
- package/genome/skills/resolve-spec-conflict/SKILL.md +35 -29
- package/package.json +7 -6
- package/dist/regenerate/choose-conformance.d.ts +0 -32
- package/dist/regenerate/choose-conformance.js +0 -117
- /package/dist/{validate-cli.d.ts → parse-cli.d.ts} +0 -0
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { defineTool } from "@earendil-works/pi-coding-agent";
|
|
2
2
|
import { Type } from "typebox";
|
|
3
3
|
import { DEFAULT_CONCEPTS, extractConceptsFromDefinition, extractConceptsFromSpecText } from "../plain/concepts.js";
|
|
4
|
-
import { ACCEPTANCE_TESTS, DEFINITIONS, FUNCTIONAL_REQUIREMENTS, NON_FUNCTIONAL_REQUIREMENTS, REQUIRED_FUNCTIONAL_SPECS, TEST_REQUIREMENTS } from "../plain/spec.js";
|
|
4
|
+
import { ACCEPTANCE_TESTS, CONFORMANCE_TESTS, DEFINITIONS, FUNCTIONAL_REQUIREMENTS, NON_FUNCTIONAL_REQUIREMENTS, REQUIRED_FUNCTIONAL_SPECS, TEST_REQUIREMENTS } from "../plain/spec.js";
|
|
5
5
|
import { ask, BEGIN, createExecutor } from "../pi.js";
|
|
6
6
|
import { reportActivity, reportStage } from "./activity.js";
|
|
7
7
|
import { firstLine, record } from "./ledger.js";
|
|
@@ -65,7 +65,8 @@ export function stepsOf(input, draft) {
|
|
|
65
65
|
const changes = job.changes.map((id) => byId.get(id));
|
|
66
66
|
sofar.push(...changes);
|
|
67
67
|
const after = applyChanges(serializeTree(input.base), sofar);
|
|
68
|
-
const
|
|
68
|
+
const testPlain = input.track === "conformance" && input.testPlainPath !== undefined && input.moduleName !== undefined ? { module: input.moduleName, pathOf: input.testPlainPath } : undefined;
|
|
69
|
+
const step = makeStep(job.title, job.reason, changes, before, after, input.specFile, layout, testPlain);
|
|
69
70
|
before = after;
|
|
70
71
|
return { ...job, step };
|
|
71
72
|
});
|
|
@@ -95,12 +96,16 @@ export function shortTitle(text, chars = TITLE_CHARS, words = TITLE_WORDS) {
|
|
|
95
96
|
export const MAX_LINES_PER_JOB = 120;
|
|
96
97
|
/** How often an answer that does not hold goes back to its agent before the plan falls back. */
|
|
97
98
|
const RETRIES = 2;
|
|
98
|
-
/**
|
|
99
|
+
/**
|
|
100
|
+
* An item's text, from its serialized line: its markdown - a functional spec's body, its func-id given beside it - and
|
|
101
|
+
* its scenarios, each under its scenario-id, where the tree has them: the conformance track's.
|
|
102
|
+
*/
|
|
99
103
|
function textOf(change) {
|
|
100
104
|
const line = change.after ?? change.before ?? "";
|
|
101
105
|
try {
|
|
102
106
|
const item = JSON.parse(line);
|
|
103
|
-
|
|
107
|
+
const scenarios = [...(item[CONFORMANCE_TESTS] ?? []), ...(item[ACCEPTANCE_TESTS] ?? [])].map((scenario) => `# ${scenario.id ?? ""}\n${scenario.text ?? ""}`);
|
|
108
|
+
return [item.markdown ?? "", ...scenarios].filter(Boolean).join("\n");
|
|
104
109
|
}
|
|
105
110
|
catch {
|
|
106
111
|
return line;
|
|
@@ -118,27 +123,60 @@ function namedIn(text) {
|
|
|
118
123
|
function definedBy(text) {
|
|
119
124
|
return extractConceptsFromDefinition(text)[0];
|
|
120
125
|
}
|
|
121
|
-
/**
|
|
122
|
-
|
|
126
|
+
/**
|
|
127
|
+
* What is wrong with a grouping: every new functional spec once, in order, next to each other, none too big - and
|
|
128
|
+
* every change to what is built placed once, with a group or first. A functional spec changed counts in its group: it
|
|
129
|
+
* is built as the new ones are. One taken out builds nothing, and does not.
|
|
130
|
+
*/
|
|
131
|
+
export function groupProblems(specs, answer, maxSpecs = MAX_SPECS_PER_JOB, built = []) {
|
|
132
|
+
const problems = newSpecProblems(specs, answer, maxSpecs, built);
|
|
133
|
+
const placed = new Map();
|
|
134
|
+
for (const entry of answer.built ?? []) {
|
|
135
|
+
if (!built.some((change) => change.number === entry.change))
|
|
136
|
+
problems.push(`There is no change ${entry.change} to what is built.`);
|
|
137
|
+
placed.set(entry.change, (placed.get(entry.change) ?? 0) + 1);
|
|
138
|
+
const valid = entry.to === "first" || (Number.isInteger(entry.to) && entry.to >= 1 && entry.to <= answer.groups.length);
|
|
139
|
+
if (!valid)
|
|
140
|
+
problems.push(`Change ${entry.change} to what is built goes to ${entry.to}, which is no group: a group's number, or "first".`);
|
|
141
|
+
}
|
|
142
|
+
for (const change of built) {
|
|
143
|
+
const times = placed.get(change.number) ?? 0;
|
|
144
|
+
if (times === 0)
|
|
145
|
+
problems.push(`change ${change.number} to what is built is not placed: every one goes with a group, or "first".`);
|
|
146
|
+
if (times > 1)
|
|
147
|
+
problems.push(`Change ${change.number} to what is built is placed ${times} times: once.`);
|
|
148
|
+
}
|
|
149
|
+
return problems;
|
|
150
|
+
}
|
|
151
|
+
/** How many functional specs a group builds: its new ones, and those changed that go with it. */
|
|
152
|
+
function builtIn(group, answer, built) {
|
|
153
|
+
const changed = new Set(built.filter((change) => change.kind !== "taken out" && change.what === "functional spec").map((change) => change.number));
|
|
154
|
+
return (answer.built ?? []).filter((entry) => entry.to === group && changed.has(entry.change)).length;
|
|
155
|
+
}
|
|
156
|
+
function newSpecProblems(specs, answer, maxSpecs, built) {
|
|
123
157
|
const problems = [];
|
|
124
158
|
const numbers = specs.map((spec) => spec.number);
|
|
125
159
|
const given = answer.groups.flatMap((group) => group.specs);
|
|
126
160
|
if (given.join(",") !== numbers.join(","))
|
|
127
161
|
problems.push(`The groups must hold every new functional spec once, in the order the spec has them: ${numbers.join(", ")}. They hold ${given.join(", ") || "none"}.`);
|
|
128
|
-
// Next to each other: in the same module, one
|
|
162
|
+
// Next to each other: in the same module, one in the place after the other there.
|
|
129
163
|
const byNumber = new Map(specs.map((spec) => [spec.number, spec]));
|
|
130
164
|
const nextTo = (a, b) => {
|
|
131
165
|
const first = byNumber.get(a);
|
|
132
166
|
const second = byNumber.get(b);
|
|
133
167
|
if (first === undefined || second === undefined)
|
|
134
168
|
return b === a + 1;
|
|
135
|
-
return first.module === second.module &&
|
|
169
|
+
return first.module === second.module && (second.place ?? second.number) === (first.place ?? first.number) + 1;
|
|
136
170
|
};
|
|
137
|
-
|
|
171
|
+
answer.groups.forEach((group, index) => {
|
|
138
172
|
if (group.specs.length === 0)
|
|
139
173
|
problems.push(`"${group.title}" has no functional spec.`);
|
|
140
|
-
|
|
141
|
-
|
|
174
|
+
const changed = builtIn(index + 1, answer, built);
|
|
175
|
+
if (group.specs.length + changed > maxSpecs) {
|
|
176
|
+
problems.push(changed === 0
|
|
177
|
+
? `"${group.title}" has ${group.specs.length} functional specs - at most ${maxSpecs}.`
|
|
178
|
+
: `"${group.title}" has ${group.specs.length + changed} functional specs to build - ${group.specs.length} new, ${changed} changed - at most ${maxSpecs}.`);
|
|
179
|
+
}
|
|
142
180
|
for (let i = 1; i < group.specs.length; i += 1) {
|
|
143
181
|
if (!nextTo(group.specs[i - 1], group.specs[i]))
|
|
144
182
|
problems.push(`"${group.title}" groups ${group.specs.join(", ")}: only functional specs next to each other in the same module go together.`);
|
|
@@ -147,7 +185,7 @@ export function groupProblems(specs, answer, maxSpecs = MAX_SPECS_PER_JOB) {
|
|
|
147
185
|
problems.push("Every group needs a title.");
|
|
148
186
|
else if (!fitsTitle(group.title))
|
|
149
187
|
problems.push(`"${group.title}" is too long for a title: at most ${TITLE_WORDS} words and ${TITLE_CHARS} characters.`);
|
|
150
|
-
}
|
|
188
|
+
});
|
|
151
189
|
return problems;
|
|
152
190
|
}
|
|
153
191
|
/** What is wrong with a titling: every group titled once, by its number, none empty, none too long. */
|
|
@@ -236,7 +274,7 @@ function placeDefinitions(chunks, definitions) {
|
|
|
236
274
|
const placed = new Set();
|
|
237
275
|
for (const chunk of chunks) {
|
|
238
276
|
chunk.definitions = [];
|
|
239
|
-
const queue = [...chunk.specs, ...chunk.reqs, ...(chunk.built ?? [])].flatMap((change) => namedIn(textOf(change)));
|
|
277
|
+
const queue = [...chunk.specs, ...chunk.reqs, ...(chunk.built ?? []), ...(chunk.joined ?? [])].flatMap((change) => namedIn(textOf(change)));
|
|
240
278
|
for (let concept = queue.shift(); concept !== undefined; concept = queue.shift()) {
|
|
241
279
|
const definition = byConcept.get(concept);
|
|
242
280
|
if (definition === undefined || placed.has(definition))
|
|
@@ -258,7 +296,7 @@ function movableSize(chunk) {
|
|
|
258
296
|
return [...chunk.specs, ...chunk.definitions].reduce((total, change) => total + linesOf(change), 0);
|
|
259
297
|
}
|
|
260
298
|
function sizeOf(chunk) {
|
|
261
|
-
return [...chunk.specs, ...chunk.definitions, ...chunk.reqs, ...(chunk.built ?? [])].reduce((total, change) => total + linesOf(change), 0);
|
|
299
|
+
return [...chunk.specs, ...chunk.definitions, ...chunk.reqs, ...(chunk.built ?? []), ...(chunk.joined ?? [])].reduce((total, change) => total + linesOf(change), 0);
|
|
262
300
|
}
|
|
263
301
|
/**
|
|
264
302
|
* The draft a chunking is: a job per chunk, its changes in the order the net has them. A job runs as the queued job
|
|
@@ -268,7 +306,7 @@ function sizeOf(chunk) {
|
|
|
268
306
|
function draftOf(chunks, net) {
|
|
269
307
|
return chunks
|
|
270
308
|
.map((chunk) => {
|
|
271
|
-
const ids = new Set([...chunk.specs, ...chunk.definitions, ...chunk.reqs, ...(chunk.built ?? [])].map((change) => change.id));
|
|
309
|
+
const ids = new Set([...chunk.specs, ...chunk.definitions, ...chunk.reqs, ...(chunk.built ?? []), ...(chunk.joined ?? [])].map((change) => change.id));
|
|
272
310
|
return { chunk, changes: net.filter((change) => ids.has(change.id)).map((change) => change.id) };
|
|
273
311
|
})
|
|
274
312
|
.filter((entry) => entry.changes.length > 0)
|
|
@@ -311,7 +349,7 @@ export async function planChunks(input, agents, notes) {
|
|
|
311
349
|
*/
|
|
312
350
|
function anchored(draft, net) {
|
|
313
351
|
const sectionOf = new Map(net.map((change) => [change.id, change.section]));
|
|
314
|
-
const testsASpec = (job) => job.changes.some((id) => sectionOf.get(id) === FUNCTIONAL_REQUIREMENTS);
|
|
352
|
+
const testsASpec = (job) => job.changes.some((id) => sectionOf.get(id) === FUNCTIONAL_REQUIREMENTS || sectionOf.get(id) === REQUIRED_FUNCTIONAL_SPECS);
|
|
315
353
|
if (!draft.some(testsASpec))
|
|
316
354
|
return draft;
|
|
317
355
|
const jobs = [];
|
|
@@ -329,22 +367,17 @@ function anchored(draft, net) {
|
|
|
329
367
|
return jobs;
|
|
330
368
|
}
|
|
331
369
|
/**
|
|
332
|
-
* How the conformance tests are cut, with no agent: every functional spec tested in one job - those of
|
|
333
|
-
*
|
|
334
|
-
*
|
|
335
|
-
*
|
|
370
|
+
* How the conformance tests are cut, with no agent: every functional spec tested in one job - those of the modules below
|
|
371
|
+
* and the codebase's own alike, the tip's suite holding the chain's - a new job begun where the next functional spec
|
|
372
|
+
* would take the job's functional specs and the definitions they bring past `maxLines`; or, `separately`, a job a
|
|
373
|
+
* functional spec, as the person asked.
|
|
336
374
|
* `size`: the movable lines of the last group of a grouping, its definitions placed.
|
|
337
375
|
*/
|
|
338
|
-
function conformanceGroups(specs, separately, maxLines, size, titleOfSpec
|
|
339
|
-
/** The group a functional spec was asked for in - none, -1: a job never takes two. */
|
|
340
|
-
askedIn = () => -1) {
|
|
376
|
+
function conformanceGroups(specs, separately, maxLines, size, titleOfSpec) {
|
|
341
377
|
const groups = [];
|
|
342
378
|
for (const spec of specs) {
|
|
343
379
|
const last = groups.at(-1);
|
|
344
|
-
|
|
345
|
-
const sameModule = first !== undefined && first.module === spec.module;
|
|
346
|
-
const sameAsk = first !== undefined && askedIn(first) === askedIn(spec);
|
|
347
|
-
if (!separately && last !== undefined && sameModule && sameAsk) {
|
|
380
|
+
if (!separately && last !== undefined) {
|
|
348
381
|
const trial = [...groups.slice(0, -1), { specs: [...last.specs, spec.number] }].map((group) => ({ ...group, title: "" }));
|
|
349
382
|
if (size(trial) <= maxLines) {
|
|
350
383
|
last.specs.push(spec.number);
|
|
@@ -355,15 +388,46 @@ askedIn = () => -1) {
|
|
|
355
388
|
}
|
|
356
389
|
return groups.map((group) => ({ specs: group.specs, title: group.specs.map(titleOfSpec).join("; ") }));
|
|
357
390
|
}
|
|
358
|
-
/**
|
|
359
|
-
|
|
391
|
+
/** What an item of a section is, in the words the grouping agent is told it in. */
|
|
392
|
+
const WHAT = {
|
|
393
|
+
[FUNCTIONAL_REQUIREMENTS]: "functional spec",
|
|
394
|
+
[REQUIRED_FUNCTIONAL_SPECS]: "functional spec",
|
|
395
|
+
[DEFINITIONS]: "definition",
|
|
396
|
+
[NON_FUNCTIONAL_REQUIREMENTS]: "implementation req",
|
|
397
|
+
[TEST_REQUIREMENTS]: "test req",
|
|
398
|
+
};
|
|
399
|
+
/** A change to what is built as the grouping agent is shown it: what it is, how it changes, and its text as it is now - or was, taken out. */
|
|
400
|
+
function builtChangeOf(change, number, moduleName) {
|
|
401
|
+
const id = funcIdOf(change);
|
|
402
|
+
let module;
|
|
403
|
+
if (change.section === REQUIRED_FUNCTIONAL_SPECS) {
|
|
404
|
+
try {
|
|
405
|
+
module = JSON.parse(change.after ?? change.before ?? "{}").module;
|
|
406
|
+
}
|
|
407
|
+
catch {
|
|
408
|
+
// Not an item with a module.
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
else if (change.section === FUNCTIONAL_REQUIREMENTS)
|
|
412
|
+
module = moduleName;
|
|
413
|
+
return {
|
|
414
|
+
number,
|
|
415
|
+
kind: change.kind === "change" ? "changed" : change.kind === "remove" ? "taken out" : "added",
|
|
416
|
+
what: WHAT[change.section] ?? change.section,
|
|
417
|
+
text: textOf(change),
|
|
418
|
+
...(id !== undefined ? { id } : {}),
|
|
419
|
+
...(module !== undefined ? { module } : {}),
|
|
420
|
+
};
|
|
421
|
+
}
|
|
422
|
+
/** A functional spec's func-id, as the tree it is in has it - or nothing when it says none. */
|
|
423
|
+
function funcIdOf(change) {
|
|
360
424
|
for (const side of [change.after, change.before]) {
|
|
361
425
|
if (side === undefined)
|
|
362
426
|
continue;
|
|
363
427
|
try {
|
|
364
|
-
const
|
|
365
|
-
if (typeof
|
|
366
|
-
return
|
|
428
|
+
const id = JSON.parse(side).id;
|
|
429
|
+
if (typeof id === "string" && id !== "")
|
|
430
|
+
return id;
|
|
367
431
|
}
|
|
368
432
|
catch {
|
|
369
433
|
// Not an item with an id.
|
|
@@ -384,10 +448,14 @@ async function cut(input, agents, notes) {
|
|
|
384
448
|
return [];
|
|
385
449
|
// What is grouped into jobs: the functional specs regenerated again - the modules' below first, as the codebase is
|
|
386
450
|
// built on them - and the codebase's own, regenerated again or new at the end, in the spec's order.
|
|
387
|
-
|
|
388
|
-
//
|
|
451
|
+
// The conformance tests are written on the code as built: every functional spec of the chain whose tests are added,
|
|
452
|
+
// changed or taken out is tested alike, wherever it is - so one edit, however mixed, is one pulse.
|
|
453
|
+
const testedIn = (section) => net.filter((change) => change.section === section).sort((a, b) => a.position - b.position);
|
|
454
|
+
const below = input.track === "conformance"
|
|
455
|
+
? testedIn(REQUIRED_FUNCTIONAL_SPECS)
|
|
456
|
+
: net.filter((change) => regeneratesAgain(change) && change.section === REQUIRED_FUNCTIONAL_SPECS).sort((a, b) => a.position - b.position);
|
|
389
457
|
const own = input.track === "conformance"
|
|
390
|
-
?
|
|
458
|
+
? testedIn(FUNCTIONAL_REQUIREMENTS)
|
|
391
459
|
: [...net.filter((change) => regeneratesAgain(change) && change.section === FUNCTIONAL_REQUIREMENTS), ...newSpecsOf(net, input.current)].sort((a, b) => a.position - b.position);
|
|
392
460
|
const specs = [...below, ...own];
|
|
393
461
|
const inSpecs = new Set(specs);
|
|
@@ -400,7 +468,10 @@ async function cut(input, agents, notes) {
|
|
|
400
468
|
const otherAdds = net.filter((change) => change.kind === "add" && !inSpecs.has(change) && !placedByKind.has(change) && change.section !== FUNCTIONAL_REQUIREMENTS);
|
|
401
469
|
// Anything else changes what is built on - an edit, a removal, a functional spec built changed or put in among them.
|
|
402
470
|
const builtChanges = net.filter((change) => !inSpecs.has(change) && !placedByKind.has(change) && !otherAdds.includes(change));
|
|
403
|
-
|
|
471
|
+
// Shown to the grouping agent, which puts each with the new work it belongs with or first; unplaced, each goes first.
|
|
472
|
+
const builtShown = builtChanges.map((change, index) => builtChangeOf(change, index + 1, input.moduleName));
|
|
473
|
+
/** Where each change to what is built goes: with the group holding a new functional spec - by that spec's number - or first. */
|
|
474
|
+
const builtTo = new Map();
|
|
404
475
|
// Named as the person asks for it: "shorty.plain regenerate".
|
|
405
476
|
const whole = [{ title: "regenerate", reason: "the whole of the work, in one job", changes: net.map((change) => change.id) }];
|
|
406
477
|
if (specs.length === 0)
|
|
@@ -408,19 +479,27 @@ async function cut(input, agents, notes) {
|
|
|
408
479
|
// 1. Which functional specs go together.
|
|
409
480
|
reportStage(`planning the queue: which of the ${specs.length} functional spec${specs.length === 1 ? "" : "s"} to build go together`);
|
|
410
481
|
const numbered = specs.map((change, index) => {
|
|
411
|
-
const required = change.section === REQUIRED_FUNCTIONAL_SPECS ? JSON.parse(change.after) : undefined;
|
|
482
|
+
const required = change.section === REQUIRED_FUNCTIONAL_SPECS ? JSON.parse(change.after ?? change.before) : undefined;
|
|
412
483
|
return {
|
|
413
484
|
number: index + 1,
|
|
414
485
|
text: textOf(change),
|
|
415
|
-
id:
|
|
486
|
+
id: funcIdOf(change) ?? "",
|
|
487
|
+
place: change.position,
|
|
416
488
|
...(required?.module !== undefined ? { module: required.module } : input.moduleName !== undefined ? { module: input.moduleName } : {}),
|
|
417
489
|
...(regeneratesAgain(change) ? { again: true } : {}),
|
|
418
490
|
};
|
|
419
491
|
});
|
|
420
492
|
const bySpec = new Map(specs.map((change, index) => [index + 1, change]));
|
|
493
|
+
const firstBuilt = () => builtChanges.filter((change) => (builtTo.get(change) ?? "first") === "first");
|
|
421
494
|
const chunksFor = (list) => [
|
|
422
|
-
...(
|
|
423
|
-
...list.map((group) => ({
|
|
495
|
+
...(firstBuilt().length > 0 ? [{ title: "Changes to what is built", specs: [], definitions: [], reqs: [], built: firstBuilt() }] : []),
|
|
496
|
+
...list.map((group) => ({
|
|
497
|
+
title: group.title,
|
|
498
|
+
specs: group.specs.map((number) => bySpec.get(number)),
|
|
499
|
+
definitions: [],
|
|
500
|
+
reqs: [],
|
|
501
|
+
joined: builtChanges.filter((change) => group.specs.includes(builtTo.get(change))),
|
|
502
|
+
})),
|
|
424
503
|
];
|
|
425
504
|
const conformance = input.track === "conformance";
|
|
426
505
|
// The conformance tests are grouped and their reqs placed by the harness: their agents are only asked for titles.
|
|
@@ -443,17 +522,22 @@ async function cut(input, agents, notes) {
|
|
|
443
522
|
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
523
|
return list;
|
|
445
524
|
};
|
|
446
|
-
const grouped = conformance ? undefined : ((await agents?.group(numbered, (answer) => groupProblems(numbered, answer, maxSpecs), input.steering, maxSpecs).catch((error) => plannerFailed("group", error))) ?? undefined);
|
|
447
|
-
const holds = grouped !== undefined && groupProblems(numbered, grouped, maxSpecs).length === 0;
|
|
525
|
+
const grouped = conformance ? undefined : ((await agents?.group(numbered, (answer) => groupProblems(numbered, answer, maxSpecs, builtShown), input.steering, maxSpecs, builtShown.length > 0 ? builtShown : undefined).catch((error) => plannerFailed("group", error))) ?? undefined);
|
|
526
|
+
const holds = grouped !== undefined && groupProblems(numbered, grouped, maxSpecs, builtShown).length === 0;
|
|
527
|
+
// The grouping holds: each change to what is built goes where it was placed.
|
|
528
|
+
if (holds) {
|
|
529
|
+
for (const entry of grouped.built ?? []) {
|
|
530
|
+
const change = builtChanges[entry.change - 1];
|
|
531
|
+
if (change !== undefined)
|
|
532
|
+
builtTo.set(change, entry.to === "first" ? "first" : grouped.groups[entry.to - 1].specs[0]);
|
|
533
|
+
}
|
|
534
|
+
}
|
|
448
535
|
const made = conformance
|
|
449
536
|
? conformanceGroups(numbered, input.separately === true, maxLines, (list) => {
|
|
450
537
|
const trial = chunksFor(list);
|
|
451
538
|
placeDefinitions(trial, definitions);
|
|
452
539
|
return movableSize(trial.at(-1));
|
|
453
|
-
}, (number) => titleOf(bySpec.get(number))
|
|
454
|
-
const frid = fridOf(bySpec.get(spec.number));
|
|
455
|
-
return frid === undefined ? -1 : (input.groups ?? []).findIndex((group) => group.includes(frid));
|
|
456
|
-
})
|
|
540
|
+
}, (number) => titleOf(bySpec.get(number)))
|
|
457
541
|
: grouped !== undefined && holds
|
|
458
542
|
? grouped.groups
|
|
459
543
|
: numbered.map((spec, index) => ({ specs: [spec.number], title: titleOf(specs[index]) }));
|
|
@@ -464,9 +548,9 @@ async function cut(input, agents, notes) {
|
|
|
464
548
|
record({ kind: "planner.fellBack", step: "group", why: "no grouping was given" });
|
|
465
549
|
say("the grouping was not given - a functional spec a pulse");
|
|
466
550
|
}
|
|
467
|
-
else if (groupProblems(numbered, grouped, maxSpecs).length > 0) {
|
|
468
|
-
record({ kind: "planner.fellBack", step: "group", why: firstLine(groupProblems(numbered, grouped, maxSpecs).join(" "), 300) });
|
|
469
|
-
say(`the grouping did not hold (${groupProblems(numbered, grouped, maxSpecs).join(" ")}) - a functional spec a pulse`);
|
|
551
|
+
else if (groupProblems(numbered, grouped, maxSpecs, builtShown).length > 0) {
|
|
552
|
+
record({ kind: "planner.fellBack", step: "group", why: firstLine(groupProblems(numbered, grouped, maxSpecs, builtShown).join(" "), 300) });
|
|
553
|
+
say(`the grouping did not hold (${groupProblems(numbered, grouped, maxSpecs, builtShown).join(" ")}) - a functional spec a pulse`);
|
|
470
554
|
}
|
|
471
555
|
}
|
|
472
556
|
// Titled before the chunks are made of them: a group split for its size keeps its title, numbered.
|
|
@@ -483,7 +567,7 @@ async function cut(input, agents, notes) {
|
|
|
483
567
|
const placeInput = {
|
|
484
568
|
...(input.steering !== undefined ? { steering: input.steering } : {}),
|
|
485
569
|
groups: newChunks().map((chunk, index) => ({ number: index + 1, title: chunk.title, specs: chunk.specs.map(textOf), definitions: chunk.definitions.map(textOf) })),
|
|
486
|
-
built:
|
|
570
|
+
built: chunks.some((chunk) => chunk.built !== undefined),
|
|
487
571
|
implementationReqs: implementationReqs.map((change, index) => ({ number: index + 1, text: textOf(change) })),
|
|
488
572
|
testReqs: testReqs.map((change, index) => ({ number: index + 1, text: textOf(change) })),
|
|
489
573
|
};
|
|
@@ -570,6 +654,7 @@ async function cut(input, agents, notes) {
|
|
|
570
654
|
// It does not hold together: a functional spec a job, every req with the first.
|
|
571
655
|
say(`the plan did not hold together (${checkDraft(input, draft)[0]?.slice(0, 120)}) - a functional spec a pulse`);
|
|
572
656
|
placement = undefined;
|
|
657
|
+
builtTo.clear();
|
|
573
658
|
groups.splice(0, groups.length, ...(await titled(numbered.map((spec, index) => ({ specs: [spec.number], title: titleOf(specs[index]) })))));
|
|
574
659
|
chunks = chunksFor(groups);
|
|
575
660
|
place();
|
|
@@ -580,11 +665,11 @@ async function cut(input, agents, notes) {
|
|
|
580
665
|
}
|
|
581
666
|
// ---------------------------------------------------------------------------------------------
|
|
582
667
|
// The agents.
|
|
583
|
-
export function groupPrompt(specs, steering, maxSpecs = MAX_SPECS_PER_JOB) {
|
|
668
|
+
export function groupPrompt(specs, steering, maxSpecs = MAX_SPECS_PER_JOB, built = []) {
|
|
584
669
|
return [
|
|
585
670
|
"You plan how new work on a codebase is built: which of its new functional specs are built together, in one job.",
|
|
586
671
|
"",
|
|
587
|
-
"Each job is
|
|
672
|
+
"Each job is regenerated by a coding agent, reviewed and tested, then the next - fewer, fuller jobs: each job costs a",
|
|
588
673
|
"coding session, a test run and a review. So group by default: what builds one feature goes together - the feature,",
|
|
589
674
|
`its edge cases, and the functional specs that build on it - up to ${maxSpecs}: a component and the parts only it uses`,
|
|
590
675
|
"(a timeout handler, a retry handler and the executor that runs a function through both), an endpoint and the one",
|
|
@@ -592,12 +677,12 @@ export function groupPrompt(specs, steering, maxSpecs = MAX_SPECS_PER_JOB) {
|
|
|
592
677
|
"independent - nothing next to it builds on it or is part of the same thing - or when it is very large on its own.",
|
|
593
678
|
"Never group specs only because they are of the same kind.",
|
|
594
679
|
"",
|
|
595
|
-
"Only functional specs next to each other in the same module go together, and they stay in the order given: the",
|
|
680
|
+
"Only functional specs next to each other in the same module - one in the place after the other there - go together, and they stay in the order given: the",
|
|
596
681
|
`order is what each is built on. Give each group a title that says what it builds - at most ${TITLE_WORDS} words and ${TITLE_CHARS}`,
|
|
597
682
|
"characters: it heads the group's pulse where the person watches the work, and a longer one is cut short there.",
|
|
598
683
|
"",
|
|
599
|
-
"The specification is split into modules - a ***plain matter, not how the code is laid out.
|
|
600
|
-
"
|
|
684
|
+
"The specification is split into modules - a ***plain matter, not how the code is laid out. A functional spec has a",
|
|
685
|
+
"func-id, unique in its module: each below is given with its module, its func-id, its place among the module's functional specs, and a number of its own to answer with.",
|
|
601
686
|
"",
|
|
602
687
|
...steeringBlock(steering),
|
|
603
688
|
...(specs.some((spec) => spec.again === true)
|
|
@@ -609,9 +694,27 @@ export function groupPrompt(specs, steering, maxSpecs = MAX_SPECS_PER_JOB) {
|
|
|
609
694
|
: []),
|
|
610
695
|
"The functional specs to build:",
|
|
611
696
|
"",
|
|
612
|
-
...specs.map((spec) => `<functional spec number="${spec.number}"${spec.module !== undefined ? ` module="${spec.module}"` : ""} id="${spec.id ?? spec.number}"${spec.again === true ? ' again="true"' : ""}>\n${spec.text.trim()}\n</functional spec>`),
|
|
697
|
+
...specs.map((spec) => `<functional spec number="${spec.number}"${spec.module !== undefined ? ` module="${spec.module}"` : ""} id="${spec.id ?? spec.number}"${spec.place !== undefined ? ` place="${spec.place}"` : ""}${spec.again === true ? ' again="true"' : ""}>\n${spec.text.trim()}\n</functional spec>`),
|
|
613
698
|
"",
|
|
614
|
-
|
|
699
|
+
...(built.length > 0
|
|
700
|
+
? [
|
|
701
|
+
"The same work changes what is built already - below. Place each change: with the group of new functional specs",
|
|
702
|
+
'it belongs with, by the group\'s number, or "first" - in a pulse of its own, before the new work.',
|
|
703
|
+
"",
|
|
704
|
+
"- With a group, by default, when it is about the same thing as the group's new functional specs: one they build",
|
|
705
|
+
" on changed, or one of the same feature changed or taken out. One pulse costs less than two.",
|
|
706
|
+
'- "first" when it reshapes what the new work is built on - a concept many functional specs use redefined, a',
|
|
707
|
+
" functional spec others rely on taken out, a large rewrite - so the new work is built on code already changed and",
|
|
708
|
+
' tested. "first" too when it has nothing to do with any group, or is too large to share a pulse.',
|
|
709
|
+
`- A functional spec changed counts toward its group's ${maxSpecs}; one taken out builds nothing, and does not.`,
|
|
710
|
+
"",
|
|
711
|
+
"The changes to what is built:",
|
|
712
|
+
"",
|
|
713
|
+
...built.map((change) => `<change number="${change.number}" kind="${change.kind}" what="${change.what}"${change.module !== undefined ? ` module="${change.module}"` : ""}${change.id !== undefined ? ` id="${change.id}"` : ""}>\n${change.text.trim()}\n</change>`),
|
|
714
|
+
"",
|
|
715
|
+
]
|
|
716
|
+
: []),
|
|
717
|
+
`Call submit_groups once, with every one of them in a group${built.length > 0 ? ", and every change to what is built placed" : ""}.`,
|
|
615
718
|
].join("\n");
|
|
616
719
|
}
|
|
617
720
|
export function placePrompt(input) {
|
|
@@ -681,11 +784,11 @@ async function askStep(options) {
|
|
|
681
784
|
export function llmChunkAgents(options) {
|
|
682
785
|
const label = options.label ?? "queue planner";
|
|
683
786
|
return {
|
|
684
|
-
group: (specs, check, steering, maxSpecs) => askStep({
|
|
787
|
+
group: (specs, check, steering, maxSpecs, built) => askStep({
|
|
685
788
|
cwd: options.cwd,
|
|
686
789
|
model: options.model,
|
|
687
790
|
label: `${label} grouping`,
|
|
688
|
-
task: groupPrompt(specs, steering, maxSpecs),
|
|
791
|
+
task: groupPrompt(specs, steering, maxSpecs, built),
|
|
689
792
|
check,
|
|
690
793
|
tool: (take) => defineTool({
|
|
691
794
|
name: "submit_groups",
|
|
@@ -693,6 +796,9 @@ export function llmChunkAgents(options) {
|
|
|
693
796
|
description: `The new functional specs in groups, in order: each group's spec numbers and a title of at most ${TITLE_WORDS} words and ${TITLE_CHARS} characters.`,
|
|
694
797
|
parameters: Type.Object({
|
|
695
798
|
groups: Type.Array(Type.Object({ specs: Type.Array(Type.Number()), title: Type.String({ description: `What the group builds: at most ${TITLE_WORDS} words and ${TITLE_CHARS} characters.` }) })),
|
|
799
|
+
built: Type.Optional(Type.Array(Type.Object({ change: Type.Number(), to: Type.Union([Type.Number(), Type.Literal("first")]) }), {
|
|
800
|
+
description: 'Where each change to what is built goes: with a group, by its number, or "first" - a pulse of its own, before the new work.',
|
|
801
|
+
})),
|
|
696
802
|
notes: Type.Optional(Type.String({ description: NOTES })),
|
|
697
803
|
}),
|
|
698
804
|
async execute(_id, params) {
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { JobCases } from "./drift.ts";
|
|
2
|
+
import { type MarshalledPlainSource } from "../plain/spec.ts";
|
|
2
3
|
import { type RenderConformanceTestsResult } from "./conformance.ts";
|
|
3
4
|
import { type RegressionResult } from "./regression.ts";
|
|
4
5
|
import { type SpecSnapshot } from "./regenerate.ts";
|
|
@@ -40,6 +41,8 @@ export interface RenderSpecDiffConformanceOptions {
|
|
|
40
41
|
applied?: "applied" | "already applied";
|
|
41
42
|
/** The spec as parsed with the diff in. Taken now, after applying it, when not given. */
|
|
42
43
|
spec?: SpecSnapshot;
|
|
44
|
+
/** What the tests are written for once the job lands - the tests record it writes: its labels are checked against it. */
|
|
45
|
+
recorded?: MarshalledPlainSource;
|
|
43
46
|
model?: string;
|
|
44
47
|
onDelta?: (delta: string) => void;
|
|
45
48
|
}
|
|
@@ -23,8 +23,8 @@ export async function renderSpecDiffConformance(options) {
|
|
|
23
23
|
stage(`the spec diff is ${applied === "applied" ? "applied to the .plain files" : "already in the .plain files"} - ` +
|
|
24
24
|
`bringing the conformance tests in ${moduleName}'s codebase in line with it`);
|
|
25
25
|
// The change is tested whatever it touches - with or without a functional spec yet.
|
|
26
|
-
const
|
|
27
|
-
const
|
|
26
|
+
const funcIds = [...getFrids(tree)];
|
|
27
|
+
const lastFuncId = funcIds.at(-1);
|
|
28
28
|
if ((sectionsOf(tree)[TEST_REQUIREMENTS] ?? []).length === 0) {
|
|
29
29
|
throw new Error(`${moduleName} has no ${TEST_REQUIREMENTS}, so conformance tests cannot be written: they say how conformance tests ` +
|
|
30
30
|
"are written and run. Add them first - a change to them is a spec diff like any other.");
|
|
@@ -45,19 +45,20 @@ export async function renderSpecDiffConformance(options) {
|
|
|
45
45
|
const gapsFrom = gapMark();
|
|
46
46
|
const conformance = await renderConformanceTests({
|
|
47
47
|
plainFile: tip.plainFile,
|
|
48
|
-
|
|
48
|
+
funcId: lastFuncId,
|
|
49
49
|
templateDirs,
|
|
50
50
|
buildDir,
|
|
51
51
|
conformanceDir,
|
|
52
52
|
diff: options.diff,
|
|
53
53
|
...(options.change !== undefined ? { change: options.change } : {}),
|
|
54
54
|
...(options.cases !== undefined ? { cases: options.cases } : {}),
|
|
55
|
+
...(options.recorded !== undefined ? { recorded: options.recorded } : {}),
|
|
55
56
|
spec: { moduleName, tree },
|
|
56
57
|
model: options.model,
|
|
57
58
|
onDelta: options.onDelta,
|
|
58
59
|
});
|
|
59
60
|
// What the codebase now has conformance tests for, as their labels say.
|
|
60
|
-
const tested = () =>
|
|
61
|
+
const tested = () => funcIds.filter((funcId) => missingTestLabels(conformanceDir, moduleName, { funcId, conformance: [], acceptance: [] }).length === 0);
|
|
61
62
|
// A gap is the person's to decide, whether the tests passed or not.
|
|
62
63
|
const base = { moduleName, buildDir, conformanceDir, diff: applied, conformance, specGaps: gapsSince(gapsFrom, [buildDir, conformanceDir]) };
|
|
63
64
|
if (!conformance.passed) {
|
|
@@ -78,7 +79,7 @@ export async function renderSpecDiffConformance(options) {
|
|
|
78
79
|
buildDir,
|
|
79
80
|
conformanceRoot,
|
|
80
81
|
moduleName,
|
|
81
|
-
|
|
82
|
+
funcIds,
|
|
82
83
|
tree,
|
|
83
84
|
required: getInheritedFunctionalSpecs(tree),
|
|
84
85
|
scripts,
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { type TestedSpec } from "./test-kinds.ts";
|
|
1
2
|
import type { AgentSession, ToolDefinition } from "@earendil-works/pi-coding-agent";
|
|
2
3
|
import { type ScriptRun, type TestScripts } from "./test-scripts.ts";
|
|
3
4
|
import type { JobCases } from "./drift.ts";
|
|
@@ -18,8 +19,8 @@ export interface SpecContext {
|
|
|
18
19
|
text: string;
|
|
19
20
|
}
|
|
20
21
|
/**
|
|
21
|
-
* What an explorer hands the author of the conformance tests: how the software is built and run, how each functionality is
|
|
22
|
-
* what it needs around it
|
|
22
|
+
* What an explorer hands the author of the conformance tests: how the software is built and run, how each functionality is
|
|
23
|
+
* reached from outside, and what it needs around it. What to test is the scenarios', never the explorer's.
|
|
23
24
|
*/
|
|
24
25
|
export interface Exploration {
|
|
25
26
|
buildAndRun: string;
|
|
@@ -31,15 +32,10 @@ export interface Exploration {
|
|
|
31
32
|
testsReachIt: string;
|
|
32
33
|
interfaces: Array<{
|
|
33
34
|
module: string;
|
|
34
|
-
|
|
35
|
+
funcId: string;
|
|
35
36
|
how: string;
|
|
36
37
|
}>;
|
|
37
38
|
environment: string;
|
|
38
|
-
plan: Array<{
|
|
39
|
-
module: string;
|
|
40
|
-
frid: string;
|
|
41
|
-
tests: string;
|
|
42
|
-
}>;
|
|
43
39
|
other?: string;
|
|
44
40
|
}
|
|
45
41
|
/** An exploration as the test author, and the judge, read it. */
|
|
@@ -47,7 +43,7 @@ export declare function explorationText(exploration: Exploration, replies?: stri
|
|
|
47
43
|
/**
|
|
48
44
|
* What an exploration lacks for the conformance tests to be written from it alone, or nothing when
|
|
49
45
|
* it is whole: how to build and run it, and - for every functionality the tests are for - how it
|
|
50
|
-
* is reached
|
|
46
|
+
* is reached.
|
|
51
47
|
*/
|
|
52
48
|
/**
|
|
53
49
|
* How many tool calls the agents with one thing to decide may make before they are told to decide:
|
|
@@ -62,18 +58,11 @@ export declare const JUDGE_CALLS = 30;
|
|
|
62
58
|
export declare const MAX_EXPLORATION_CHARS = 12000;
|
|
63
59
|
/** How many times the explorer may hand an exploration over: each ends its turn, and is checked. */
|
|
64
60
|
export declare const EXPLORATION_SUBMISSIONS = 3;
|
|
65
|
-
export declare function explorationProblems(exploration: Exploration | undefined, scope: ConflictScope, moduleName: string
|
|
66
|
-
/** The functionalities whose acceptance tests the plan has to carry out, and how many each has. */
|
|
67
|
-
acceptance?: Array<{
|
|
68
|
-
module: string;
|
|
69
|
-
frid: string;
|
|
70
|
-
count: number;
|
|
71
|
-
}>): string[];
|
|
61
|
+
export declare function explorationProblems(exploration: Exploration | undefined, scope: ConflictScope, moduleName: string): string[];
|
|
72
62
|
/**
|
|
73
|
-
* How the built software is invoked from outside,
|
|
74
|
-
*
|
|
75
|
-
*
|
|
76
|
-
* back until it does. What the explorer wrote in its replies goes with an account it never completed.
|
|
63
|
+
* How the built software is invoked from outside, as an agent that explored the code writes it - everything of the code
|
|
64
|
+
* the tests' author works from; what to test is the scenarios'. Checked before it is used: it has to cover every
|
|
65
|
+
* functionality the tests are for, and is sent back until it does. What the explorer wrote in its replies goes with an account it never completed.
|
|
77
66
|
*/
|
|
78
67
|
export declare function exploreInvocation(options: {
|
|
79
68
|
buildDir: string;
|
|
@@ -90,12 +79,6 @@ export declare function exploreInvocation(options: {
|
|
|
90
79
|
failure: string;
|
|
91
80
|
account: string;
|
|
92
81
|
};
|
|
93
|
-
/** The functionalities whose acceptance tests the plan has to carry out, and how many each has. */
|
|
94
|
-
acceptance?: Array<{
|
|
95
|
-
module: string;
|
|
96
|
-
frid: string;
|
|
97
|
-
count: number;
|
|
98
|
-
}>;
|
|
99
82
|
label: string;
|
|
100
83
|
model?: string;
|
|
101
84
|
onDelta?: (delta: string) => void;
|
|
@@ -142,7 +125,7 @@ export interface ReviewProblem {
|
|
|
142
125
|
kind: "missing" | "bad";
|
|
143
126
|
what: string;
|
|
144
127
|
/**
|
|
145
|
-
* The functionality is not yet tested, black box: a
|
|
128
|
+
* The functionality is not yet tested, black box: a scenario's test missing or cut down,
|
|
146
129
|
* the software mocked or reached other than from outside, a test that cannot fail or skips what it
|
|
147
130
|
* checks. Never let go. Anything else is a way to make good tests better.
|
|
148
131
|
*/
|
|
@@ -150,7 +133,7 @@ export interface ReviewProblem {
|
|
|
150
133
|
}
|
|
151
134
|
/**
|
|
152
135
|
* The review gate, after the conformance suite and the unit tests pass: do the tests this run added
|
|
153
|
-
* or changed test the software black box, and do
|
|
136
|
+
* or changed test the software black box, and do they carry out the scenarios as they say - every
|
|
154
137
|
* step, every expectation. Passing tests prove nothing when they assert nothing, mock the software
|
|
155
138
|
* they test, or skip what the spec asks for. It reads the tests and the code and changes neither.
|
|
156
139
|
* No verdict is no pass: a gate that cannot say yes stays shut.
|
|
@@ -174,11 +157,8 @@ export declare function createTestReviewer(options: {
|
|
|
174
157
|
buildDir: string;
|
|
175
158
|
conformanceDir: string;
|
|
176
159
|
spec: SpecContext;
|
|
177
|
-
/** The functionality under test - or, for a spec change, those it touches - with their
|
|
178
|
-
functionalities:
|
|
179
|
-
frid: string;
|
|
180
|
-
acceptanceTests: string[];
|
|
181
|
-
}>;
|
|
160
|
+
/** The functionality under test - or, for a spec change, those it touches - with their scenarios. */
|
|
161
|
+
functionalities: TestedSpec[];
|
|
182
162
|
forChange?: boolean;
|
|
183
163
|
invocation: string;
|
|
184
164
|
label: string;
|
|
@@ -192,10 +172,7 @@ export declare function testReviewTask(options: {
|
|
|
192
172
|
buildDir: string;
|
|
193
173
|
conformanceDir: string;
|
|
194
174
|
spec: SpecContext;
|
|
195
|
-
functionalities:
|
|
196
|
-
frid: string;
|
|
197
|
-
acceptanceTests: string[];
|
|
198
|
-
}>;
|
|
175
|
+
functionalities: TestedSpec[];
|
|
199
176
|
forChange?: boolean;
|
|
200
177
|
invocation: string;
|
|
201
178
|
}): string;
|
|
@@ -241,7 +218,7 @@ export interface Escalation {
|
|
|
241
218
|
/** A functionality, by the module it is in and its id there: a conflict can cross modules. */
|
|
242
219
|
export interface FunctionalityRef {
|
|
243
220
|
module: string;
|
|
244
|
-
|
|
221
|
+
funcId: string;
|
|
245
222
|
}
|
|
246
223
|
/** "billing 4": a functionality as the judge and the person read it. */
|
|
247
224
|
export declare function refLabel(ref: FunctionalityRef): string;
|
|
@@ -269,12 +246,14 @@ export interface ConflictScope {
|
|
|
269
246
|
functionalities: FunctionalityRef[];
|
|
270
247
|
/** The functionality under test, which every conflict raised must include. None for a spec diff. */
|
|
271
248
|
tested?: FunctionalityRef;
|
|
249
|
+
/** For a spec diff: the modules whose functional specs it touches - a module below's, tested in the tip, among them. */
|
|
250
|
+
touchedModules?: string[];
|
|
272
251
|
}
|
|
273
252
|
/** A conflict's functionalities as the judge gave them, tidied: trimmed, and each once. */
|
|
274
253
|
export declare function conflictRefs(given: Array<{
|
|
275
254
|
module?: string;
|
|
276
255
|
id?: string;
|
|
277
|
-
|
|
256
|
+
funcId?: string;
|
|
278
257
|
}> | undefined): FunctionalityRef[];
|
|
279
258
|
/** Why a spec conflict cannot be raised as given, or nothing when it can. */
|
|
280
259
|
export declare function specConflictProblem(functionalities: FunctionalityRef[] | undefined, scope: ConflictScope): string | undefined;
|