@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
@@ -35,7 +35,7 @@
35
35
  "patterns": [
36
36
  {
37
37
  "name": "entity.name.function.plain",
38
- "match": "\\*\\*\\*(definitions|implementation reqs|test reqs|functional specs|acceptance tests)\\*\\*\\*"
38
+ "match": "^\\*\\*\\*(definitions|implementation reqs|test reqs|functional specs|conformance tests|acceptance tests)\\*\\*\\*"
39
39
  },
40
40
  {
41
41
  "name": "meta.frontmatter.plain",
@@ -63,7 +63,29 @@
63
63
  "match": "^\\s*(requires):"
64
64
  },
65
65
  {
66
- "match": "^\\s*-\\s+([a-zA-Z_][a-zA-Z0-9_]*)",
66
+ "match": "^\\s*(module:)\\s*([^\\s#][^\\s#]*)?",
67
+ "captures": {
68
+ "1": {
69
+ "name": "keyword.control.import.plain"
70
+ },
71
+ "2": {
72
+ "name": "entity.name.type.class.plain"
73
+ }
74
+ }
75
+ },
76
+ {
77
+ "match": "^\\s*(func_id:)\\s*([a-z0-9]+(?:-[a-z0-9]+)*)?(?=\\s|$)",
78
+ "captures": {
79
+ "1": {
80
+ "name": "keyword.control.import.plain"
81
+ },
82
+ "2": {
83
+ "name": "entity.name.tag.id.plain"
84
+ }
85
+ }
86
+ },
87
+ {
88
+ "match": "^\\s*-\\s+([^\\s#:][^\\s#]*)",
67
89
  "captures": {
68
90
  "1": {
69
91
  "name": "entity.name.type.class.plain"
@@ -93,6 +115,10 @@
93
115
  "begin": "\\{%\\s*include",
94
116
  "end": "%\\}"
95
117
  },
118
+ {
119
+ "name": "entity.name.tag.id.plain",
120
+ "match": "^#\\s+[a-z0-9]+(?:-[a-z0-9]+)*\\s*$"
121
+ },
96
122
  {
97
123
  "name": "entity.punctuation.definition.bullet.plain",
98
124
  "match": "^\\s*-"
@@ -121,4 +147,4 @@
121
147
  }
122
148
  },
123
149
  "scopeName": "source.plain"
124
- }
150
+ }
@@ -0,0 +1,48 @@
1
+ import { type MarshalledPlainSource } from "./spec.ts";
2
+ import { type TestPlainFile } from "./test-plain-file.ts";
3
+ /** Port of project.py: a module with its requires chain and the test plain files bound to its functionalities. */
4
+ export interface BoundTestPlainFile {
5
+ /** The file on disk. */
6
+ path: string;
7
+ /** The path relative to the entry module's folder, with `/` as separator. */
8
+ name: string;
9
+ parsed: TestPlainFile;
10
+ }
11
+ export interface ProjectParseResult {
12
+ /** As `parseModuleChain` gives it, with the scenarios of the test plain files merged into the functionalities. */
13
+ chain: Array<[string, MarshalledPlainSource]>;
14
+ testPlainFiles: BoundTestPlainFile[];
15
+ }
16
+ /**
17
+ * `[path, name]` of every .plain file under the test folders: folder by folder, and within one by path - each
18
+ * folder's files before its subfolders', as `os.walk` gives them. A test folder that does not exist is skipped.
19
+ */
20
+ export declare function discoverTestPlainFiles(testDirs: string[], entryDir: string): Array<[string, string]>;
21
+ export declare function readTestPlainFileText(path: string, name: string): string;
22
+ /**
23
+ * Read every test plain file under the test folders and add its scenarios to the functionality it binds to, in
24
+ * place in the chain's trees.
25
+ *
26
+ * A `.plain` file under a test folder without the `module:` and `func_id:` keys is an error, as is a file that binds
27
+ * to a module outside the chain or to a func-id the module does not have. The concepts of the scenarios must be
28
+ * visible to the bound module. Files bound to one functionality are merged in discovery order; a scenario-id is
29
+ * unique across that merged set.
30
+ */
31
+ export declare function bindTestPlainFiles(chain: Array<[string, MarshalledPlainSource]>, testDirs: string[], templateDirs: string[], entryDir: string): BoundTestPlainFile[];
32
+ /**
33
+ * Throw for the first functionality of the chain, in render order, that has no test plain file bound to it.
34
+ * The sections of the file may be empty; the file itself is what the check asks for.
35
+ */
36
+ export declare function checkTestPlainFiles(chain: Array<[string, MarshalledPlainSource]>, bound: BoundTestPlainFile[]): void;
37
+ /**
38
+ * Parse a module with its requires chain and bind the test plain files under `testDirs` to its functionalities.
39
+ *
40
+ * `templateDirs[0]` is the entry module's folder; test plain files are named relative to it. With
41
+ * `requireTestPlainFiles`, every functionality of every module in the chain needs a test plain file.
42
+ */
43
+ export declare function parseProject(plainFileName: string, templateDirs: string[], testDirs: string[], requireTestPlainFiles: boolean): ProjectParseResult;
44
+ /** Every functionality of the chain, in render order, with no test plain file bound to it. turri's: it lists them all. */
45
+ export declare function missingTestPlainFiles(chain: Array<[string, MarshalledPlainSource]>, bound: BoundTestPlainFile[]): Array<{
46
+ module: string;
47
+ funcId: string;
48
+ }>;
@@ -0,0 +1,155 @@
1
+ import { readdirSync, readFileSync, statSync } from "node:fs";
2
+ import { join } from "node:path";
3
+ import { collectConcepts } from "./concepts.js";
4
+ import { MissingTestPlainFileError, PlainSyntaxError, UnsupportedBase64Content } from "./errors.js";
5
+ import { findLargeBase64Blob, relativeResourcePath } from "./files.js";
6
+ import { parseModuleChain } from "./parser.js";
7
+ import { ACCEPTANCE_TESTS, CONFORMANCE_TESTS, getFunctionalSpecs, } from "./spec.js";
8
+ import { isTestPlainFile, loadFrontmatter, parseTestPlainFileSource, scenarioItem, validateScenarioConcepts, } from "./test-plain-file.js";
9
+ const PLAIN_SOURCE_FILE_EXTENSION = ".plain";
10
+ function isDirectory(path) {
11
+ try {
12
+ return statSync(path).isDirectory();
13
+ }
14
+ catch {
15
+ return false;
16
+ }
17
+ }
18
+ /**
19
+ * `[path, name]` of every .plain file under the test folders: folder by folder, and within one by path - each
20
+ * folder's files before its subfolders', as `os.walk` gives them. A test folder that does not exist is skipped.
21
+ */
22
+ export function discoverTestPlainFiles(testDirs, entryDir) {
23
+ const found = [];
24
+ const walk = (root) => {
25
+ const entries = readdirSync(root, { withFileTypes: true });
26
+ const byName = (a, b) => (a.name < b.name ? -1 : a.name > b.name ? 1 : 0);
27
+ for (const entry of entries.filter((e) => !e.isDirectory()).sort(byName)) {
28
+ if (entry.name.endsWith(PLAIN_SOURCE_FILE_EXTENSION)) {
29
+ found.push([join(root, entry.name), relativeResourcePath(entryDir, root, entry.name)]);
30
+ }
31
+ }
32
+ for (const entry of entries.filter((e) => e.isDirectory()).sort(byName))
33
+ walk(join(root, entry.name));
34
+ };
35
+ for (const testDir of testDirs)
36
+ if (isDirectory(testDir))
37
+ walk(testDir);
38
+ return found;
39
+ }
40
+ export function readTestPlainFileText(path, name) {
41
+ let text;
42
+ try {
43
+ text = new TextDecoder("utf-8", { fatal: true }).decode(readFileSync(path));
44
+ }
45
+ catch {
46
+ throw new PlainSyntaxError(`Plain syntax error: File '${name}' under a test folder is not a text file.`);
47
+ }
48
+ const blob = findLargeBase64Blob(text);
49
+ if (blob !== null) {
50
+ throw new UnsupportedBase64Content(`Test plain file '${name}' contains a base64-encoded blob (${blob.length} characters) inlined in the ` +
51
+ "scenarios. This is not supported. Remove the base64 data from the file.");
52
+ }
53
+ return text;
54
+ }
55
+ /**
56
+ * Read every test plain file under the test folders and add its scenarios to the functionality it binds to, in
57
+ * place in the chain's trees.
58
+ *
59
+ * A `.plain` file under a test folder without the `module:` and `func_id:` keys is an error, as is a file that binds
60
+ * to a module outside the chain or to a func-id the module does not have. The concepts of the scenarios must be
61
+ * visible to the bound module. Files bound to one functionality are merged in discovery order; a scenario-id is
62
+ * unique across that merged set.
63
+ */
64
+ export function bindTestPlainFiles(chain, testDirs, templateDirs, entryDir) {
65
+ const trees = new Map(chain);
66
+ const bound = [];
67
+ // (module, func-id) -> scenario-id -> where it is first used.
68
+ const scenarioPlaces = new Map();
69
+ for (const [path, name] of discoverTestPlainFiles(testDirs, entryDir)) {
70
+ const text = readTestPlainFileText(path, name);
71
+ let metadata;
72
+ try {
73
+ metadata = loadFrontmatter(text.split("\r\n").join("\n")).metadata;
74
+ }
75
+ catch (e) {
76
+ throw new PlainSyntaxError(`Plain syntax error: File '${name}' under a test folder has invalid frontmatter: ${e instanceof Error ? e.message : String(e)}`);
77
+ }
78
+ if (!isTestPlainFile(metadata)) {
79
+ throw new PlainSyntaxError(`Plain syntax error: File '${name}' is under a test folder but is not a test plain file: its ` +
80
+ "frontmatter needs the keys `module:` and `func_id:`.");
81
+ }
82
+ const parsed = parseTestPlainFileSource(text, name, templateDirs, {});
83
+ const tree = trees.get(parsed.module);
84
+ if (tree === undefined) {
85
+ throw new PlainSyntaxError(`Plain syntax error: Test plain file '${name}' binds to module '${parsed.module}', which is not the ` +
86
+ `entry module or one of its required modules (${[...trees.keys()].join(", ")}).`);
87
+ }
88
+ const functionality = getFunctionalSpecs(tree).find((item) => item.id === parsed.funcId);
89
+ if (functionality === undefined) {
90
+ throw new PlainSyntaxError(`Plain syntax error: Test plain file '${name}' binds to functionality ` +
91
+ `'${parsed.module}:${parsed.funcId}', which module '${parsed.module}' does not have.`);
92
+ }
93
+ const [concepts] = collectConcepts(tree);
94
+ const errors = validateScenarioConcepts(parsed, new Set(concepts));
95
+ if (errors.length > 0) {
96
+ throw new PlainSyntaxError(`Plain syntax error: Found ${errors.length} errors in test plain file '${name}':\n${errors.join("\n")}`);
97
+ }
98
+ const key = `${parsed.module}\0${parsed.funcId}`;
99
+ if (!scenarioPlaces.has(key))
100
+ scenarioPlaces.set(key, new Map());
101
+ const places = scenarioPlaces.get(key);
102
+ for (const [listKey, scenarios] of [
103
+ [CONFORMANCE_TESTS, parsed.conformanceTests],
104
+ [ACCEPTANCE_TESTS, parsed.acceptanceTests],
105
+ ]) {
106
+ for (const scenario of scenarios) {
107
+ if (places.has(scenario.id)) {
108
+ throw new PlainSyntaxError(`Plain syntax error: Syntax error at line ${scenario.lineNumber} in test plain file ` +
109
+ `'${name}': Duplicate scenario-id \`${scenario.id}\` for functionality ` +
110
+ `'${parsed.module}:${parsed.funcId}'; it is first used ${places.get(scenario.id)}.`);
111
+ }
112
+ places.set(scenario.id, `at line ${scenario.lineNumber} in test plain file '${name}'`);
113
+ functionality[listKey].push(scenarioItem(scenario));
114
+ }
115
+ }
116
+ bound.push({ path, name, parsed });
117
+ }
118
+ return bound;
119
+ }
120
+ /**
121
+ * Throw for the first functionality of the chain, in render order, that has no test plain file bound to it.
122
+ * The sections of the file may be empty; the file itself is what the check asks for.
123
+ */
124
+ export function checkTestPlainFiles(chain, bound) {
125
+ const missing = missingTestPlainFiles(chain, bound)[0];
126
+ if (missing !== undefined) {
127
+ throw new MissingTestPlainFileError(`Functionality ${missing.module}:${missing.funcId} has no test plain file. Add one, by convention ` +
128
+ `tests/${missing.module}/${missing.funcId}.plain, with the add-test-scenarios skill.`);
129
+ }
130
+ }
131
+ /**
132
+ * Parse a module with its requires chain and bind the test plain files under `testDirs` to its functionalities.
133
+ *
134
+ * `templateDirs[0]` is the entry module's folder; test plain files are named relative to it. With
135
+ * `requireTestPlainFiles`, every functionality of every module in the chain needs a test plain file.
136
+ */
137
+ export function parseProject(plainFileName, templateDirs, testDirs, requireTestPlainFiles) {
138
+ const chain = parseModuleChain(plainFileName, templateDirs).map(([name, tree]) => [name, structuredClone(tree)]);
139
+ const bound = bindTestPlainFiles(chain, testDirs, templateDirs, templateDirs[0]);
140
+ if (requireTestPlainFiles)
141
+ checkTestPlainFiles(chain, bound);
142
+ return { chain, testPlainFiles: bound };
143
+ }
144
+ /** Every functionality of the chain, in render order, with no test plain file bound to it. turri's: it lists them all. */
145
+ export function missingTestPlainFiles(chain, bound) {
146
+ const boundFunctionalities = new Set(bound.map((item) => `${item.parsed.module}\0${item.parsed.funcId}`));
147
+ const missing = [];
148
+ for (const [moduleName, tree] of chain) {
149
+ for (const functionality of getFunctionalSpecs(tree)) {
150
+ if (!boundFunctionalities.has(`${moduleName}\0${functionality.id}`))
151
+ missing.push({ module: moduleName, funcId: functionality.id });
152
+ }
153
+ }
154
+ return missing;
155
+ }
@@ -1,90 +1,152 @@
1
1
  import type { LinkedResource } from "./markdown.ts";
2
- /** Port of plain_spec.py. */
2
+ /**
3
+ * Port of plain_spec.py. The reference keeps its names for a functional spec's id - `frid`, `get_frids` - and so do
4
+ * the ports here: a frid is the functional spec's func-id, the `# <func-id>` heading it is written under.
5
+ */
3
6
  export declare const DEFINITIONS = "definitions";
4
7
  export declare const NON_FUNCTIONAL_REQUIREMENTS = "implementation reqs";
5
8
  export declare const TEST_REQUIREMENTS = "test reqs";
6
9
  export declare const FUNCTIONAL_REQUIREMENTS = "functional specs";
10
+ export declare const CONFORMANCE_TESTS = "conformance_tests";
7
11
  export declare const ACCEPTANCE_TESTS = "acceptance_tests";
12
+ export declare const CONFORMANCE_TEST_HEADING = "conformance tests";
8
13
  export declare const ACCEPTANCE_TEST_HEADING = "acceptance tests";
9
14
  /**
10
15
  * Where a module's tree carries the functional specs it inherits through `requires`: every
11
- * functional spec of every module it requires, transitively, bottom of the chain first.
12
- * Kept apart from its own, which are numbered 1, 2, 3 on their own.
16
+ * functional spec of every module it requires, transitively, bottom of the chain first, each
17
+ * with its module. turri's, not the reference parser's.
13
18
  */
14
19
  export declare const REQUIRED_FUNCTIONAL_SPECS = "required functional specs";
20
+ /**
21
+ * Where a module's tree carries the test plain files bound to its chain, as the reference's parse output has them
22
+ * (`test_plain_files`): each file's path, from the module's folder, to the functional spec it binds to. turri's.
23
+ */
24
+ export declare const TEST_PLAIN_FILES = "test plain files";
25
+ /** The sections whose content is a list of `- ` items. */
26
+ export declare const LIST_SECTION_HEADINGS: string[];
15
27
  export declare const ALLOWED_SPECIFICATION_HEADINGS: string[];
16
28
  export declare const ALLOWED_IMPORT_SPECIFICATION_HEADINGS: string[];
29
+ /**
30
+ * A func-id or scenario-id: segments of lowercase letters and digits, with one hyphen between segments. So a hyphen
31
+ * never starts or ends an id, two hyphens never touch, and an id is never only hyphens.
32
+ */
33
+ export declare const SEMANTIC_ID_PATTERN: RegExp;
34
+ export declare function isSemanticId(text: string): boolean;
17
35
  export interface CodeVariable {
18
36
  name: string;
19
37
  value: string;
20
38
  }
39
+ /** An item of a list section: definitions, implementation reqs, test reqs. */
21
40
  export interface SpecificationItem {
22
41
  markdown: string;
23
42
  linked_resources?: LinkedResource[];
24
43
  code_variables?: CodeVariable[];
25
- [ACCEPTANCE_TESTS]?: SpecificationItem[];
26
44
  }
27
- /** A functional spec inherited through `requires`, with the module it belongs to and its id there. */
28
- export interface InheritedSpecificationItem extends SpecificationItem {
45
+ /** A conformance or acceptance scenario, bound to its functional spec from a test plain file. */
46
+ export interface ScenarioItem {
47
+ id: string;
48
+ text: string;
49
+ linked_resources: LinkedResource[];
50
+ code_variables?: CodeVariable[];
51
+ }
52
+ /** A functional spec: its func-id, its body, its links, and the scenarios of its test plain files. */
53
+ export interface FunctionalSpecItem {
54
+ id: string;
55
+ markdown: string;
56
+ linked_resources: LinkedResource[];
57
+ [CONFORMANCE_TESTS]: ScenarioItem[];
58
+ [ACCEPTANCE_TESTS]: ScenarioItem[];
59
+ code_variables?: CodeVariable[];
60
+ }
61
+ /** A functional spec inherited through `requires`, with the module it belongs to. */
62
+ export interface InheritedSpecificationItem extends FunctionalSpecItem {
29
63
  module: string;
30
- frid: string;
64
+ }
65
+ /** What `TEST_PLAIN_FILES` holds for each test plain file: the functional spec it binds to. */
66
+ export interface TestPlainFileBinding {
67
+ module: string;
68
+ func_id: string;
69
+ }
70
+ /** A scenario as it is regenerated with: its scenario-id and its text, code variables put in place. */
71
+ export interface ScenarioText {
72
+ id: string;
73
+ text: string;
31
74
  }
32
75
  /**
33
76
  * The functional specs a module inherits through `requires`, rendered: each with its
34
- * module, its id there, and its acceptance tests.
77
+ * module, its func-id, and its scenarios.
35
78
  */
36
79
  export declare function getInheritedFunctionalSpecs(tree: MarshalledPlainSource, replaceCodeVariables?: boolean): Array<{
37
80
  module: string;
38
- frid: string;
81
+ funcId: string;
39
82
  spec: string;
40
- acceptanceTests?: string[];
83
+ conformanceTests: ScenarioText[];
84
+ acceptanceTests: ScenarioText[];
41
85
  }>;
42
- /**
43
- * The marshalled tree: heading -> list of items. Flat: functionalities are numbered 1, 2, 3
44
- * in the order they appear. Nested sections with dotted ids ("2.3") are not supported.
45
- */
86
+ /** The marshalled tree: heading -> list of items. Functional specs are named by their func-ids, in file order. */
46
87
  export interface MarshalledPlainSource {
47
88
  [heading: string]: unknown;
48
89
  }
49
90
  export declare function getSpecificationItems(tree: MarshalledPlainSource, heading: string): SpecificationItem[] | undefined;
91
+ /** The functional specs of a tree, in file order. */
92
+ export declare function getFunctionalSpecs(tree: MarshalledPlainSource): FunctionalSpecItem[];
93
+ /** True if any functional spec of the tree has acceptance scenarios. */
50
94
  export declare function hasAcceptanceTests(tree: MarshalledPlainSource): boolean;
51
95
  export interface CollectedLinkedResource extends LinkedResource {
52
96
  sections: string[];
53
97
  }
54
- export declare function collectSpecificationLinkedResources(specification: SpecificationItem, specificationHeading: string, linkedResourcesList: CollectedLinkedResource[]): void;
55
- export declare function collectLinkedResourcesInSection(section: MarshalledPlainSource, linkedResourcesList: CollectedLinkedResource[], specificationsList: string[] | null, includeAcceptanceTests: boolean, frid: string | null): boolean;
56
- export declare function collectLinkedResources(tree: MarshalledPlainSource, linkedResourcesList: CollectedLinkedResource[], specificationsList: string[] | null, includeAcceptanceTests: boolean, frid?: string | null): boolean;
98
+ export declare function collectSpecificationLinkedResources(specification: {
99
+ linked_resources?: LinkedResource[];
100
+ }, specificationHeading: string, linkedResourcesList: CollectedLinkedResource[]): void;
57
101
  /**
58
- * The FRID iterator: one functionality ID per functional spec, 1, 2, 3 in order.
59
- * Everything that walks the spec one functionality at a time - rendering, ranges, context
102
+ * Add the linked resources of the tree to `linkedResourcesList` and sort the list by link text.
103
+ *
104
+ * `specificationsList` limits the sections read; null or empty reads every section. The functional specs are read up
105
+ * to and including `frid`, or all of them when it is null. With `includeTestScenarios` the links of the conformance
106
+ * and acceptance scenarios are added too, for `frid` alone when one is given. Returns true when `frid` was reached.
107
+ */
108
+ export declare function collectLinkedResources(tree: MarshalledPlainSource, linkedResourcesList: CollectedLinkedResource[], specificationsList: string[] | null, includeTestScenarios: boolean, frid?: string | null): boolean;
109
+ /**
110
+ * The FRID iterator: the func-ids of the tree's functional specs, in file order.
111
+ * Everything that walks the spec one functional spec at a time - rendering, ranges, context
60
112
  * building - is driven by this.
61
113
  */
62
114
  export declare function getFrids(tree: MarshalledPlainSource): Generator<string>;
63
115
  export declare function getFirstFrid(tree: MarshalledPlainSource): string | null;
64
116
  export declare function getNextFrid(tree: MarshalledPlainSource, frid: string): string | null;
65
117
  export declare function getPreviousFrid(tree: MarshalledPlainSource, frid: string): string | null;
118
+ /** The func-ids that come before `targetFrid` in the specification, in order. */
66
119
  export declare function getFridsBefore(tree: MarshalledPlainSource, targetFrid: string): string[];
67
- export declare function getSpecificationItemMarkdown(specificationItem: SpecificationItem, codeVariables: Record<string, string>, replaceCodeVariables: boolean): string;
120
+ /**
121
+ * The text under `key` of the item, with its code variables recorded in `codeVariables` and, when
122
+ * `replaceCodeVariables` is set, their values put in place of the `{{ name }}` placeholders.
123
+ */
124
+ export declare function getSpecificationItemMarkdown(specificationItem: {
125
+ code_variables?: CodeVariable[];
126
+ }, codeVariables: Record<string, string>, replaceCodeVariables: boolean, key?: string): string;
68
127
  export interface Specifications {
69
128
  [DEFINITIONS]: string[];
70
129
  [NON_FUNCTIONAL_REQUIREMENTS]: string[];
71
130
  [TEST_REQUIREMENTS]: string[];
72
131
  [FUNCTIONAL_REQUIREMENTS]: string[];
73
- [ACCEPTANCE_TESTS]?: string[];
132
+ [CONFORMANCE_TESTS]: ScenarioText[];
133
+ [ACCEPTANCE_TESTS]: ScenarioText[];
74
134
  }
75
135
  /**
76
- * Build the rendering context for one functionality: every definition and
77
- * implementation/test req in scope, the functionalities up to and including this one,
78
- * and this one's acceptance tests.
136
+ * The specifications that build `frid`: every list section, the functional specs up to and including `frid`, and
137
+ * the conformance and acceptance scenarios of `frid` as `{id, text}` entries.
138
+ *
139
+ * Returns the specifications and the code variables they use (null when there are none). Throws when `frid` is not
140
+ * in the tree.
79
141
  */
80
142
  export declare function getSpecificationsForFrid(tree: MarshalledPlainSource, frid: string, replaceCodeVariables?: boolean): [Specifications, Record<string, string> | null];
81
- /** Iterate every functionality with the context that should be rendered for it. */
143
+ /** Iterate every functional spec with the context that should be rendered for it. turri's, for a host. */
82
144
  export declare function iterateFunctionalSpecs(tree: MarshalledPlainSource, replaceCodeVariables?: boolean): Generator<{
83
- frid: string;
145
+ funcId: string;
84
146
  specifications: Specifications;
85
147
  codeVariables: Record<string, string> | null;
86
148
  }>;
87
149
  export declare function hashText(text: string): string;
88
150
  export declare function getHashValue(specifications: unknown): string;
89
- export declare function getRenderRange(renderRange: string, tree: MarshalledPlainSource): string[];
90
- export declare function getRenderRangeFrom(start: string, tree: MarshalledPlainSource): string[];
151
+ /** The func-ids from `start` through `end` inclusive, or through the last one when `end` is not given. */
152
+ export declare function getFridsRange(tree: MarshalledPlainSource, start: string, end?: string): string[];