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