@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
|
@@ -3,11 +3,14 @@ import { type MarshalledPlainSource } from "../plain/spec.ts";
|
|
|
3
3
|
* What a module is built from, in two records - see docs/regenerating.md.
|
|
4
4
|
*
|
|
5
5
|
* - The code record, `code/.turri.json`: the spec the code is built from, without what is only
|
|
6
|
-
* for conformance - the test reqs,
|
|
6
|
+
* for conformance - the test reqs, the test plain files, and each functional spec's scenarios.
|
|
7
7
|
* - The tests record, `tests/.turri.json`: the functional specs the conformance tests are
|
|
8
|
-
* written for - a subset of the code record's, each with its
|
|
8
|
+
* written for - a subset of the code record's, each with its func-id and its scenarios - the
|
|
9
9
|
* definitions and implementation reqs they were written against, and the test reqs they follow.
|
|
10
10
|
*
|
|
11
|
+
* A functional spec is known by its func-id - with its module, for one a module below has - in both: a record
|
|
12
|
+
* written when functional specs were numbered is old (`isOldRecord`), and its codebase is regenerated from scratch.
|
|
13
|
+
*
|
|
11
14
|
* The tests record is always a substate of the code record: a conformance test exercises
|
|
12
15
|
* functionality, and only functionality in the code can be tested. Each record is committed with
|
|
13
16
|
* what it describes, in the module's `.plaingit`, so rolling a unit of work back puts it back too.
|
|
@@ -21,8 +24,20 @@ export { RECORD_FILE } from "./paths.ts";
|
|
|
21
24
|
export declare const REGENERATE_AGAIN = "regenerate_again";
|
|
22
25
|
/** Whether a record's item is marked to regenerate again. */
|
|
23
26
|
export declare function marksRegenerateAgain(item: unknown): boolean;
|
|
24
|
-
/**
|
|
27
|
+
/**
|
|
28
|
+
* Whether a functional spec has scenarios - conformance or acceptance - and so is tested: one with an empty test plain
|
|
29
|
+
* file is not.
|
|
30
|
+
*/
|
|
31
|
+
export declare function hasScenarios(item: unknown): boolean;
|
|
32
|
+
/** The func-ids of a tree's own functional specs that have scenarios: those its conformance tests are written for. */
|
|
33
|
+
export declare function testedFuncIds(tree: MarshalledPlainSource | null | undefined): string[];
|
|
34
|
+
/** The spec as the code is built from it: no test reqs, no test plain files, and no scenarios on the functional specs. */
|
|
25
35
|
export declare function codeTree(tree: MarshalledPlainSource): MarshalledPlainSource;
|
|
36
|
+
/** A functional spec's identity in a tree: its func-id, with its module for one a module below has. */
|
|
37
|
+
export declare function identityOf(item: {
|
|
38
|
+
id?: string;
|
|
39
|
+
module?: string;
|
|
40
|
+
}): string;
|
|
26
41
|
/**
|
|
27
42
|
* A module's tree with the hash of each linked resource's content beside its link: the file is part of the spec,
|
|
28
43
|
* so a change to what it holds is a change to the item that links it - and, a definition linking it, to every
|
|
@@ -55,12 +70,11 @@ export declare function chainTree(trees: Array<{
|
|
|
55
70
|
* specs among the ones inherited. The new module has built nothing of its own yet.
|
|
56
71
|
*/
|
|
57
72
|
export declare function inheritedRecord(source: MarshalledPlainSource, sourceModule: string): MarshalledPlainSource;
|
|
58
|
-
/** Which functional specs are to have conformance tests, by number. Default: every one. */
|
|
59
|
-
export type Chosen = (frid: string) => boolean;
|
|
60
73
|
/**
|
|
61
74
|
* What the conformance tests are to be written for, now: of the functional specs the code is built
|
|
62
|
-
* from, those
|
|
63
|
-
* definitions and implementation reqs, with the test reqs as the spec has them now.
|
|
75
|
+
* from, those with scenarios, each as the spec has it now with its scenarios - and the code's
|
|
76
|
+
* definitions and implementation reqs, with the test reqs as the spec has them now. In the spec's order, a functional
|
|
77
|
+
* spec matched to the code and the tests by its func-id. One whose scenarios are all taken out loses its tests.
|
|
64
78
|
*
|
|
65
79
|
* Conformance follows the code, never goes ahead of it: a functional spec whose code is not caught up
|
|
66
80
|
* with the spec - its text in the code record is not the spec's, the code record does not have it at
|
|
@@ -69,13 +83,30 @@ export type Chosen = (frid: string) => boolean;
|
|
|
69
83
|
* (`regenerateAgain`) differ from the target once the code lands: written against the code it replaced, they are
|
|
70
84
|
* conformance work again, though the spec's text never changed.
|
|
71
85
|
*/
|
|
72
|
-
export declare function conformanceTarget(code: MarshalledPlainSource | null, tests: MarshalledPlainSource | null, spec: MarshalledPlainSource
|
|
86
|
+
export declare function conformanceTarget(code: MarshalledPlainSource | null, tests: MarshalledPlainSource | null, spec: MarshalledPlainSource): MarshalledPlainSource;
|
|
87
|
+
/** A functional spec as a person names it: its func-id, with its module - the codebase's own, when the item names none. */
|
|
88
|
+
export interface SpecRef {
|
|
89
|
+
module?: string;
|
|
90
|
+
funcId: string;
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Of the functional specs given, those the code record does not have as `tree` has them - not built, or built from
|
|
94
|
+
* other text: no conformance test can be written for them yet. `tree` is a spec, or what conformance tests are written
|
|
95
|
+
* for; `module` the codebase's own, whose functional specs carry no module of their own.
|
|
96
|
+
*/
|
|
97
|
+
export declare function notBuilt(code: MarshalledPlainSource | null, tree: MarshalledPlainSource, specs: SpecRef[], module?: string): SpecRef[];
|
|
98
|
+
/**
|
|
99
|
+
* What a new module's conformance tests start from, copied up with the code below: that module's tests record, as a
|
|
100
|
+
* lower module's - what they were written against tagged with it, its functional specs among those inherited, each with
|
|
101
|
+
* the scenarios its tests carry out. The tip's suite holds the tests of the whole chain, and they are kept up to date there.
|
|
102
|
+
*/
|
|
103
|
+
export declare function inheritedTestsRecord(source: MarshalledPlainSource, sourceModule: string): MarshalledPlainSource;
|
|
73
104
|
/**
|
|
74
|
-
*
|
|
75
|
-
* or
|
|
76
|
-
*
|
|
105
|
+
* Whether a record was written when functional specs were numbered, before they had func-ids: a functional spec without
|
|
106
|
+
* one, or one a module below has named by its number. Nothing is built on such a record - the codebase is regenerated
|
|
107
|
+
* from scratch, with its chain, once the person agrees.
|
|
77
108
|
*/
|
|
78
|
-
export declare function
|
|
109
|
+
export declare function isOldRecord(record: MarshalledPlainSource | null): boolean;
|
|
79
110
|
/**
|
|
80
111
|
* A record in `dir`: as the module's last commit has it, by default - what the harness itself wrote
|
|
81
112
|
* when the last unit of work landed, never what an agent's shell changed since - or, with `onDisk`,
|
|
@@ -1,17 +1,20 @@
|
|
|
1
1
|
import { createHash } from "node:crypto";
|
|
2
2
|
import { mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
3
3
|
import { join, resolve } from "node:path";
|
|
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_PLAIN_FILES, TEST_REQUIREMENTS, } from "../plain/spec.js";
|
|
5
5
|
import { committedText } from "./lineage.js";
|
|
6
6
|
/**
|
|
7
7
|
* What a module is built from, in two records - see docs/regenerating.md.
|
|
8
8
|
*
|
|
9
9
|
* - The code record, `code/.turri.json`: the spec the code is built from, without what is only
|
|
10
|
-
* for conformance - the test reqs,
|
|
10
|
+
* for conformance - the test reqs, the test plain files, and each functional spec's scenarios.
|
|
11
11
|
* - The tests record, `tests/.turri.json`: the functional specs the conformance tests are
|
|
12
|
-
* written for - a subset of the code record's, each with its
|
|
12
|
+
* written for - a subset of the code record's, each with its func-id and its scenarios - the
|
|
13
13
|
* definitions and implementation reqs they were written against, and the test reqs they follow.
|
|
14
14
|
*
|
|
15
|
+
* A functional spec is known by its func-id - with its module, for one a module below has - in both: a record
|
|
16
|
+
* written when functional specs were numbered is old (`isOldRecord`), and its codebase is regenerated from scratch.
|
|
17
|
+
*
|
|
15
18
|
* The tests record is always a substate of the code record: a conformance test exercises
|
|
16
19
|
* functionality, and only functionality in the code can be tested. Each record is committed with
|
|
17
20
|
* what it describes, in the module's `.plaingit`, so rolling a unit of work back puts it back too.
|
|
@@ -34,21 +37,37 @@ const itemsOf = (tree, heading) => (tree?.[heading] ?? []).slice();
|
|
|
34
37
|
function withoutEmpty(tree) {
|
|
35
38
|
return Object.fromEntries(Object.entries(tree).filter(([, value]) => !Array.isArray(value) || value.length > 0));
|
|
36
39
|
}
|
|
37
|
-
/**
|
|
38
|
-
|
|
39
|
-
|
|
40
|
+
/**
|
|
41
|
+
* Whether a functional spec has scenarios - conformance or acceptance - and so is tested: one with an empty test plain
|
|
42
|
+
* file is not.
|
|
43
|
+
*/
|
|
44
|
+
export function hasScenarios(item) {
|
|
45
|
+
const scenarios = (key) => item?.[key] ?? [];
|
|
46
|
+
return scenarios(CONFORMANCE_TESTS).length + scenarios(ACCEPTANCE_TESTS).length > 0;
|
|
47
|
+
}
|
|
48
|
+
/** The func-ids of a tree's own functional specs that have scenarios: those its conformance tests are written for. */
|
|
49
|
+
export function testedFuncIds(tree) {
|
|
50
|
+
return itemsOf(tree, FUNCTIONAL_REQUIREMENTS).filter(hasScenarios).map((item) => item.id ?? "");
|
|
51
|
+
}
|
|
52
|
+
/** A functional spec as the code sees it: without its scenarios, which are the conformance tests' alone. */
|
|
53
|
+
function withoutScenarios(item) {
|
|
54
|
+
const { [CONFORMANCE_TESTS]: _conformance, [ACCEPTANCE_TESTS]: _acceptance, ...rest } = item;
|
|
40
55
|
return rest;
|
|
41
56
|
}
|
|
42
|
-
/** The spec as the code is built from it: no test reqs, and no
|
|
57
|
+
/** The spec as the code is built from it: no test reqs, no test plain files, and no scenarios on the functional specs. */
|
|
43
58
|
export function codeTree(tree) {
|
|
44
|
-
const { [TEST_REQUIREMENTS]: _testReqs, ...rest } = tree;
|
|
59
|
+
const { [TEST_REQUIREMENTS]: _testReqs, [TEST_PLAIN_FILES]: _testPlainFiles, ...rest } = tree;
|
|
45
60
|
return withoutEmpty({
|
|
46
61
|
...rest,
|
|
47
|
-
[FUNCTIONAL_REQUIREMENTS]: itemsOf(tree, FUNCTIONAL_REQUIREMENTS).map(
|
|
48
|
-
[REQUIRED_FUNCTIONAL_SPECS]: itemsOf(tree, REQUIRED_FUNCTIONAL_SPECS).map(
|
|
62
|
+
[FUNCTIONAL_REQUIREMENTS]: itemsOf(tree, FUNCTIONAL_REQUIREMENTS).map(withoutScenarios),
|
|
63
|
+
[REQUIRED_FUNCTIONAL_SPECS]: itemsOf(tree, REQUIRED_FUNCTIONAL_SPECS).map(withoutScenarios),
|
|
49
64
|
});
|
|
50
65
|
}
|
|
51
|
-
/**
|
|
66
|
+
/** A functional spec's identity in a tree: its func-id, with its module for one a module below has. */
|
|
67
|
+
export function identityOf(item) {
|
|
68
|
+
return `${item.module ?? ""}:${item.id ?? ""}`;
|
|
69
|
+
}
|
|
70
|
+
/** Every linked resource in a tree, wherever an item has them - a functional spec, its scenarios, a definition, a req. */
|
|
52
71
|
function mapResources(value, map) {
|
|
53
72
|
if (Array.isArray(value))
|
|
54
73
|
return value.map((entry) => mapResources(entry, map));
|
|
@@ -143,7 +162,7 @@ export function chainTree(trees) {
|
|
|
143
162
|
export function inheritedRecord(source, sourceModule) {
|
|
144
163
|
const inherited = [
|
|
145
164
|
...itemsOf(source, REQUIRED_FUNCTIONAL_SPECS),
|
|
146
|
-
...itemsOf(source, FUNCTIONAL_REQUIREMENTS).map((item
|
|
165
|
+
...itemsOf(source, FUNCTIONAL_REQUIREMENTS).map((item) => ({ ...withoutScenarios(item), module: sourceModule })),
|
|
147
166
|
];
|
|
148
167
|
return withoutEmpty({
|
|
149
168
|
[DEFINITIONS]: taggedWith(itemsOf(source, DEFINITIONS), sourceModule),
|
|
@@ -153,8 +172,9 @@ export function inheritedRecord(source, sourceModule) {
|
|
|
153
172
|
}
|
|
154
173
|
/**
|
|
155
174
|
* What the conformance tests are to be written for, now: of the functional specs the code is built
|
|
156
|
-
* from, those
|
|
157
|
-
* definitions and implementation reqs, with the test reqs as the spec has them now.
|
|
175
|
+
* from, those with scenarios, each as the spec has it now with its scenarios - and the code's
|
|
176
|
+
* definitions and implementation reqs, with the test reqs as the spec has them now. In the spec's order, a functional
|
|
177
|
+
* spec matched to the code and the tests by its func-id. One whose scenarios are all taken out loses its tests.
|
|
158
178
|
*
|
|
159
179
|
* Conformance follows the code, never goes ahead of it: a functional spec whose code is not caught up
|
|
160
180
|
* with the spec - its text in the code record is not the spec's, the code record does not have it at
|
|
@@ -163,94 +183,89 @@ export function inheritedRecord(source, sourceModule) {
|
|
|
163
183
|
* (`regenerateAgain`) differ from the target once the code lands: written against the code it replaced, they are
|
|
164
184
|
* conformance work again, though the spec's text never changed.
|
|
165
185
|
*/
|
|
166
|
-
export function conformanceTarget(code, tests, spec
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
const
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
const
|
|
174
|
-
const
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
186
|
+
export function conformanceTarget(code, tests, spec) {
|
|
187
|
+
// The codebase's own functional specs, and - in their own section - those of the modules below it: the tip's
|
|
188
|
+
// conformance suite holds the tests of the whole chain.
|
|
189
|
+
const section = (heading) => {
|
|
190
|
+
const built = new Map(itemsOf(code, heading).map((item) => [identityOf(item), item]));
|
|
191
|
+
const now = itemsOf(spec, heading);
|
|
192
|
+
const inSpec = new Set(now.map(identityOf));
|
|
193
|
+
const tested = itemsOf(tests, heading);
|
|
194
|
+
const testedById = new Map(tested.map((item) => [identityOf(item), item]));
|
|
195
|
+
const specs = [];
|
|
196
|
+
for (const current of now) {
|
|
197
|
+
const have = built.get(identityOf(current));
|
|
198
|
+
if (have !== undefined && have.markdown.trim() === current.markdown.trim() && !marksRegenerateAgain(have)) {
|
|
199
|
+
if (hasScenarios(current))
|
|
200
|
+
specs.push(current);
|
|
201
|
+
continue;
|
|
202
|
+
}
|
|
203
|
+
// The code is behind the spec here - built from other text, not built, or being regenerated again: its tests stay
|
|
204
|
+
// as they are until it catches up.
|
|
205
|
+
const kept = testedById.get(identityOf(current));
|
|
206
|
+
if (kept !== undefined)
|
|
207
|
+
specs.push(kept);
|
|
179
208
|
}
|
|
180
|
-
//
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
specs.push(kept);
|
|
187
|
-
}
|
|
209
|
+
// Taken out of the spec, its tests go once the code has dropped it too.
|
|
210
|
+
for (const kept of tested)
|
|
211
|
+
if (!inSpec.has(identityOf(kept)) && built.has(identityOf(kept)))
|
|
212
|
+
specs.push(kept);
|
|
213
|
+
return specs;
|
|
214
|
+
};
|
|
188
215
|
return withoutEmpty({
|
|
189
216
|
[DEFINITIONS]: itemsOf(code, DEFINITIONS),
|
|
190
217
|
[NON_FUNCTIONAL_REQUIREMENTS]: itemsOf(code, NON_FUNCTIONAL_REQUIREMENTS),
|
|
191
218
|
[TEST_REQUIREMENTS]: itemsOf(spec, TEST_REQUIREMENTS),
|
|
192
|
-
[FUNCTIONAL_REQUIREMENTS]:
|
|
219
|
+
[FUNCTIONAL_REQUIREMENTS]: section(FUNCTIONAL_REQUIREMENTS),
|
|
220
|
+
[REQUIRED_FUNCTIONAL_SPECS]: section(REQUIRED_FUNCTIONAL_SPECS),
|
|
193
221
|
});
|
|
194
222
|
}
|
|
195
223
|
/**
|
|
196
|
-
* Of the functional specs given,
|
|
197
|
-
*
|
|
198
|
-
*
|
|
224
|
+
* Of the functional specs given, those the code record does not have as `tree` has them - not built, or built from
|
|
225
|
+
* other text: no conformance test can be written for them yet. `tree` is a spec, or what conformance tests are written
|
|
226
|
+
* for; `module` the codebase's own, whose functional specs carry no module of their own.
|
|
199
227
|
*/
|
|
200
|
-
export function notBuilt(code, tree,
|
|
201
|
-
const
|
|
202
|
-
const
|
|
203
|
-
const
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
const
|
|
228
|
+
export function notBuilt(code, tree, specs, module) {
|
|
229
|
+
const keyed = (items, own) => items.map((item) => [`${own ? (module ?? "") : (item.module ?? "")}:${item.id ?? ""}`, item]);
|
|
230
|
+
const all = (from) => new Map([...keyed(itemsOf(from, FUNCTIONAL_REQUIREMENTS), true), ...keyed(itemsOf(from, REQUIRED_FUNCTIONAL_SPECS), false)]);
|
|
231
|
+
const built = all(code);
|
|
232
|
+
const wanted = all(tree);
|
|
233
|
+
return specs.filter((spec) => {
|
|
234
|
+
const key = `${spec.module ?? module ?? ""}:${spec.funcId}`;
|
|
235
|
+
const item = wanted.get(key);
|
|
236
|
+
const have = built.get(key);
|
|
207
237
|
return item === undefined || have === undefined || have.markdown.trim() !== item.markdown.trim();
|
|
208
238
|
});
|
|
209
239
|
}
|
|
210
|
-
const asItem = (markdown, acceptanceTests) => ({
|
|
211
|
-
markdown,
|
|
212
|
-
...(acceptanceTests !== undefined && acceptanceTests.length > 0 ? { [ACCEPTANCE_TESTS]: acceptanceTests.map((test) => ({ markdown: test })) } : {}),
|
|
213
|
-
});
|
|
214
240
|
/**
|
|
215
|
-
*
|
|
216
|
-
*
|
|
241
|
+
* What a new module's conformance tests start from, copied up with the code below: that module's tests record, as a
|
|
242
|
+
* lower module's - what they were written against tagged with it, its functional specs among those inherited, each with
|
|
243
|
+
* the scenarios its tests carry out. The tip's suite holds the tests of the whole chain, and they are kept up to date there.
|
|
217
244
|
*/
|
|
218
|
-
function
|
|
219
|
-
const own = stored[FUNCTIONAL_REQUIREMENTS].filter((entry) => entry.module === stored.module).sort((a, b) => Number(a.frid) - Number(b.frid));
|
|
220
|
-
const built = [];
|
|
221
|
-
for (const [index, entry] of own.entries()) {
|
|
222
|
-
if (Number(entry.frid) !== index + 1 || entry.unitTests !== "passed")
|
|
223
|
-
break;
|
|
224
|
-
built.push(asItem(entry.spec, entry["acceptance tests"]));
|
|
225
|
-
}
|
|
226
|
-
const required = stored[FUNCTIONAL_REQUIREMENTS]
|
|
227
|
-
.filter((entry) => entry.module !== stored.module)
|
|
228
|
-
.map((entry) => ({ ...asItem(entry.spec, entry["acceptance tests"]), module: entry.module, frid: entry.frid }));
|
|
245
|
+
export function inheritedTestsRecord(source, sourceModule) {
|
|
229
246
|
return withoutEmpty({
|
|
230
|
-
[DEFINITIONS]: (
|
|
231
|
-
[NON_FUNCTIONAL_REQUIREMENTS]: (
|
|
232
|
-
[TEST_REQUIREMENTS]: (
|
|
233
|
-
[
|
|
234
|
-
[REQUIRED_FUNCTIONAL_SPECS]: required,
|
|
247
|
+
[DEFINITIONS]: taggedWith(itemsOf(source, DEFINITIONS), sourceModule),
|
|
248
|
+
[NON_FUNCTIONAL_REQUIREMENTS]: taggedWith(itemsOf(source, NON_FUNCTIONAL_REQUIREMENTS), sourceModule),
|
|
249
|
+
[TEST_REQUIREMENTS]: itemsOf(source, TEST_REQUIREMENTS),
|
|
250
|
+
[REQUIRED_FUNCTIONAL_SPECS]: [...itemsOf(source, REQUIRED_FUNCTIONAL_SPECS), ...itemsOf(source, FUNCTIONAL_REQUIREMENTS).map((item) => ({ ...item, module: sourceModule }))],
|
|
235
251
|
});
|
|
236
252
|
}
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
[TEST_REQUIREMENTS]: (latest?.[TEST_REQUIREMENTS] ?? []).map((text) => asItem(text)),
|
|
250
|
-
[FUNCTIONAL_REQUIREMENTS]: built,
|
|
251
|
-
});
|
|
253
|
+
/**
|
|
254
|
+
* Whether a record was written when functional specs were numbered, before they had func-ids: a functional spec without
|
|
255
|
+
* one, or one a module below has named by its number. Nothing is built on such a record - the codebase is regenerated
|
|
256
|
+
* from scratch, with its chain, once the person agrees.
|
|
257
|
+
*/
|
|
258
|
+
export function isOldRecord(record) {
|
|
259
|
+
if (record === null)
|
|
260
|
+
return false;
|
|
261
|
+
if (record[OLD] === true)
|
|
262
|
+
return true;
|
|
263
|
+
const items = [...itemsOf(record, FUNCTIONAL_REQUIREMENTS), ...itemsOf(record, REQUIRED_FUNCTIONAL_SPECS)];
|
|
264
|
+
return items.some((item) => typeof item.id !== "string" || "frid" in item);
|
|
252
265
|
}
|
|
253
|
-
/**
|
|
266
|
+
/** What an older layout of a record is read as: old, and nothing else of it. */
|
|
267
|
+
const OLD = "old record";
|
|
268
|
+
/** A record's text as the tree it holds - an older layout as an old record - or null when it is not one. */
|
|
254
269
|
function readRecordText(text) {
|
|
255
270
|
if (text === undefined)
|
|
256
271
|
return null;
|
|
@@ -263,10 +278,9 @@ function readRecordText(text) {
|
|
|
263
278
|
}
|
|
264
279
|
if (raw === null || typeof raw !== "object" || Array.isArray(raw))
|
|
265
280
|
return null;
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
return treeFromLegacy(raw);
|
|
281
|
+
// A layout from before the tree was all a record held: older still than numbered functional specs.
|
|
282
|
+
if (typeof raw.version === "number" || "functionalities" in raw)
|
|
283
|
+
return { [OLD]: true };
|
|
270
284
|
return raw;
|
|
271
285
|
}
|
|
272
286
|
function readFileText(path) {
|
|
@@ -4,7 +4,7 @@ import { tmpdir } from "node:os";
|
|
|
4
4
|
import { basename, dirname, join, relative, resolve } from "node:path";
|
|
5
5
|
import { IMPORT_DIRECTIVE, plainFileParser, readPlainSourceMetadata, withParseCache } from "../plain/parser.js";
|
|
6
6
|
import { isStdlibModule, STDLIB_ROOT } from "../plain/stdlib.js";
|
|
7
|
-
import { FUNCTIONAL_REQUIREMENTS, getFrids, getInheritedFunctionalSpecs, getSpecificationsForFrid, TEST_REQUIREMENTS, } from "../plain/spec.js";
|
|
7
|
+
import { CONFORMANCE_TESTS, ACCEPTANCE_TESTS, FUNCTIONAL_REQUIREMENTS, getFrids, getInheritedFunctionalSpecs, getSpecificationsForFrid, TEST_REQUIREMENTS, } from "../plain/spec.js";
|
|
8
8
|
import { ask, BEGIN, createExecutor, refuseIfModelIsBlind } from "../pi.js";
|
|
9
9
|
import { reportActivity, reportLogNote, reportStage, unitOf } from "./activity.js";
|
|
10
10
|
import { debugFailure, diagnosisText } from "./debugger.js";
|
|
@@ -28,14 +28,15 @@ import { summarizeDiff } from "./job-panel.js";
|
|
|
28
28
|
import { conversationWriting } from "./conversation-writes.js";
|
|
29
29
|
import { resolveTemplateDirs } from "./paths.js";
|
|
30
30
|
import { copyRequiredCode, findCodebaseTip, newlyRequired } from "./requires.js";
|
|
31
|
-
import {
|
|
31
|
+
import { baseOfCodebase, inCodebaseOf, seedFromBaseFolder } from "./base-folder.js";
|
|
32
|
+
import { createCopyResourceTool, loadLinkedResources, resourceFile, resourceImages } from "./resources.js";
|
|
32
33
|
import { markTold, waitingSteer } from "./steering.js";
|
|
33
34
|
import { misplacedConformanceTests, misplacedTestsPrompt } from "./test-kinds.js";
|
|
34
35
|
import { sectionsOf } from "./state.js";
|
|
35
|
-
import { inheritedRecord, readRecord, withResourceDigests, writeCodeRecord } from "./records.js";
|
|
36
|
+
import { codeTree, inheritedRecord, readRecord, withResourceDigests, writeCodeRecord } from "./records.js";
|
|
36
37
|
import { changesBetween, sameSpec, serializeTree } from "./spec-steps.js";
|
|
37
38
|
import { untilUnitTestsReviewed } from "./unit-review.js";
|
|
38
|
-
import { configForSpec, inheritModel, modelOnly, modelSpec } from "../config.js";
|
|
39
|
+
import { configForSpec, inheritModel, modelOnly, modelSpec, testDirOf } from "../config.js";
|
|
39
40
|
import { findTestRemovals, inventoryTests, listFiles, removalChallengePrompt, AGAIN_ASKS, againPrompt, draftPrompt, DRAFT_ASKS, } from "./testing.js";
|
|
40
41
|
/**
|
|
41
42
|
* Render a change to the specs, given as a git diff of the .plain files.
|
|
@@ -157,7 +158,7 @@ export function snapshotSpec(plainFile, options = {}) {
|
|
|
157
158
|
// yet is still a module to build.
|
|
158
159
|
const { tip, chain } = withParseCache(() => findCodebaseTip(resolve(cwd, plainFile), { templateDirs, inModule: options.inModule, allowNoFunctionalities: true }));
|
|
159
160
|
const dirs = resolveTemplateDirs(tip.plainFile, templateDirs);
|
|
160
|
-
const [moduleName, parsed] = plainFileParser(basename(tip.plainFile), dirs, { allowNoFunctionalities: true });
|
|
161
|
+
const [moduleName, parsed] = plainFileParser(basename(tip.plainFile), dirs, { allowNoFunctionalities: true, testDir: testDirOf(tip.plainFile) });
|
|
161
162
|
// What a linked resource holds is part of the spec: hashed beside its link, as the work loop has it.
|
|
162
163
|
return { moduleName, plainFile: tip.plainFile, tree: withResourceDigests(parsed, dirs), chain };
|
|
163
164
|
}
|
|
@@ -191,7 +192,7 @@ export async function renderSpecDiff(options) {
|
|
|
191
192
|
// of the chain: the code already is what the spec says. Nothing to render; the spec is recorded
|
|
192
193
|
// as it is now laid out.
|
|
193
194
|
const builtBefore = readRecord(tip.buildDir, { onDisk: true });
|
|
194
|
-
if (builtBefore !== null && sameSpec(builtBefore, tree) && !dryRun) {
|
|
195
|
+
if (builtBefore !== null && sameSpec(builtBefore, codeTree(tree)) && !dryRun) {
|
|
195
196
|
stage("the specs were only restructured - nothing to render; recording the spec as it is now laid out");
|
|
196
197
|
writeCodeRecord(tip.buildDir, tree);
|
|
197
198
|
landWork(tip.buildDir, `spec restructured: ${summarizeDiff(options.diff)}`, options.diff);
|
|
@@ -216,7 +217,7 @@ export async function renderSpecDiff(options) {
|
|
|
216
217
|
const scripts = withoutConformance(findTestScripts(root, tip.plainFile, tip.buildDir));
|
|
217
218
|
requireTestScripts(moduleName, scripts, { conformance: false });
|
|
218
219
|
reportLogNote(` unit testing script: ${relative(root, scripts.unit)}`);
|
|
219
|
-
const
|
|
220
|
+
const lastFuncId = [...getFrids(tree)].at(-1);
|
|
220
221
|
const buildDir = tip.buildDir;
|
|
221
222
|
// From scratch there is no build directory yet, and everything the agent runs runs in it.
|
|
222
223
|
if (!dryRun)
|
|
@@ -227,6 +228,12 @@ export async function renderSpecDiff(options) {
|
|
|
227
228
|
stage(`no code here yet - starting from ${source.moduleName}'s code`);
|
|
228
229
|
copyRequiredCode(source.buildDir, buildDir, moduleName, source.moduleName);
|
|
229
230
|
}
|
|
231
|
+
// A codebase that requires nothing starts from its base folder, when the config names one: code to build on, files to use.
|
|
232
|
+
if (empty && tip.requires === null && !dryRun) {
|
|
233
|
+
const seeded = seedFromBaseFolder(buildDir, tip.plainFile, moduleName);
|
|
234
|
+
if (seeded !== undefined)
|
|
235
|
+
stage(`no code here yet - starting from the base folder ${seeded.folder}, ${seeded.files.length} file${seeded.files.length === 1 ? "" : "s"}`);
|
|
236
|
+
}
|
|
230
237
|
// What the agent is shown it starts from: a dry run copies nothing, and reads the code it would start from where it is.
|
|
231
238
|
const seen = dryRun && source !== undefined ? source.buildDir : buildDir;
|
|
232
239
|
stage("mapping the codebase and writing the prompt");
|
|
@@ -252,21 +259,32 @@ export async function renderSpecDiff(options) {
|
|
|
252
259
|
const now = changesBetween(serializeTree(builtNow), serializeTree(options.steering.agent)).filter((change) => change.section !== TEST_REQUIREMENTS);
|
|
253
260
|
return now.length > 0 ? describeSpecChange(now, moduleName) : undefined;
|
|
254
261
|
};
|
|
255
|
-
const
|
|
256
|
-
const specificationsBefore =
|
|
257
|
-
|
|
262
|
+
const builtFuncIds = [...getFrids(builtNow)];
|
|
263
|
+
const specificationsBefore = builtFuncIds.length > 0
|
|
264
|
+
? getSpecificationsForFrid(builtNow, builtFuncIds.at(-1))[0]
|
|
265
|
+
: { ...sectionsOf(builtNow), [FUNCTIONAL_REQUIREMENTS]: [], [CONFORMANCE_TESTS]: [], [ACCEPTANCE_TESTS]: [] };
|
|
266
|
+
const resourceDirs = resolveTemplateDirs(tip.plainFile, templateDirs);
|
|
267
|
+
// A linked file the base folder held, in the codebase still, is used from there: its path is said, and it is not copied.
|
|
268
|
+
const linkedResources = loadLinkedResources(tree, lastFuncId ?? null, resourceDirs).map((resource) => {
|
|
269
|
+
const file = resourceFile(resource.target, resourceDirs);
|
|
270
|
+
const inCodebase = file === null ? undefined : inCodebaseOf(file, seen, tip.plainFile);
|
|
271
|
+
return inCodebase !== undefined ? { ...resource, inCodebase } : resource;
|
|
272
|
+
});
|
|
273
|
+
const startedFrom = baseOfCodebase(seen);
|
|
258
274
|
const prompt = buildSpecDiffPrompt({
|
|
259
275
|
moduleName,
|
|
260
276
|
diff: options.diff,
|
|
261
277
|
// The change as it is to the code: from what it is built from to the spec this job records, section by section.
|
|
262
278
|
changes: codingChanges,
|
|
263
279
|
specifications: specificationsBefore,
|
|
280
|
+
funcIds: builtFuncIds,
|
|
264
281
|
modules: sectionModules(builtNow, moduleName),
|
|
265
282
|
required: getInheritedFunctionalSpecs(builtNow),
|
|
266
283
|
linkedResources,
|
|
267
284
|
codebase: codebaseMap(seen),
|
|
268
285
|
empty: listFiles(seen).length === 0,
|
|
269
286
|
...(options.cases !== undefined ? { cases: options.cases } : {}),
|
|
287
|
+
...(startedFrom !== undefined ? { base: startedFrom } : {}),
|
|
270
288
|
});
|
|
271
289
|
const digestsBefore = digestFiles(buildDir);
|
|
272
290
|
// The spec gaps its agents report from here on are told with the result.
|
|
@@ -528,7 +546,7 @@ export async function renderSpecDiff(options) {
|
|
|
528
546
|
what: `the spec change to module \`${moduleName}\``,
|
|
529
547
|
// As the coding agent had it: the spec the code was built from, and the change.
|
|
530
548
|
// Steered before the review, the change as the agent has it now.
|
|
531
|
-
spec: reviewedSpec({ moduleName, specifications: specificationsBefore, modules: sectionModules(builtNow, moduleName), required: getInheritedFunctionalSpecs(builtNow), change: steeredChange() ?? codingText ?? changeText ?? options.diff }),
|
|
549
|
+
spec: reviewedSpec({ moduleName, specifications: specificationsBefore, funcIds: builtFuncIds, modules: sectionModules(builtNow, moduleName), required: getInheritedFunctionalSpecs(builtNow), change: steeredChange() ?? codingText ?? changeText ?? options.diff, ...(startedFrom !== undefined ? { base: startedFrom } : {}) }),
|
|
532
550
|
forChange: true,
|
|
533
551
|
...(options.cases !== undefined ? { cases: options.cases } : {}),
|
|
534
552
|
label: `${moduleName} spec change`,
|
|
@@ -8,14 +8,14 @@ export type RegressionEvent = {
|
|
|
8
8
|
type: "run";
|
|
9
9
|
command: string;
|
|
10
10
|
exitCode: number;
|
|
11
|
-
|
|
11
|
+
funcIds?: string[];
|
|
12
12
|
} | {
|
|
13
13
|
type: "repair";
|
|
14
|
-
|
|
14
|
+
funcIds: string[];
|
|
15
15
|
};
|
|
16
16
|
export interface RegressionRun extends TestRun {
|
|
17
17
|
/** Functionalities whose tests took part in this run. */
|
|
18
|
-
|
|
18
|
+
funcIds: string[];
|
|
19
19
|
}
|
|
20
20
|
export interface RegressionResult {
|
|
21
21
|
passed: boolean;
|
|
@@ -38,7 +38,7 @@ export declare function ensureRegressionPasses(input: {
|
|
|
38
38
|
buildDir: string;
|
|
39
39
|
conformanceRoot: string;
|
|
40
40
|
moduleName: string;
|
|
41
|
-
|
|
41
|
+
funcIds: string[];
|
|
42
42
|
tree: MarshalledPlainSource;
|
|
43
43
|
/** The required modules' functional specs, for the repair agent. */
|
|
44
44
|
required: RequiredSpec[];
|
|
@@ -6,6 +6,7 @@ import { reportActivity, reportStage, unitOf } from "./activity.js";
|
|
|
6
6
|
import { confinedTools } from "./confine.js";
|
|
7
7
|
import { projectRootOf } from "./paths.js";
|
|
8
8
|
import { buildRegressionFixPrompt, sectionModules } from "./prompt.js";
|
|
9
|
+
import { baseOfCodebase } from "./base-folder.js";
|
|
9
10
|
import { abandonWork, landWork, markWork } from "./lineage.js";
|
|
10
11
|
import { record } from "./ledger.js";
|
|
11
12
|
import { missingToolchainError } from "./environment-error.js";
|
|
@@ -31,9 +32,11 @@ function conformanceDirs(conformanceRoot, moduleName) {
|
|
|
31
32
|
}
|
|
32
33
|
/** Repair the implementation after the merged suite went red. */
|
|
33
34
|
async function repairRegression(input) {
|
|
34
|
-
const [specifications] = getSpecificationsForFrid(input.tree, input.
|
|
35
|
+
const [specifications] = getSpecificationsForFrid(input.tree, input.funcIds[input.funcIds.length - 1]);
|
|
35
36
|
const runs = [];
|
|
36
37
|
const fence = confinedTools({ workdir: input.buildDir, reportsEnvironment: true });
|
|
38
|
+
// What came from the base folder its codebase started from: the repair keeps it.
|
|
39
|
+
const startedFrom = baseOfCodebase(input.buildDir);
|
|
37
40
|
const notes = notesForSession(input.buildDir, `${input.step} regression repair`);
|
|
38
41
|
reportStage(`${unitOf(input.moduleName)}: starting the repair agent${input.model ? ` (${input.model})` : ""}`);
|
|
39
42
|
const session = await createExecutor({
|
|
@@ -58,10 +61,11 @@ async function repairRegression(input) {
|
|
|
58
61
|
system: `${fence.note}\n\n${workspaceBlock({ dir: input.buildDir, hasMyDiff: FIX_TOOLS.includes("my_diff") })}`,
|
|
59
62
|
task: withNotes(withTestingScripts(buildRegressionFixPrompt({
|
|
60
63
|
moduleName: input.moduleName,
|
|
61
|
-
|
|
64
|
+
funcIds: input.funcIds,
|
|
62
65
|
specifications,
|
|
63
66
|
modules: sectionModules(input.tree, input.moduleName),
|
|
64
67
|
required: input.required,
|
|
68
|
+
...(startedFrom !== undefined ? { base: startedFrom } : {}),
|
|
65
69
|
failure: input.failure,
|
|
66
70
|
}), input.scripts, ["conformance", "unit"], projectRootOf(input.buildDir)), input.buildDir),
|
|
67
71
|
compaction: { instructions: CODER_COMPACTION },
|
|
@@ -113,7 +117,7 @@ export async function ensureRegressionPasses(input) {
|
|
|
113
117
|
const maxRounds = input.maxRounds ?? DEFAULT_MAX_REGRESSION_ROUNDS;
|
|
114
118
|
for (let round = 0; round <= maxRounds; round += 1) {
|
|
115
119
|
reportStage(round === 0
|
|
116
|
-
? `${unitOf(input.moduleName)}: running the codebase's whole conformance suite - every functionality's tests together (${input.
|
|
120
|
+
? `${unitOf(input.moduleName)}: running the codebase's whole conformance suite - every functionality's tests together (${input.funcIds.join(", ")})`
|
|
117
121
|
: `${unitOf(input.moduleName)}: repair ${round} done - running the whole conformance suite again`);
|
|
118
122
|
const result = await runConformanceScript(input.buildDir, dirs, input.scripts);
|
|
119
123
|
// The script cannot find its toolchain: no repair would pass it - the pulse stops, and the person is told.
|
|
@@ -123,7 +127,7 @@ export async function ensureRegressionPasses(input) {
|
|
|
123
127
|
throw lacking;
|
|
124
128
|
}
|
|
125
129
|
command = result.command;
|
|
126
|
-
runs.push({ command, exitCode: result.exitCode,
|
|
130
|
+
runs.push({ command, exitCode: result.exitCode, funcIds: input.funcIds });
|
|
127
131
|
// The suite passes and its static checks do not: the findings are in the tests, which no repair may touch - the
|
|
128
132
|
// conformance tests' own job fixes them.
|
|
129
133
|
if (staticChecksOnly(result)) {
|
|
@@ -132,7 +136,7 @@ export async function ensureRegressionPasses(input) {
|
|
|
132
136
|
return { passed: false, runs, repairs, command };
|
|
133
137
|
}
|
|
134
138
|
reportActivity(result.exitCode === 0 ? " ✓ whole suite passing" : ` ✗ whole suite failing (exit ${result.exitCode})`);
|
|
135
|
-
input.onEvent?.({ type: "run", command, exitCode: result.exitCode,
|
|
139
|
+
input.onEvent?.({ type: "run", command, exitCode: result.exitCode, funcIds: input.funcIds });
|
|
136
140
|
// The whole suite passing is half of it: the unit tests have to pass with it.
|
|
137
141
|
let failure = result.output;
|
|
138
142
|
let passing = result.exitCode === 0;
|
|
@@ -144,8 +148,8 @@ export async function ensureRegressionPasses(input) {
|
|
|
144
148
|
abandonWork(start, input.moduleName, "the unit testing script could not run");
|
|
145
149
|
throw lackingUnit;
|
|
146
150
|
}
|
|
147
|
-
runs.push({ command: unit.command, exitCode: unit.exitCode,
|
|
148
|
-
input.onEvent?.({ type: "run", command: unit.command, exitCode: unit.exitCode,
|
|
151
|
+
runs.push({ command: unit.command, exitCode: unit.exitCode, funcIds: input.funcIds });
|
|
152
|
+
input.onEvent?.({ type: "run", command: unit.command, exitCode: unit.exitCode, funcIds: input.funcIds });
|
|
149
153
|
if (unit.exitCode !== 0) {
|
|
150
154
|
reportActivity(` ✗ unit tests failing (exit ${unit.exitCode}) with the conformance suite passing`);
|
|
151
155
|
passing = false;
|
|
@@ -161,18 +165,18 @@ export async function ensureRegressionPasses(input) {
|
|
|
161
165
|
}
|
|
162
166
|
if (round === maxRounds)
|
|
163
167
|
break;
|
|
164
|
-
input.onEvent?.({ type: "repair",
|
|
168
|
+
input.onEvent?.({ type: "repair", funcIds: input.funcIds });
|
|
165
169
|
record({ kind: "gate.round", gate: "regression", round: round + 1, of: maxRounds });
|
|
166
170
|
reportStage(`${unitOf(input.moduleName)}: an earlier functionality broke - repairing (round ${round + 1} of ${maxRounds})`);
|
|
167
171
|
repairs.push(await repairRegression({
|
|
168
172
|
testsDirs: dirs,
|
|
169
173
|
buildDir: input.buildDir,
|
|
170
174
|
moduleName: input.moduleName,
|
|
171
|
-
|
|
175
|
+
funcIds: input.funcIds,
|
|
172
176
|
tree: input.tree,
|
|
173
177
|
required: input.required,
|
|
174
178
|
failure,
|
|
175
|
-
step: input.step ?? `${input.moduleName} ${input.
|
|
179
|
+
step: input.step ?? `${input.moduleName} ${input.funcIds.at(-1)}`,
|
|
176
180
|
scripts: input.scripts,
|
|
177
181
|
model: input.model,
|
|
178
182
|
onDelta: input.onDelta,
|