@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.
Files changed (216) hide show
  1. package/README.md +6 -6
  2. package/dist/advisor-chat.d.ts +63 -0
  3. package/dist/advisor-chat.js +290 -0
  4. package/dist/background-skills.d.ts +53 -0
  5. package/dist/background-skills.js +121 -0
  6. package/dist/bedrock-region.d.ts +66 -0
  7. package/dist/bedrock-region.js +144 -0
  8. package/dist/btw.d.ts +1 -1
  9. package/dist/btw.js +1 -1
  10. package/dist/change-plain-cli.js +20 -13
  11. package/dist/chat-idle.d.ts +7 -0
  12. package/dist/chat-idle.js +20 -0
  13. package/dist/chats.d.ts +10 -0
  14. package/dist/chats.js +23 -0
  15. package/dist/cli-options.d.ts +1 -2
  16. package/dist/cli-options.js +2 -7
  17. package/dist/cli.js +11 -3
  18. package/dist/config-command.d.ts +14 -0
  19. package/dist/config-command.js +35 -15
  20. package/dist/config-edit.d.ts +3 -3
  21. package/dist/config-edit.js +16 -12
  22. package/dist/config.d.ts +54 -34
  23. package/dist/config.js +40 -44
  24. package/dist/conform-cli.js +12 -10
  25. package/dist/conformance-switch.d.ts +34 -0
  26. package/dist/conformance-switch.js +108 -0
  27. package/dist/cultivate-cli.js +15 -5
  28. package/dist/drift-cli.js +2 -0
  29. package/dist/extension.d.ts +15 -0
  30. package/dist/extension.js +260 -83
  31. package/dist/host-session.d.ts +26 -0
  32. package/dist/host-session.js +30 -2
  33. package/dist/host.d.ts +5 -2
  34. package/dist/host.js +7 -1
  35. package/dist/init-config.d.ts +1 -1
  36. package/dist/init-config.js +20 -11
  37. package/dist/loop-cli.d.ts +3 -3
  38. package/dist/loop-cli.js +13 -16
  39. package/dist/loop-tools.js +18 -10
  40. package/dist/model-check.d.ts +8 -3
  41. package/dist/model-check.js +13 -8
  42. package/dist/new-project.js +12 -8
  43. package/dist/{validate-cli.js → parse-cli.js} +11 -5
  44. package/dist/parse-tool.d.ts +9 -0
  45. package/dist/parse-tool.js +33 -0
  46. package/dist/pi-launch.js +1 -1
  47. package/dist/plain/check.d.ts +53 -6
  48. package/dist/plain/check.js +217 -24
  49. package/dist/plain/concepts.d.ts +8 -0
  50. package/dist/plain/concepts.js +66 -23
  51. package/dist/plain/cycles.d.ts +21 -0
  52. package/dist/plain/cycles.js +190 -0
  53. package/dist/plain/errors.d.ts +4 -0
  54. package/dist/plain/errors.js +7 -0
  55. package/dist/plain/files.d.ts +18 -1
  56. package/dist/plain/files.js +39 -2
  57. package/dist/plain/graph.d.ts +33 -4
  58. package/dist/plain/graph.js +60 -12
  59. package/dist/plain/highlight.d.ts +3 -2
  60. package/dist/plain/highlight.js +4 -3
  61. package/dist/plain/index.d.ts +8 -5
  62. package/dist/plain/index.js +8 -5
  63. package/dist/plain/liquid.js +1 -6
  64. package/dist/plain/markdown.d.ts +32 -3
  65. package/dist/plain/markdown.js +146 -24
  66. package/dist/plain/module-concepts.js +2 -2
  67. package/dist/plain/parser.d.ts +83 -28
  68. package/dist/plain/parser.js +381 -260
  69. package/dist/plain/plain.tmLanguage.json +29 -3
  70. package/dist/plain/project.d.ts +48 -0
  71. package/dist/plain/project.js +155 -0
  72. package/dist/plain/spec.d.ts +90 -28
  73. package/dist/plain/spec.js +107 -133
  74. package/dist/plain/test-plain-file.d.ts +59 -0
  75. package/dist/plain/test-plain-file.js +232 -0
  76. package/dist/regenerate/activity.d.ts +20 -9
  77. package/dist/regenerate/activity.js +11 -4
  78. package/dist/regenerate/advisor-records.d.ts +93 -0
  79. package/dist/regenerate/advisor-records.js +110 -0
  80. package/dist/regenerate/advisor-runs.d.ts +87 -0
  81. package/dist/regenerate/advisor-runs.js +327 -0
  82. package/dist/regenerate/base-folder.d.ts +68 -0
  83. package/dist/regenerate/base-folder.js +168 -0
  84. package/dist/regenerate/cases.d.ts +9 -6
  85. package/dist/regenerate/cases.js +71 -15
  86. package/dist/regenerate/change-view.d.ts +8 -3
  87. package/dist/regenerate/change-view.js +71 -22
  88. package/dist/regenerate/chunk-plan.d.ts +36 -15
  89. package/dist/regenerate/chunk-plan.js +165 -59
  90. package/dist/regenerate/conform.d.ts +3 -0
  91. package/dist/regenerate/conform.js +6 -5
  92. package/dist/regenerate/conformance-agents.d.ts +18 -39
  93. package/dist/regenerate/conformance-agents.js +60 -88
  94. package/dist/regenerate/conformance.d.ts +23 -20
  95. package/dist/regenerate/conformance.js +118 -121
  96. package/dist/regenerate/diff-view.d.ts +1 -1
  97. package/dist/regenerate/diff-view.js +20 -4
  98. package/dist/regenerate/drift.d.ts +47 -15
  99. package/dist/regenerate/drift.js +101 -30
  100. package/dist/regenerate/jobs.d.ts +2 -2
  101. package/dist/regenerate/jobs.js +1 -1
  102. package/dist/regenerate/ledger.d.ts +19 -0
  103. package/dist/regenerate/loop.d.ts +61 -49
  104. package/dist/regenerate/loop.js +473 -340
  105. package/dist/regenerate/paths.d.ts +4 -0
  106. package/dist/regenerate/paths.js +7 -1
  107. package/dist/regenerate/plain-palette.js +2 -0
  108. package/dist/regenerate/prompt.d.ts +57 -19
  109. package/dist/regenerate/prompt.js +157 -122
  110. package/dist/regenerate/records.d.ts +43 -12
  111. package/dist/regenerate/records.js +102 -88
  112. package/dist/regenerate/regenerate.js +29 -11
  113. package/dist/regenerate/regression.d.ts +4 -4
  114. package/dist/regenerate/regression.js +14 -10
  115. package/dist/regenerate/renames.d.ts +42 -0
  116. package/dist/regenerate/renames.js +185 -0
  117. package/dist/regenerate/requires.d.ts +1 -1
  118. package/dist/regenerate/requires.js +10 -3
  119. package/dist/regenerate/resources.d.ts +5 -3
  120. package/dist/regenerate/resources.js +4 -4
  121. package/dist/regenerate/scripts-being-written.d.ts +19 -0
  122. package/dist/regenerate/scripts-being-written.js +30 -0
  123. package/dist/regenerate/spec-map-view.d.ts +2 -2
  124. package/dist/regenerate/spec-map-view.js +46 -15
  125. package/dist/regenerate/spec-map.d.ts +27 -5
  126. package/dist/regenerate/spec-map.js +92 -60
  127. package/dist/regenerate/spec-steps.d.ts +48 -5
  128. package/dist/regenerate/spec-steps.js +213 -28
  129. package/dist/regenerate/state.d.ts +4 -3
  130. package/dist/regenerate/stop-report.d.ts +1 -1
  131. package/dist/regenerate/stop-report.js +10 -10
  132. package/dist/regenerate/test-advisor.d.ts +101 -0
  133. package/dist/regenerate/test-advisor.js +379 -0
  134. package/dist/regenerate/test-kinds.d.ts +69 -31
  135. package/dist/regenerate/test-kinds.js +109 -80
  136. package/dist/regenerate/test-scripts.d.ts +2 -0
  137. package/dist/regenerate/test-scripts.js +4 -0
  138. package/dist/regenerate/testing-steps.d.ts +41 -0
  139. package/dist/regenerate/testing-steps.js +110 -0
  140. package/dist/regenerate/testing.d.ts +1 -1
  141. package/dist/regenerate/testing.js +5 -4
  142. package/dist/regenerate/unit-review.d.ts +1 -1
  143. package/dist/regenerate/unit-review.js +3 -3
  144. package/dist/regenerate-cli.js +11 -12
  145. package/dist/release-agent.js +4 -0
  146. package/dist/release-cli.js +2 -0
  147. package/dist/release-tools.js +2 -0
  148. package/dist/run-report.d.ts +2 -2
  149. package/dist/run-report.js +4 -3
  150. package/dist/self-invocation.d.ts +1 -1
  151. package/dist/self-invocation.js +1 -1
  152. package/dist/sentinel.js +1 -1
  153. package/dist/setup.d.ts +10 -0
  154. package/dist/setup.js +52 -4
  155. package/dist/skill.d.ts +10 -0
  156. package/dist/skill.js +6 -0
  157. package/dist/skills.js +3 -1
  158. package/dist/spec-tools.js +21 -31
  159. package/dist/test-plain-files.d.ts +41 -0
  160. package/dist/test-plain-files.js +177 -0
  161. package/docs/README.md +3 -1
  162. package/docs/authoring-plain.md +46 -13
  163. package/docs/chats.md +3 -1
  164. package/docs/config.md +20 -15
  165. package/docs/deploy.md +1 -1
  166. package/docs/development.md +2 -1
  167. package/docs/headless.md +15 -18
  168. package/docs/hosting.md +28 -12
  169. package/docs/installation.md +1 -0
  170. package/docs/parser.md +88 -27
  171. package/docs/regenerating.md +89 -54
  172. package/docs/sentinel.md +1 -1
  173. package/docs/skills-and-gates.md +3 -1
  174. package/docs/test-advisor.md +110 -0
  175. package/docs/theming.md +1 -1
  176. package/docs/using.md +17 -15
  177. package/genome/rules/bullet-continuation.md +8 -8
  178. package/genome/rules/concise-specs-examples.md +111 -80
  179. package/genome/rules/concise-specs-integration-examples.md +56 -41
  180. package/genome/rules/concise-specs.md +17 -16
  181. package/genome/rules/definitions.md +5 -5
  182. package/genome/rules/examples/integration-embedded-testing/run_conformance_tests_java.sh +1 -1
  183. package/genome/rules/func-specs.md +69 -74
  184. package/genome/rules/impl-reqs.md +4 -4
  185. package/genome/rules/integration-embedded-testing.md +20 -20
  186. package/genome/rules/integration-embedded.md +31 -31
  187. package/genome/rules/integration-standalone.md +9 -9
  188. package/genome/rules/integrations.md +18 -18
  189. package/genome/rules/linked-resources.md +19 -16
  190. package/genome/rules/module-structure.md +21 -14
  191. package/genome/rules/project-model.md +18 -17
  192. package/genome/rules/required-concepts.md +1 -1
  193. package/genome/rules/requires-modules.md +6 -5
  194. package/genome/rules/semantic-ids.md +49 -0
  195. package/genome/rules/stdlib.md +4 -5
  196. package/genome/rules/test-plain-files.md +130 -0
  197. package/genome/rules/test-reqs.md +8 -8
  198. package/genome/rules/turri-regeneration.md +24 -8
  199. package/genome/skills/analyze-2-func-specs/SKILL.md +36 -21
  200. package/genome/skills/analyze-func-specs/SKILL.md +46 -32
  201. package/genome/skills/break-down-func-spec/SKILL.md +91 -67
  202. package/genome/skills/consolidate-concepts/SKILL.md +10 -10
  203. package/genome/skills/convert-old-syntax/SKILL.md +68 -0
  204. package/genome/skills/convert-old-syntax/references/rubric.md +11 -0
  205. package/genome/skills/debug-specs/SKILL.md +62 -56
  206. package/genome/skills/implement-conformance-testing-script/SKILL.md +19 -15
  207. package/genome/skills/implement-conformance-testing-script/assets/run_conformance_tests_java.sh +2 -2
  208. package/genome/skills/implement-conformance-testing-script/assets/run_conformance_tests_python.sh +2 -2
  209. package/genome/skills/implement-unit-testing-script/SKILL.md +14 -10
  210. package/genome/skills/refactor-module/SKILL.md +35 -35
  211. package/genome/skills/resolve-section-ownership/SKILL.md +9 -9
  212. package/genome/skills/resolve-spec-conflict/SKILL.md +35 -29
  213. package/package.json +7 -6
  214. package/dist/regenerate/choose-conformance.d.ts +0 -32
  215. package/dist/regenerate/choose-conformance.js +0 -117
  216. /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, and the acceptance tests under each functional spec.
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 number and its acceptance tests - the
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
- /** The spec as the code is built from it: no test reqs, and no acceptance tests under the functional specs. */
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 chosen, each as the spec has it now with its acceptance tests - and the code's
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, chosen?: Chosen): 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
- * Of the functional specs given, by number, those the code record does not have as `tree` has them - not built,
75
- * or built from other text: no conformance test can be written for them yet. `tree` is a spec, its functional
76
- * specs numbered by place, or what conformance tests are written for, each with its number.
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 notBuilt(code: MarshalledPlainSource | null, tree: MarshalledPlainSource, frids: string[]): string[];
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, and the acceptance tests under each functional spec.
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 number and its acceptance tests - the
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
- /** A functional spec as the code sees it: without its acceptance tests. */
38
- function withoutAcceptanceTests(item) {
39
- const { [ACCEPTANCE_TESTS]: _tests, ...rest } = item;
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 acceptance tests under the functional specs. */
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(withoutAcceptanceTests),
48
- [REQUIRED_FUNCTIONAL_SPECS]: itemsOf(tree, REQUIRED_FUNCTIONAL_SPECS).map(withoutAcceptanceTests),
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
- /** Every linked resource in a tree, wherever an item has them - a functional spec, its acceptance tests, a definition, a req. */
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, index) => ({ ...withoutAcceptanceTests(item), module: sourceModule, frid: String(index + 1) })),
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 chosen, each as the spec has it now with its acceptance tests - and the code's
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, chosen = () => true) {
167
- const built = itemsOf(code, FUNCTIONAL_REQUIREMENTS);
168
- const now = itemsOf(spec, FUNCTIONAL_REQUIREMENTS);
169
- const tested = new Map(itemsOf(tests, FUNCTIONAL_REQUIREMENTS).map((item) => [item.frid, item]));
170
- const inCode = new Set(built.map((item) => item.markdown.trim()));
171
- const specs = [];
172
- for (let index = 0; index < Math.max(built.length, now.length); index += 1) {
173
- const frid = String(index + 1);
174
- const current = now[index];
175
- if (current !== undefined && built[index]?.markdown.trim() === current.markdown.trim() && !marksRegenerateAgain(built[index])) {
176
- if (chosen(frid))
177
- specs.push({ ...current, frid });
178
- continue;
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
- // The code is behind the spec here - built from other text, not built, or being regenerated again: its tests stay
181
- // as they are until it catches up. Taken out of the spec, they go once the code has dropped it too.
182
- const kept = tested.get(frid);
183
- if (kept === undefined)
184
- continue;
185
- if (current !== undefined || inCode.has(kept.markdown.trim()))
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]: specs,
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, by number, those the code record does not have as `tree` has them - not built,
197
- * or built from other text: no conformance test can be written for them yet. `tree` is a spec, its functional
198
- * specs numbered by place, or what conformance tests are written for, each with its number.
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, frids) {
201
- const built = itemsOf(code, FUNCTIONAL_REQUIREMENTS);
202
- const wanted = itemsOf(tree, FUNCTIONAL_REQUIREMENTS);
203
- const numbered = wanted.some((item) => item.frid !== undefined);
204
- return frids.filter((frid) => {
205
- const item = numbered ? wanted.find((entry) => entry.frid === frid) : wanted[Number(frid) - 1];
206
- const have = built[Number(frid) - 1];
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
- * An older record, as the tree it describes. Its own functional specs are taken up to the first
216
- * that did not build: the tree says what is built, and the one that failed is not.
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 treeFromV2(stored) {
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]: (stored[DEFINITIONS] ?? []).map((text) => asItem(text)),
231
- [NON_FUNCTIONAL_REQUIREMENTS]: (stored[NON_FUNCTIONAL_REQUIREMENTS] ?? []).map((text) => asItem(text)),
232
- [TEST_REQUIREMENTS]: (stored[TEST_REQUIREMENTS] ?? []).map((text) => asItem(text)),
233
- [FUNCTIONAL_REQUIREMENTS]: built,
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
- function treeFromLegacy(stored) {
238
- const entries = Object.values(stored.functionalities).sort((a, b) => Number(a.frid) - Number(b.frid));
239
- const latest = entries.at(-1)?.specifications;
240
- const built = [];
241
- for (const [index, entry] of entries.entries()) {
242
- if (Number(entry.frid) !== index + 1 || entry.unitTests !== "passed")
243
- break;
244
- built.push(asItem(entry.specifications[FUNCTIONAL_REQUIREMENTS]?.at(-1) ?? "", entry.specifications[ACCEPTANCE_TESTS]));
245
- }
246
- return withoutEmpty({
247
- [DEFINITIONS]: (latest?.[DEFINITIONS] ?? []).map((text) => asItem(text)),
248
- [NON_FUNCTIONAL_REQUIREMENTS]: (latest?.[NON_FUNCTIONAL_REQUIREMENTS] ?? []).map((text) => asItem(text)),
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
- /** A record's text as the tree it holds, whatever layout wrote it - or null when it is not one. */
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
- if (typeof raw.version === "number")
267
- return treeFromV2(raw);
268
- if ("functionalities" in raw)
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 { createCopyResourceTool, loadLinkedResources, resourceImages } from "./resources.js";
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 lastFrid = [...getFrids(tree)].at(-1);
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 builtFrids = [...getFrids(builtNow)];
256
- const specificationsBefore = builtFrids.length > 0 ? getSpecificationsForFrid(builtNow, builtFrids.at(-1))[0] : { ...sectionsOf(builtNow), [FUNCTIONAL_REQUIREMENTS]: [] };
257
- const linkedResources = loadLinkedResources(tree, lastFrid ?? null, resolveTemplateDirs(tip.plainFile, templateDirs));
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
- frids?: string[];
11
+ funcIds?: string[];
12
12
  } | {
13
13
  type: "repair";
14
- frids: string[];
14
+ funcIds: string[];
15
15
  };
16
16
  export interface RegressionRun extends TestRun {
17
17
  /** Functionalities whose tests took part in this run. */
18
- frids: string[];
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
- frids: string[];
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.frids[input.frids.length - 1]);
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
- frids: input.frids,
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.frids.join(", ")})`
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, frids: input.frids });
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, frids: input.frids });
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, frids: input.frids });
148
- input.onEvent?.({ type: "run", command: unit.command, exitCode: unit.exitCode, frids: input.frids });
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", frids: input.frids });
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
- frids: input.frids,
175
+ funcIds: input.funcIds,
172
176
  tree: input.tree,
173
177
  required: input.required,
174
178
  failure,
175
- step: input.step ?? `${input.moduleName} ${input.frids.at(-1)}`,
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,