@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,17 +1,26 @@
1
1
  import { existsSync, readdirSync, readFileSync, statSync } from "node:fs";
2
2
  import { basename, dirname, join, relative, resolve } from "node:path";
3
3
  import { withParseCache } from "../plain/parser.js";
4
- import { ACCEPTANCE_TESTS, DEFINITIONS, FUNCTIONAL_REQUIREMENTS, NON_FUNCTIONAL_REQUIREMENTS, REQUIRED_FUNCTIONAL_SPECS, TEST_REQUIREMENTS, } from "../plain/spec.js";
5
- import { configForSpec, conformanceSwitchedOn, readProjectConfig } from "../config.js";
4
+ import { ACCEPTANCE_TESTS, CONFORMANCE_TESTS, DEFINITIONS, FUNCTIONAL_REQUIREMENTS, NON_FUNCTIONAL_REQUIREMENTS, REQUIRED_FUNCTIONAL_SPECS, TEST_REQUIREMENTS, } from "../plain/spec.js";
5
+ import { readProjectConfig, testDirOf } from "../config.js";
6
+ import { testPlainFilesOf } from "../test-plain-files.js";
6
7
  import { STDLIB_ROOT } from "../plain/stdlib.js";
7
8
  import { conformanceDirFor } from "./conformance.js";
8
9
  import { chainSpec } from "./loop.js";
9
10
  import { modulesRoot, projectDirOf } from "./paths.js";
10
11
  import { HISTORY_DIR } from "./lineage.js";
11
- import { codeTree, marksRegenerateAgain, readRecord, RECORD_FILE } from "./records.js";
12
+ import { codeTree, hasScenarios, marksRegenerateAgain, readRecord, RECORD_FILE } from "./records.js";
13
+ import { pureRenames } from "./renames.js";
12
14
  import { specFilesOf } from "./regenerate.js";
13
15
  import { builtTips, resolveRequiresChain } from "./requires.js";
14
16
  import { changesBetween, layoutOf, serializeTree } from "./spec-steps.js";
17
+ import { SCRIPTS_DIR } from "./test-scripts.js";
18
+ import { testingGap } from "./testing-steps.js";
19
+ const NO_SCENARIOS = { conformance: [], acceptance: [] };
20
+ /** Whether a functional spec has scenarios - and so conformance tests to write. */
21
+ export function hasScenarioViews(scenarios) {
22
+ return scenarios.conformance.length + scenarios.acceptance.length > 0;
23
+ }
15
24
  const itemsOf = (tree, heading) => (tree?.[heading] ?? []).slice();
16
25
  /** The sections an item of the code is in, as a page has them: a module below's functional specs among them. */
17
26
  const CODE_SECTIONS = [DEFINITIONS, NON_FUNCTIONAL_REQUIREMENTS, FUNCTIONAL_REQUIREMENTS];
@@ -37,6 +46,20 @@ function changesOf(before, after) {
37
46
  changesSeen.set(key, changes);
38
47
  return changes;
39
48
  }
49
+ /** A functional spec's scenarios, as the map shows them. */
50
+ function scenarioViews(item) {
51
+ const views = (list) => (list ?? []).map((scenario) => ({ id: scenario.id ?? "", text: scenario.text ?? "" }));
52
+ return { conformance: views(item[CONFORMANCE_TESTS]), acceptance: views(item[ACCEPTANCE_TESTS]) };
53
+ }
54
+ /** A functional spec's func-id, from its line in a serialized tree. */
55
+ const idOf = (line) => {
56
+ try {
57
+ return JSON.parse(line ?? "").id;
58
+ }
59
+ catch {
60
+ return undefined;
61
+ }
62
+ };
40
63
  const markdownOf = (line) => {
41
64
  try {
42
65
  return JSON.parse(line ?? "").markdown ?? "";
@@ -57,13 +80,8 @@ export function pageItems(input) {
57
80
  const before = serializeTree(moduleTree(input.record === null ? null : codeTree(input.record), input.module, input.own));
58
81
  const after = serializeTree(codeTree(now));
59
82
  const changes = changesOf(before, after);
60
- // Each item's acceptance tests, by its line in the code's tree.
61
- const acceptance = new Map();
62
- for (const item of itemsOf(now, FUNCTIONAL_REQUIREMENTS)) {
63
- const line = serializeTree(codeTree({ [FUNCTIONAL_REQUIREMENTS]: [item] })).split("\n").find((entry) => entry.startsWith("{"));
64
- if (line !== undefined)
65
- acceptance.set(line, (item[ACCEPTANCE_TESTS] ?? []).map((test) => test.markdown ?? ""));
66
- }
83
+ // Each functional spec's scenarios now, by its func-id.
84
+ const scenariosNow = new Map(itemsOf(now, FUNCTIONAL_REQUIREMENTS).map((item) => [item.id ?? "", scenarioViews(item)]));
67
85
  // The record's lines, with what is added in front of each and what becomes of each: the spec's order, the ghosts in theirs.
68
86
  const lines = before.split("\n").slice(0, -1);
69
87
  const removedBy = new Map();
@@ -77,13 +95,15 @@ export function pageItems(input) {
77
95
  const items = [];
78
96
  const push = (section, state, line, wasLine) => {
79
97
  const text = markdownOf(line ?? wasLine);
98
+ const funcId = section === FUNCTIONAL_REQUIREMENTS ? idOf(line ?? wasLine) : undefined;
80
99
  items.push({
81
100
  key: "",
82
101
  section,
83
102
  state,
84
103
  text,
85
104
  ...(state === "changed" ? { was: markdownOf(wasLine) } : {}),
86
- acceptance: line !== undefined ? (acceptance.get(line) ?? []) : [],
105
+ ...(funcId !== undefined ? { funcId } : {}),
106
+ scenarios: funcId !== undefined && line !== undefined ? (scenariosNow.get(funcId) ?? NO_SCENARIOS) : NO_SCENARIOS,
87
107
  ...(line !== undefined ? { line } : {}),
88
108
  ...(wasLine !== undefined ? { wasLine } : {}),
89
109
  });
@@ -111,33 +131,34 @@ export function pageItems(input) {
111
131
  const testReqs = new Set(itemsOf(input.tested ?? null, TEST_REQUIREMENTS).map((item) => item.markdown.trim()));
112
132
  if (input.own) {
113
133
  for (const item of itemsOf(input.spec, TEST_REQUIREMENTS)) {
114
- items.push({ key: "", section: TEST_REQUIREMENTS, state: "test", text: item.markdown, acceptance: [], ...(testReqs.has(item.markdown.trim()) ? { tests: "now" } : {}) });
134
+ items.push({ key: "", section: TEST_REQUIREMENTS, state: "test", text: item.markdown, scenarios: NO_SCENARIOS, ...(testReqs.has(item.markdown.trim()) ? { tests: "now" } : {}) });
115
135
  }
116
136
  }
117
- // Conformance tests: of the functional specs, those the tests record has as they read now, as they read before, or taken out.
118
- // Tests marked to regenerate again were written against code since replaced: written for an earlier build, not now.
119
- const testedItems = itemsOf(input.tested ?? null, FUNCTIONAL_REQUIREMENTS);
120
- const tested = new Set(testedItems.filter((item) => !marksRegenerateAgain(item)).map((item) => item.markdown.trim()));
121
- const replaced = new Set(testedItems.filter((item) => marksRegenerateAgain(item)).map((item) => item.markdown.trim()));
137
+ // Conformance tests: of the functional specs, by func-id, those the tests record has as they read now - their text and
138
+ // their scenarios - as they read before, or taken out. Tests marked to regenerate again were written against code since
139
+ // replaced: written for an earlier build, not now.
140
+ const testedItems = new Map(itemsOf(input.tested ?? null, FUNCTIONAL_REQUIREMENTS).map((item) => [item.id ?? "", item]));
141
+ const asNow = new Map(itemsOf(now, FUNCTIONAL_REQUIREMENTS).map((item) => [item.id ?? "", item]));
142
+ const reads = (item) => JSON.stringify({ markdown: item.markdown.trim(), scenarios: scenarioViews(item) });
122
143
  for (const item of items) {
123
- if (item.section !== FUNCTIONAL_REQUIREMENTS || testedItems.length === 0)
144
+ if (item.section !== FUNCTIONAL_REQUIREMENTS || item.funcId === undefined)
124
145
  continue;
125
- if (item.state === "removed") {
126
- if (tested.has(item.text.trim()) || replaced.has(item.text.trim()))
127
- item.tests = "removed";
128
- }
129
- else if (tested.has(item.text.trim()))
130
- item.tests = "now";
131
- else if (replaced.has(item.text.trim()) || (item.was !== undefined && (tested.has(item.was.trim()) || replaced.has(item.was.trim()))))
132
- item.tests = "older";
146
+ const tested = testedItems.get(item.funcId);
147
+ if (tested === undefined)
148
+ continue;
149
+ const current = asNow.get(item.funcId);
150
+ if (item.state === "removed")
151
+ item.tests = "removed";
152
+ else
153
+ item.tests = !marksRegenerateAgain(tested) && current !== undefined && reads(tested) === reads(current) ? "now" : "older";
133
154
  }
134
155
  return keyed(items);
135
156
  }
136
- /** Each item's key: its section, whether it is a ghost, its text - and which of the same it is. */
157
+ /** Each item's key: its section, whether it is a ghost, its text - a functional spec's func-id - and which of the same it is. */
137
158
  function keyed(items) {
138
159
  const seen = new Map();
139
160
  for (const item of items) {
140
- const base = `${item.section}\n${item.state === "removed" ? "-" : "+"}${item.text.trim()}`;
161
+ const base = `${item.section}\n${item.state === "removed" ? "-" : "+"}${item.funcId !== undefined ? `# ${item.funcId}` : item.text.trim()}`;
141
162
  const count = seen.get(base) ?? 0;
142
163
  seen.set(base, count + 1);
143
164
  item.key = count === 0 ? base : `${base}\n${count}`;
@@ -201,7 +222,9 @@ const started = (entry) => {
201
222
  /** The files a module's page is read from: its spec files, its records, and its history - a commit moves the record read. */
202
223
  function depsOf(entry, codebase, testsDir) {
203
224
  const history = (dir) => [join(dirname(dir), HISTORY_DIR, "logs", "HEAD"), join(dir, RECORD_FILE)];
204
- return [entry.plainFile, ...history(codebase.buildDir), ...history(testsDir), dirname(dirname(codebase.buildDir))];
225
+ // Its test plain files, and their folder: one added or taken out changes what the map reads.
226
+ const testPlain = [join(testDirOf(entry.plainFile), entry.moduleName), ...testPlainFilesOf(entry.plainFile)];
227
+ return [entry.plainFile, ...testPlain, ...history(codebase.buildDir), ...history(testsDir), dirname(dirname(codebase.buildDir))];
205
228
  }
206
229
  /** The chain a file's map reads: where its code lives - its furthest-built tip - or its own requires chain. */
207
230
  function chainOf(plainFile) {
@@ -258,7 +281,6 @@ export function readSpecMapWithDeps(files) {
258
281
  imports: [],
259
282
  items: [],
260
283
  ...(entry.requires !== null ? { requires: entry.requires } : {}),
261
- conformance: conformanceOn(entry.plainFile),
262
284
  };
263
285
  const spec = specOf(home);
264
286
  if (spec instanceof Error) {
@@ -266,14 +288,26 @@ export function readSpecMapWithDeps(files) {
266
288
  return;
267
289
  }
268
290
  const projectDir = projectDirOf(entry.plainFile);
269
- const record = home <= furthest ? readRecord(codebase.buildDir) : null;
270
- const tested = readRecord(conformanceDirFor(entry.moduleName, modulesRoot(projectDir)));
291
+ // A pure rename not made yet is no work: shown as built, under its new func-id, with the one it is renamed from.
292
+ const renamed = home <= furthest ? pureRenames(readRecord(codebase.buildDir), readRecord(conformanceDirFor(codebase.moduleName, modulesRoot(projectDir))), spec, codebase.moduleName) : undefined;
293
+ const record = renamed?.code ?? null;
294
+ // What the conformance tests are written for, in the codebase that holds its code: the tip's suite holds the chain's.
295
+ const testsRecord = renamed?.tests ?? null;
296
+ const tested = testsRecord === null ? null : { ...testsRecord, [FUNCTIONAL_REQUIREMENTS]: moduleTree(testsRecord, entry.moduleName, home === at)[FUNCTIONAL_REQUIREMENTS] };
271
297
  const paths = specFilesOf(entry.plainFile);
272
- for (const path of [...paths, ...depsOf(entry, codebase, conformanceDirFor(entry.moduleName, modulesRoot(projectDir)))])
298
+ for (const path of [...paths, ...depsOf(entry, codebase, conformanceDirFor(codebase.moduleName, modulesRoot(projectDir)))])
273
299
  deps.add(path);
274
300
  const texts = paths.map((path, index) => ({ ...(index > 0 ? { label: importLabel(path, projectDir) } : {}), text: readText(path) }));
275
- const items = pageItems({ module: entry.moduleName, own: home === at, spec, record, tested });
276
- pages.push({ ...page, imports: texts.slice(1).map((text) => text.label), items: layOut(items, texts) });
301
+ const renamedFrom = new Map((renamed?.renames ?? []).flatMap((rename) => (rename.kind === "func-id" && rename.module === entry.moduleName ? [[rename.to, rename.from]] : [])));
302
+ const items = pageItems({ module: entry.moduleName, own: home === at, spec, record, tested }).map((item) => item.funcId !== undefined && renamedFrom.has(item.funcId) && item.state !== "removed" ? { ...item, renamedFrom: renamedFrom.get(item.funcId) } : item);
303
+ const own = home === at ? (spec[FUNCTIONAL_REQUIREMENTS] ?? []) : (spec[REQUIRED_FUNCTIONAL_SPECS] ?? []).filter((item) => item.module === entry.moduleName);
304
+ // Its scenarios untested for a step of adding tests skipped in the codebase that writes their tests: said on its row.
305
+ // Read again once a script is added to the project, or taken out.
306
+ if (own.some(hasScenarios))
307
+ for (const path of [projectDir, join(projectDir, SCRIPTS_DIR)])
308
+ deps.add(path);
309
+ const gap = own.some(hasScenarios) ? testingGap({ moduleName: codebase.moduleName, plainFile: codebase.plainFile, tree: spec, buildDir: codebase.buildDir }) : undefined;
310
+ pages.push({ ...page, conformance: own.some(hasScenarios), ...(gap !== undefined ? { testingMissing: gap.short } : {}), imports: texts.slice(1).map((text) => text.label), items: layOut(items, texts) });
277
311
  });
278
312
  }
279
313
  return pages;
@@ -294,15 +328,6 @@ function fingerprint(paths) {
294
328
  })
295
329
  .join("\n");
296
330
  }
297
- /** Whether a module's config has its conformance tests on: `conformance-tests:` is all (true) or chosen - off when not set. */
298
- function conformanceOn(plainFile) {
299
- try {
300
- return conformanceSwitchedOn(configForSpec(plainFile).conformanceTests);
301
- }
302
- catch {
303
- return false;
304
- }
305
- }
306
331
  function readText(path) {
307
332
  try {
308
333
  return readFileSync(path, "utf8");
@@ -318,7 +343,7 @@ function linesOf(pulse) {
318
343
  let seen = linesSeen.get(pulse);
319
344
  if (seen === undefined) {
320
345
  const set = (text) => new Set(text.split("\n").filter((line) => line.startsWith("{")));
321
- // A conformance pulse's trees are the tests record's: its functional specs' lines - each with its number and acceptance tests.
346
+ // A conformance pulse's trees are the tests record's: its functional specs' lines - each with its func-id and scenarios.
322
347
  const specLines = (text, of = FUNCTIONAL_REQUIREMENTS) => {
323
348
  const out = [];
324
349
  let section = "";
@@ -336,25 +361,28 @@ function linesOf(pulse) {
336
361
  return new Set(specLines(pulse.after, TEST_REQUIREMENTS).filter((line) => !before.has(line)).map((line) => markdownOf(line).trim()));
337
362
  };
338
363
  const testing = () => {
339
- // What it writes tests for: each functional spec whose line is new - its text, its acceptance tests or its number
340
- // changed - by its text; none, and it is the definitions, reqs or test reqs that changed: it tests every one again.
364
+ // What it writes tests for: each functional spec whose line is new - its text or its scenarios changed - by its
365
+ // func-id; none, and it is the definitions, reqs or test reqs that changed: it tests every one again.
341
366
  const before = new Set(specLines(pulse.before));
342
367
  const after = specLines(pulse.after);
343
368
  const changed = after.filter((line) => !before.has(line));
344
- return new Set((changed.length > 0 ? changed : after).map((line) => markdownOf(line).trim()));
369
+ return new Set((changed.length > 0 ? changed : after).map((line) => idOf(line) ?? ""));
345
370
  };
346
371
  seen = pulse.track === "code" ? { before: set(pulse.before), after: set(pulse.after), testing: new Set(), testReqs: new Set() } : { before: new Set(), after: new Set(), testing: testing(), testReqs: testReqs() };
347
372
  linesSeen.set(pulse, seen);
348
373
  }
349
374
  return seen;
350
375
  }
351
- /** A functional spec's first line, as a conformance pulse scheduled ahead of its code names it. */
352
- const headline = (text) => (text.trim().split("\n")[0] ?? "").replace(/^-\s*/, "").trim();
353
- /** Whether a conformance pulse writes tests for a functional spec: by its trees, or - scheduled, with none - by the specs it names. */
354
- function writesTestsFor(pulse, item) {
376
+ /**
377
+ * Whether a conformance pulse writes tests for a functional spec, by its func-id: by its trees, or - following the code
378
+ * still to build, with none - by the functional specs it names, `module:func-id`.
379
+ */
380
+ function writesTestsFor(pulse, item, module) {
381
+ if (item.funcId === undefined)
382
+ return false;
355
383
  if (pulse.specs !== undefined)
356
- return pulse.specs.some((text) => headline(text) === headline(item.text));
357
- return linesOf(pulse).testing.has(item.text.trim());
384
+ return pulse.specs.includes(`${module}:${item.funcId}`);
385
+ return linesOf(pulse).testing.has(item.funcId);
358
386
  }
359
387
  /** Whether a pulse regenerates an item: brings its line now, or takes out its line in the record. */
360
388
  function regenerates(pulse, item) {
@@ -383,6 +411,9 @@ function needsText(stuck) {
383
411
  * for in the running conformance pulse, planned in one, or not yet - none, where no conformance test is written at all.
384
412
  */
385
413
  function testReqMark(item, codebase, page) {
414
+ // No functional spec of the module has scenarios: nothing uses its test reqs.
415
+ if (page.conformance === false)
416
+ return { mark: "none", unused: true };
386
417
  if (item.tests === "now")
387
418
  return { mark: "built" };
388
419
  const text = item.text.trim();
@@ -409,16 +440,15 @@ export function markSpecMap(pages, plan) {
409
440
  const stuckPulse = codebase?.stuck?.pulse;
410
441
  const failed = stuckPulse !== undefined ? { number: stuckPulse.number, title: "", track: "code", running: false, rowId: "", before: stuckPulse.before, after: stuckPulse.after } : undefined;
411
442
  const conformance = codebase?.pulses.find((pulse) => pulse.running && pulse.track === "conformance");
412
- // Test reqs only where conformance tests are on: the loop's word for it - the config, or asked for this session - else the config's.
413
- const conformanceIsOn = codebase?.conformance ?? page.conformance !== false;
414
- const items = page.items.filter((item) => item.state !== "test" || conformanceIsOn).map((item) => {
443
+ // Test reqs are always shown: where no functional spec has scenarios, as not used.
444
+ const items = page.items.map((item) => {
415
445
  const testing = conformance !== undefined && item.section === FUNCTIONAL_REQUIREMENTS && item.state !== "removed" && (() => {
416
- return linesOf(conformance).testing.has(item.text.trim());
446
+ return item.funcId !== undefined && linesOf(conformance).testing.has(item.funcId);
417
447
  })();
418
448
  // Not being tested now: the conformance pulse planned to - its tests written for it as it reads now, or not.
419
449
  const testsPulse = testing || item.section !== FUNCTIONAL_REQUIREMENTS || item.state === "removed"
420
450
  ? undefined
421
- : codebase?.pulses.find((pulse) => !pulse.running && pulse.track === "conformance" && writesTestsFor(pulse, item));
451
+ : codebase?.pulses.find((pulse) => !pulse.running && pulse.track === "conformance" && writesTestsFor(pulse, item, page.module));
422
452
  const extra = testing ? { testing: true } : testsPulse !== undefined ? { testsPulse } : {};
423
453
  if (item.state === "test")
424
454
  return { ...item, ...testReqMark(item, codebase, page), ...extra };
@@ -460,7 +490,9 @@ export function markSpecMap(pages, plan) {
460
490
  : differing === 0
461
491
  ? "all built"
462
492
  : (codebase.why ?? "waiting");
463
- return { ...page, items, tracked: codebase !== undefined, ...(codebase !== undefined ? { codebase } : {}), status, stuck: codebase?.stuck !== undefined && running === undefined };
493
+ // A step of adding tests skipped: said after whatever else is happening, on the module's row.
494
+ const said = page.testingMissing !== undefined ? `${status} · ${page.testingMissing}` : status;
495
+ return { ...page, items, tracked: codebase !== undefined, ...(codebase !== undefined ? { codebase } : {}), status: said, stuck: codebase?.stuck !== undefined && running === undefined };
464
496
  });
465
497
  }
466
498
  /**
@@ -1,13 +1,14 @@
1
- import { type MarshalledPlainSource } from "../plain/spec.ts";
1
+ import { ACCEPTANCE_TESTS, CONFORMANCE_TESTS, type MarshalledPlainSource } from "../plain/spec.ts";
2
2
  /**
3
3
  * The steps from one spec to another: a sequence of spec diffs that, rendered one after the
4
4
  * other, take code built from the first spec to code built from the second.
5
5
  *
6
6
  * Both specs are marshalled trees, written out as text with one JSON value per line - a
7
7
  * section's name, then each of its items on a line of its own - and compared with git. So an
8
- * item is the unit of change: a functional spec with its acceptance tests and linked resources,
8
+ * item is the unit of change: a functional spec with its scenarios and linked resources,
9
9
  * a definition, a req. Git lines them up, so a spec inserted in the middle is one addition and
10
- * the specs after it are untouched, though their numbers moved.
10
+ * the specs after it are untouched. A functional spec is known by its func-id: one taken out and
11
+ * one put in with the same func-id are the same functional spec, changed - or, the same text, moved.
11
12
  *
12
13
  * The planner (`chunk-plan.ts`) cuts the changes into steps - which go together, which go first. It
13
14
  * picks changes by id; it never writes a diff. `makeStep` applies what it picked, and `inconsistencies`
@@ -63,10 +64,39 @@ export interface SpecChange {
63
64
  }
64
65
  /** The changes that take one serialized tree to another, in the order git lists them. */
65
66
  export declare function changesBetween(before: string, after: string): SpecChange[];
67
+ /** Whether a change moves a functional spec and leaves what it says as it was. */
68
+ export declare function isMove(change: SpecChange): boolean;
66
69
  /** How alike two items are: the share of their words they have in common. */
67
70
  export declare function similarity(a: string, b: string): number;
68
71
  /** A selection of changes, applied to the serialized tree they were found in. */
69
72
  export declare function applyChanges(before: string, changes: SpecChange[]): string;
73
+ /** A functional spec's scenarios of one kind, as a person reads them: each under its scenario-id, in its section's tag. */
74
+ export declare function scenarioLines(tag: string, scenarios: Array<{
75
+ id?: string;
76
+ text?: string;
77
+ }> | undefined): string[];
78
+ type ScenarioItem = {
79
+ id?: string;
80
+ text?: string;
81
+ };
82
+ type ScenarioCarrier = {
83
+ id?: string;
84
+ [CONFORMANCE_TESTS]?: ScenarioItem[];
85
+ [ACCEPTANCE_TESTS]?: ScenarioItem[];
86
+ };
87
+ /** A functional spec's test plain file, as it would read: its binding, then each section that has scenarios, each scenario under its id. */
88
+ export declare function testPlainFileText(module: string, item: ScenarioCarrier): string;
89
+ /** One file's two texts as git diffs them, named `path` on both sides: an empty side is the file added or taken out whole. */
90
+ export declare function diffFileTexts(before: string, after: string, path: string): string;
91
+ /** Where a functional spec's test plain file is, by its func-id and - a module below's - its module: the path a diff names it by. */
92
+ export type TestPlainPath = (funcId: string, module?: string) => string;
93
+ /**
94
+ * The functional specs' test plain files a change between two serialized trees touches, each as a diff of its own,
95
+ * rebuilt from the trees as the module's file is: only the trees that carry scenarios - the conformance tests' - have
96
+ * any. A functional spec that had no scenarios has no test plain file before, so its file shows as added whole; one whose
97
+ * scenarios all went, as deleted whole - an empty one is a syntax error.
98
+ */
99
+ export declare function diffTestPlainFiles(before: string, after: string, module: string, pathOf: TestPlainPath): string;
70
100
  /** A serialized tree as a person reads it, section by section. */
71
101
  export declare function treeText(serialized: string): string;
72
102
  /** A change as the planning agent is shown it. */
@@ -107,7 +137,7 @@ export interface SpecLayout {
107
137
  sections: string[];
108
138
  /** Each concept's place in the file's definitions - the parser orders them by what uses what. */
109
139
  concepts: Map<string, number>;
110
- /** The sections whose items the file writes with no blank line between them. */
140
+ /** The sections whose items the file writes with no blank line between them - definitions and reqs: functional specs never are. */
111
141
  tight: Set<string>;
112
142
  }
113
143
  export declare function layoutOf(text: string): SpecLayout;
@@ -122,6 +152,11 @@ export declare function layoutOf(text: string): SpecLayout;
122
152
  export declare function plainText(serialized: string, layout?: SpecLayout): string;
123
153
  /** git's diff of two serialized trees, as ***plain text - see plainText. */
124
154
  export declare function diffSpecText(before: string, after: string, file?: string, layout?: SpecLayout): string;
155
+ /**
156
+ * The functional specs of the modules below that a change between two serialized trees touches, each module's as a diff
157
+ * of its own file's `***functional specs***`: the tip is built with them, but its own file does not hold them.
158
+ */
159
+ export declare function diffRequiredSpecs(before: string, after: string, file?: string): string;
125
160
  /** The linked resources of a changed item whose content is different now - each hashed on its own: by their path, with both hashes. */
126
161
  export declare function resourcesChanged(before: string | undefined, after: string | undefined): Array<{
127
162
  target: string;
@@ -134,4 +169,12 @@ export declare function resourcesChanged(before: string | undefined, after: stri
134
169
  * held before - so the hunk says that it changed, and what it holds now is the file itself.
135
170
  */
136
171
  export declare function resourceDiff(changes: SpecChange[], file?: string): string;
137
- export declare function makeStep(title: string, reason: string, changes: SpecChange[], before: string, after: string, file: string, layout?: SpecLayout): SpecStep;
172
+ /**
173
+ * A step of the work. `testPlain`: for the conformance tests' steps, the module and where its functional specs' test plain
174
+ * files are - a scenario changed shows as a diff of its test plain file, after the module's.
175
+ */
176
+ export declare function makeStep(title: string, reason: string, changes: SpecChange[], before: string, after: string, file: string, layout?: SpecLayout, testPlain?: {
177
+ module: string;
178
+ pathOf: TestPlainPath;
179
+ }): SpecStep;
180
+ export {};