@turri-dev/cli 0.1.27-next.763d091 → 0.1.28-next.d5f2680
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 +71 -15
- 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 -20
- package/dist/regenerate/conformance.js +118 -121
- 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 +61 -49
- package/dist/regenerate/loop.js +473 -340
- 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 +69 -31
- package/dist/regenerate/test-kinds.js +109 -80
- 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 +89 -54
- 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 +24 -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
|
@@ -2,7 +2,7 @@ import { spawnSync } from "node:child_process";
|
|
|
2
2
|
import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs";
|
|
3
3
|
import { tmpdir } from "node:os";
|
|
4
4
|
import { dirname, join } from "node:path";
|
|
5
|
-
import { ACCEPTANCE_TESTS, DEFINITIONS, FUNCTIONAL_REQUIREMENTS, NON_FUNCTIONAL_REQUIREMENTS, REQUIRED_FUNCTIONAL_SPECS, TEST_REQUIREMENTS, getInheritedFunctionalSpecs, } from "../plain/spec.js";
|
|
5
|
+
import { ACCEPTANCE_TESTS, CONFORMANCE_TESTS, DEFINITIONS, FUNCTIONAL_REQUIREMENTS, NON_FUNCTIONAL_REQUIREMENTS, REQUIRED_FUNCTIONAL_SPECS, TEST_REQUIREMENTS, getInheritedFunctionalSpecs, } from "../plain/spec.js";
|
|
6
6
|
import { validateConcepts } from "../plain/concepts.js";
|
|
7
7
|
import { sectionTag } from "./prompt.js";
|
|
8
8
|
/**
|
|
@@ -11,9 +11,10 @@ import { sectionTag } from "./prompt.js";
|
|
|
11
11
|
*
|
|
12
12
|
* Both specs are marshalled trees, written out as text with one JSON value per line - a
|
|
13
13
|
* section's name, then each of its items on a line of its own - and compared with git. So an
|
|
14
|
-
* item is the unit of change: a functional spec with its
|
|
14
|
+
* item is the unit of change: a functional spec with its scenarios and linked resources,
|
|
15
15
|
* a definition, a req. Git lines them up, so a spec inserted in the middle is one addition and
|
|
16
|
-
* the specs after it are untouched
|
|
16
|
+
* the specs after it are untouched. A functional spec is known by its func-id: one taken out and
|
|
17
|
+
* one put in with the same func-id are the same functional spec, changed - or, the same text, moved.
|
|
17
18
|
*
|
|
18
19
|
* The planner (`chunk-plan.ts`) cuts the changes into steps - which go together, which go first. It
|
|
19
20
|
* picks changes by id; it never writes a diff. `makeStep` applies what it picked, and `inconsistencies`
|
|
@@ -72,8 +73,9 @@ export function sameSpec(before, after) {
|
|
|
72
73
|
const had = modulesOf(before);
|
|
73
74
|
if ([...modulesOf(after)].some((module) => !had.has(module)))
|
|
74
75
|
return false;
|
|
76
|
+
// A functional spec by its func-id and what it says, wherever it is written: moved, it is the same spec.
|
|
75
77
|
const text = (item) => {
|
|
76
|
-
const { module: _module,
|
|
78
|
+
const { module: _module, ...rest } = item;
|
|
77
79
|
return JSON.stringify(sortKeys(rest));
|
|
78
80
|
};
|
|
79
81
|
const list = (tree, heading) => (tree[heading] ?? []).map(text);
|
|
@@ -84,7 +86,7 @@ export function sameSpec(before, after) {
|
|
|
84
86
|
.map((key) => [key, sortKeys(tree[key])]);
|
|
85
87
|
return JSON.stringify({
|
|
86
88
|
sections: [DEFINITIONS, NON_FUNCTIONAL_REQUIREMENTS, TEST_REQUIREMENTS].map((heading) => list(tree, heading).sort()),
|
|
87
|
-
specs: [...list(tree, REQUIRED_FUNCTIONAL_SPECS), ...list(tree, FUNCTIONAL_REQUIREMENTS)],
|
|
89
|
+
specs: [...list(tree, REQUIRED_FUNCTIONAL_SPECS), ...list(tree, FUNCTIONAL_REQUIREMENTS)].sort(),
|
|
88
90
|
others,
|
|
89
91
|
});
|
|
90
92
|
};
|
|
@@ -245,7 +247,51 @@ export function changesBetween(before, after) {
|
|
|
245
247
|
});
|
|
246
248
|
});
|
|
247
249
|
}
|
|
248
|
-
return changes;
|
|
250
|
+
return mergeMoves(changes);
|
|
251
|
+
}
|
|
252
|
+
/** The sections whose items are functional specs, each known by its func-id. */
|
|
253
|
+
const FUNCTIONAL_SECTIONS = [FUNCTIONAL_REQUIREMENTS, REQUIRED_FUNCTIONAL_SPECS];
|
|
254
|
+
/** A functional spec's identity - its func-id, and its module for one a module below has - or undefined for any other line. */
|
|
255
|
+
function identityOfLine(line) {
|
|
256
|
+
if (line === undefined || headerOf(line) !== undefined)
|
|
257
|
+
return undefined;
|
|
258
|
+
try {
|
|
259
|
+
const item = JSON.parse(line);
|
|
260
|
+
return typeof item.id === "string" ? `${typeof item.module === "string" ? item.module : ""}:${item.id}` : undefined;
|
|
261
|
+
}
|
|
262
|
+
catch {
|
|
263
|
+
return undefined;
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
/**
|
|
267
|
+
* A functional spec taken out in one place and put in at another - the same func-id - is one change, not two: moved,
|
|
268
|
+
* and changed where its text differs. git lines up a move as a removal and an addition, in hunks of their own; the change
|
|
269
|
+
* takes the line out where it was and puts it in where it now is.
|
|
270
|
+
*/
|
|
271
|
+
function mergeMoves(changes) {
|
|
272
|
+
// Paired first, whichever comes first: a spec moved up is added before it is removed.
|
|
273
|
+
const pairs = new Map();
|
|
274
|
+
const used = new Set();
|
|
275
|
+
for (const change of changes) {
|
|
276
|
+
const identity = change.kind === "remove" && FUNCTIONAL_SECTIONS.includes(change.section) ? identityOfLine(change.before) : undefined;
|
|
277
|
+
const added = identity === undefined ? undefined : changes.find((other) => !used.has(other) && other.kind === "add" && other.section === change.section && identityOfLine(other.after) === identity);
|
|
278
|
+
if (added === undefined)
|
|
279
|
+
continue;
|
|
280
|
+
used.add(added);
|
|
281
|
+
pairs.set(change, added);
|
|
282
|
+
}
|
|
283
|
+
const merged = [];
|
|
284
|
+
for (const change of changes) {
|
|
285
|
+
if (used.has(change))
|
|
286
|
+
continue;
|
|
287
|
+
const added = pairs.get(change);
|
|
288
|
+
merged.push(added === undefined ? change : { ...change, kind: "change", after: added.after, position: added.position, inserts: added.inserts });
|
|
289
|
+
}
|
|
290
|
+
return merged.map((change, index) => ({ ...change, id: `c${index + 1}` }));
|
|
291
|
+
}
|
|
292
|
+
/** Whether a change moves a functional spec and leaves what it says as it was. */
|
|
293
|
+
export function isMove(change) {
|
|
294
|
+
return change.kind === "change" && change.before === change.after;
|
|
249
295
|
}
|
|
250
296
|
/** The words of an item's text, for telling a changed item from a different one. */
|
|
251
297
|
function wordsOf(line) {
|
|
@@ -268,12 +314,18 @@ export function similarity(a, b) {
|
|
|
268
314
|
const SAME_ITEM = 0.5;
|
|
269
315
|
/**
|
|
270
316
|
* Which removed item became which added one, in order on both sides: the pairing that matches
|
|
271
|
-
* the most alike items.
|
|
317
|
+
* the most alike items. A functional spec pairs only with the one of its func-id; any other item only with one at
|
|
318
|
+
* least SAME_ITEM alike.
|
|
272
319
|
*/
|
|
273
320
|
function pairItems(removed, added) {
|
|
274
|
-
const alike = (r, a) =>
|
|
275
|
-
|
|
276
|
-
|
|
321
|
+
const alike = (r, a) => {
|
|
322
|
+
if (headerOf(removed[r]) !== undefined || headerOf(added[a]) !== undefined)
|
|
323
|
+
return 0;
|
|
324
|
+
const identity = identityOfLine(removed[r]);
|
|
325
|
+
if (identity !== undefined || identityOfLine(added[a]) !== undefined)
|
|
326
|
+
return identity === identityOfLine(added[a]) ? 1 : 0;
|
|
327
|
+
return similarity(removed[r], added[a]) >= SAME_ITEM ? similarity(removed[r], added[a]) : 0;
|
|
328
|
+
};
|
|
277
329
|
// Best total likeness over order-keeping pairings - a longest common subsequence, weighted.
|
|
278
330
|
const best = Array.from({ length: removed.length + 1 }, () => new Array(added.length + 1).fill(0));
|
|
279
331
|
for (let r = removed.length - 1; r >= 0; r -= 1) {
|
|
@@ -314,15 +366,82 @@ export function applyChanges(before, changes) {
|
|
|
314
366
|
}
|
|
315
367
|
return `${out.join("\n")}\n`;
|
|
316
368
|
}
|
|
317
|
-
/**
|
|
369
|
+
/** A functional spec's scenarios of one kind, as a person reads them: each under its scenario-id, in its section's tag. */
|
|
370
|
+
export function scenarioLines(tag, scenarios) {
|
|
371
|
+
if (scenarios === undefined || scenarios.length === 0)
|
|
372
|
+
return [];
|
|
373
|
+
const indent = (text) => text.split("\n").map((line) => (line === "" ? "" : ` ${line}`)).join("\n");
|
|
374
|
+
return [` <${tag}>`, ...scenarios.map((scenario) => indent(`# ${scenario.id ?? ""}\n${scenario.text ?? ""}`)), ` </${tag}>`];
|
|
375
|
+
}
|
|
376
|
+
/**
|
|
377
|
+
* An item as a person reads it: a functional spec under its `# <func-id>` heading, as its module's file writes it,
|
|
378
|
+
* with its conformance and acceptance scenarios under it where the tree has them; anything else as it reads.
|
|
379
|
+
*/
|
|
318
380
|
function itemText(line) {
|
|
319
381
|
const item = JSON.parse(line);
|
|
320
|
-
const tests = item[ACCEPTANCE_TESTS] ?? [];
|
|
321
382
|
return [
|
|
322
|
-
item.markdown ?? line,
|
|
323
|
-
...(
|
|
383
|
+
item.id !== undefined ? `# ${item.id}\n${item.markdown ?? ""}` : (item.markdown ?? line),
|
|
384
|
+
...scenarioLines("conformance tests", item[CONFORMANCE_TESTS]),
|
|
385
|
+
...scenarioLines("acceptance tests", item[ACCEPTANCE_TESTS]),
|
|
324
386
|
].join("\n");
|
|
325
387
|
}
|
|
388
|
+
/** An item as its module's file writes it: a functional spec under its `# <func-id>` heading - its scenarios are in its test plain file. */
|
|
389
|
+
function fileItemText(line) {
|
|
390
|
+
const item = JSON.parse(line);
|
|
391
|
+
return item.id !== undefined ? `# ${item.id}\n${item.markdown ?? ""}` : (item.markdown ?? line);
|
|
392
|
+
}
|
|
393
|
+
/** A functional spec's test plain file, as it would read: its binding, then each section that has scenarios, each scenario under its id. */
|
|
394
|
+
export function testPlainFileText(module, item) {
|
|
395
|
+
// A section with no scenarios is left out: it is a syntax error.
|
|
396
|
+
const section = (title, scenarios) => (scenarios ?? []).length === 0 ? [] : [`${title}\n\n${(scenarios ?? []).map((scenario) => `# ${scenario.id ?? ""}\n${(scenario.text ?? "").trim()}`).join("\n\n")}`];
|
|
397
|
+
const sections = [...section("***conformance tests***", item[CONFORMANCE_TESTS]), ...section("***acceptance tests***", item[ACCEPTANCE_TESTS])];
|
|
398
|
+
return `---\nmodule: ${module}\nfunc_id: ${item.id ?? ""}\n---\n\n${sections.join("\n\n")}\n`;
|
|
399
|
+
}
|
|
400
|
+
/** One file's two texts as git diffs them, named `path` on both sides: an empty side is the file added or taken out whole. */
|
|
401
|
+
export function diffFileTexts(before, after, path) {
|
|
402
|
+
return before === after ? "" : gitDiff(before, after, 3, [path, path]);
|
|
403
|
+
}
|
|
404
|
+
/**
|
|
405
|
+
* The functional specs' test plain files a change between two serialized trees touches, each as a diff of its own,
|
|
406
|
+
* rebuilt from the trees as the module's file is: only the trees that carry scenarios - the conformance tests' - have
|
|
407
|
+
* any. A functional spec that had no scenarios has no test plain file before, so its file shows as added whole; one whose
|
|
408
|
+
* scenarios all went, as deleted whole - an empty one is a syntax error.
|
|
409
|
+
*/
|
|
410
|
+
export function diffTestPlainFiles(before, after, module, pathOf) {
|
|
411
|
+
const carriers = (serialized) => {
|
|
412
|
+
const found = new Map();
|
|
413
|
+
let section;
|
|
414
|
+
for (const line of serialized.split("\n")) {
|
|
415
|
+
if (line === "")
|
|
416
|
+
continue;
|
|
417
|
+
const header = headerOf(line);
|
|
418
|
+
if (header !== undefined) {
|
|
419
|
+
section = header.key;
|
|
420
|
+
continue;
|
|
421
|
+
}
|
|
422
|
+
// A module below's functional specs too, by module and func-id: the tip's conformance suite holds the chain's.
|
|
423
|
+
if (section === undefined || !FUNCTIONAL_SECTIONS.includes(section))
|
|
424
|
+
continue;
|
|
425
|
+
const item = JSON.parse(line);
|
|
426
|
+
// A functional spec with no scenarios - none listed, or the lists empty - has no test plain file.
|
|
427
|
+
if (item.id !== undefined && (item[CONFORMANCE_TESTS] ?? []).length + (item[ACCEPTANCE_TESTS] ?? []).length > 0)
|
|
428
|
+
found.set(`${item.module ?? ""}:${item.id}`, item);
|
|
429
|
+
}
|
|
430
|
+
return found;
|
|
431
|
+
};
|
|
432
|
+
const was = carriers(before);
|
|
433
|
+
const now = carriers(after);
|
|
434
|
+
return [...new Set([...now.keys(), ...was.keys()])]
|
|
435
|
+
.map((key) => {
|
|
436
|
+
const item = now.get(key) ?? was.get(key);
|
|
437
|
+
const owner = item.module ?? module;
|
|
438
|
+
const text = (found) => (found === undefined ? "" : testPlainFileText(owner, found));
|
|
439
|
+
const [from, to] = [text(was.get(key)), text(now.get(key))];
|
|
440
|
+
const path = pathOf(item.id ?? "", owner);
|
|
441
|
+
return from === to ? "" : gitDiff(from, to, 3, [path, path]);
|
|
442
|
+
})
|
|
443
|
+
.join("");
|
|
444
|
+
}
|
|
326
445
|
/** A serialized tree as a person reads it, section by section. */
|
|
327
446
|
export function treeText(serialized) {
|
|
328
447
|
const out = [];
|
|
@@ -345,25 +464,52 @@ export function treeText(serialized) {
|
|
|
345
464
|
out.push(`</${open}>`);
|
|
346
465
|
return out.join("\n").trim();
|
|
347
466
|
}
|
|
348
|
-
/** What of an item a change touches: its text, its
|
|
467
|
+
/** What of an item a change touches: its text, its scenarios, its linked resources. */
|
|
349
468
|
function changedParts(before, after) {
|
|
350
469
|
if (headerOf(before) !== undefined || headerOf(after) !== undefined)
|
|
351
470
|
return "the text";
|
|
352
471
|
const x = JSON.parse(before);
|
|
353
472
|
const y = JSON.parse(after);
|
|
354
|
-
const names = {
|
|
473
|
+
const names = {
|
|
474
|
+
markdown: "the text",
|
|
475
|
+
[CONFORMANCE_TESTS]: "the conformance scenarios",
|
|
476
|
+
[ACCEPTANCE_TESTS]: "the acceptance scenarios",
|
|
477
|
+
linked_resources: "the linked resources",
|
|
478
|
+
code_variables: "the code variables",
|
|
479
|
+
};
|
|
355
480
|
const parts = [...new Set([...Object.keys(x), ...Object.keys(y)])]
|
|
356
481
|
.filter((key) => JSON.stringify(x[key]) !== JSON.stringify(y[key]))
|
|
357
482
|
.map((key) => names[key] ?? key);
|
|
358
483
|
return parts.length === 0 ? "the text" : parts.join(" and ");
|
|
359
484
|
}
|
|
485
|
+
/** A functional spec a change is to, by its func-id - `module:func-id` for one of a module below - or nothing for another item. */
|
|
486
|
+
function funcSpecOf(change) {
|
|
487
|
+
if (!FUNCTIONAL_SECTIONS.includes(change.section))
|
|
488
|
+
return undefined;
|
|
489
|
+
try {
|
|
490
|
+
const item = JSON.parse(change.after ?? change.before ?? "");
|
|
491
|
+
if (item.id === undefined)
|
|
492
|
+
return undefined;
|
|
493
|
+
return `functional spec \`${item.module !== undefined ? `${item.module}:` : ""}${item.id}\``;
|
|
494
|
+
}
|
|
495
|
+
catch {
|
|
496
|
+
return undefined;
|
|
497
|
+
}
|
|
498
|
+
}
|
|
360
499
|
/** A change as the planning agent is shown it. */
|
|
361
500
|
export function describeChange(change) {
|
|
362
|
-
const
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
? `
|
|
366
|
-
:
|
|
501
|
+
const spec = funcSpecOf(change);
|
|
502
|
+
const what = spec !== undefined
|
|
503
|
+
? change.kind === "add"
|
|
504
|
+
? `add ${spec} to ${change.section}, as item ${change.position}`
|
|
505
|
+
: change.kind === "remove"
|
|
506
|
+
? `remove ${spec} from ${change.section}`
|
|
507
|
+
: `change ${changedParts(change.before, change.after)} of ${spec}`
|
|
508
|
+
: change.kind === "add"
|
|
509
|
+
? `add to ${change.section}, as item ${change.position}`
|
|
510
|
+
: change.kind === "remove"
|
|
511
|
+
? `remove item ${change.position} of ${change.section}`
|
|
512
|
+
: `change ${changedParts(change.before, change.after)} of item ${change.position} of ${change.section}`;
|
|
367
513
|
const header = (line) => (line !== undefined && headerOf(line) !== undefined ? `the section ${headerOf(line).key}` : undefined);
|
|
368
514
|
const body = [
|
|
369
515
|
...(change.before !== undefined ? [" before:", ` ${(header(change.before) ?? itemText(change.before)).replace(/\n/g, "\n ")}`] : []),
|
|
@@ -397,7 +543,7 @@ export function layoutOf(text) {
|
|
|
397
543
|
continue;
|
|
398
544
|
}
|
|
399
545
|
// An item straight after another's last line, with no blank line: the section is written tight.
|
|
400
|
-
if (section !== undefined && line.startsWith("- ") && previous.trim() !== "" && !SECTION_HEADING.test(previous))
|
|
546
|
+
if (section !== undefined && section !== FUNCTIONAL_REQUIREMENTS && line.startsWith("- ") && previous.trim() !== "" && !SECTION_HEADING.test(previous))
|
|
401
547
|
tight.add(section);
|
|
402
548
|
previous = line;
|
|
403
549
|
const concept = CONCEPT.exec(line);
|
|
@@ -423,7 +569,7 @@ export function plainText(serialized, layout) {
|
|
|
423
569
|
if (header !== undefined)
|
|
424
570
|
sections.push({ key: header.key, items: [] });
|
|
425
571
|
else
|
|
426
|
-
sections.at(-1)?.items.push(
|
|
572
|
+
sections.at(-1)?.items.push(fileItemText(line));
|
|
427
573
|
}
|
|
428
574
|
const own = sections.filter((section) => section.key !== REQUIRED_FUNCTIONAL_SPECS && section.items.length > 0);
|
|
429
575
|
if (layout === undefined)
|
|
@@ -450,6 +596,41 @@ export function plainText(serialized, layout) {
|
|
|
450
596
|
export function diffSpecText(before, after, file = "spec.plain", layout) {
|
|
451
597
|
return gitDiff(`${plainText(before, layout)}\n`, `${plainText(after, layout)}\n`, 3, [file, file]);
|
|
452
598
|
}
|
|
599
|
+
/**
|
|
600
|
+
* The functional specs of the modules below that a change between two serialized trees touches, each module's as a diff
|
|
601
|
+
* of its own file's `***functional specs***`: the tip is built with them, but its own file does not hold them.
|
|
602
|
+
*/
|
|
603
|
+
export function diffRequiredSpecs(before, after, file = "spec.plain") {
|
|
604
|
+
const byModule = (serialized) => {
|
|
605
|
+
const found = new Map();
|
|
606
|
+
let section;
|
|
607
|
+
for (const line of serialized.split("\n")) {
|
|
608
|
+
if (line === "")
|
|
609
|
+
continue;
|
|
610
|
+
const header = headerOf(line);
|
|
611
|
+
if (header !== undefined) {
|
|
612
|
+
section = header.key;
|
|
613
|
+
continue;
|
|
614
|
+
}
|
|
615
|
+
if (section !== REQUIRED_FUNCTIONAL_SPECS)
|
|
616
|
+
continue;
|
|
617
|
+
const module = JSON.parse(line).module ?? "";
|
|
618
|
+
found.set(module, [...(found.get(module) ?? []), fileItemText(line)]);
|
|
619
|
+
}
|
|
620
|
+
return found;
|
|
621
|
+
};
|
|
622
|
+
const was = byModule(before);
|
|
623
|
+
const now = byModule(after);
|
|
624
|
+
const folder = dirname(file);
|
|
625
|
+
return [...new Set([...was.keys(), ...now.keys()])]
|
|
626
|
+
.map((module) => {
|
|
627
|
+
const text = (items) => (items === undefined ? "" : `***${FUNCTIONAL_REQUIREMENTS}***\n\n${items.join("\n\n")}\n`);
|
|
628
|
+
const [from, to] = [text(was.get(module)), text(now.get(module))];
|
|
629
|
+
const path = folder === "." ? `${module}.plain` : `${folder}/${module}.plain`;
|
|
630
|
+
return from === to ? "" : gitDiff(from, to, 3, [path, path]);
|
|
631
|
+
})
|
|
632
|
+
.join("");
|
|
633
|
+
}
|
|
453
634
|
/** The linked resources of a changed item whose content is different now - each hashed on its own: by their path, with both hashes. */
|
|
454
635
|
export function resourcesChanged(before, after) {
|
|
455
636
|
const resources = (line) => {
|
|
@@ -494,7 +675,7 @@ export function resourceDiff(changes, file = "spec.plain") {
|
|
|
494
675
|
].join("\n"))
|
|
495
676
|
.join("");
|
|
496
677
|
}
|
|
497
|
-
/** The item a change is to, as the person finds it: its concept or its number, in the spec file it is in - a module below's by its name. */
|
|
678
|
+
/** The item a change is to, as the person finds it: its concept, its func-id or its number, in the spec file it is in - a module below's by its name. */
|
|
498
679
|
function linkedBy(change, file) {
|
|
499
680
|
let item = {};
|
|
500
681
|
try {
|
|
@@ -506,12 +687,16 @@ function linkedBy(change, file) {
|
|
|
506
687
|
const concept = /^-?\s*(:[^:\s]+:)/.exec(item.markdown?.trim() ?? "")?.[1];
|
|
507
688
|
const what = change.section === DEFINITIONS && concept !== undefined
|
|
508
689
|
? concept
|
|
509
|
-
: change.section
|
|
510
|
-
? `functional spec ${
|
|
690
|
+
: FUNCTIONAL_SECTIONS.includes(change.section) && item.id !== undefined
|
|
691
|
+
? `functional spec ${item.id}`
|
|
511
692
|
: `${sectionTag(change.section).replace(/s$/, "")} ${change.position}`;
|
|
512
693
|
return `${what} in ${item.module !== undefined ? `${item.module}.plain` : file}`;
|
|
513
694
|
}
|
|
514
|
-
|
|
695
|
+
/**
|
|
696
|
+
* A step of the work. `testPlain`: for the conformance tests' steps, the module and where its functional specs' test plain
|
|
697
|
+
* files are - a scenario changed shows as a diff of its test plain file, after the module's.
|
|
698
|
+
*/
|
|
699
|
+
export function makeStep(title, reason, changes, before, after, file, layout, testPlain) {
|
|
515
700
|
return {
|
|
516
701
|
title,
|
|
517
702
|
reason,
|
|
@@ -521,7 +706,7 @@ export function makeStep(title, reason, changes, before, after, file, layout) {
|
|
|
521
706
|
tree: parseTree(after),
|
|
522
707
|
diff: diffTrees(before, after),
|
|
523
708
|
// What a linked resource holds changed shows in the step's diff, after the spec's own.
|
|
524
|
-
textDiff: `${diffSpecText(before, after, file, layout)}${resourceDiff(changes, file)}`,
|
|
709
|
+
textDiff: `${diffSpecText(before, after, file, layout)}${diffRequiredSpecs(before, after, file)}${testPlain !== undefined ? diffTestPlainFiles(before, after, testPlain.module, testPlain.pathOf) : ""}${resourceDiff(changes, file)}`,
|
|
525
710
|
sections: [...new Set(changes.map((change) => change.section))],
|
|
526
711
|
};
|
|
527
712
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { DEFINITIONS, NON_FUNCTIONAL_REQUIREMENTS, TEST_REQUIREMENTS, type MarshalledPlainSource } from "../plain/spec.ts";
|
|
1
|
+
import { DEFINITIONS, NON_FUNCTIONAL_REQUIREMENTS, TEST_REQUIREMENTS, type MarshalledPlainSource, type ScenarioText } from "../plain/spec.ts";
|
|
2
2
|
/**
|
|
3
3
|
* A module's spec as the prompts read it: the sections every functional spec shares, and a
|
|
4
4
|
* functional spec a module inherits. What the code and the tests are built from is in `records.ts`.
|
|
@@ -12,9 +12,10 @@ export interface SharedSections {
|
|
|
12
12
|
/** A functional spec of a module this one requires: documented, not built here. */
|
|
13
13
|
export interface RequiredSpec {
|
|
14
14
|
module: string;
|
|
15
|
-
|
|
15
|
+
funcId: string;
|
|
16
16
|
spec: string;
|
|
17
|
-
|
|
17
|
+
conformanceTests?: ScenarioText[];
|
|
18
|
+
acceptanceTests?: ScenarioText[];
|
|
18
19
|
}
|
|
19
20
|
/** A module's shared sections as they stand, with or without a functional spec to read them through. */
|
|
20
21
|
export declare function sectionsOf(tree: MarshalledPlainSource): SharedSections;
|
|
@@ -15,7 +15,7 @@ export declare const ATTEMPTS: Record<StopPoint, number>;
|
|
|
15
15
|
export interface StopFacts {
|
|
16
16
|
moduleName: string;
|
|
17
17
|
/** The functionality it stopped at; none for a spec change. */
|
|
18
|
-
|
|
18
|
+
funcId?: string;
|
|
19
19
|
at: StopPoint;
|
|
20
20
|
/** The unit test runs, the last of which is the failing one. */
|
|
21
21
|
runs?: TestRun[];
|
|
@@ -11,7 +11,7 @@ export const ATTEMPTS = {
|
|
|
11
11
|
* offered here is a change to a `.plain` file, never to code, tests or the machine.
|
|
12
12
|
*/
|
|
13
13
|
export function describeStop(facts) {
|
|
14
|
-
const what = facts.
|
|
14
|
+
const what = facts.funcId !== undefined ? `functional spec ${facts.moduleName}:${facts.funcId}` : `the spec change in ${facts.moduleName}`;
|
|
15
15
|
// What actually happened, not the most it could have been.
|
|
16
16
|
const tries = facts.at === "unit tests" ? (facts.attempts ?? ATTEMPTS[facts.at]) : facts.at === "conformance" ? (facts.conformance?.rounds ?? ATTEMPTS[facts.at]) : ATTEMPTS[facts.at];
|
|
17
17
|
let why;
|
|
@@ -50,12 +50,12 @@ export function describeStop(facts) {
|
|
|
50
50
|
}
|
|
51
51
|
else if (facts.at === "conformance" && facts.conformance?.specConflict !== undefined) {
|
|
52
52
|
const { functionalities, reason } = facts.conformance.specConflict;
|
|
53
|
-
const [one, two] = functionalities.map((ref) => `${ref.module}
|
|
53
|
+
const [one, two] = functionalities.map((ref) => `${ref.module}:${ref.funcId}`);
|
|
54
54
|
why = `its spec contradicts itself: functionalities ${one} and ${two} cannot both hold. ${reason.trim()}`;
|
|
55
55
|
todo = [
|
|
56
56
|
`Compare functional specs ${one} and ${two} (\`analyze-2-func-specs\`, \`resolve-spec-conflict\`) and change whichever is wrong - ` +
|
|
57
57
|
"or both, so they say the same thing about where they meet.",
|
|
58
|
-
"If one is meant to change what the other does, say so in the one that changes it, and update its
|
|
58
|
+
"If one is meant to change what the other does, say so in the one that changes it, and update its scenarios to match.",
|
|
59
59
|
];
|
|
60
60
|
}
|
|
61
61
|
else if (facts.at === "conformance" && facts.conformance?.review !== undefined && facts.conformance.stuck === undefined) {
|
|
@@ -64,14 +64,14 @@ export function describeStop(facts) {
|
|
|
64
64
|
`its conformance tests pass, but never passed review in ${tries} round${tries === 1 ? "" : "s"}: they do not yet test what the spec says, ` +
|
|
65
65
|
`black box. What the review last found:\n${problems.slice(0, 8).map((problem) => ` - ${problem.kind === "missing" ? "missing" : "bad"} · ${problem.file}${problem.test ? ` · ${problem.test}` : ""}: ${problem.what.trim().replace(/\s+/g, " ")}`).join("\n")}`;
|
|
66
66
|
todo = [
|
|
67
|
-
"If
|
|
68
|
-
"reqs how they do it, or change the
|
|
67
|
+
"If a scenario asks for something the tests cannot do - a service they cannot reach, data they cannot set up - say in the test " +
|
|
68
|
+
"reqs how they do it, or change the scenario to what can be checked.",
|
|
69
69
|
"If the test reqs leave open how the software is reached or what may be mocked, say so exactly.",
|
|
70
70
|
];
|
|
71
71
|
}
|
|
72
72
|
else if (facts.at === "conformance" && facts.conformance?.unlabelled !== undefined) {
|
|
73
73
|
why =
|
|
74
|
-
"its conformance tests were never written and labelled as the
|
|
74
|
+
"its conformance tests were never written and labelled as the scenarios they carry out - each conformance and acceptance scenario - " +
|
|
75
75
|
`though they were sent back for it again and again. Still missing:\n${facts.conformance.unlabelled.map((line) => ` - ${line}`).join("\n")}`;
|
|
76
76
|
todo = ["If the test reqs leave open where the tests go or how they are built, say so exactly - the folder, the files, the build."];
|
|
77
77
|
}
|
|
@@ -109,7 +109,7 @@ export function describeStop(facts) {
|
|
|
109
109
|
why = `after ${tries} round${tries === 1 ? "" : "s"}, its conformance tests and its code still disagree about what the spec means.`;
|
|
110
110
|
todo = [
|
|
111
111
|
"The functional spec can be read more than one way. Say exactly what should happen - the inputs, the outputs, " +
|
|
112
|
-
"the edge cases - and add
|
|
112
|
+
"the edge cases - and add scenarios to its test plain file that pin down the expected result.",
|
|
113
113
|
"If the conformance tests are meant to be written or run differently - framework, command, setup - change the test reqs.",
|
|
114
114
|
];
|
|
115
115
|
}
|
|
@@ -121,7 +121,7 @@ export function describeStop(facts) {
|
|
|
121
121
|
"This most often means its spec contradicts an earlier one. Compare them (`analyze-func-specs`, " +
|
|
122
122
|
"`resolve-spec-conflict`) and change whichever is wrong.",
|
|
123
123
|
"If the new behaviour is meant to change what an earlier functionality does, change that earlier spec and its " +
|
|
124
|
-
"
|
|
124
|
+
"scenarios to say so.",
|
|
125
125
|
];
|
|
126
126
|
}
|
|
127
127
|
return [
|
|
@@ -140,13 +140,13 @@ export function describeStop(facts) {
|
|
|
140
140
|
* spec is all they work on, and the fix is theirs to choose.
|
|
141
141
|
*/
|
|
142
142
|
export function describeSpecConflict(conflict) {
|
|
143
|
-
const name = (spec) => `functional spec ${spec.
|
|
143
|
+
const name = (spec) => `functional spec ${spec.module}:${spec.funcId}`;
|
|
144
144
|
const [one, two] = conflict.specs;
|
|
145
145
|
return [
|
|
146
146
|
`The spec contradicts itself: ${name(one)} and ${name(two)} cannot both hold.`,
|
|
147
147
|
"",
|
|
148
148
|
...conflict.specs.flatMap((spec) => [
|
|
149
|
-
`Functional spec ${spec.
|
|
149
|
+
`Functional spec ${spec.module}:${spec.funcId} (${basename(spec.plainFile)}):`,
|
|
150
150
|
...spec.text.trim().split("\n").map((line) => ` ${line}`),
|
|
151
151
|
"",
|
|
152
152
|
]),
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import type { AdvisorAnswer, AdvisorInput, AdvisorReport } from "./advisor-records.ts";
|
|
2
|
+
/**
|
|
3
|
+
* The test advisor's judgement of one functional spec: does it need explicit testing scenarios, and if so, which inputs
|
|
4
|
+
* should they cover (docs/test-advisor.md). Every step is a plain model call on `models.advisor`, answered through one
|
|
5
|
+
* tool and checked in code, as the planner's grouping agent is - none is a skill, and none can touch the project.
|
|
6
|
+
*
|
|
7
|
+
* - **Classify:** four questions, each yes carrying its evidence - a wrong output that looks right (silent failure), an
|
|
8
|
+
* input two reasonable readings answer differently (divergence), what would change (revisited), the consequence of a
|
|
9
|
+
* bug (cost). A yes whose evidence is empty or malformed counts as no. Silent failure, divergence or cost make the
|
|
10
|
+
* verdict yes; revisited alone does not - the unit tests are the regression protection.
|
|
11
|
+
* - **On a yes, pick the inputs:** one call writes 1-10 boundary probes (and the divergence's input), readers that
|
|
12
|
+
* differ only by their system prompt each predict every probe's output, code compares them, and any disagreement is a
|
|
13
|
+
* candidate - the union, never a majority. The third reader is spared when the first two already disagree. One last
|
|
14
|
+
* call keeps, of each candidate, the readings a reasonable person could hold.
|
|
15
|
+
*/
|
|
16
|
+
/** The functional spec the advisor looks at, and what it reads it with. */
|
|
17
|
+
export interface AdvisorSubject {
|
|
18
|
+
module: string;
|
|
19
|
+
funcId: string;
|
|
20
|
+
/** The functional spec's text. */
|
|
21
|
+
text: string;
|
|
22
|
+
/** The module's .plain file, whole: its definitions, its reqs, the functional specs around it. */
|
|
23
|
+
moduleText: string;
|
|
24
|
+
/** The project's own words on what matters: its AGENTS.md, the steering given in the conversation. */
|
|
25
|
+
context: string[];
|
|
26
|
+
/** A module below's: the tip its scenarios are tested in - built on it, its own functional specs beside it. */
|
|
27
|
+
testedIn?: {
|
|
28
|
+
module: string;
|
|
29
|
+
text: string;
|
|
30
|
+
};
|
|
31
|
+
/** Why the chat asked for the evaluation (evaluate_tests) - "the coding agent failed three times to implement it" - for the classifier, as context. */
|
|
32
|
+
reason?: string;
|
|
33
|
+
}
|
|
34
|
+
export interface AdvisorCallOptions {
|
|
35
|
+
/** Where the calls run: the project. */
|
|
36
|
+
cwd: string;
|
|
37
|
+
/** The advisor's model (advisorModel) - the `advisor` role's config is laid over it as for any agent. */
|
|
38
|
+
model?: string;
|
|
39
|
+
}
|
|
40
|
+
/** The classification: the verdict, the questions that were a yes with their evidence, and the divergence's input. */
|
|
41
|
+
export interface Classification {
|
|
42
|
+
verdict: "yes" | "no";
|
|
43
|
+
questions: AdvisorAnswer[];
|
|
44
|
+
divergence?: {
|
|
45
|
+
input: string;
|
|
46
|
+
readings: [string, string];
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
/** The fewest and most probes a functional spec is given. */
|
|
50
|
+
export declare const MIN_PROBES = 1;
|
|
51
|
+
export declare const MAX_PROBES = 10;
|
|
52
|
+
/** What a reader answers for an input the functional spec does not settle: a scenario on it would pin a guess. */
|
|
53
|
+
export declare const UNSPECIFIED = "unspecified";
|
|
54
|
+
/** The readers: the same task, decorrelated by their system prompts alone. */
|
|
55
|
+
export declare const READERS: ReadonlyArray<{
|
|
56
|
+
name: string;
|
|
57
|
+
system: string;
|
|
58
|
+
}>;
|
|
59
|
+
/** Text compared as the readers' outputs are: whitespace collapsed, case left aside. */
|
|
60
|
+
export declare function normalised(text: string): string;
|
|
61
|
+
type ClassificationAnswer = {
|
|
62
|
+
silent_failure: {
|
|
63
|
+
answer: boolean;
|
|
64
|
+
wrong_output?: string;
|
|
65
|
+
};
|
|
66
|
+
divergence: {
|
|
67
|
+
answer: boolean;
|
|
68
|
+
input?: string;
|
|
69
|
+
reading_a?: string;
|
|
70
|
+
reading_b?: string;
|
|
71
|
+
};
|
|
72
|
+
revisited: {
|
|
73
|
+
answer: boolean;
|
|
74
|
+
would_change?: string;
|
|
75
|
+
};
|
|
76
|
+
cost: {
|
|
77
|
+
answer: boolean;
|
|
78
|
+
consequence?: string;
|
|
79
|
+
};
|
|
80
|
+
};
|
|
81
|
+
/**
|
|
82
|
+
* The classification, from the call's answer: each yes kept only with its evidence - a yes without it, or with it
|
|
83
|
+
* malformed (two readings that are the same), is a no. The verdict is yes on silent failure, divergence or cost.
|
|
84
|
+
*/
|
|
85
|
+
export declare function classificationOf(answer: ClassificationAnswer): Classification;
|
|
86
|
+
/** Classify a functional spec: one call, four evidence-backed questions. Throws when the call gives no answer. */
|
|
87
|
+
export declare function classify(subject: AdvisorSubject, options: AdvisorCallOptions): Promise<Classification>;
|
|
88
|
+
/**
|
|
89
|
+
* The inputs the readers split on - every probe where any two predictions differ once normalised: the union, never a
|
|
90
|
+
* majority - each with its distinct readings, in the order the readers gave them.
|
|
91
|
+
*/
|
|
92
|
+
export declare function splitInputs(readings: string[][]): Map<number, string[]>;
|
|
93
|
+
/**
|
|
94
|
+
* Pick the inputs a functional spec's scenarios should cover: the probes, the readers' predictions of them, the
|
|
95
|
+
* disagreements in code, and the verifier's word on each. An input the readers agree on comes with their output; one
|
|
96
|
+
* they split on, with every reading the verifier kept - a single reading left is an agreed one, and none left drops it.
|
|
97
|
+
*/
|
|
98
|
+
export declare function pickInputs(subject: AdvisorSubject, classification: Classification, options: AdvisorCallOptions): Promise<AdvisorInput[]>;
|
|
99
|
+
/** The whole judgement: classify, and on a yes pick the inputs. Throws when a call that cannot be done without fails. */
|
|
100
|
+
export declare function advise(subject: AdvisorSubject, options: AdvisorCallOptions): Promise<AdvisorReport>;
|
|
101
|
+
export {};
|