@printpractical/cruze 0.0.12 → 0.0.14

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 (297) hide show
  1. package/README.md +61 -118
  2. package/dist/adapters/inbound/cli/cli_context.js +0 -27
  3. package/dist/adapters/inbound/cli/commands/code.js +5 -18
  4. package/dist/adapters/inbound/cli/commands/documents.js +11 -37
  5. package/dist/adapters/inbound/cli/commands/init.js +1 -1
  6. package/dist/adapters/inbound/cli/commands/review.js +2 -9
  7. package/dist/adapters/inbound/cli/commands/status.js +16 -63
  8. package/dist/adapters/inbound/cli/commands.js +10 -37
  9. package/dist/adapters/inbound/cli/parse_command.js +0 -54
  10. package/dist/adapters/outbound/git_repository.js +2 -71
  11. package/dist/app/ports/repository.js +0 -12
  12. package/dist/app/project_context.js +15 -25
  13. package/dist/app/use_cases/approve_document.js +29 -0
  14. package/dist/app/use_cases/check_code.js +21 -56
  15. package/dist/app/use_cases/new_document.js +25 -0
  16. package/dist/app/use_cases/project_status.js +43 -67
  17. package/dist/app/use_cases/run_review.js +11 -30
  18. package/dist/app/use_cases/validate_project.js +4 -4
  19. package/dist/domain/approvals.js +51 -0
  20. package/dist/domain/check/check_source.js +2 -31
  21. package/dist/domain/config.js +8 -84
  22. package/dist/domain/hashing.js +16 -30
  23. package/dist/domain/ids.js +13 -27
  24. package/dist/domain/next_step.js +41 -0
  25. package/dist/domain/project/features.js +71 -0
  26. package/dist/domain/project/layout.js +11 -27
  27. package/dist/domain/project/project_docs.js +48 -0
  28. package/dist/domain/project/release.js +30 -0
  29. package/dist/domain/scaffold.js +0 -1
  30. package/dist/domain/skill_budget.js +4 -4
  31. package/dist/domain/trace.js +8 -25
  32. package/dist/domain/validation/document_rules.js +70 -0
  33. package/dist/domain/validation/release_rules.js +45 -0
  34. package/dist/domain/validation/validate_project.js +20 -27
  35. package/dist/domain/versions/cruze_version.js +3 -9
  36. package/dist/main.js +1 -1
  37. package/package.json +1 -1
  38. package/skills/asking/SKILL.md +51 -0
  39. package/skills/behavioural-testing/SKILL.md +14 -16
  40. package/skills/behavioural-testing/examples.md +1 -2
  41. package/skills/behavioural-testing/fakes.md +2 -2
  42. package/skills/build/SKILL.md +34 -38
  43. package/skills/dependency-approval/SKILL.md +34 -31
  44. package/skills/envision/SKILL.md +23 -18
  45. package/skills/envision/architecture.md +16 -0
  46. package/skills/envision/breakdown.md +14 -0
  47. package/skills/envision/possibilities.md +6 -7
  48. package/skills/fix/SKILL.md +34 -0
  49. package/skills/formats/SKILL.md +39 -87
  50. package/skills/formats/config.md +47 -0
  51. package/skills/formats/templates/architecture.md +18 -74
  52. package/skills/formats/templates/feature.md +18 -34
  53. package/skills/formats/templates/vision.md +15 -27
  54. package/skills/hexagonal-design/SKILL.md +11 -11
  55. package/skills/hexagonal-design/contracts.md +3 -3
  56. package/skills/hexagonal-design/fundamentals.md +1 -1
  57. package/skills/hexagonal-design/languages/cpp.md +1 -1
  58. package/skills/hexagonal-design/languages/rust.md +1 -1
  59. package/skills/hexagonal-design/languages/swift.md +2 -2
  60. package/skills/hexagonal-design/runtime.md +2 -2
  61. package/skills/research/SKILL.md +4 -4
  62. package/skills/research/evaluation.md +1 -1
  63. package/skills/review/SKILL.md +27 -0
  64. package/skills/roles/SKILL.md +8 -59
  65. package/skills/roles/code-reviewer.md +21 -32
  66. package/skills/roles/researcher.md +3 -3
  67. package/skills/shape/SKILL.md +37 -0
  68. package/skills/shape/self-check.md +12 -0
  69. package/src/adapters/inbound/cli/cli_context.ts +0 -27
  70. package/src/adapters/inbound/cli/commands/code.ts +5 -18
  71. package/src/adapters/inbound/cli/commands/documents.ts +11 -37
  72. package/src/adapters/inbound/cli/commands/init.ts +1 -1
  73. package/src/adapters/inbound/cli/commands/review.ts +2 -9
  74. package/src/adapters/inbound/cli/commands/status.ts +16 -63
  75. package/src/adapters/inbound/cli/commands.ts +10 -37
  76. package/src/adapters/inbound/cli/parse_command.ts +0 -54
  77. package/src/adapters/outbound/git_repository.ts +2 -71
  78. package/src/app/ports/repository.ts +0 -12
  79. package/src/app/project_context.ts +15 -25
  80. package/src/app/use_cases/approve_document.ts +29 -0
  81. package/src/app/use_cases/check_code.ts +21 -56
  82. package/src/app/use_cases/new_document.ts +25 -0
  83. package/src/app/use_cases/project_status.ts +49 -73
  84. package/src/app/use_cases/run_review.ts +11 -30
  85. package/src/app/use_cases/validate_project.ts +4 -4
  86. package/src/domain/approvals.ts +51 -0
  87. package/src/domain/check/check_source.ts +2 -31
  88. package/src/domain/config.ts +9 -85
  89. package/src/domain/hashing.ts +16 -30
  90. package/src/domain/ids.ts +13 -27
  91. package/src/domain/next_step.ts +41 -0
  92. package/src/domain/project/features.ts +71 -0
  93. package/src/domain/project/layout.ts +11 -27
  94. package/src/domain/project/project_docs.ts +48 -0
  95. package/src/domain/project/release.ts +30 -0
  96. package/src/domain/scaffold.ts +0 -1
  97. package/src/domain/skill_budget.ts +4 -4
  98. package/src/domain/trace.ts +8 -25
  99. package/src/domain/validation/document_rules.ts +70 -0
  100. package/src/domain/validation/release_rules.ts +45 -0
  101. package/src/domain/validation/validate_project.ts +21 -28
  102. package/src/domain/versions/cruze_version.ts +3 -9
  103. package/src/main.ts +1 -1
  104. package/templates/init/AGENTS.md +4 -12
  105. package/templates/init/ci.yml +3 -3
  106. package/templates/init/config.yaml +8 -17
  107. package/templates/init/review/claude.yaml +2 -2
  108. package/dist/adapters/inbound/cli/argument_files.js +0 -29
  109. package/dist/adapters/inbound/cli/commands/diff.js +0 -23
  110. package/dist/adapters/inbound/cli/commands/journal.js +0 -32
  111. package/dist/adapters/inbound/cli/commands/progress.js +0 -55
  112. package/dist/adapters/inbound/cli/commands/realign.js +0 -28
  113. package/dist/adapters/inbound/cli/commands/show.js +0 -96
  114. package/dist/app/use_cases/abandon_work.js +0 -42
  115. package/dist/app/use_cases/approve_artifact.js +0 -197
  116. package/dist/app/use_cases/journal_events.js +0 -64
  117. package/dist/app/use_cases/land_change.js +0 -134
  118. package/dist/app/use_cases/new_project_doc.js +0 -48
  119. package/dist/app/use_cases/new_work_item.js +0 -75
  120. package/dist/app/use_cases/realign_done.js +0 -45
  121. package/dist/app/use_cases/realign_status.js +0 -106
  122. package/dist/app/use_cases/record_progress.js +0 -118
  123. package/dist/app/use_cases/show_elements.js +0 -18
  124. package/dist/app/use_cases/show_notes.js +0 -11
  125. package/dist/app/use_cases/show_scope.js +0 -51
  126. package/dist/app/use_cases/upstream_diff.js +0 -55
  127. package/dist/domain/approvals/agent_approval.js +0 -68
  128. package/dist/domain/approvals/evaluate.js +0 -36
  129. package/dist/domain/approvals/fingerprint.js +0 -52
  130. package/dist/domain/approvals/rebase.js +0 -39
  131. package/dist/domain/approvals/records.js +0 -50
  132. package/dist/domain/approvals/upstream_text.js +0 -36
  133. package/dist/domain/architecture_sections.js +0 -23
  134. package/dist/domain/check/decision_comments.js +0 -38
  135. package/dist/domain/decision_citations.js +0 -62
  136. package/dist/domain/edits/agents_file.js +0 -58
  137. package/dist/domain/edits/managed_tables.js +0 -60
  138. package/dist/domain/edits/progress_block.js +0 -33
  139. package/dist/domain/edits/status_lines.js +0 -52
  140. package/dist/domain/elements.js +0 -126
  141. package/dist/domain/journal.js +0 -109
  142. package/dist/domain/land/bookkeeping.js +0 -54
  143. package/dist/domain/land/merge_plan.js +0 -137
  144. package/dist/domain/land/text_ops.js +0 -66
  145. package/dist/domain/line_diff.js +0 -56
  146. package/dist/domain/project/artifact_ref.js +0 -42
  147. package/dist/domain/project/deltas.js +0 -49
  148. package/dist/domain/project/element_lookup.js +0 -124
  149. package/dist/domain/project/living_model.js +0 -55
  150. package/dist/domain/project/new_items.js +0 -48
  151. package/dist/domain/project/notes.js +0 -74
  152. package/dist/domain/project/plan_parts.js +0 -111
  153. package/dist/domain/project/progress.js +0 -43
  154. package/dist/domain/project/project_view.js +0 -72
  155. package/dist/domain/project/roadmap.js +0 -43
  156. package/dist/domain/project/scope_view.js +0 -134
  157. package/dist/domain/project/work_items.js +0 -68
  158. package/dist/domain/status/next_step.js +0 -181
  159. package/dist/domain/status/pending_rethink.js +0 -22
  160. package/dist/domain/status/verification.js +0 -29
  161. package/dist/domain/status/work_status.js +0 -125
  162. package/dist/domain/validation/decision_rules.js +0 -76
  163. package/dist/domain/validation/delta_rules.js +0 -81
  164. package/dist/domain/validation/element_rules.js +0 -104
  165. package/dist/domain/validation/future_rules.js +0 -40
  166. package/dist/domain/validation/living_rules.js +0 -99
  167. package/dist/domain/validation/plan_rules.js +0 -124
  168. package/dist/domain/validation/project_rules.js +0 -115
  169. package/dist/domain/validation/size_rules.js +0 -45
  170. package/dist/domain/validation/vision_rules.js +0 -41
  171. package/dist/domain/validation/work_item_rules.js +0 -175
  172. package/dist/domain/versions/realign_notes.js +0 -72
  173. package/dist/domain/versions/rule_versions.js +0 -29
  174. package/dist/domain/versions/scheduled_exceptions.js +0 -41
  175. package/skills/about/SKILL.md +0 -21
  176. package/skills/architect/SKILL.md +0 -49
  177. package/skills/architect/feature.md +0 -40
  178. package/skills/architect/project.md +0 -58
  179. package/skills/architect/tweak.md +0 -22
  180. package/skills/architect/walkthrough.md +0 -35
  181. package/skills/domain-language/SKILL.md +0 -37
  182. package/skills/envision/feature.md +0 -25
  183. package/skills/envision/intake.md +0 -28
  184. package/skills/envision/project.md +0 -24
  185. package/skills/explore/SKILL.md +0 -39
  186. package/skills/formats/reference/architecture.md +0 -84
  187. package/skills/formats/reference/config.md +0 -80
  188. package/skills/formats/reference/durable-docs.md +0 -74
  189. package/skills/formats/reference/specs.md +0 -65
  190. package/skills/formats/reference/work-items.md +0 -100
  191. package/skills/formats/templates/adr.md +0 -7
  192. package/skills/formats/templates/change.md +0 -29
  193. package/skills/formats/templates/glossary.md +0 -8
  194. package/skills/formats/templates/roadmap.md +0 -32
  195. package/skills/formats/templates/spec.md +0 -11
  196. package/skills/formats/templates/standalone-change.md +0 -46
  197. package/skills/grilling/SKILL.md +0 -90
  198. package/skills/grilling/illustrating.md +0 -38
  199. package/skills/improve/SKILL.md +0 -51
  200. package/skills/land/SKILL.md +0 -48
  201. package/skills/next/SKILL.md +0 -31
  202. package/skills/plan/SKILL.md +0 -62
  203. package/skills/plan/tasks.md +0 -35
  204. package/skills/plan/test-plan.md +0 -14
  205. package/skills/realign/SKILL.md +0 -37
  206. package/skills/realign/notes/0.0.10.md +0 -131
  207. package/skills/realign/notes/0.0.11.md +0 -15
  208. package/skills/realign/notes/0.0.12.md +0 -31
  209. package/skills/realign/notes/0.0.2.md +0 -69
  210. package/skills/realign/notes/0.0.3.md +0 -11
  211. package/skills/realign/notes/0.0.4.md +0 -27
  212. package/skills/realign/notes/0.0.5.md +0 -38
  213. package/skills/realign/notes/0.0.6.md +0 -44
  214. package/skills/realign/notes/0.0.7.md +0 -11
  215. package/skills/realign/notes/0.0.8.md +0 -97
  216. package/skills/realign/notes/0.0.9.md +0 -59
  217. package/skills/realign/notes.md +0 -39
  218. package/skills/rethink/SKILL.md +0 -62
  219. package/skills/rethink/levels.md +0 -54
  220. package/skills/roadmap/SKILL.md +0 -54
  221. package/skills/roles/auditor.md +0 -35
  222. package/skills/roles/design-reviewer.md +0 -43
  223. package/skills/roles/design-rubric.md +0 -41
  224. package/skills/roles/plan-rubric.md +0 -11
  225. package/skills/roles/surveyor.md +0 -41
  226. package/skills/roles/verifier.md +0 -42
  227. package/skills/triage/SKILL.md +0 -37
  228. package/skills/verify/SKILL.md +0 -45
  229. package/skills/verify/manual-test.md +0 -23
  230. package/src/adapters/inbound/cli/argument_files.ts +0 -29
  231. package/src/adapters/inbound/cli/commands/diff.ts +0 -23
  232. package/src/adapters/inbound/cli/commands/journal.ts +0 -32
  233. package/src/adapters/inbound/cli/commands/progress.ts +0 -55
  234. package/src/adapters/inbound/cli/commands/realign.ts +0 -28
  235. package/src/adapters/inbound/cli/commands/show.ts +0 -96
  236. package/src/app/use_cases/abandon_work.ts +0 -42
  237. package/src/app/use_cases/approve_artifact.ts +0 -197
  238. package/src/app/use_cases/journal_events.ts +0 -64
  239. package/src/app/use_cases/land_change.ts +0 -134
  240. package/src/app/use_cases/new_project_doc.ts +0 -48
  241. package/src/app/use_cases/new_work_item.ts +0 -75
  242. package/src/app/use_cases/realign_done.ts +0 -45
  243. package/src/app/use_cases/realign_status.ts +0 -106
  244. package/src/app/use_cases/record_progress.ts +0 -118
  245. package/src/app/use_cases/show_elements.ts +0 -18
  246. package/src/app/use_cases/show_notes.ts +0 -11
  247. package/src/app/use_cases/show_scope.ts +0 -51
  248. package/src/app/use_cases/upstream_diff.ts +0 -55
  249. package/src/domain/approvals/agent_approval.ts +0 -68
  250. package/src/domain/approvals/evaluate.ts +0 -36
  251. package/src/domain/approvals/fingerprint.ts +0 -52
  252. package/src/domain/approvals/rebase.ts +0 -39
  253. package/src/domain/approvals/records.ts +0 -50
  254. package/src/domain/approvals/upstream_text.ts +0 -36
  255. package/src/domain/architecture_sections.ts +0 -23
  256. package/src/domain/check/decision_comments.ts +0 -38
  257. package/src/domain/decision_citations.ts +0 -62
  258. package/src/domain/edits/agents_file.ts +0 -58
  259. package/src/domain/edits/managed_tables.ts +0 -60
  260. package/src/domain/edits/progress_block.ts +0 -33
  261. package/src/domain/edits/status_lines.ts +0 -52
  262. package/src/domain/elements.ts +0 -126
  263. package/src/domain/journal.ts +0 -109
  264. package/src/domain/land/bookkeeping.ts +0 -54
  265. package/src/domain/land/merge_plan.ts +0 -137
  266. package/src/domain/land/text_ops.ts +0 -66
  267. package/src/domain/line_diff.ts +0 -56
  268. package/src/domain/project/artifact_ref.ts +0 -42
  269. package/src/domain/project/deltas.ts +0 -49
  270. package/src/domain/project/element_lookup.ts +0 -124
  271. package/src/domain/project/living_model.ts +0 -55
  272. package/src/domain/project/new_items.ts +0 -48
  273. package/src/domain/project/notes.ts +0 -74
  274. package/src/domain/project/plan_parts.ts +0 -111
  275. package/src/domain/project/progress.ts +0 -43
  276. package/src/domain/project/project_view.ts +0 -72
  277. package/src/domain/project/roadmap.ts +0 -43
  278. package/src/domain/project/scope_view.ts +0 -134
  279. package/src/domain/project/work_items.ts +0 -68
  280. package/src/domain/status/next_step.ts +0 -181
  281. package/src/domain/status/pending_rethink.ts +0 -22
  282. package/src/domain/status/verification.ts +0 -29
  283. package/src/domain/status/work_status.ts +0 -125
  284. package/src/domain/validation/decision_rules.ts +0 -76
  285. package/src/domain/validation/delta_rules.ts +0 -81
  286. package/src/domain/validation/element_rules.ts +0 -104
  287. package/src/domain/validation/future_rules.ts +0 -40
  288. package/src/domain/validation/living_rules.ts +0 -99
  289. package/src/domain/validation/plan_rules.ts +0 -124
  290. package/src/domain/validation/project_rules.ts +0 -115
  291. package/src/domain/validation/size_rules.ts +0 -45
  292. package/src/domain/validation/vision_rules.ts +0 -41
  293. package/src/domain/validation/work_item_rules.ts +0 -175
  294. package/src/domain/versions/realign_notes.ts +0 -72
  295. package/src/domain/versions/rule_versions.ts +0 -29
  296. package/src/domain/versions/scheduled_exceptions.ts +0 -41
  297. package/templates/init/gitattributes +0 -2
@@ -1,4 +1,4 @@
1
- /** Where Cruze documents live in a project, and the snapshot of them the domain works on. */
1
+ /** Where Cruze documents live in a project. */
2
2
 
3
3
  /** Project files by path, relative to the project root, with forward slashes. */
4
4
 
@@ -6,36 +6,20 @@
6
6
  export const PATHS = {
7
7
  readme: "README.md",
8
8
  vision: "docs/vision.md",
9
- glossary: "docs/glossary.md",
10
9
  architecture: "docs/architecture.md",
11
- roadmap: "docs/roadmap.md",
12
- adrDir: "docs/adr",
13
- specsDir: "docs/specs",
10
+ featuresDir: "docs/features",
14
11
  config: ".cruze/config.yaml",
15
- featuresDir: ".cruze/features",
16
- changesDir: ".cruze/changes",
17
- archiveDir: ".cruze/archive",
18
- notesDir: ".cruze/notes",
19
- projectApprovals: ".cruze/approvals.json",
20
- projectJournal: ".cruze/journal.jsonl",
12
+ approvals: ".cruze/approvals.json",
21
13
  } ;
22
14
 
23
- /** Directories whose files make up the snapshot. */
24
- export const SNAPSHOT_ROOTS = ["docs", ".cruze"] ;
25
-
26
- export function specPath(capability ) {
27
- return `${PATHS.specsDir}/${capability}.md`;
28
- }
29
-
30
- export function filesUnder(snapshot , dir ) {
31
- const prefix = `${dir}/`;
32
- return [...snapshot.keys()].filter((path) => path.startsWith(prefix)).sort();
15
+ export function featurePath(slug ) {
16
+ return `${PATHS.featuresDir}/${slug}.md`;
33
17
  }
34
18
 
35
- /** Paths directly matching `<dir>/<name><suffix>`, one level deep. */
36
- export function childFiles(snapshot , dir , suffix ) {
37
- return filesUnder(snapshot, dir).filter((path) => {
38
- const rest = path.slice(dir.length + 1);
39
- return !rest.includes("/") && rest.endsWith(suffix);
40
- });
19
+ /** The feature slug of a path directly under docs/features/, or undefined. */
20
+ export function slugOfFeaturePath(path ) {
21
+ const prefix = `${PATHS.featuresDir}/`;
22
+ if (!path.startsWith(prefix) || !path.endsWith(".md")) return undefined;
23
+ const rest = path.slice(prefix.length, -3);
24
+ return rest.includes("/") ? undefined : rest;
41
25
  }
@@ -0,0 +1,48 @@
1
+ import { parseApprovals, } from "../approvals.js";
2
+ import { parseConfig, } from "../config.js";
3
+ import { parseMarkdown, } from "../markdown.js";
4
+ import { readFeature, } from "./features.js";
5
+ import { PATHS, slugOfFeaturePath, } from "./layout.js";
6
+ import { readRelease, } from "./release.js";
7
+
8
+ /** Every Cruze document of a project, parsed: what the use cases work on. */
9
+
10
+
11
+
12
+
13
+
14
+
15
+
16
+
17
+
18
+
19
+
20
+ export function buildProjectDocs(snapshot ) {
21
+ const read = (path ) => {
22
+ const text = snapshot.get(path);
23
+ return text === undefined ? undefined : parseMarkdown(text);
24
+ };
25
+ const configText = snapshot.get(PATHS.config);
26
+ const vision = read(PATHS.vision);
27
+ const features = [...snapshot.keys()]
28
+ .flatMap((path) => {
29
+ const slug = slugOfFeaturePath(path);
30
+ return slug === undefined ? [] : [readFeature(slug, snapshot.get(path) ?? "")];
31
+ })
32
+ .sort((a, b) => a.slug.localeCompare(b.slug));
33
+ return {
34
+ snapshot,
35
+ config: configText === undefined ? undefined : parseConfig(configText),
36
+ vision,
37
+ architecture: read(PATHS.architecture),
38
+ release: vision === undefined ? [] : readRelease(vision),
39
+ features,
40
+ approvals: parseApprovals(snapshot.get(PATHS.approvals)),
41
+ };
42
+ }
43
+
44
+ /** The document a reference names: `vision`, `architecture`, a feature slug, or a path. */
45
+ export function resolveDocument(docs , ref ) {
46
+ const path = ref === "vision" ? PATHS.vision : ref === "architecture" ? PATHS.architecture : (docs.features.find((f) => f.slug === ref)?.path ?? ref);
47
+ return docs.snapshot.has(path) ? path : undefined;
48
+ }
@@ -0,0 +1,30 @@
1
+ import { findSection, } from "../markdown.js";
2
+ import { tablesIn } from "../markdown_tables.js";
3
+
4
+ /** One row of the vision's `## Release` table: a feature of the release, in build order, and the features it needs. */
5
+
6
+
7
+
8
+
9
+
10
+
11
+
12
+
13
+ /** The release's features, from the first table under `## Release` with Feature and Needs columns. */
14
+ export function readRelease(vision ) {
15
+ const section = findSection(vision, "Release");
16
+ if (section === null) return [];
17
+ const table = tablesIn(vision.lines, vision.inFence, section.start, section.end).find((t) => t.header.includes("Feature") && t.header.includes("Needs"));
18
+ if (table === undefined) return [];
19
+ const at = (name ) => table.header.indexOf(name);
20
+ return table.rows.map(({ cells, line }) => ({
21
+ slug: unquote(cells[at("Feature")] ?? ""),
22
+ summary: at("Summary") === -1 ? "" : (cells[at("Summary")] ?? ""),
23
+ needs: (cells[at("Needs")] ?? "").split(",").map(unquote).filter((need) => need !== "" && !/^[-—–]+$/.test(need)),
24
+ line,
25
+ }));
26
+ }
27
+
28
+ function unquote(cell ) {
29
+ return cell.trim().replace(/^`(.*)`$/, "$1").trim();
30
+ }
@@ -13,7 +13,6 @@ export const INIT_SCAFFOLD = [
13
13
  { path: "AGENTS.md", template: "init/AGENTS.md" },
14
14
  { path: ".cruze/config.yaml", template: "init/config.yaml" },
15
15
  { path: ".github/workflows/ci.yml", template: "init/ci.yml" },
16
- { path: ".gitattributes", template: "init/gitattributes" },
17
16
  ];
18
17
 
19
18
  /**
@@ -7,11 +7,11 @@
7
7
  export const MAX_FILE_WORDS = 2100;
8
8
 
9
9
  /**
10
- * The words agents may read across the whole bundle. Every retro adds rules, and between 0.0.1 and
11
- * 0.0.10 the skills grew by half while nothing was removed. Lower this when text is cut. Raising it
12
- * is the maintainer's decision, with the reason in the commit that raises it.
10
+ * The words agents may read across the whole bundle. By 0.0.13 the skills had grown to 45,800 words
11
+ * as every fix added rules and nothing was removed; 0.0.14 cut them to this. Lower it when text is
12
+ * cut. Raising it is the maintainer's decision, with the reason in the commit that raises it.
13
13
  */
14
- export const BUNDLE_WORD_BUDGET = 45450;
14
+ export const BUNDLE_WORD_BUDGET = 17605;
15
15
 
16
16
  /** Data the CLI reads or renders, rather than instructions an agent reads. */
17
17
  const NOT_INSTRUCTIONS = /(^|\/)(templates|notes)\//;
@@ -1,44 +1,27 @@
1
- import { headingElements, statusOf } from "./elements.js";
2
- import { findIds } from "./ids.js";
3
- import { PATHS } from "./project/layout.js";
4
-
5
- import { readScope } from "./project/plan_parts.js";
6
- import { readDelta } from "./project/deltas.js";
7
-
1
+ import { findScenarioIds } from "./ids.js";
2
+
8
3
 
9
4
 
10
5
 
11
6
 
12
7
 
13
-
8
+
14
9
 
15
10
 
16
11
 
17
- /**
18
- * Links scenarios to the tests that prove them. For a change: every scenario it delivers.
19
- * For the whole project: every built scenario in the living specs.
20
- */
21
- export function traceScenarios(view , tests , change ) {
22
- const required = change === undefined ? builtScenarios(view) : (readScope(change.doc)?.delivers ?? []).filter((id) => id.startsWith("SCN-"));
23
- const known = new Set([...view.living.elements.keys()]);
24
- for (const item of view.items.filter((i) => !i.archived)) for (const id of readDelta(item.doc).ids) known.add(id);
25
-
12
+ /** Links the required features' scenarios to the tests that carry their IDs. */
13
+ export function traceScenarios(features , required , tests ) {
14
+ const known = new Set(features.flatMap((f) => f.scenarios.map((s) => s.id)));
26
15
  const carriers = new Map ();
27
16
  const unknown = [];
28
17
  for (const [path, text] of tests) {
29
18
  text.split("\n").forEach((line, index) => {
30
- for (const id of findIds(line).filter((id) => id.startsWith("SCN-"))) {
19
+ for (const id of findScenarioIds(line)) {
31
20
  carriers.set(id, [...new Set([...(carriers.get(id) ?? []), path])]);
32
21
  if (!known.has(id)) unknown.push({ id, path, line: index + 1 });
33
22
  }
34
23
  });
35
24
  }
36
- const scenarios = required.map((id) => ({ id, tests: carriers.get(id) ?? [] }));
25
+ const scenarios = required.flatMap((f) => f.scenarios).map((s) => ({ id: s.id, tests: carriers.get(s.id) ?? [] }));
37
26
  return { scenarios, missing: scenarios.filter((s) => s.tests.length === 0).map((s) => s.id), unknown };
38
27
  }
39
-
40
- function builtScenarios(view ) {
41
- return [...view.living.docs]
42
- .filter(([path]) => path.startsWith(`${PATHS.specsDir}/`))
43
- .flatMap(([, doc]) => headingElements(doc).filter((e) => e.kind === "SCN" && statusOf(doc, e) === "built").map((e) => e.id));
44
- }
@@ -0,0 +1,70 @@
1
+ import { featureOfScenario } from "../ids.js";
2
+ import { findSection, } from "../markdown.js";
3
+ import { FEATURE_SECTIONS, FEATURE_STATUSES, statusLine, strayScenarioIds, } from "../project/features.js";
4
+ import { PATHS } from "../project/layout.js";
5
+
6
+ import { error, warning, } from "./problem.js";
7
+
8
+ /** Each document's required sections, its size, its scenarios, and leftover template guides. */
9
+
10
+ const VISION_SECTIONS = ["Problem", "Users", "Release"];
11
+ const ARCHITECTURE_SECTIONS = ["Overview", "Ports", "Adapters", "Runtime"];
12
+
13
+ /** Words past which a document stops being something a person reads in one sitting. */
14
+ export const WORD_LIMITS = { vision: 1500, architecture: 3000, feature: 2500 } ;
15
+
16
+ const GUIDE = /(?<!`)<(?!!--|\/|https?:|br\b)[A-Za-z][^<>\n]*>(?!`)/;
17
+ const PLACEHOLDER = /\{\{[a-z_]+\}\}/;
18
+
19
+ export function documentProblems(docs ) {
20
+ const problems = [];
21
+ if (docs.vision !== undefined) problems.push(...shapeOf(PATHS.vision, docs.vision, VISION_SECTIONS, WORD_LIMITS.vision));
22
+ if (docs.architecture !== undefined) problems.push(...shapeOf(PATHS.architecture, docs.architecture, ARCHITECTURE_SECTIONS, WORD_LIMITS.architecture));
23
+ for (const feature of docs.features) {
24
+ problems.push(...shapeOf(feature.path, feature.doc, [...FEATURE_SECTIONS], WORD_LIMITS.feature), ...featureProblems(feature));
25
+ }
26
+ problems.push(...duplicateScenarios(docs.features));
27
+ return problems;
28
+ }
29
+
30
+ function shapeOf(path , doc , sections , limit ) {
31
+ const problems = sections.filter((title) => findSection(doc, title) === null).map((title) => error(path, undefined, "missing-section", `needs a ## ${title} section`));
32
+ const words = doc.lines.filter((_, i) => !doc.inFence[i]).join(" ").split(/\s+/).filter((w) => w !== "").length;
33
+ if (words > limit) problems.push(warning(path, undefined, "too-long", `${words} words, over the ${limit} a person reads in one sitting; cut what the agent decided and the user needn't read, or split it`));
34
+ doc.lines.forEach((line, i) => {
35
+ if (doc.inFence[i]) return;
36
+ const withoutCode = line.replace(/`[^`]*`/g, "");
37
+ if (GUIDE.test(withoutCode) || PLACEHOLDER.test(withoutCode)) problems.push(error(path, i, "template-leftover", "replace the template guide or placeholder on this line"));
38
+ });
39
+ return problems;
40
+ }
41
+
42
+ function featureProblems(feature ) {
43
+ const problems = [];
44
+ const status = statusLine(feature.doc);
45
+ if (status === undefined) problems.push(error(feature.path, undefined, "status", `needs a "- Status: shaped" line under the title`));
46
+ else if (feature.status === undefined) problems.push(error(feature.path, status.line, "status", `the status is one of ${FEATURE_STATUSES.join(" or ")}`));
47
+ if (feature.scenarios.length === 0 && findSection(feature.doc, "Behaviour") !== null) {
48
+ problems.push(error(feature.path, undefined, "no-scenarios", `## Behaviour needs at least one ### SCN-${feature.slug}.<name>: <title> scenario`));
49
+ }
50
+ for (const scenario of feature.scenarios) {
51
+ if (featureOfScenario(scenario.id) !== feature.slug) problems.push(error(feature.path, scenario.line, "scenario-id", `${scenario.id} belongs to this feature, so it starts SCN-${feature.slug}.`));
52
+ }
53
+ for (const stray of strayScenarioIds(feature)) {
54
+ problems.push(error(feature.path, stray.line, "scenario-heading", `a scenario heading reads exactly "${stray.id}: <title>"`));
55
+ }
56
+ return problems;
57
+ }
58
+
59
+ function duplicateScenarios(features ) {
60
+ const seen = new Map ();
61
+ const problems = [];
62
+ for (const feature of features) {
63
+ for (const scenario of feature.scenarios) {
64
+ const first = seen.get(scenario.id);
65
+ if (first !== undefined) problems.push(error(feature.path, scenario.line, "duplicate-scenario", `${scenario.id} is already defined in ${first}`));
66
+ else seen.set(scenario.id, feature.path);
67
+ }
68
+ }
69
+ return problems;
70
+ }
@@ -0,0 +1,45 @@
1
+ import { isSlug } from "../ids.js";
2
+ import { PATHS } from "../project/layout.js";
3
+
4
+ import { error, warning, } from "./problem.js";
5
+
6
+ /** The vision's Release table: feature slugs, what each needs, and no cycles. */
7
+ export function releaseProblems(docs ) {
8
+ if (docs.vision === undefined) return [];
9
+ if (docs.release.length === 0) return [warning(PATHS.vision, undefined, "release", "## Release has no Feature | Summary | Needs table with features in it yet")];
10
+ const problems = [];
11
+ const slugs = new Set ();
12
+ for (const row of docs.release) {
13
+ if (!isSlug(row.slug)) problems.push(error(PATHS.vision, row.line, "release", `"${row.slug}" must be a feature slug: lowercase words joined by single hyphens`));
14
+ if (slugs.has(row.slug)) problems.push(error(PATHS.vision, row.line, "release", `${row.slug} is listed twice`));
15
+ slugs.add(row.slug);
16
+ }
17
+ for (const row of docs.release) {
18
+ for (const need of row.needs.filter((n) => !slugs.has(n))) {
19
+ problems.push(error(PATHS.vision, row.line, "release-needs", `${row.slug} needs ${need}, which isn't a feature of this release`));
20
+ }
21
+ }
22
+ const cycle = findCycle(docs.release.map((r) => [r.slug, r.needs.filter((n) => slugs.has(n))] ));
23
+ if (cycle !== undefined) problems.push(error(PATHS.vision, docs.release.find((r) => r.slug === cycle[0])?.line, "release-needs", `features need each other in a circle: ${cycle.join(" → ")}`));
24
+ return problems;
25
+ }
26
+
27
+ function findCycle(graph ) {
28
+ const edges = new Map(graph);
29
+ const done = new Set ();
30
+ const visit = (node , path ) => {
31
+ if (path.includes(node)) return [...path.slice(path.indexOf(node)), node];
32
+ if (done.has(node)) return undefined;
33
+ for (const next of edges.get(node) ?? []) {
34
+ const found = visit(next, [...path, node]);
35
+ if (found !== undefined) return found;
36
+ }
37
+ done.add(node);
38
+ return undefined;
39
+ };
40
+ for (const [node] of graph) {
41
+ const found = visit(node, []);
42
+ if (found !== undefined) return found;
43
+ }
44
+ return undefined;
45
+ }
@@ -1,36 +1,29 @@
1
+ import { PATHS } from "../project/layout.js";
1
2
 
2
- import { futureProblems } from "./future_rules.js";
3
- import { livingProblems } from "./living_rules.js";
3
+ import { documentProblems } from "./document_rules.js";
4
4
  import { error, sortProblems, } from "./problem.js";
5
- import { configProblems, readmeProblems, roadmapProblems, templateLeftovers } from "./project_rules.js";
6
- import { visionProblems } from "./vision_rules.js";
7
- import { workItemProblems } from "./work_item_rules.js";
5
+ import { releaseProblems } from "./release_rules.js";
8
6
 
9
7
  /** Every rule in the cruze-formats skill, applied to the whole project. */
10
- export function validateProject(view ) {
11
- const approvalProblems = [...view.approvalProblems].map(([path, message]) => error(path, undefined, "approvals-file", message));
12
- const journalProblems = [...view.journalProblems].flatMap(([path, lines]) =>
13
- lines.map((line) => error(path, line - 1, "journal-line", "this journal line is not a valid entry")),
14
- );
15
- return sortProblems([
16
- ...configProblems(view),
17
- ...livingProblems(view),
18
- ...visionProblems(view),
19
- ...futureProblems(view),
20
- ...roadmapProblems(view),
21
- ...workItemProblems(view),
22
- ...templateLeftovers(view),
23
- ...readmeProblems(view),
24
- ...approvalProblems,
25
- ...journalProblems,
26
- ]);
27
- }
28
-
29
- /** Problems that concern one document. */
30
- export function problemsFor(problems , path ) {
31
- return problems.filter((problem) => problem.path === path);
8
+ export function validateProject(docs ) {
9
+ const config = docs.config === undefined
10
+ ? [error(PATHS.config, undefined, "config", "the project has no config; run cruze init")]
11
+ : docs.config.problems.map((message) => error(PATHS.config, undefined, "config", message));
12
+ const approvals = approvalsFileProblems(docs.snapshot.get(PATHS.approvals));
13
+ return sortProblems([...config, ...approvals, ...documentProblems(docs), ...releaseProblems(docs)]);
32
14
  }
33
15
 
34
16
  export function hasErrors(problems ) {
35
17
  return problems.some((problem) => problem.severity === "error");
36
18
  }
19
+
20
+ function approvalsFileProblems(text ) {
21
+ if (text === undefined) return [];
22
+ try {
23
+ const parsed = JSON.parse(text);
24
+ if (!Array.isArray((parsed ).approvals)) return [error(PATHS.approvals, undefined, "approvals-file", "needs an approvals list; only cruze approve writes this file")];
25
+ return [];
26
+ } catch {
27
+ return [error(PATHS.approvals, undefined, "approvals-file", "not valid JSON; only cruze approve writes this file")];
28
+ }
29
+ }
@@ -36,15 +36,9 @@ export function versionSkew(cli , recorded )
36
36
  * after `version:`, so the file's comments and layout survive.
37
37
  */
38
38
  export function withRecordedVersion(configText , version ) {
39
- return withVersionLine(configText, "cruze", version);
40
- }
41
-
42
- /** The config text with a top-level version key, such as `cruze:` or `standards:`, set in place. */
43
- export function withVersionLine(configText , key , version ) {
44
- const line = `${key}: ${JSON.stringify(version)}`;
45
- const existing = new RegExp(`^${key}:.*$`, "m");
46
- if (existing.test(configText)) return configText.replace(existing, line);
47
- const anchor = key === "standards" ? /^cruze:.*$/m.exec(configText) ?? /^version:.*$/m.exec(configText) : /^version:.*$/m.exec(configText);
39
+ const line = `cruze: ${JSON.stringify(version)}`;
40
+ if (/^cruze:.*$/m.test(configText)) return configText.replace(/^cruze:.*$/m, line);
41
+ const anchor = /^version:.*$/m.exec(configText);
48
42
  if (anchor === null) return `${line}\n${configText}`;
49
43
  const end = anchor.index + anchor[0].length;
50
44
  return `${configText.slice(0, end)}\n${line}${configText.slice(end)}`;
package/dist/main.js CHANGED
@@ -10,7 +10,7 @@ import { ProcessAgentRunner } from "./adapters/outbound/process_agent_runner.js"
10
10
  import { SystemClock } from "./adapters/outbound/system_clock.js";
11
11
  import { DefaultAnswerPrompter, TerminalPrompter } from "./adapters/outbound/terminal_prompter.js";
12
12
 
13
- // A reader that stops early, such as `cruze journal list | head`, closes the pipe; that is not an error worth a stack trace.
13
+ // A reader that stops early, such as `cruze status --text | head`, closes the pipe; that is not an error worth a stack trace.
14
14
  process.stdout.on("error", (error ) => {
15
15
  if (error.code === "EPIPE") process.exit(0);
16
16
  throw error;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@printpractical/cruze",
3
- "version": "0.0.12",
3
+ "version": "0.0.14",
4
4
  "description": "Spec-driven development for coding agents: one living model of the system, small deltas, convergent review.",
5
5
  "license": "Apache-2.0",
6
6
  "author": "Chris Buchter",
@@ -0,0 +1,51 @@
1
+ ---
2
+ name: cruze-asking
3
+ description: When to ask the user and when to decide, and how to show a question so the user can answer it cold, with plain words, code sketches and diagrams. Use before asking the user anything during envision, shape, build, review or fix.
4
+ ---
5
+
6
+ # Asking the user
7
+
8
+ The user's time is the scarcest thing in the project. Every question costs them a context switch, and a long run of questions turns into rubber-stamping: in one project the user answered 71% of 187 design questions with a bare "agree". So ask little, ask what only the user can answer, and show it so it can be answered at a glance.
9
+
10
+ ## What to ask and what to decide
11
+
12
+ Ask when a choice changes something the user will see or live with:
13
+
14
+ - behaviour a user of the product will notice, including what they see when something fails;
15
+ - a public API: anything another program, person or team calls, types, or reads;
16
+ - scope: what is in a feature and what is out;
17
+ - a significant dependency, through `.agents/skills/cruze-dependency-approval/SKILL.md`;
18
+ - security, privacy, stored data, compatibility, or a commitment that is costly to reverse.
19
+
20
+ Decide everything else yourself: internal structure, private names, which module owns what, internal types, file layout, equivalent idioms, limits and timeouts with an obvious value. Pick the conventional option. Uncertainty alone doesn't make a choice the user's: investigate first. Then list the decisions a user might question in the document's `## Decisions`, one line each with the reason, so the user sees them when they approve, without being asked.
21
+
22
+ ## How many
23
+
24
+ At most one question round per step, of five questions or fewer, before you show the document. When a choice has a sensible default, take it and list it under Decisions instead of asking. When the user defers ("you decide"), decide, and don't ask again.
25
+
26
+ ## Showing a question
27
+
28
+ The user knows what the product should do, not the design's internals, and is often coming back from other work.
29
+
30
+ - **Say where things stand** in one line first: the step and the feature, such as "Shaping export-csv."
31
+ - **Explain the part before asking about it:** what it is, what it does, and why the question comes up now.
32
+ - **Use plain words.** Name a scenario or decision by what it says, and add its ID after it in parentheses only when the user may want to look it up. Never let an ID stand alone.
33
+ - **Show each option** with the form that fits:
34
+
35
+ | The question decides | Show |
36
+ | --- | --- |
37
+ | What a user does or sees | The command and its exact output, or a sketch of the screen, with real values |
38
+ | A public API or a port | A code sketch in the project's language: types, signatures, errors, and one call |
39
+ | Where something lives | A box-and-arrow diagram, or the directory tree |
40
+ | An order of steps, or what happens on failure | A numbered sequence with the failure branch |
41
+ | A stored format or config | A sample of the file |
42
+ | A dependency | The code with it and without it |
43
+
44
+ - **Keep sketches short:** shape only, bodies left out, about 15 lines per option. Draw diagrams in plain text, which reads in a terminal; documents keep their Mermaid.
45
+ - **Mark your recommendation** and why, in one line.
46
+
47
+ A shape the user chose from a sketch is part of the decision: write it into the document, so build makes it as the user saw it.
48
+
49
+ ## Challenge, then defer
50
+
51
+ When the user steers toward something that breaks the architecture or a standard, name the rule, the concrete risk here and your alternative, once. Then the user decides, and their decision stands.
@@ -1,30 +1,28 @@
1
1
  ---
2
2
  name: cruze-behavioural-testing
3
- description: The testing standard for a Cruze project, covering which tests to write, the seam each one drives, the ID it carries, and fakes instead of mocks. Use when writing a change's test plan, when writing, changing or fixing a test, when building a fake for a port, when deciding whether a unit test is warranted, or when reviewing tests.
3
+ description: The testing standard for a Cruze project, covering which tests to write, the seam each one drives, the ID it carries, and fakes instead of mocks. Use when writing, changing or fixing a test, when building a fake for a port, when deciding whether a unit test is warranted, or when reviewing tests.
4
4
  ---
5
5
 
6
6
  # Behavioural testing
7
7
 
8
- Behaviour is the contract. Every scenario in the specs becomes a test that drives the system through a use case and checks what a caller would see. Tests pin behaviour, not structure, so the code behind them can be rewritten freely.
8
+ Behaviour is the contract. Every scenario in a feature becomes a test that drives the system through a use case and checks what a caller would see. Tests pin behaviour, not structure, so the code behind them can be rewritten freely.
9
9
 
10
- ## Writing tests for a task
10
+ ## Writing tests for a feature
11
11
 
12
- 1. Find the test plan rows whose Subject the task proves. Done when you know the seam, test file and kind of every test the task needs.
13
- 2. Write one test at its seam, carrying its subject ID. Take the inputs and expected values from the scenario's GIVEN, WHEN and THEN.
12
+ 1. For each scenario, pick its seam from the table below. Done when you know the seam, test file and kind of every test the feature needs.
13
+ 2. Write one test at its seam, carrying the scenario's ID. Take the inputs and expected values from the scenario's GIVEN, WHEN and THEN.
14
14
  3. Run it and watch it fail for the reason you expect (red). A test that passes before the code exists proves nothing, so fix the test. A test that guards a race or an ordering must be seen failing against code that has the flaw: run the racing work concurrently, on a multi-threaded runtime where there is one, and wait for a signal the system gives, such as a watch having opened, never for a fixed sleep.
15
15
  4. Write the least code that makes it pass (green). Then write the next test. Work in tracer bullets, one test and one slice of code at a time, never all the tests first.
16
- 5. Done when every test plan row whose Subject the task proves has a passing test carrying that ID, and each test fails when the behaviour it checks is removed. Tasks that prove only a `UC`, `ADP` or `MOD` are proved by the rows of the scenarios and ports they serve.
16
+ 5. Done when every scenario has a passing test carrying its ID, every port with a real adapter has contract tests, and each test fails when the behaviour it checks is removed.
17
17
 
18
18
  ## The four kinds
19
19
 
20
- These match the test plan in `.agents/skills/cruze-formats/reference/work-items.md`.
21
-
22
20
  | Kind | Subject | Seam | When |
23
21
  | --- | --- | --- | --- |
24
- | `behaviour` | `SCN` | The use case's API with a fake for each driven port, or the inbound adapter when the outcome is what that adapter shows, such as CLI output, an exit code or an HTTP status | One or more per scenario, always |
25
- | `contract` | `PORT` | The same cases run against the fake and every real adapter | Every driven port that has a real adapter |
26
- | `domain` | `ENT` | The entity's own API | Only for real logic: state machines, calculations, invariants with edge cases |
27
- | `smoke` | `FLOW` | The real inbound adapter, wired by the real composition root | The top 1 to 3 flows |
22
+ | `behaviour` | a scenario | The use case's API with a fake for each driven port, or the inbound adapter when the outcome is what that adapter shows, such as CLI output, an exit code or an HTTP status | One or more per scenario, always |
23
+ | `contract` | a port | The same cases run against the fake and every real adapter | Every driven port that has a real adapter |
24
+ | `domain` | a domain type | The entity's own API | Only for real logic: state machines, calculations, invariants with edge cases |
25
+ | `smoke` | the main path | The real inbound adapter, wired by the real composition root | The top 1 to 3 paths through the system |
28
26
 
29
27
  A behaviour test drives a use case, not a single domain type, so it keeps passing when rules move between entities. In a library with no use cases, the seam is the library's public API. Write a domain test only when a rule's edge cases are too many to reach through use cases one by one.
30
28
 
@@ -51,9 +49,9 @@ A behaviour test drives a use case, not a single domain type, so it keeps passin
51
49
 
52
50
  ## Protecting the check
53
51
 
54
- - Behaviour, contract and smoke tests for approved scenarios, ports and flows are the safety net. Never edit one to get green. When one fails, either the code is wrong, or the spec is. A wrong spec or design goes through the rethink step, and the test changes only with the delta that changes its subject: a spec delta for a `SCN`, an architecture delta for a `PORT` or `FLOW`.
52
+ - Behaviour, contract and smoke tests for approved scenarios and ports are the safety net. Never edit one to get green. When one fails, either the code is wrong, or the scenario is. A wrong scenario goes back through shape, and the test changes only after the user approves the changed scenario.
55
53
  - Domain tests are disposable. Rewrite or delete them along with the code they test.
56
- - Never skip a test, delete one, or weaken an assertion to finish a task. Verify flags any diff to a safety-net test that has no matching delta.
54
+ - Never skip a test, delete one, or weaken an assertion to finish. Review flags any weakened safety-net test.
57
55
 
58
56
  ## What not to write
59
57
 
@@ -65,6 +63,6 @@ Side-by-side examples are in [examples.md](examples.md).
65
63
 
66
64
  ## Naming and tracing
67
65
 
68
- - Every test carries its subject ID where `.agents/skills/cruze-formats/reference/work-items.md` says, such as `// SCN-access.direct-serial` on the line above it.
66
+ - Every behaviour test carries its scenario's ID in a comment or string, such as `// SCN-open-serial.attach` on the line above it. A test name alone doesn't count, since `cruze trace` looks for the literal ID.
69
67
  - A scenario may need several tests, and each of them carries its ID. One test covers one scenario.
70
- - `cruze trace` checks scenarios only: it fails when a scenario the change delivers has no test carrying its ID, or when a test cites a scenario nobody defined. Run it before calling a change's tests complete, and quote its result. For the `contract`, `domain` and `smoke` rows, list each subject ID next to the test that carries it.
68
+ - `cruze trace <slug>` fails when one of the feature's scenarios has no test carrying its ID, or when a test cites a scenario no feature defines. Run it before calling a feature's tests complete, and quote its result.
@@ -7,7 +7,7 @@ The examples are in Rust, from a console-access tool. The shape is the same in a
7
7
  Good. It drives the use case, fakes the ports, and checks what the caller sees:
8
8
 
9
9
  ```rust
10
- // SCN-access.second-hop-refused
10
+ // SCN-ssh-hops.second-hop-refused
11
11
  #[test]
12
12
  fn second_hop_refused_closes_the_first_hop() {
13
13
  let catalog = InMemoryDeviceCatalog::with(device("lab-router", ssh_via("jump-1")));
@@ -89,7 +89,6 @@ The file format is the TOML adapter's business. Its contract tests cover it, and
89
89
  Good. A state machine with real rules, tested through the entity's own API:
90
90
 
91
91
  ```rust
92
- // ENT-access.console-session
93
92
  #[test]
94
93
  fn a_closed_session_cannot_reopen() {
95
94
  let mut session = ConsoleSession::opening();
@@ -4,7 +4,7 @@ A fake is a real, working implementation of a driven port that keeps its state i
4
4
 
5
5
  ## Writing a fake
6
6
 
7
- 1. Read the port's `Operations`, with `cruze show <PORT ID>`. The fake implements every operation with the guarantees and failures listed there, and nothing more. That includes every way the real adapter ends on its own, such as a session closed when its stream ends or its server shuts down.
7
+ 1. Read the port's operations in `docs/architecture.md`. The fake implements every operation with the guarantees and failures listed there, and nothing more. That includes every way the real adapter ends on its own, such as a session closed when its stream ends or its server shuts down.
8
8
  2. Keep state in plain in-memory structures: a map for a catalog, a list for recorded effects, a settable value for a clock.
9
9
  3. Give tests a way to arrange state and to inject each failure the contract lists. Use a constructor that takes the initial state, and a method such as `fail_next_with(HopError::Refused)`. When a use case writes through the port, also let a test stall an operation, and fail one that writes several things partway through, so each partial failure and cancellation can be tested from every state it can leave.
10
10
  4. Record effects the contract makes observable, such as the order hops were closed in, so tests can assert them.
@@ -21,7 +21,7 @@ A contract test states the port's contract once and runs it against every implem
21
21
  3. Run the suite against the fake in the ordinary test run.
22
22
  4. Run it against each real adapter too. When the real adapter needs something external, such as an SSH server, a serial device or a database, use a local stand-in (a test server, a pseudo-terminal, a container), and mark the run so CI can provide it.
23
23
 
24
- When the contract changes, the port's `Operations` change first through a delta, then the contract suite, then the fake and the adapters.
24
+ When the contract changes, the port's operations in `docs/architecture.md` change first, then the contract suite, then the fake and the adapters.
25
25
 
26
26
  ## What a fake is not
27
27