@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
@@ -1,38 +1,32 @@
1
1
  import { readFileSync } from "node:fs";
2
2
  import { join } from "node:path";
3
+ import { ACCEPTANCE_TESTS, CONFORMANCE_TESTS, FUNCTIONAL_REQUIREMENTS, REQUIRED_FUNCTIONAL_SPECS } from "../plain/spec.js";
3
4
  import { listFiles } from "./testing.js";
4
- /**
5
- * The two kinds of test the conformance step writes, and how each is told apart in the code.
6
- *
7
- * Conformance tests are written from the functional spec itself: what the functionality does,
8
- * its edge cases, its errors - as if no acceptance tests had been given. Acceptance tests are the
9
- * ones the spec's author wrote, each implemented as a test of its own, doing what it says. The
10
- * two are kept apart even when the acceptance tests would seem to cover the functionality: tests
11
- * derived only from the acceptance tests check what the author thought to check, and nothing
12
- * the spec says beyond it.
13
- *
14
- * Each kind is labelled where anyone reading the tests can see it - in its file names, and in a
15
- * comment the harness looks for - so which test is which, and which acceptance test a test
16
- * implements, never has to be guessed.
17
- */
18
- export function conformanceLabel(frid) {
19
- return `conformance tests of functionality ${frid}`;
5
+ /** The label of a file of a functional spec's conformance tests. */
6
+ export function conformanceLabel(module, funcId) {
7
+ return `conformance tests of ${module}:${funcId}`;
20
8
  }
21
- export function acceptanceLabel(frid, n) {
22
- return `acceptance test ${n} of functionality ${frid}`;
9
+ /** The label right above a test that carries out a scenario. */
10
+ export function scenarioLabel(kind, scenarioId, module, funcId) {
11
+ return `${kind} scenario ${scenarioId} of ${module}:${funcId}`;
23
12
  }
24
- /** An acceptance test as the spec gives it, without its bullet. */
25
- export function acceptanceText(item) {
26
- return item.trim().replace(/^[-*]\s+/, "");
13
+ /** Every scenario of a functional spec, each with its kind, in its file's order: the conformance ones, then the acceptance ones. */
14
+ export function scenariosOf(spec) {
15
+ return [...spec.conformance.map((scenario) => ({ ...scenario, kind: "conformance" })), ...spec.acceptance.map((scenario) => ({ ...scenario, kind: "acceptance" }))];
16
+ }
17
+ /** A scenario as the agents are given it: its label, and its text under it. */
18
+ export function scenarioText(module, funcId, scenario) {
19
+ return `${scenarioLabel(scenario.kind, scenario.id, module, funcId)}:\n${scenario.text.trim()}`;
27
20
  }
28
21
  function normalise(text) {
29
22
  return text.toLowerCase().replace(/\s+/g, " ");
30
23
  }
31
24
  /**
32
- * What is missing from the labelling of a functionality's tests: the conformance label, when no
33
- * test file carries it, and each acceptance test no test is labelled as implementing.
25
+ * What is missing from the labelling of a functional spec's tests: the file label, when no test file carries it, and each
26
+ * scenario no test is labelled as carrying out.
34
27
  */
35
- export function missingTestLabels(testsDir, frid, acceptanceTests) {
28
+ export function missingTestLabels(testsDir, codebase, spec) {
29
+ const module = spec.module ?? codebase;
36
30
  const text = normalise(listFiles(testsDir)
37
31
  .map((file) => {
38
32
  try {
@@ -43,74 +37,110 @@ export function missingTestLabels(testsDir, frid, acceptanceTests) {
43
37
  }
44
38
  })
45
39
  .join("\n"));
46
- // Functionality 2's label must not be found in functionality 2.1's.
47
- const has = (label) => new RegExp(`${normalise(label).replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}(?![0-9.]*[0-9])`).test(text);
40
+ // Matched on the whole key: the label of `todo:log-in` must not be found in that of `todo:log-in-2`.
41
+ const has = (label) => new RegExp(`${normalise(label).replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}(?![a-z0-9_]|-[a-z0-9])`).test(text);
48
42
  const missing = [];
49
- if (!has(conformanceLabel(frid)))
50
- missing.push(`No test file is labelled "${conformanceLabel(frid)}".`);
51
- acceptanceTests.forEach((item, i) => {
52
- if (!has(acceptanceLabel(frid, i + 1)))
53
- missing.push(`No test is labelled "${acceptanceLabel(frid, i + 1)}": ${acceptanceText(item)}`);
54
- });
43
+ if (!has(conformanceLabel(module, spec.funcId)))
44
+ missing.push(`No test file is labelled "${conformanceLabel(module, spec.funcId)}".`);
45
+ for (const scenario of scenariosOf(spec)) {
46
+ const label = scenarioLabel(scenario.kind, scenario.id, module, spec.funcId);
47
+ if (!has(label))
48
+ missing.push(`No test is labelled "${label}": ${scenario.text.trim().split("\n")[0]}`);
49
+ }
55
50
  return missing;
56
51
  }
57
52
  /**
58
- * The test files that carry a functionality's labels - its conformance tests' or any of its acceptance tests'. The
59
- * harness's own files beside them - the notes, the record - are not tests, whatever they mention.
53
+ * Sent back when the labels of functional specs are missing - one, or every one a spec change touches: every one that
54
+ * is missing, in one request, so none is asked for in place of another.
60
55
  */
61
- export function labelledFiles(testsDir, frid) {
62
- const id = frid.replace(/\./g, "\\.");
63
- const label = new RegExp(`(conformance tests|acceptance test \\d+) of functionality ${id}(?![0-9.]*[0-9])`);
64
- return listFiles(testsDir).filter((file) => {
65
- if (file.split(/[\\/]/).at(-1).startsWith(".turri"))
66
- return false;
56
+ export function labelsPromptFor(codebase, items, empty = false) {
57
+ const pending = items.filter((item) => item.missing.length > 0);
58
+ const names = pending.map((item) => `${item.module ?? codebase}:${item.funcId}`);
59
+ const module = pending[0]?.module ?? codebase;
60
+ const which = names.length === 1 ? `functional spec ${names[0]}` : `functional specs ${names.slice(0, -1).join(", ")} and ${names.at(-1)}`;
61
+ const first = pending[0]?.funcId ?? "<func-id>";
62
+ return [
63
+ empty
64
+ ? "There are no tests in your tests folder - nothing ran. Whatever a build folder or an earlier report there says, write " +
65
+ `the conformance tests of ${which} now, as files in your tests folder, labelled as below:`
66
+ : `The tests are not labelled yet as the scenarios they carry out. Missing, for ${which}:`,
67
+ "",
68
+ ...pending.flatMap((item) => item.missing.map((line) => `- ${line}`)),
69
+ "",
70
+ `Every file of a functional spec's tests carries its label, in a comment of its own line: "${conformanceLabel(module, first)}". ` +
71
+ "A file that tests several functional specs has one such line for each.",
72
+ `Every scenario is carried out by at least one test, with a comment right above it: "${scenarioLabel("conformance", "<scenario-id>", module, first)}" or ` +
73
+ `"${scenarioLabel("acceptance", "<scenario-id>", module, first)}" - one not carried out yet is written as a test of its own.`,
74
+ "Add every label missing above. Keep every label and every test that is there already: adding one scenario's label never takes another's away. Then run the tests again.",
75
+ ].join("\n");
76
+ }
77
+ /**
78
+ * The functional specs of a tree of what conformance tests are written for, with their scenarios - those of the modules
79
+ * below with their module, the codebase's own after them.
80
+ */
81
+ export function testedSpecsOf(tree) {
82
+ const of = (section, below) => (tree?.[section] ?? []).map((item) => ({
83
+ funcId: item.id ?? "",
84
+ ...(below && item.module !== undefined ? { module: item.module } : {}),
85
+ conformance: (item[CONFORMANCE_TESTS] ?? []).map(({ id, text }) => ({ id, text })),
86
+ acceptance: (item[ACCEPTANCE_TESTS] ?? []).map(({ id, text }) => ({ id, text })),
87
+ }));
88
+ return [...of(REQUIRED_FUNCTIONAL_SPECS, true), ...of(FUNCTIONAL_REQUIREMENTS, false)];
89
+ }
90
+ const ID = "[a-z0-9]+(?:-[a-z0-9]+)*";
91
+ const END = "(?![a-z0-9_]|-[a-z0-9])";
92
+ /**
93
+ * The labels in the tests folder that name nothing the tests are written for: a functional spec, or a scenario of one,
94
+ * that `specs` does not have - left over from a scenario or a functional spec taken out or renamed, mistyped, or
95
+ * invented. Each with the file it is in.
96
+ */
97
+ export function orphanedLabels(testsDir, codebase, specs) {
98
+ const known = new Set();
99
+ for (const spec of specs) {
100
+ const module = spec.module ?? codebase;
101
+ known.add(conformanceLabel(module, spec.funcId).toLowerCase());
102
+ for (const scenario of scenariosOf(spec))
103
+ known.add(scenarioLabel(scenario.kind, scenario.id, module, spec.funcId).toLowerCase());
104
+ }
105
+ const pattern = new RegExp(`\\b(?:conformance tests of|(?:conformance|acceptance) scenario ${ID} of) [\\w./-]+:${ID}${END}`, "gi");
106
+ const found = [];
107
+ for (const file of listFiles(testsDir)) {
108
+ let text;
67
109
  try {
68
- return label.test(normalise(readFileSync(join(testsDir, file), "utf8")));
110
+ text = readFileSync(join(testsDir, file), "utf8");
69
111
  }
70
112
  catch {
71
- return false;
113
+ continue;
72
114
  }
73
- });
74
- }
75
- /** Sent back when the labels are missing: which, and how to label them. */
76
- export function labelsPrompt(frid, missing, empty = false, hasAcceptance = true) {
77
- return labelsPromptFor([{ frid, missing }], empty, hasAcceptance);
115
+ for (const match of new Set(text.match(pattern) ?? []))
116
+ if (!known.has(match.replace(/\s+/g, " ").toLowerCase()))
117
+ found.push({ file, label: match });
118
+ }
119
+ return found;
78
120
  }
79
- /**
80
- * Sent back when the labels of several functionalities are missing - a spec change that touches them: every one that is
81
- * missing, in one request, so none is asked for in place of another.
82
- */
83
- export function labelsPromptFor(items, empty = false, hasAcceptance = true) {
84
- const pending = items.filter((item) => item.missing.length > 0);
85
- const frids = pending.map((item) => item.frid);
86
- const which = frids.length === 1 ? `functionality ${frids[0]}` : `functionalities ${frids.slice(0, -1).join(", ")} and ${frids.at(-1)}`;
121
+ /** Sent back when labels name nothing the tests are written for. */
122
+ export function orphansPrompt(orphans) {
87
123
  return [
88
- empty
89
- ? "There are no tests in your tests folder - nothing ran. Whatever a build folder or an earlier report there says, write " +
90
- `the conformance tests of ${which} now, as files in your tests folder, labelled as the kinds they are:`
91
- : `The tests are not labelled yet as the kinds they must be kept apart as. Missing, for ${which}:`,
124
+ "These labels name no functional spec, or no scenario of one, that the tests are written for - a scenario or a functional spec",
125
+ "taken out or renamed, a label mistyped, or a test of your own beside the scenarios:",
92
126
  "",
93
- ...pending.flatMap((item) => item.missing.map((line) => `- ${line}`)),
127
+ ...orphans.map((orphan) => `- ${orphan.file}: "${orphan.label}"`),
94
128
  "",
95
- "Every functionality's conformance tests carry its own label, in a comment of its own line: " +
96
- `"${conformanceLabel(frids[0] ?? "1")}"${frids.length > 1 ? `, "${conformanceLabel(frids[1])}", and so on - one line a functionality` : ""}. ` +
97
- `A file that tests several functionalities has one such line for each. A label naming several at once - "conformance tests of functionality 1, 2 and 3" - counts for the first alone.`,
98
- ...(hasAcceptance
99
- ? [
100
- `Every acceptance test is its own test, with a comment right above it: "${acceptanceLabel(frids[0] ?? "1", 1)}:" and the acceptance test's text - one not implemented yet is written as a test of its own, not folded into a conformance test.`,
101
- ]
102
- : []),
103
- "Add every label missing above. Keep every label and every test that is there already: adding one functionality's label never takes another's away. Then run the tests again.",
129
+ "Remove each such test and its label - or, where it carries out a scenario that is there under another label, relabel it with",
130
+ "that scenario's label. Every test carries out a scenario, labelled as it is now. Then run the tests again.",
104
131
  ].join("\n");
105
132
  }
133
+ /** Sent back when the labels of one functional spec are missing. */
134
+ export function labelsPrompt(module, funcId, missing, empty = false) {
135
+ return labelsPromptFor(module, [{ funcId, missing }], empty);
136
+ }
106
137
  /**
107
- * Conformance and acceptance tests in the code folder, where they do not belong: they live in the
108
- * module's tests folder, which the harness runs as the conformance suite. Found by their label, or
109
- * by a test file under a name or folder that says conformance or acceptance - one written without
110
- * a label is misplaced all the same.
138
+ * Conformance tests in the code folder, where they do not belong: they live in the module's tests folder, which the
139
+ * harness runs as the conformance suite. Found by their label, or by a test file under a name or folder that says
140
+ * conformance or acceptance - one written without a label is misplaced all the same.
111
141
  */
112
142
  export function misplacedConformanceTests(codeDir) {
113
- const label = /conformance tests of (functionality [0-9]|the spec)|acceptance test [0-9]+ of functionality/i;
143
+ const label = /conformance tests of [a-z0-9_]+:[a-z0-9]|(conformance|acceptance) scenario \S+ of [a-z0-9_]+:[a-z0-9]/i;
114
144
  return listFiles(codeDir).filter((file) => {
115
145
  const parts = file.toLowerCase().split("/");
116
146
  const named = parts.some((part) => /conformance|acceptance/.test(part)) && /test/.test(parts.at(-1));
@@ -128,15 +158,14 @@ export function misplacedConformanceTests(codeDir) {
128
158
  /** Sent back when there are: to the agent writing the code, or to the one writing the tests. */
129
159
  export function misplacedTestsPrompt(files, codeDir, testsDir) {
130
160
  return [
131
- `These are conformance or acceptance tests, in the code folder (${codeDir}), where they do not belong:`,
161
+ `These are conformance tests, in the code folder (${codeDir}), where they do not belong:`,
132
162
  "",
133
163
  ...files.map((file) => `- ${file}`),
134
164
  "",
135
165
  testsDir === undefined
136
- ? "Conformance and acceptance tests are not yours to write: the conformance step writes them, in the module's " +
137
- "tests folder. Remove these from the code folder. Your tests are the unit tests - write them as unit tests."
138
- : `Conformance and acceptance tests live in your working directory, ${testsDir}, and nowhere else. Move these ` +
139
- "there - labelled as they must be - and remove them from the code folder; the harness runs only that folder as " +
140
- "the conformance suite.",
166
+ ? "Conformance tests are not yours to write: the conformance step writes them, in the module's tests folder. " +
167
+ "Remove these from the code folder. Your tests are the unit tests - write them as unit tests."
168
+ : `Conformance tests live in your working directory, ${testsDir}, and nowhere else. Move these there - labelled as ` +
169
+ "they must be - and remove them from the code folder; the harness runs only that folder as the conformance suite.",
141
170
  ].join("\n");
142
171
  }
@@ -84,6 +84,8 @@ export declare function staticChecksOnly(run: {
84
84
  exitCode: number;
85
85
  timedOut?: boolean;
86
86
  }): boolean;
87
+ /** The kind of testing script a skill writes, by its name - one that writes none, undefined. */
88
+ export declare function scriptKindOfSkill(skill: string): ScriptKind | undefined;
87
89
  /** Raised before a build starts when the script that would decide it is done is missing. */
88
90
  export declare class MissingTestScriptError extends Error {
89
91
  readonly moduleName: string;
@@ -181,6 +181,10 @@ const SKILL = {
181
181
  unit: { skill: "implement-unit-testing-script", file: "run_unittests", what: "unit testing script" },
182
182
  conformance: { skill: "implement-conformance-testing-script", file: "run_conformance_tests", what: "conformance testing script" },
183
183
  };
184
+ /** The kind of testing script a skill writes, by its name - one that writes none, undefined. */
185
+ export function scriptKindOfSkill(skill) {
186
+ return Object.keys(SKILL).find((kind) => SKILL[kind].skill === skill);
187
+ }
184
188
  /** Raised before a build starts when the script that would decide it is done is missing. */
185
189
  export class MissingTestScriptError extends Error {
186
190
  // Declared fields, not parameter properties: node's own type stripping cannot run those.
@@ -0,0 +1,41 @@
1
+ import { type MarshalledPlainSource } from "../plain/spec.ts";
2
+ /**
3
+ * The steps of adding tests to a module, and the one it skipped. Nothing about testing exists until the person asks
4
+ * for it; then, in order: its test reqs - the stack's conformance template, or its own - the conformance testing script
5
+ * for its stack, and the scenarios in its test plain files. A module whose functional specs have scenarios but which
6
+ * skipped one of the first two has its conformance pulses refused - by the work loop and by `conform` alike - and
7
+ * `parse` and the map say what is missing. A module with no scenarios skipped nothing: it is not tested.
8
+ */
9
+ export interface TestingGap {
10
+ /** The first step skipped. */
11
+ step: "test reqs" | "conformance testing script";
12
+ /** What is missing, and the next step - a refusal's, a warning's and the loop's why. */
13
+ text: string;
14
+ /** In a few words, for the map's row. */
15
+ short: string;
16
+ }
17
+ /**
18
+ * The standard library's conformance template for a module's stack, `stdlib/<kind>/conformance/<stack>`, when the module
19
+ * imports a stack template that has one - nothing otherwise.
20
+ */
21
+ export declare function conformanceTemplateOf(plainFile: string): string | undefined;
22
+ /**
23
+ * The conformance testing script a module's tests would run with, looked for under its stack - the one pinned, else
24
+ * the one `stackForModule` gives it - or nothing, with why when there is a why.
25
+ */
26
+ export declare function conformanceScriptOf(plainFile: string, buildDir?: string): {
27
+ script?: string;
28
+ note?: string;
29
+ };
30
+ /**
31
+ * The step of adding tests a module skipped, when its functional specs have scenarios: no test reqs - its own or
32
+ * imported - or no conformance testing script for its stack. `tree` is what its conformance tests would be written from:
33
+ * the codebase's spec, the module's own sections with its imports in, and the functional specs it inherits. The script
34
+ * is looked for under the module's stack - the one pinned, else the one `stackForModule` gives it.
35
+ */
36
+ export declare function testingGap(input: {
37
+ moduleName: string;
38
+ plainFile: string;
39
+ tree: MarshalledPlainSource;
40
+ buildDir?: string;
41
+ }): TestingGap | undefined;
@@ -0,0 +1,110 @@
1
+ import { existsSync, readFileSync } from "node:fs";
2
+ import { dirname, join, resolve } from "node:path";
3
+ import { conformanceTestsOn } from "../conformance-switch.js";
4
+ import { stackForModule, stackPinned, withStack } from "../config.js";
5
+ import { IMPORT_DIRECTIVE, readPlainSourceMetadata } from "../plain/parser.js";
6
+ import { FUNCTIONAL_REQUIREMENTS, REQUIRED_FUNCTIONAL_SPECS, TEST_REQUIREMENTS } from "../plain/spec.js";
7
+ import { isStdlibModule, STDLIB_ROOT } from "../plain/stdlib.js";
8
+ import { projectDirOf, resolveTemplateDirs } from "./paths.js";
9
+ import { hasScenarios } from "./records.js";
10
+ import { findTestScripts, projectRootFor } from "./test-scripts.js";
11
+ /** The stack template a module imports, `stdlib/<kind>/<stack>`, through its imports - none, when it imports none. */
12
+ function stdlibStackOf(plainFile) {
13
+ const dirs = resolveTemplateDirs(resolve(plainFile));
14
+ const seen = new Set();
15
+ const visit = (path) => {
16
+ if (seen.has(path) || !existsSync(path))
17
+ return undefined;
18
+ seen.add(path);
19
+ let imports;
20
+ try {
21
+ imports = readPlainSourceMetadata(readFileSync(path, "utf8")).metadata[IMPORT_DIRECTIVE] ?? [];
22
+ }
23
+ catch {
24
+ return undefined;
25
+ }
26
+ for (const name of imports) {
27
+ const bare = name.replace(/^\.\//, "");
28
+ if (isStdlibModule(bare)) {
29
+ const [, kind, stack, more] = bare.split("/");
30
+ if (kind !== undefined && stack !== undefined && more === undefined && kind !== "lang" && stack !== "common")
31
+ return { kind, stack };
32
+ continue;
33
+ }
34
+ const found = [dirname(path), ...dirs].map((dir) => join(dir, `${bare}.plain`)).find((candidate) => existsSync(candidate));
35
+ const stack = found !== undefined ? visit(resolve(found)) : undefined;
36
+ if (stack !== undefined)
37
+ return stack;
38
+ }
39
+ return undefined;
40
+ };
41
+ return visit(resolve(plainFile));
42
+ }
43
+ /**
44
+ * The standard library's conformance template for a module's stack, `stdlib/<kind>/conformance/<stack>`, when the module
45
+ * imports a stack template that has one - nothing otherwise.
46
+ */
47
+ export function conformanceTemplateOf(plainFile) {
48
+ const template = stdlibStackOf(plainFile);
49
+ const conformance = template !== undefined ? `stdlib/${template.kind}/conformance/${template.stack}` : undefined;
50
+ return conformance !== undefined && existsSync(join(STDLIB_ROOT, `${conformance}.plain`)) ? conformance : undefined;
51
+ }
52
+ /**
53
+ * The conformance testing script a module's tests would run with, looked for under its stack - the one pinned, else
54
+ * the one `stackForModule` gives it - or nothing, with why when there is a why.
55
+ */
56
+ export function conformanceScriptOf(plainFile, buildDir) {
57
+ const stack = stackPinned() ? undefined : stackForModule(plainFile);
58
+ const scripts = () => {
59
+ const root = buildDir !== undefined ? projectRootFor(plainFile, buildDir) : projectDirOf(plainFile);
60
+ return findTestScripts(root, plainFile, buildDir);
61
+ };
62
+ const found = stackPinned() ? scripts() : withStack(stack, scripts);
63
+ return { ...(found.conformance !== undefined ? { script: found.conformance } : {}), ...(found.notes?.conformance !== undefined ? { note: found.notes.conformance } : {}) };
64
+ }
65
+ /** The functional specs of a codebase's tree - its own, or ones it inherits - that have scenarios, as `module:func-id`, a module below's first. */
66
+ function withScenarios(tree, moduleName) {
67
+ const items = (section) => (tree[section] ?? []);
68
+ return [...items(REQUIRED_FUNCTIONAL_SPECS), ...items(FUNCTIONAL_REQUIREMENTS)].filter(hasScenarios).map((item) => `${item.module ?? moduleName}:${item.id}`);
69
+ }
70
+ /** Specs named in a line: the first three, and how many more. */
71
+ function named(specs) {
72
+ return specs.length <= 3 ? specs.join(", ") : `${specs.slice(0, 3).join(", ")} and ${specs.length - 3} more`;
73
+ }
74
+ /**
75
+ * The step of adding tests a module skipped, when its functional specs have scenarios: no test reqs - its own or
76
+ * imported - or no conformance testing script for its stack. `tree` is what its conformance tests would be written from:
77
+ * the codebase's spec, the module's own sections with its imports in, and the functional specs it inherits. The script
78
+ * is looked for under the module's stack - the one pinned, else the one `stackForModule` gives it.
79
+ */
80
+ export function testingGap(input) {
81
+ const { moduleName, plainFile } = input;
82
+ // Conformance tests off: none is written, so no step of adding them is skipped.
83
+ if (!conformanceTestsOn(plainFile))
84
+ return undefined;
85
+ const tested = withScenarios(input.tree, moduleName);
86
+ if (tested.length === 0)
87
+ return undefined;
88
+ const found = conformanceScriptOf(plainFile, input.buildDir);
89
+ const scriptStep = "write the conformance testing script with the implement-conformance-testing-script skill";
90
+ if ((input.tree[TEST_REQUIREMENTS] ?? []).length === 0) {
91
+ const conformance = conformanceTemplateOf(plainFile);
92
+ const reqs = conformance !== undefined ? `import the stack's conformance template, ${conformance}` : "write the module's own ***test reqs***";
93
+ return {
94
+ step: "test reqs",
95
+ text: `${moduleName} has scenarios to test - ${named(tested)} - but no ***test reqs***, so its conformance tests cannot be written - the test reqs are ` +
96
+ `the first step of adding tests. Next step: ${reqs}, with change_plain${found.script === undefined ? `; then ${scriptStep}` : ""}.`,
97
+ short: "scenarios not tested: no test reqs",
98
+ };
99
+ }
100
+ if (found.script === undefined) {
101
+ const note = found.note;
102
+ return {
103
+ step: "conformance testing script",
104
+ text: `${moduleName} has scenarios to test - ${named(tested)} - and ***test reqs*** but no conformance testing script, so nothing can run its conformance ` +
105
+ `tests - the script is the second step of adding tests. Next step: ${scriptStep}.${note !== undefined ? ` ${note}` : ""}`,
106
+ short: "scenarios not tested: no conformance testing script",
107
+ };
108
+ }
109
+ return undefined;
110
+ }
@@ -93,7 +93,7 @@ export declare function draftPrompt(): string;
93
93
  export declare const AGAIN_ASKS = 2;
94
94
  /** For a coding agent regenerating functional specs again that has written nothing: no change is no answer. */
95
95
  export declare function againPrompt(again: Array<{
96
- frid: string;
96
+ funcId: string;
97
97
  module?: string;
98
98
  }>): string;
99
99
  export type UnitVerdict = {
@@ -6,7 +6,7 @@ import { reportActivity } from "./activity.js";
6
6
  import { maskSecrets } from "./masking.js";
7
7
  import { NOTES_FILE } from "./notes.js";
8
8
  import { modulesScratchDir } from "./paths.js";
9
- import { RECORD_FILE } from "./paths.js";
9
+ import { BASE_RECORD_FILE, RECORD_FILE } from "./paths.js";
10
10
  import { missingToolchain, runUnitScript, scriptFailure } from "./test-scripts.js";
11
11
  import { COMMAND_TIMEOUT_MS } from "./shell.js";
12
12
  import { pulseWrote } from "./pulse-files.js";
@@ -210,6 +210,7 @@ export const NOT_CODE = new Set([
210
210
  // Working notes for the agents: not part of the codebase either (see notes.ts).
211
211
  NOTES_FILE,
212
212
  RECORD_FILE,
213
+ BASE_RECORD_FILE,
213
214
  ]);
214
215
  export function listFiles(root) {
215
216
  const found = [];
@@ -305,8 +306,8 @@ export function draftPrompt() {
305
306
  return [
306
307
  "You have not written anything yet: the code folder is as it was when you started.",
307
308
  "",
308
- "If the change needs code - anything it adds to or changes in a functional spec, an implementation req or an",
309
- "acceptance test - stop planning and write it now: your best guess at what the change asks, the code and its",
309
+ "If the change needs code - anything it adds to or changes in a functional spec, a definition or an implementation",
310
+ "req - stop planning and write it now: your best guess at what the change asks, the code and its",
310
311
  "unit tests, as a draft. If you are sure it needs no code at all - it only rewords, describes or renames - say so",
311
312
  "in one line, and why.",
312
313
  "Where the specification leaves something open, pick the most likely reading, write it, and note the assumption",
@@ -317,7 +318,7 @@ export function draftPrompt() {
317
318
  export const AGAIN_ASKS = 2;
318
319
  /** For a coding agent regenerating functional specs again that has written nothing: no change is no answer. */
319
320
  export function againPrompt(again) {
320
- const named = again.map((spec) => `fs ${spec.frid}${spec.module !== undefined ? ` of module \`${spec.module}\`` : ""}`).join(", ");
321
+ const named = again.map((spec) => (spec.module !== undefined ? `\`${spec.module}:${spec.funcId}\`` : `\`${spec.funcId}\``)).join(", ");
321
322
  return [
322
323
  "You have not written anything yet: the code folder is as it was when you started.",
323
324
  "",
@@ -47,7 +47,7 @@ export declare function changedUnitTests(buildDir: string, atStart: Map<string,
47
47
  */
48
48
  export declare function unitReviewTask(options: {
49
49
  buildDir: string;
50
- /** What the work built: "functionality 3 of module `m`", "the spec change to module `m`". */
50
+ /** What the work built: "functional spec `m:add-task`", "the spec change to module `m`". */
51
51
  what: string;
52
52
  /** The specification it built, as the coding agent had it, what is new in this work apart (`reviewedSpec`). */
53
53
  spec: string;
@@ -111,7 +111,7 @@ export function unitReviewTask(options) {
111
111
  "This work is the <spec change> in <new in this work>, section by section: what it adds or changes is what needs",
112
112
  "testing, and the tests it changed still have to test the rest as they did. A change that adds no behaviour - a",
113
113
  "description, a renamed concept, a reworded spec - needs no new test.",
114
- "Behaviour is what a functional spec, an acceptance test or an implementation req states. A definition says what",
114
+ "Behaviour is what a functional spec or an implementation req states. A definition says what",
115
115
  "a word means: a page, a component or a record that only <definitions> name, and no functional spec or",
116
116
  "implementation req of this codebase asks for, has no code yet, and a test for it is not missing.",
117
117
  ]
@@ -303,8 +303,8 @@ async function reviewLoop(options, reviewer) {
303
303
  workdir: options.buildDir,
304
304
  read: [options.buildDir],
305
305
  uphold: [
306
- "- uphold it when because of it a behaviour this work's specification states - in a functional spec, an",
307
- " acceptance test or an implementation req - is not implemented, is implemented other than it says, is not tested, or a",
306
+ "- uphold it when because of it a behaviour this work's specification states - in a functional spec or an",
307
+ " implementation req - is not implemented, is implemented other than it says, is not tested, or a",
308
308
  " test cannot fail when that behaviour breaks. Then the reviewer is right, and the coding agent fixes it.",
309
309
  ],
310
310
  changed,
@@ -1,19 +1,20 @@
1
1
  import { CONFIG_FILE, readProjectConfig } from "./config.js";
2
2
  import { parseArgs } from "./cli-options.js";
3
3
  import { headlessOptions, runLoop } from "./loop-cli.js";
4
- const USAGE = `usage: turri regenerate [file.plain] [frid ...] [options]
4
+ const USAGE = `usage: turri regenerate [file.plain] [module:func-id ...] [options]
5
5
 
6
6
  Regenerates the module's code from its spec as it is now, and everything it requires, bottom first, as
7
7
  the chat does: what is left to regenerate - from what each module's code was regenerated from to its
8
8
  spec now - is planned into jobs, and they run one after the other until the code is regenerated from
9
- the spec, each followed by the conformance tests of what it regenerated when they are on. Then it says
10
- how each went. With functional specs named, those are regenerated though their spec did not change - as regenerate
11
- does in the chat with functionalities named: their code taken out and built anew, doing what the spec says.
9
+ the spec, then the conformance tests of every functional spec with scenarios whose tests are behind. Then it says
10
+ how each went. With functional specs named - each as module:func-id - those are regenerated though their spec did
11
+ not change, as regenerate does in the chat with funcIds named: their code taken out and built anew, doing what the
12
+ spec says.
12
13
 
13
14
  The project's config.turri.yaml, in the current directory, gives the defaults: module (the file to
14
- build when none is given), coding-model (the agents that build), harness-model (the planner), and
15
- conformance-tests. It exits 0 only when every module is complete: regenerated from the spec as it is,
16
- its conformance tests too when they are on.
15
+ build when none is given), coding-model (the agents that build) and harness-model (the planner). It exits
16
+ 0 only when every module is complete: regenerated from the spec as it is, and its conformance tests written
17
+ for every functional spec's scenarios.
17
18
 
18
19
  --dry-run plan the work and print the prompt of the first job, and run nothing (turri glimpse)
19
20
  --start-over start over, without asking, a codebase regenerated without a module it now requires -
@@ -21,23 +22,21 @@ its conformance tests too when they are on.
21
22
  --from-scratch delete the module's code and conformance tests (its lineage keeps them) and regenerate all of it
22
23
  --from-scratch-chain the same for every module of its requires chain, built again from the bottom - after
23
24
  modules were split or moved
24
- --harness-model <p/id> config.turri.yaml's harness-model for this run: the planner and the conformance chooser
25
+ --harness-model <p/id> config.turri.yaml's harness-model for this run: the planner
25
26
  --coding-model <p/id> its coding-model, --model too: the agents that build; p/id:level for how hard a model thinks
26
27
  --judge-model <p/id> its judge-model: the judge and the test review
27
28
  --review <true|false> its review: whether the code and its tests are reviewed once they pass
28
29
  --mutation-testing <true|false>
29
30
  its mutation-testing: whether the coding agent and the reviewer can put a bug in the code
30
31
  for one test run (can_it_fail), to see that a test catches it
31
- --conformance-tests <all|chosen|module|false>
32
- its conformance-tests: which conformance tests are written and run
33
32
  --build-dir <dir> as the run ends, however it ends, export the code there: the furthest-built codebase's
34
33
  last commit, which holds every module it requires - the folder's contents are replaced
35
34
  --report <file.json> write a report of the run there - outcome, usage, pulses, functional specs completed -
36
35
  as it starts, as each pulse ends and as it exits (docs/headless.md)
37
36
 
38
37
  PLAIN_TEMPLATE_DIRS colon-separated template directories`;
39
- const { positional, options } = parseArgs(process.argv.slice(2), ["--dry-run", "--start-over", "--from-scratch", "--from-scratch-chain"], USAGE, ["--build-dir", "--report", "--conformance-tests", "--harness-model", "--judge-model", "--review", "--mutation-testing"]);
40
- const { report, buildDir } = headlessOptions(options, true);
38
+ const { positional, options } = parseArgs(process.argv.slice(2), ["--dry-run", "--start-over", "--from-scratch", "--from-scratch-chain"], USAGE, ["--build-dir", "--report", "--harness-model", "--judge-model", "--review", "--mutation-testing"]);
39
+ const { report, buildDir } = headlessOptions(options);
41
40
  const named = positional[0] ?? readProjectConfig(process.cwd()).module;
42
41
  // Functional specs named after the file: regenerated again though their spec did not change.
43
42
  const again = positional.slice(1);
@@ -22,6 +22,10 @@ function outsideBuiltCode(tool, dir) {
22
22
  if (relative(dir, path).split(sep).includes("plain_modules")) {
23
23
  throw new Error(`Not written: ${shortPath(path)} is built code. A release never changes it - a fix belongs in the spec.`);
24
24
  }
25
+ // The ***plain - a module, a test plain file - is the person's: a release that finds it wrong says so, never rewrites it.
26
+ if (path.endsWith(".plain")) {
27
+ throw new Error(`Not written: ${shortPath(path)} is ***plain. A release never changes it - say what is wrong with it instead.`);
28
+ }
25
29
  return tool.execute(toolCallId, params, signal, onUpdate, ctx);
26
30
  },
27
31
  };
@@ -1,4 +1,5 @@
1
1
  import { closeSync, openSync, readSync, statSync } from "node:fs";
2
+ import { refuseTestPlainFile } from "./test-plain-files.js";
2
3
  import { resolve } from "node:path";
3
4
  import { configForSpec, modelSpec, projectModels, readProjectConfig } from "./config.js";
4
5
  import { createDeployState } from "./release-tools.js";
@@ -90,6 +91,7 @@ async function main() {
90
91
  process.exit(1);
91
92
  }
92
93
  const plainFile = resolve(named);
94
+ refuseTestPlainFile(plainFile);
93
95
  // A release follows steps - a script, a start command: the coding model does that well, thinking low.
94
96
  const model = args.model ?? modelSpec(projectModels(configForSpec(plainFile)).coding);
95
97
  const state = createDeployState();
@@ -1,4 +1,5 @@
1
1
  import { isAbsolute, relative, resolve } from "node:path";
2
+ import { refuseTestPlainFile } from "./test-plain-files.js";
2
3
  import { defineTool } from "@earendil-works/pi-coding-agent";
3
4
  import { Type } from "typebox";
4
5
  import { deployLogDir, deployTarget, describeDeployment, exportModule, hostDirOf, shortPath, startDeployed, stopDeployed, } from "./regenerate/release.js";
@@ -88,6 +89,7 @@ export function createDeployTools(options) {
88
89
  if (named === undefined)
89
90
  throw new Error("Which spec? config.turri.yaml names no module: pass plain_file.");
90
91
  const plainFile = resolve(options.cwd, named);
92
+ refuseTestPlainFile(plainFile);
91
93
  const target = deployTarget(plainFile, params.in_module);
92
94
  const guidance = params.guidance?.trim() || undefined;
93
95
  // Released already: the module's last commit is what the last release shipped. Nothing is exported or stopped -
@@ -8,7 +8,7 @@ import { type Job } from "./regenerate/jobs.ts";
8
8
  * pulse failed, a crash, a signal. A process killed outright (SIGKILL) leaves the one written as the last pulse ended.
9
9
  * Each write is atomic: the file is written beside it and renamed over it, so a reader never sees half of one.
10
10
  */
11
- export declare const REPORT_SCHEMA_VERSION = 1;
11
+ export declare const REPORT_SCHEMA_VERSION = 2;
12
12
  /** What the usage of some model calls adds up to, as the report gives it. */
13
13
  export interface UsageTotals {
14
14
  input_tokens: number;
@@ -42,6 +42,7 @@ export interface CompletionCounts {
42
42
  modules: Array<{
43
43
  module: string;
44
44
  specified: number;
45
+ withScenarios: number;
45
46
  completed: number;
46
47
  conformanceTested: number;
47
48
  incomplete: string[];
@@ -71,7 +72,6 @@ export interface RunReportOptions {
71
72
  specFile: string;
72
73
  projectDir: string;
73
74
  buildDir?: string;
74
- conformanceTests: string;
75
75
  /** Whether the code and its tests are reviewed once they pass. */
76
76
  review?: boolean;
77
77
  /** Whether the coding agent and the reviewer had can_it_fail. */