@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,5 +1,6 @@
1
1
  import { readFileSync, writeFileSync } from "node:fs";
2
2
  import { basename, dirname, join, resolve } from "node:path";
3
+ import { scenariosOf, scenarioText } from "./test-kinds.js";
3
4
  import { defineTool } from "@earendil-works/pi-coding-agent";
4
5
  import { Type } from "typebox";
5
6
  import { ask, BEGIN, createExecutor } from "../pi.js";
@@ -10,7 +11,8 @@ import { reportActivity } from "./activity.js";
10
11
  import { runTiming, withTestingScripts } from "./test-scripts.js";
11
12
  import { createWebTools, WEB_TOOLS } from "./web.js";
12
13
  import { diffFiles, digestFiles } from "./codebase.js";
13
- import { ERROR_CAUSE_RULES, fixMethod, INTEGRITY_RULES, specPrimer } from "./prompt.js";
14
+ import { BASE_FOLDER_RULES, baseFolderBlock, ERROR_CAUSE_RULES, fixMethod, INTEGRITY_RULES, specPrimer } from "./prompt.js";
15
+ import { baseOfCodebase } from "./base-folder.js";
14
16
  import { conformanceReviewCasesBlock } from "./cases.js";
15
17
  import { REVIEW_LESSON } from "./notes.js";
16
18
  import { CODER_COMPACTION, REVIEWER_COMPACTION } from "./compaction.js";
@@ -20,9 +22,8 @@ export function explorationText(exploration, replies = []) {
20
22
  const sections = [
21
23
  `## How to build and run it\n\n${exploration.buildAndRun.trim()}`,
22
24
  `## How the conformance tests reach it, run by the testing script\n\n${exploration.testsReachIt.trim()}`,
23
- `## How each functionality is reached\n\n${exploration.interfaces.map((item) => `### ${item.module} ${item.frid}\n\n${item.how.trim()}`).join("\n\n")}`,
25
+ `## How each functionality is reached\n\n${exploration.interfaces.map((item) => `### ${item.module} ${item.funcId}\n\n${item.how.trim()}`).join("\n\n")}`,
24
26
  `## What it needs around it\n\n${exploration.environment.trim()}`,
25
- `## Plan for the conformance tests\n\n${exploration.plan.map((item) => `### ${item.module} ${item.frid}\n\n${item.tests.trim()}`).join("\n\n")}`,
26
27
  ...(exploration.other?.trim() ? [`## Anything else\n\n${exploration.other.trim()}`] : []),
27
28
  // An account it could not complete is passed on with what it wrote, so nothing it found is lost.
28
29
  ...(replies.length > 0 ? [`## What the explorer wrote as it worked\n\n${replies.map((reply) => reply.trim()).join("\n\n")}`] : []),
@@ -32,7 +33,7 @@ export function explorationText(exploration, replies = []) {
32
33
  /**
33
34
  * What an exploration lacks for the conformance tests to be written from it alone, or nothing when
34
35
  * it is whole: how to build and run it, and - for every functionality the tests are for - how it
35
- * is reached and what to test.
36
+ * is reached.
36
37
  */
37
38
  /**
38
39
  * How many tool calls the agents with one thing to decide may make before they are told to decide:
@@ -47,9 +48,7 @@ export const JUDGE_CALLS = 30;
47
48
  export const MAX_EXPLORATION_CHARS = 12_000;
48
49
  /** How many times the explorer may hand an exploration over: each ends its turn, and is checked. */
49
50
  export const EXPLORATION_SUBMISSIONS = 3;
50
- export function explorationProblems(exploration, scope, moduleName,
51
- /** The functionalities whose acceptance tests the plan has to carry out, and how many each has. */
52
- acceptance = []) {
51
+ export function explorationProblems(exploration, scope, moduleName) {
53
52
  if (exploration === undefined)
54
53
  return ["You have not called submit_exploration."];
55
54
  const problems = [];
@@ -58,11 +57,11 @@ acceptance = []) {
58
57
  problems.push(`It is ${size} characters - keep it under ${MAX_EXPLORATION_CHARS}. Hand over the interface, not the code: the routes, ` +
59
58
  "commands, fields and their types, and the setup - no code listings, no files pasted whole.");
60
59
  }
61
- const unknown = [...exploration.interfaces, ...exploration.plan].filter((item) => item.frid !== "" && !scope.functionalities.some((ref) => ref.module === item.module && ref.frid === item.frid));
60
+ const unknown = exploration.interfaces.filter((item) => item.funcId !== "" && !scope.functionalities.some((ref) => ref.module === item.module && ref.funcId === item.funcId));
62
61
  if (unknown.length > 0 && scope.tested === undefined) {
63
- const names = [...new Set(unknown.map((item) => `${item.module} ${item.frid}`))];
64
- problems.push(`${names.join(", ")} ${names.length === 1 ? "is not a functionality" : "are not functionalities"}: give module and id as ` +
65
- `the specification numbers them - ${scope.functionalities.filter((ref) => ref.module === moduleName).map(refLabel).join(", ") || "none yet"}.`);
62
+ const names = [...new Set(unknown.map((item) => `${item.module} ${item.funcId}`))];
63
+ problems.push(`${names.join(", ")} ${names.length === 1 ? "is not a functionality" : "are not functionalities"}: give module and func-id as ` +
64
+ `the specification has them - ${scope.functionalities.filter((ref) => ref.module === moduleName).map(refLabel).join(", ") || "none yet"}.`);
66
65
  }
67
66
  const short = (text) => (text ?? "").trim().length < 40;
68
67
  if (short(exploration.buildAndRun))
@@ -75,46 +74,26 @@ acceptance = []) {
75
74
  if (short(exploration.environment))
76
75
  problems.push("environment says too little: every variable, configuration file, service, database and fixture it needs, and how each test gets a fresh one - or say plainly it needs none.");
77
76
  const reached = exploration.interfaces.filter((item) => !short(item.how));
78
- const planned = exploration.plan.filter((item) => !short(item.tests));
79
77
  if (scope.tested !== undefined) {
80
- // A functionality's run: the one under test, reached and planned for.
78
+ // A functionality's run: the one under test, reached.
81
79
  const tested = scope.tested;
82
- const has = (list) => list.some((item) => item.module === tested.module && item.frid === tested.frid);
80
+ const has = (list) => list.some((item) => item.module === tested.module && item.funcId === tested.funcId);
83
81
  if (!has(reached))
84
82
  problems.push(`interfaces does not say how ${refLabel(tested)} is reached: the command and its arguments, or the route, method, headers and the request and response fields.`);
85
- if (!has(planned))
86
- problems.push(`plan has no tests for ${refLabel(tested)}: the cases to write, how each is set up and invoked, and what it checks as the specification says.`);
87
83
  }
88
84
  else {
89
- // A spec change's: what it touches is for the explorer to say, but some of this module has to be reached and planned.
90
- if (!reached.some((item) => item.module === moduleName))
91
- problems.push(`interfaces says nothing of how \`${moduleName}\`'s functionalities the change touches are reached - one entry each.`);
92
- if (!planned.some((item) => item.module === moduleName))
93
- problems.push(`plan has no tests for \`${moduleName}\`: the cases the change adds, changes or removes, for each functionality it touches.`);
94
- }
95
- // Every acceptance test is carried out, step by step, from outside: a plan that leaves one out
96
- // leaves its author to write a placeholder.
97
- for (const item of acceptance) {
98
- const text = exploration.plan
99
- .filter((entry) => entry.module === item.module && entry.frid === item.frid)
100
- .map((entry) => entry.tests)
101
- .join("\n");
102
- const named = new Set([...text.matchAll(/acceptance test\s*(\d+)/gi)].map((match) => Number(match[1])));
103
- const missing = Array.from({ length: item.count }, (_, index) => index + 1).filter((n) => !named.has(n));
104
- if (missing.length > 0) {
105
- problems.push(`plan does not carry out ${missing.map((n) => `acceptance test ${n}`).join(", ")} of ${item.module} ${item.frid}: name each ` +
106
- '"acceptance test N" as the specification numbers them, and say how each of its steps is done from outside - through ' +
107
- "which endpoint, or which class and method where the test reqs let the tests call a component directly - with what, " +
108
- "and what it asserts. A long acceptance test is planned whole, never cut down.");
85
+ // A spec change's: what it touches is for the explorer to say, but some of a module it touches has to be reached.
86
+ const modules = scope.touchedModules !== undefined && scope.touchedModules.length > 0 ? scope.touchedModules : [moduleName];
87
+ if (!reached.some((item) => modules.includes(item.module))) {
88
+ problems.push(`interfaces says nothing of how the functionalities of ${modules.map((module) => `\`${module}\``).join(" or ")} the change touches are reached - one entry each.`);
109
89
  }
110
90
  }
111
91
  return problems;
112
92
  }
113
93
  /**
114
- * How the built software is invoked from outside, and a plan for its conformance tests, as an agent
115
- * that explored the code writes it - everything the tests' author works
116
- * from. Checked before it is used: it has to cover every functionality the tests are for, and is sent
117
- * back until it does. What the explorer wrote in its replies goes with an account it never completed.
94
+ * How the built software is invoked from outside, as an agent that explored the code writes it - everything of the code
95
+ * the tests' author works from; what to test is the scenarios'. Checked before it is used: it has to cover every
96
+ * 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.
118
97
  */
119
98
  export async function exploreInvocation(options) {
120
99
  // The codebase's account from an earlier run, and what of the code changed since: the explorer starts from it.
@@ -126,14 +105,14 @@ export async function exploreInvocation(options) {
126
105
  module: Type.Optional(Type.String({ description: "The module the functionality is in" })),
127
106
  id: Type.Optional(Type.String({
128
107
  description: target !== undefined
129
- ? `The functionality's id as the specification numbers it, e.g. "${target.frid}" - leave both out for ${refLabel(target)}, the one under test`
130
- : 'The functionality\'s id as the specification numbers it, e.g. "3" - never a name of your own',
108
+ ? `The functionality's func-id as the specification has it, e.g. "${target.funcId}" - leave both out for ${refLabel(target)}, the one under test`
109
+ : 'The functionality\'s func-id as the specification has it, e.g. "add-task" - never a name of your own',
131
110
  })),
132
111
  };
133
112
  /** An entry's functionality: as given when it is one, otherwise - in a functionality's run - the one under test. */
134
113
  const owner = (item) => {
135
- const given = { module: (item.module ?? "").trim() || options.spec.moduleName, frid: (item.id ?? "").trim() };
136
- const known = options.scope.functionalities.some((known) => known.module === given.module && known.frid === given.frid);
114
+ const given = { module: (item.module ?? "").trim() || options.spec.moduleName, funcId: (item.id ?? "").trim() };
115
+ const known = options.scope.functionalities.some((known) => known.module === given.module && known.funcId === given.funcId);
137
116
  return known || target === undefined ? given : { ...target };
138
117
  };
139
118
  const submit = defineTool({
@@ -158,12 +137,6 @@ export async function exploreInvocation(options) {
158
137
  environment: Type.String({
159
138
  description: "What it needs around it: environment variables, configuration, services, databases, files, fixtures - and how each test gets a fresh one",
160
139
  }),
161
- plan: Type.Array(Type.Object({
162
- ...ref,
163
- tests: Type.String({
164
- description: "The conformance tests to write for it, from the specification and its acceptance tests: each case, how it is set up and invoked, what it checks as the specification says, and where its files go as the test reqs say - and every acceptance test, named \"acceptance test N\", with how each of its steps is done from outside and what it asserts",
165
- }),
166
- })),
167
140
  other: Type.Optional(Type.String({ description: "Anything else the test author needs" })),
168
141
  }),
169
142
  async execute(_id, params) {
@@ -174,7 +147,6 @@ export async function exploreInvocation(options) {
174
147
  testsReachIt: given.tests_reach_it ?? "",
175
148
  interfaces: given.interfaces.map((item) => ({ ...owner(item), how: item.how })),
176
149
  environment: given.environment,
177
- plan: given.plan.map((item) => ({ ...owner(item), tests: item.tests })),
178
150
  other: given.other,
179
151
  };
180
152
  // Every hand-over ends the turn and is checked: the harness says what is missing, a few times at most.
@@ -199,7 +171,7 @@ export async function exploreInvocation(options) {
199
171
  try {
200
172
  replies.push(await ask(session, BEGIN, options.onDelta));
201
173
  for (let again = 0; again < EXPLORATION_SUBMISSIONS; again += 1) {
202
- const problems = explorationProblems(exploration, options.scope, options.spec.moduleName, options.acceptance);
174
+ const problems = explorationProblems(exploration, options.scope, options.spec.moduleName);
203
175
  if (problems.length === 0 || submissions >= EXPLORATION_SUBMISSIONS)
204
176
  break;
205
177
  reportActivity(` the exploration is not complete yet: ${problems[0].slice(0, 120)}`);
@@ -209,7 +181,7 @@ export async function exploreInvocation(options) {
209
181
  finally {
210
182
  session.dispose();
211
183
  }
212
- const complete = explorationProblems(exploration, options.scope, options.spec.moduleName, options.acceptance).length === 0;
184
+ const complete = explorationProblems(exploration, options.scope, options.spec.moduleName).length === 0;
213
185
  const found = replies.filter((reply) => reply.trim() !== "");
214
186
  if (exploration === undefined) {
215
187
  return found.length > 0
@@ -332,7 +304,7 @@ export function createAskExplorerTool(options) {
332
304
  }
333
305
  export function explorePrompt(buildDir, spec, scope, testingScripts, question, previous) {
334
306
  const target = scope.tested !== undefined
335
- ? `functionality ${refLabel(scope.tested)}`
307
+ ? `functional spec ${refLabel(scope.tested)}`
336
308
  : `the spec change - every functionality of \`${spec.moduleName}\` it can reach`;
337
309
  return [
338
310
  specPrimer({ testReqs: true }),
@@ -358,17 +330,11 @@ export function explorePrompt(buildDir, spec, scope, testingScripts, question, p
358
330
  " reached from outside: the command and its arguments, or the route, method and headers, and the fields",
359
331
  " of its request and response and their types, as the code takes them;",
360
332
  "- environment: environment variables, configuration, services and databases it talks to, files and",
361
- " fixtures - and how each test gets a fresh one;",
362
- `- plan: the conformance tests to write for ${target}, from the specification and its acceptance tests:`,
363
- " each case, how it is set up and invoked, what it checks as the specification says, and where the files",
364
- " go as the test reqs say. Every acceptance test, named \"acceptance test N\" as the specification numbers",
365
- " them, with how each of its steps is done from outside - through which endpoint, or which class and",
366
- " method where the test reqs let the tests call a component directly - and what it asserts. A long one is",
367
- " planned whole, never cut down;",
333
+ " fixtures - and how each test gets a fresh one.",
368
334
  "",
335
+ "What to test is not yours to say: the tests carry out the functional specs' scenarios, and nothing else.",
369
336
  "Describe how to call it, never what it answers: what it returns in each case is for the tests to check",
370
- "against the specification, not to copy from the code - so the plan's expectations come from the",
371
- "specification, and the code only tells you how to reach it. Read the code - its build files, its entry points, its",
337
+ "against the scenarios, not to copy from the code - the code only tells you how to reach it. Read the code - its build files, its entry points, its",
372
338
  "configuration - with the file tools: you have no shell, and run nothing. Change nothing. Then call submit_exploration - once, with the whole of it: it ends your turn, and",
373
339
  `you are told if anything is missing. You can hand it over ${EXPLORATION_SUBMISSIONS} times at most.`,
374
340
  "",
@@ -397,7 +363,7 @@ export function explorePrompt(buildDir, spec, scope, testingScripts, question, p
397
363
  "This codebase was explored before, for an earlier conformance run, and the account below was handed over then.",
398
364
  "Start from it - do not explore again what it already says. Check what it says against the files that changed",
399
365
  "since, listed below, and read further only for what it does not cover yet: how the functionality under test",
400
- "now is reached, and the plan for its tests. Then hand over the whole account, updated - what still holds kept,",
366
+ "now is reached. Then hand over the whole account, updated - what still holds kept,",
401
367
  "what changed corrected, what is new added.",
402
368
  "",
403
369
  `<the account handed over before>
@@ -450,7 +416,7 @@ export async function createTestReviewer(options) {
450
416
  }),
451
417
  what: Type.String({ description: "Exactly what is missing or bad, and what the specification says instead" }),
452
418
  blocking: Type.Boolean({
453
- description: "True when the functionality is not yet tested black box because of it: a test or an acceptance test missing, cut down or " +
419
+ description: "True when the functionality is not yet tested black box because of it: a scenario's test missing, cut down or " +
454
420
  "skipping what it checks, the software mocked or not reached from outside, a test that cannot fail. False for a way to make " +
455
421
  "tests that already do that better",
456
422
  }),
@@ -502,10 +468,10 @@ function problemText(problem) {
502
468
  }
503
469
  /** The reviewer's task, for its instructions: word for word through every review of the run. */
504
470
  export function testReviewTask(options) {
505
- const withAcceptance = options.functionalities.filter((item) => item.acceptanceTests.length > 0);
471
+ const scenarios = options.functionalities.flatMap((item) => scenariosOf(item).map((scenario) => ({ module: item.module ?? options.spec.moduleName, funcId: item.funcId, scenario })));
506
472
  const whose = options.forChange
507
- ? `the functionalities of module \`${options.spec.moduleName}\` the change touches (${options.functionalities.map((item) => item.frid).join(", ") || "none"})`
508
- : `functionality ${options.functionalities[0]?.frid ?? ""} of module \`${options.spec.moduleName}\``;
473
+ ? `the functionalities the change touches (${options.functionalities.map((item) => `${item.module ?? options.spec.moduleName}:${item.funcId}`).join(", ") || "none"})`
474
+ : `functional spec ${options.spec.moduleName}:${options.functionalities[0]?.funcId ?? ""}`;
509
475
  return [
510
476
  specPrimer({ testReqs: true }),
511
477
  `You review the conformance tests of the \`${options.spec.moduleName}\` specification a conformance run adds or changes, each time`,
@@ -524,7 +490,9 @@ export function testReviewTask(options) {
524
490
  "check it against the diff and the files. A problem it says is fixed that the tests do not fix is still a problem;",
525
491
  "one it declined, weigh its reason - it may be right.",
526
492
  "",
527
- ...(options.cases !== undefined && options.cases.mechanical ? [...conformanceReviewCasesBlock(options.cases), "", "And, of every test the run changed:"] : ["Check every test the run added or changed:"]),
493
+ ...(options.cases !== undefined && options.cases.mechanical
494
+ ? [...conformanceReviewCasesBlock(options.cases), "", "And, of every test the run changed:"]
495
+ : [...(options.cases !== undefined ? conformanceReviewCasesBlock(options.cases, options.spec.moduleName) : []), "Check every test the run added or changed:"]),
528
496
  "- Black box: it reaches the software only the way the test reqs have it - its API, its command, its public",
529
497
  " classes where the test reqs say so - and never mocks, stubs or replaces any part of the software under test:",
530
498
  " no mocked beans, services or clients of its own. A real external service the test reqs say not to mock is",
@@ -532,33 +500,33 @@ export function testReviewTask(options) {
532
500
  "- It tests something: every test asserts what the specification says, and fails when that does not hold. No",
533
501
  " placeholder, no `assertTrue(true)`, no empty or skipped test, no failure caught and swallowed, no assertion",
534
502
  " so loose any answer passes.",
535
- ...(withAcceptance.length > 0
503
+ ...(scenarios.length > 0
536
504
  ? [
537
- `- The acceptance tests of ${whose}, each implemented as a test of its own, doing every step it lists, in`,
538
- " order, with what it names, and asserting every expectation it states. A step left out, replaced by a",
539
- " stand-in, or said to be impossible to run is missing, not done:",
505
+ `- The scenarios of ${whose}, each carried out by at least one test, doing every step it lists, in order, with`,
506
+ " what it names, and asserting every expectation it states. A step left out, replaced by a stand-in, or said",
507
+ " to be impossible to run is missing, not done:",
540
508
  "",
541
- ...withAcceptance.flatMap((item) => item.acceptanceTests.map((test, index) => `<acceptance test ${index + 1} of functionality ${item.frid}>\n${test.trim()}\n</acceptance test>`)),
509
+ ...scenarios.map(({ module, funcId, scenario }) => `<scenario>\n${scenarioText(module, funcId, scenario)}\n</scenario>`),
542
510
  "",
543
511
  ]
544
512
  : []),
545
- `- The conformance tests cover what the specification says ${options.forChange ? "the change does" : `${whose} does`} - its behaviour, its`,
546
- " edge cases, its errors - each checked against the specification, not against what the code happens to do.",
547
- " Only what can be seen from outside: where the specification says one part calls or uses another, the test",
548
- " checks the outcome that follows - what comes back, what is stored, what the other service received - never",
549
- " that the call was made. Asking for a test of how the code works inside is not a problem to raise.",
513
+ "- The tests are the scenarios', and only theirs: a test that carries out no scenario is not one to ask for, and a",
514
+ " missing edge case the scenarios do not name is not a problem to raise. Each is checked against the",
515
+ " specification, not against what the code happens to do - only what can be seen from outside: where the",
516
+ " specification says one part calls or uses another, the test checks the outcome that follows - what comes back,",
517
+ " what is stored, what the other service received - never that the call was made.",
550
518
  "",
551
519
  "The first review finds every problem, in that one review - the tests go back once with all of them. A review after",
552
520
  "it checks each problem you raised last first - fixed, or still there - and raises a new one only when it is one of",
553
- "the above - a test that cannot fail, mocks the software, or leaves out or misdoes what the specification or an",
554
- "acceptance test asks for. Not a matter of style or thoroughness the last review let pass: the review is not done",
521
+ "the above - a test that cannot fail, mocks the software, or leaves out or misdoes what the specification or a",
522
+ "scenario asks for. Not a matter of style or thoroughness the last review let pass: the review is not done",
555
523
  "again from the start.",
556
524
  "",
557
525
  "Each review ends with submit_review, once: pass when nothing is missing and nothing is bad; otherwise every problem",
558
526
  '- the file, the test, its kind - "missing" for a test that is not there, "bad" for one that is weak, wrong or not',
559
527
  "black box - and exactly what, with what the specification says instead. Be specific: the agent that fixes them gets",
560
528
  "your list and nothing else. Mark a problem blocking when because of it the functionality is not yet tested black",
561
- "box - a test or an acceptance test missing, cut down or skipping what it checks, the software mocked or not reached",
529
+ "box - a scenario's test missing, cut down or skipping what it checks, the software mocked or not reached",
562
530
  "from outside, a test that cannot fail. Those are worked on first; the rest - ways to make tests that already do",
563
531
  "that better - are let go after a few reviews.",
564
532
  "",
@@ -614,15 +582,15 @@ export function reviewFeedbackPrompt(problems) {
614
582
  }
615
583
  /** "billing 4": a functionality as the judge and the person read it. */
616
584
  export function refLabel(ref) {
617
- return `${ref.module} ${ref.frid}`;
585
+ return `${ref.module} ${ref.funcId}`;
618
586
  }
619
- const same = (a, b) => a.module === b.module && a.frid === b.frid;
587
+ const same = (a, b) => a.module === b.module && a.funcId === b.funcId;
620
588
  /** A conflict's functionalities as the judge gave them, tidied: trimmed, and each once. */
621
589
  export function conflictRefs(given) {
622
590
  const refs = [];
623
591
  for (const item of given ?? []) {
624
- const ref = { module: (item.module ?? "").trim(), frid: (item.frid ?? item.id ?? "").trim() };
625
- if (ref.module !== "" && ref.frid !== "" && !refs.some((known) => same(known, ref)))
592
+ const ref = { module: (item.module ?? "").trim(), funcId: (item.funcId ?? item.id ?? "").trim() };
593
+ if (ref.module !== "" && ref.funcId !== "" && !refs.some((known) => same(known, ref)))
626
594
  refs.push(ref);
627
595
  }
628
596
  return refs;
@@ -648,7 +616,7 @@ export function conflictKey(functionalities) {
648
616
  return sortedRefs(functionalities).map(refLabel).join(" & ");
649
617
  }
650
618
  export function sortedRefs(functionalities) {
651
- return conflictRefs(functionalities).sort((a, b) => a.module.localeCompare(b.module) || a.frid.localeCompare(b.frid, undefined, { numeric: true }));
619
+ return conflictRefs(functionalities).sort((a, b) => a.module.localeCompare(b.module) || a.funcId.localeCompare(b.funcId, undefined, { numeric: true }));
652
620
  }
653
621
  /** How many times a spec conflict between the same two functionalities is raised before it counts. */
654
622
  export const SPEC_CONFLICT_RAISES = 3;
@@ -749,7 +717,7 @@ export async function judgeFailure(options) {
749
717
  functionalities: Type.Optional(Type.Array(Type.Object({
750
718
  module: Type.String({ description: "The module the functionality is in" }),
751
719
  id: Type.String({ description: "Its id in that module" }),
752
- }), { description: 'For spec: the two functionalities that cannot both hold, e.g. [{"module": "billing", "id": "4"}, {"module": "accounts", "id": "1"}]' })),
720
+ }), { description: 'For spec: the two functionalities that cannot both hold, e.g. [{"module": "billing", "id": "refund"}, {"module": "accounts", "id": "register"}]' })),
753
721
  }),
754
722
  async execute(_id, params) {
755
723
  const given = params;
@@ -1041,6 +1009,7 @@ export async function createCodeFixer(options) {
1041
1009
  * word for word through every fix of the run; each failure comes as a message (`codeFixPrompt`).
1042
1010
  */
1043
1011
  export function codeFixerTask(options) {
1012
+ const base = baseOfCodebase(options.buildDir);
1044
1013
  return [
1045
1014
  specPrimer({ testReqs: false }),
1046
1015
  `You are the coding agent of the \`${options.spec.moduleName}\` specification. Its code is in ${options.buildDir}. When its`,
@@ -1050,6 +1019,8 @@ export function codeFixerTask(options) {
1050
1019
  options.spec.text.trim(),
1051
1020
  "</specification>",
1052
1021
  "",
1022
+ // What came from the base folder its codebase started from: a fix keeps it.
1023
+ ...(base !== undefined ? [baseFolderBlock(base), ""] : []),
1053
1024
  ...fixMethod({ what: "conformance tests", fixes: "code", runTool: "run_conformance_tests" }),
1054
1025
  "",
1055
1026
  "run_conformance_tests runs the project's conformance testing script on the code as it now is, with the whole",
@@ -1072,6 +1043,7 @@ export function codeFixerTask(options) {
1072
1043
  "Keep the build's identity as it is - its name, coordinates, version, package and main class - unless the",
1073
1044
  "specification asks for another: the tests and other builds point at them. Leave in your notes what holds about",
1074
1045
  "the code for the next agent, not an account of this fix: the harness records the attempts itself.",
1046
+ ...(base !== undefined ? BASE_FOLDER_RULES : []),
1075
1047
  ...INTEGRITY_RULES,
1076
1048
  ...ERROR_CAUSE_RULES,
1077
1049
  ].join("\n");
@@ -1,6 +1,7 @@
1
- import { ACCEPTANCE_TESTS, type MarshalledPlainSource } from "../plain/spec.ts";
1
+ import { ACCEPTANCE_TESTS, CONFORMANCE_TESTS, type MarshalledPlainSource } from "../plain/spec.ts";
2
2
  import { type BuildingSpec } from "./activity.ts";
3
3
  import type { JobCases } from "./drift.ts";
4
+ import { type TestedSpec } from "./test-kinds.ts";
4
5
  import { type ScriptRun, failureSignature, STUCK_ROUNDS } from "./test-scripts.ts";
5
6
  import type { ToolDefinition } from "@earendil-works/pi-coding-agent";
6
7
  import { type Evidence, type ReviewProblem } from "./conformance-agents.ts";
@@ -15,7 +16,7 @@ export interface RenderConformanceTestsOptions {
15
16
  /** What the job does to each functional spec (work.ts): its agents are told case by case. */
16
17
  cases?: JobCases;
17
18
  plainFile: string;
18
- frid?: string;
19
+ funcId?: string;
19
20
  templateDirs?: string[];
20
21
  /** The rendered code under test. Must exist and be non-empty. */
21
22
  buildDir?: string;
@@ -32,6 +33,11 @@ export interface RenderConformanceTestsOptions {
32
33
  dryRun?: boolean;
33
34
  /** Update the tests for a spec change, given as a diff, rather than for one functionality. */
34
35
  diff?: string;
36
+ /**
37
+ * For a spec change: what the tests are written for once it lands - the tests record it writes. Every scenario in it
38
+ * has a labelled test before a run counts, and a label naming nothing in it sends the agent back.
39
+ */
40
+ recorded?: MarshalledPlainSource;
35
41
  /** That change section by section, as the agents are shown it in place of the diff. */
36
42
  change?: string;
37
43
  /**
@@ -50,7 +56,7 @@ export interface RenderConformanceTestsOptions {
50
56
  }
51
57
  export interface RenderConformanceTestsResult {
52
58
  moduleName: string;
53
- frid: string;
59
+ funcId: string;
54
60
  buildDir: string;
55
61
  conformanceDir: string;
56
62
  prompt: string;
@@ -80,7 +86,7 @@ export interface RenderConformanceTestsResult {
80
86
  specConflict?: {
81
87
  functionalities: Array<{
82
88
  module: string;
83
- frid: string;
89
+ funcId: string;
84
90
  }>;
85
91
  reason: string;
86
92
  };
@@ -132,14 +138,11 @@ export declare function renderConformanceTests(options: RenderConformanceTestsOp
132
138
  */
133
139
  export declare function notLandedWhy(what: string, unlabelled?: string[]): string;
134
140
  /**
135
- * The functionalities a spec change touches - its functional spec, or one of its acceptance tests,
136
- * has a line the change adds or removes - with their acceptance tests: what a spec diff's
141
+ * The functionalities with scenarios a spec change touches - by the job's cases, else those whose functional spec, or one of
142
+ * whose scenarios, has a line the change adds or removes - with their scenarios: what a spec diff's
137
143
  * conformance run labels, checks and reviews, as a functionality's run does for its one.
138
144
  */
139
- export declare function touchedFunctionalities(tree: MarshalledPlainSource, frids: string[], diff: string): Array<{
140
- frid: string;
141
- acceptanceTests: string[];
142
- }>;
145
+ export declare function touchedFunctionalities(tree: MarshalledPlainSource, funcIds: string[], diff: string, moduleName?: string, cases?: JobCases): TestedSpec[];
143
146
  /** From the how-manieth review that sends the tests back the arbiter weighs each one (see arbiter.ts). */
144
147
  export declare const REVIEW_ROUNDS = 1;
145
148
  /** How many times the tests are sent back to be written and labelled before the run stops. */
@@ -173,9 +176,16 @@ export declare function buildManifests(buildDir: string): Array<{
173
176
  export declare function failureLine(run: ScriptRun): string;
174
177
  /**
175
178
  * The functional spec a conformance run writes tests for, for the person watching: which one of how
176
- * many, and its text as the spec has it, with its acceptance tests after it.
179
+ * many, and its text as the spec has it, with its scenarios after it.
177
180
  */
178
- export declare function buildingSpec(moduleName: string, frid: string, total: number, specifications: {
181
+ export declare function buildingSpec(moduleName: string, funcId: string, total: number, specifications: {
179
182
  "functional specs": string[];
180
- [ACCEPTANCE_TESTS]?: string[];
183
+ [CONFORMANCE_TESTS]?: Array<{
184
+ id: string;
185
+ text: string;
186
+ }>;
187
+ [ACCEPTANCE_TESTS]?: Array<{
188
+ id: string;
189
+ text: string;
190
+ }>;
181
191
  }): BuildingSpec;