@printpractical/cruze 0.0.13 → 0.0.15

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 (308) 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/install.js +1 -0
  7. package/dist/adapters/inbound/cli/commands/review.js +2 -9
  8. package/dist/adapters/inbound/cli/commands/status.js +16 -63
  9. package/dist/adapters/inbound/cli/commands.js +10 -37
  10. package/dist/adapters/inbound/cli/parse_command.js +0 -54
  11. package/dist/adapters/outbound/git_repository.js +2 -71
  12. package/dist/app/ports/repository.js +0 -12
  13. package/dist/app/project_context.js +15 -25
  14. package/dist/app/use_cases/approve_document.js +29 -0
  15. package/dist/app/use_cases/check_code.js +21 -56
  16. package/dist/app/use_cases/install_skills.js +7 -1
  17. package/dist/app/use_cases/new_document.js +25 -0
  18. package/dist/app/use_cases/project_status.js +43 -67
  19. package/dist/app/use_cases/run_review.js +11 -30
  20. package/dist/app/use_cases/validate_project.js +8 -5
  21. package/dist/domain/approvals.js +51 -0
  22. package/dist/domain/check/check_source.js +3 -32
  23. package/dist/domain/check/imports.js +3 -3
  24. package/dist/domain/check/rust_imports.js +25 -3
  25. package/dist/domain/config.js +8 -84
  26. package/dist/domain/hashing.js +16 -30
  27. package/dist/domain/ids.js +13 -27
  28. package/dist/domain/next_step.js +41 -0
  29. package/dist/domain/project/features.js +71 -0
  30. package/dist/domain/project/layout.js +11 -27
  31. package/dist/domain/project/project_docs.js +48 -0
  32. package/dist/domain/project/release.js +30 -0
  33. package/dist/domain/scaffold.js +0 -1
  34. package/dist/domain/skill_budget.js +4 -4
  35. package/dist/domain/trace.js +8 -25
  36. package/dist/domain/validation/document_rules.js +65 -0
  37. package/dist/domain/validation/release_rules.js +45 -0
  38. package/dist/domain/validation/review_rules.js +21 -0
  39. package/dist/domain/validation/validate_project.js +20 -27
  40. package/dist/domain/versions/cruze_version.js +3 -9
  41. package/dist/main.js +1 -1
  42. package/package.json +1 -1
  43. package/skills/asking/SKILL.md +51 -0
  44. package/skills/behavioural-testing/SKILL.md +14 -16
  45. package/skills/behavioural-testing/examples.md +1 -2
  46. package/skills/behavioural-testing/fakes.md +2 -2
  47. package/skills/build/SKILL.md +34 -38
  48. package/skills/dependency-approval/SKILL.md +34 -31
  49. package/skills/envision/SKILL.md +23 -18
  50. package/skills/envision/architecture.md +16 -0
  51. package/skills/envision/breakdown.md +14 -0
  52. package/skills/envision/possibilities.md +6 -7
  53. package/skills/fix/SKILL.md +34 -0
  54. package/skills/formats/SKILL.md +39 -87
  55. package/skills/formats/config.md +47 -0
  56. package/skills/formats/templates/architecture.md +18 -74
  57. package/skills/formats/templates/feature.md +18 -34
  58. package/skills/formats/templates/vision.md +15 -27
  59. package/skills/hexagonal-design/SKILL.md +11 -11
  60. package/skills/hexagonal-design/contracts.md +3 -3
  61. package/skills/hexagonal-design/fundamentals.md +1 -1
  62. package/skills/hexagonal-design/languages/cpp.md +1 -1
  63. package/skills/hexagonal-design/languages/rust.md +1 -1
  64. package/skills/hexagonal-design/languages/swift.md +2 -2
  65. package/skills/hexagonal-design/runtime.md +2 -2
  66. package/skills/research/SKILL.md +4 -4
  67. package/skills/research/evaluation.md +1 -1
  68. package/skills/review/SKILL.md +27 -0
  69. package/skills/roles/SKILL.md +8 -59
  70. package/skills/roles/code-reviewer.md +22 -32
  71. package/skills/roles/researcher.md +3 -3
  72. package/skills/shape/SKILL.md +37 -0
  73. package/skills/shape/self-check.md +12 -0
  74. package/src/adapters/inbound/cli/cli_context.ts +0 -27
  75. package/src/adapters/inbound/cli/commands/code.ts +5 -18
  76. package/src/adapters/inbound/cli/commands/documents.ts +11 -37
  77. package/src/adapters/inbound/cli/commands/init.ts +1 -1
  78. package/src/adapters/inbound/cli/commands/install.ts +1 -0
  79. package/src/adapters/inbound/cli/commands/review.ts +2 -9
  80. package/src/adapters/inbound/cli/commands/status.ts +16 -63
  81. package/src/adapters/inbound/cli/commands.ts +10 -37
  82. package/src/adapters/inbound/cli/parse_command.ts +0 -54
  83. package/src/adapters/outbound/git_repository.ts +2 -71
  84. package/src/app/ports/repository.ts +0 -12
  85. package/src/app/project_context.ts +15 -25
  86. package/src/app/use_cases/approve_document.ts +29 -0
  87. package/src/app/use_cases/check_code.ts +21 -56
  88. package/src/app/use_cases/install_skills.ts +7 -1
  89. package/src/app/use_cases/new_document.ts +25 -0
  90. package/src/app/use_cases/project_status.ts +49 -73
  91. package/src/app/use_cases/run_review.ts +11 -30
  92. package/src/app/use_cases/validate_project.ts +8 -5
  93. package/src/domain/approvals.ts +51 -0
  94. package/src/domain/check/check_source.ts +3 -32
  95. package/src/domain/check/imports.ts +3 -3
  96. package/src/domain/check/rust_imports.ts +25 -3
  97. package/src/domain/config.ts +9 -85
  98. package/src/domain/hashing.ts +16 -30
  99. package/src/domain/ids.ts +13 -27
  100. package/src/domain/next_step.ts +41 -0
  101. package/src/domain/project/features.ts +71 -0
  102. package/src/domain/project/layout.ts +11 -27
  103. package/src/domain/project/project_docs.ts +48 -0
  104. package/src/domain/project/release.ts +30 -0
  105. package/src/domain/scaffold.ts +0 -1
  106. package/src/domain/skill_budget.ts +4 -4
  107. package/src/domain/trace.ts +8 -25
  108. package/src/domain/validation/document_rules.ts +65 -0
  109. package/src/domain/validation/release_rules.ts +45 -0
  110. package/src/domain/validation/review_rules.ts +21 -0
  111. package/src/domain/validation/validate_project.ts +21 -28
  112. package/src/domain/versions/cruze_version.ts +3 -9
  113. package/src/main.ts +1 -1
  114. package/templates/init/AGENTS.md +4 -12
  115. package/templates/init/ci.yml +3 -3
  116. package/templates/init/config.yaml +8 -17
  117. package/templates/init/review/claude.yaml +2 -2
  118. package/dist/adapters/inbound/cli/argument_files.js +0 -29
  119. package/dist/adapters/inbound/cli/commands/diff.js +0 -23
  120. package/dist/adapters/inbound/cli/commands/journal.js +0 -32
  121. package/dist/adapters/inbound/cli/commands/progress.js +0 -55
  122. package/dist/adapters/inbound/cli/commands/realign.js +0 -28
  123. package/dist/adapters/inbound/cli/commands/show.js +0 -96
  124. package/dist/app/use_cases/abandon_work.js +0 -42
  125. package/dist/app/use_cases/approve_artifact.js +0 -197
  126. package/dist/app/use_cases/journal_events.js +0 -64
  127. package/dist/app/use_cases/land_change.js +0 -134
  128. package/dist/app/use_cases/new_project_doc.js +0 -48
  129. package/dist/app/use_cases/new_work_item.js +0 -75
  130. package/dist/app/use_cases/realign_done.js +0 -45
  131. package/dist/app/use_cases/realign_status.js +0 -106
  132. package/dist/app/use_cases/record_progress.js +0 -118
  133. package/dist/app/use_cases/show_elements.js +0 -18
  134. package/dist/app/use_cases/show_notes.js +0 -11
  135. package/dist/app/use_cases/show_scope.js +0 -51
  136. package/dist/app/use_cases/upstream_diff.js +0 -55
  137. package/dist/domain/approvals/agent_approval.js +0 -68
  138. package/dist/domain/approvals/evaluate.js +0 -36
  139. package/dist/domain/approvals/fingerprint.js +0 -52
  140. package/dist/domain/approvals/rebase.js +0 -39
  141. package/dist/domain/approvals/records.js +0 -50
  142. package/dist/domain/approvals/upstream_text.js +0 -36
  143. package/dist/domain/architecture_sections.js +0 -23
  144. package/dist/domain/check/decision_comments.js +0 -38
  145. package/dist/domain/decision_citations.js +0 -62
  146. package/dist/domain/edits/agents_file.js +0 -58
  147. package/dist/domain/edits/managed_tables.js +0 -60
  148. package/dist/domain/edits/progress_block.js +0 -33
  149. package/dist/domain/edits/status_lines.js +0 -52
  150. package/dist/domain/elements.js +0 -126
  151. package/dist/domain/journal.js +0 -109
  152. package/dist/domain/land/bookkeeping.js +0 -54
  153. package/dist/domain/land/merge_plan.js +0 -137
  154. package/dist/domain/land/text_ops.js +0 -66
  155. package/dist/domain/line_diff.js +0 -56
  156. package/dist/domain/project/artifact_ref.js +0 -42
  157. package/dist/domain/project/deltas.js +0 -49
  158. package/dist/domain/project/element_lookup.js +0 -124
  159. package/dist/domain/project/living_model.js +0 -55
  160. package/dist/domain/project/new_items.js +0 -48
  161. package/dist/domain/project/notes.js +0 -74
  162. package/dist/domain/project/plan_parts.js +0 -111
  163. package/dist/domain/project/progress.js +0 -43
  164. package/dist/domain/project/project_view.js +0 -72
  165. package/dist/domain/project/roadmap.js +0 -43
  166. package/dist/domain/project/scope_view.js +0 -134
  167. package/dist/domain/project/work_items.js +0 -68
  168. package/dist/domain/status/next_step.js +0 -181
  169. package/dist/domain/status/pending_rethink.js +0 -22
  170. package/dist/domain/status/verification.js +0 -29
  171. package/dist/domain/status/work_status.js +0 -125
  172. package/dist/domain/validation/decision_rules.js +0 -76
  173. package/dist/domain/validation/delta_rules.js +0 -81
  174. package/dist/domain/validation/element_rules.js +0 -104
  175. package/dist/domain/validation/future_rules.js +0 -40
  176. package/dist/domain/validation/living_rules.js +0 -99
  177. package/dist/domain/validation/plan_rules.js +0 -124
  178. package/dist/domain/validation/project_rules.js +0 -115
  179. package/dist/domain/validation/size_rules.js +0 -45
  180. package/dist/domain/validation/vision_rules.js +0 -41
  181. package/dist/domain/validation/work_item_rules.js +0 -175
  182. package/dist/domain/versions/realign_notes.js +0 -72
  183. package/dist/domain/versions/rule_versions.js +0 -29
  184. package/dist/domain/versions/scheduled_exceptions.js +0 -41
  185. package/skills/about/SKILL.md +0 -21
  186. package/skills/architect/SKILL.md +0 -49
  187. package/skills/architect/feature.md +0 -40
  188. package/skills/architect/project.md +0 -71
  189. package/skills/architect/tweak.md +0 -22
  190. package/skills/architect/walkthrough.md +0 -35
  191. package/skills/domain-language/SKILL.md +0 -37
  192. package/skills/envision/feature.md +0 -25
  193. package/skills/envision/intake.md +0 -28
  194. package/skills/envision/project.md +0 -24
  195. package/skills/explore/SKILL.md +0 -39
  196. package/skills/formats/reference/architecture.md +0 -84
  197. package/skills/formats/reference/config.md +0 -80
  198. package/skills/formats/reference/durable-docs.md +0 -74
  199. package/skills/formats/reference/specs.md +0 -65
  200. package/skills/formats/reference/work-items.md +0 -100
  201. package/skills/formats/templates/adr.md +0 -7
  202. package/skills/formats/templates/change.md +0 -29
  203. package/skills/formats/templates/glossary.md +0 -8
  204. package/skills/formats/templates/roadmap.md +0 -32
  205. package/skills/formats/templates/spec.md +0 -11
  206. package/skills/formats/templates/standalone-change.md +0 -46
  207. package/skills/grilling/SKILL.md +0 -90
  208. package/skills/grilling/illustrating.md +0 -38
  209. package/skills/improve/SKILL.md +0 -51
  210. package/skills/land/SKILL.md +0 -48
  211. package/skills/next/SKILL.md +0 -31
  212. package/skills/plan/SKILL.md +0 -62
  213. package/skills/plan/tasks.md +0 -35
  214. package/skills/plan/test-plan.md +0 -14
  215. package/skills/realign/SKILL.md +0 -37
  216. package/skills/realign/notes/0.0.10.md +0 -131
  217. package/skills/realign/notes/0.0.11.md +0 -15
  218. package/skills/realign/notes/0.0.12.md +0 -31
  219. package/skills/realign/notes/0.0.13.md +0 -11
  220. package/skills/realign/notes/0.0.2.md +0 -69
  221. package/skills/realign/notes/0.0.3.md +0 -11
  222. package/skills/realign/notes/0.0.4.md +0 -27
  223. package/skills/realign/notes/0.0.5.md +0 -38
  224. package/skills/realign/notes/0.0.6.md +0 -44
  225. package/skills/realign/notes/0.0.7.md +0 -11
  226. package/skills/realign/notes/0.0.8.md +0 -97
  227. package/skills/realign/notes/0.0.9.md +0 -59
  228. package/skills/realign/notes.md +0 -40
  229. package/skills/rethink/SKILL.md +0 -62
  230. package/skills/rethink/levels.md +0 -54
  231. package/skills/roadmap/SKILL.md +0 -54
  232. package/skills/roles/auditor.md +0 -35
  233. package/skills/roles/design-reviewer.md +0 -43
  234. package/skills/roles/design-rubric.md +0 -41
  235. package/skills/roles/plan-rubric.md +0 -11
  236. package/skills/roles/surveyor.md +0 -41
  237. package/skills/roles/verifier.md +0 -42
  238. package/skills/triage/SKILL.md +0 -37
  239. package/skills/verify/SKILL.md +0 -45
  240. package/skills/verify/manual-test.md +0 -23
  241. package/src/adapters/inbound/cli/argument_files.ts +0 -29
  242. package/src/adapters/inbound/cli/commands/diff.ts +0 -23
  243. package/src/adapters/inbound/cli/commands/journal.ts +0 -32
  244. package/src/adapters/inbound/cli/commands/progress.ts +0 -55
  245. package/src/adapters/inbound/cli/commands/realign.ts +0 -28
  246. package/src/adapters/inbound/cli/commands/show.ts +0 -96
  247. package/src/app/use_cases/abandon_work.ts +0 -42
  248. package/src/app/use_cases/approve_artifact.ts +0 -197
  249. package/src/app/use_cases/journal_events.ts +0 -64
  250. package/src/app/use_cases/land_change.ts +0 -134
  251. package/src/app/use_cases/new_project_doc.ts +0 -48
  252. package/src/app/use_cases/new_work_item.ts +0 -75
  253. package/src/app/use_cases/realign_done.ts +0 -45
  254. package/src/app/use_cases/realign_status.ts +0 -106
  255. package/src/app/use_cases/record_progress.ts +0 -118
  256. package/src/app/use_cases/show_elements.ts +0 -18
  257. package/src/app/use_cases/show_notes.ts +0 -11
  258. package/src/app/use_cases/show_scope.ts +0 -51
  259. package/src/app/use_cases/upstream_diff.ts +0 -55
  260. package/src/domain/approvals/agent_approval.ts +0 -68
  261. package/src/domain/approvals/evaluate.ts +0 -36
  262. package/src/domain/approvals/fingerprint.ts +0 -52
  263. package/src/domain/approvals/rebase.ts +0 -39
  264. package/src/domain/approvals/records.ts +0 -50
  265. package/src/domain/approvals/upstream_text.ts +0 -36
  266. package/src/domain/architecture_sections.ts +0 -23
  267. package/src/domain/check/decision_comments.ts +0 -38
  268. package/src/domain/decision_citations.ts +0 -62
  269. package/src/domain/edits/agents_file.ts +0 -58
  270. package/src/domain/edits/managed_tables.ts +0 -60
  271. package/src/domain/edits/progress_block.ts +0 -33
  272. package/src/domain/edits/status_lines.ts +0 -52
  273. package/src/domain/elements.ts +0 -126
  274. package/src/domain/journal.ts +0 -109
  275. package/src/domain/land/bookkeeping.ts +0 -54
  276. package/src/domain/land/merge_plan.ts +0 -137
  277. package/src/domain/land/text_ops.ts +0 -66
  278. package/src/domain/line_diff.ts +0 -56
  279. package/src/domain/project/artifact_ref.ts +0 -42
  280. package/src/domain/project/deltas.ts +0 -49
  281. package/src/domain/project/element_lookup.ts +0 -124
  282. package/src/domain/project/living_model.ts +0 -55
  283. package/src/domain/project/new_items.ts +0 -48
  284. package/src/domain/project/notes.ts +0 -74
  285. package/src/domain/project/plan_parts.ts +0 -111
  286. package/src/domain/project/progress.ts +0 -43
  287. package/src/domain/project/project_view.ts +0 -72
  288. package/src/domain/project/roadmap.ts +0 -43
  289. package/src/domain/project/scope_view.ts +0 -134
  290. package/src/domain/project/work_items.ts +0 -68
  291. package/src/domain/status/next_step.ts +0 -181
  292. package/src/domain/status/pending_rethink.ts +0 -22
  293. package/src/domain/status/verification.ts +0 -29
  294. package/src/domain/status/work_status.ts +0 -125
  295. package/src/domain/validation/decision_rules.ts +0 -76
  296. package/src/domain/validation/delta_rules.ts +0 -81
  297. package/src/domain/validation/element_rules.ts +0 -104
  298. package/src/domain/validation/future_rules.ts +0 -40
  299. package/src/domain/validation/living_rules.ts +0 -99
  300. package/src/domain/validation/plan_rules.ts +0 -124
  301. package/src/domain/validation/project_rules.ts +0 -115
  302. package/src/domain/validation/size_rules.ts +0 -45
  303. package/src/domain/validation/vision_rules.ts +0 -41
  304. package/src/domain/validation/work_item_rules.ts +0 -175
  305. package/src/domain/versions/realign_notes.ts +0 -72
  306. package/src/domain/versions/rule_versions.ts +0 -29
  307. package/src/domain/versions/scheduled_exceptions.ts +0 -41
  308. package/templates/init/gitattributes +0 -2
@@ -1,37 +0,0 @@
1
- ---
2
- name: cruze-triage
3
- description: Handles a bug report by reproducing it, finding its root cause against the living docs, and fixing a code defect with a failing test first, or sending a spec gap to architect and a design flaw to rethink.
4
- ---
5
-
6
- # Triage
7
-
8
- Triage starts from a symptom and ends with a fix, or with the right step to fix it. A bug has three possible causes, and each goes somewhere different. When the code doesn't do what the specs say, triage fixes it. When the specs never covered the case, it goes to `architect` as a tweak. When the design can't deliver the specs, it goes to `rethink`.
9
-
10
- ## Steps
11
-
12
- 1. **Capture the symptom.** Get the exact steps, what happened (output, error, exit code), what the user expected, and the environment. Done when you can write the steps as commands.
13
- 2. **Reproduce it.** Run the steps against the real system. Done when you can make it fail on demand and have quoted the output. When you can't reproduce it, ask the user for what differs in their environment, and stop until you can.
14
- 3. **Place it in the living docs.** Find the requirement and scenarios in `docs/specs/` that cover this behaviour, and the flow in `docs/architecture.md` that runs it, with `cruze show --index` and `cruze show <ID>`. Then classify it:
15
- - **Code defect:** a scenario says what should happen, and the code does something else.
16
- - **Spec gap:** no scenario covers this case.
17
- - **Design flaw:** the design as written can't deliver what the scenario says.
18
- 4. **Confirm the diagnosis.** Tell the user the classification, the scenario or its absence, and your evidence. They confirm it before anything changes. For a spec gap, stop and hand off to `architect "<tweak>"`. For a design flaw, stop and hand off to `rethink`.
19
- 5. **Find the root cause.** Trace the failing path through the code until you can name the line that is wrong and why. The cause is the first wrong decision, not the place the error surfaces.
20
- 6. **Write the failing test first.** On a branch such as `fix/<slug>`, add a test at the seam of the violated scenario, following `.agents/skills/cruze-behavioural-testing/SKILL.md`. It carries the scenario's ID and reproduces the bug. Run it, and see it fail for this bug's reason.
21
- 7. **Fix it.** Make the smallest change in the module that owns the wrong decision, following `.agents/skills/cruze-hexagonal-design/SKILL.md`. Run `cruze check` on the files you changed, then the full test suite. Done when the new test passes and nothing else broke.
22
- 8. **Commit** with a message such as `fix(<scope>): <what the user saw> (SCN-<id>)`.
23
- 9. **Record it** with `cruze journal add bug --set summary="<what the user saw>" --set cause="<the root cause>" --set scenario=<SCN id> --set escaped="<the step that should have caught it>"`. `escaped` names the Cruze step that let the bug through, such as `verify`, or `none` when no step could have.
24
-
25
- ## Rules
26
-
27
- - A missing test is part of the cause. When the violated scenario had a test that passed anyway, say why it missed the bug, and strengthen it in the same fix.
28
- - Fix the cause only. Clean-ups you notice nearby go to the user as suggestions.
29
- - Never change a scenario to match the code. That is a spec change, and it goes through `architect`.
30
-
31
- ## Next step
32
-
33
- - Code defect fixed: `land`, which lands bug fixes too.
34
- - Spec gap: `architect "<tweak>"`.
35
- - Design flaw: `rethink`.
36
-
37
- Start it in a new session, so its context starts empty.
@@ -1,45 +0,0 @@
1
- ---
2
- name: cruze-verify
3
- description: Verifies a built change before it lands, with the mechanical checks, a fresh-context verifier that runs the real system, a two-lane code review and a manual test script, ending with your decision to accept it or send it back.
4
- ---
5
-
6
- # Verify
7
-
8
- Verify answers one question: does this change work when someone uses it? Green tests are evidence, not proof. A verifier in a fresh context runs the real system through each scenario, reviewers check the code against the spec and the architecture, and the user gets a script to try it themselves. The user decides whether it lands.
9
-
10
- ## Steps
11
-
12
- 1. **Check it is ready.** Run `cruze next`. It must name `verify` for this change, on this branch. Find the base commit the change branched from with `git merge-base main HEAD`. The code review's base is the same, unless `cruze journal list --event verification --item <ref>` shows an earlier verify: then it is that entry's commit, or `git rev-list -1 --before=<its at> HEAD` for one without, since the code before it was reviewed.
13
- 2. **Run the mechanical checks.** Run `cruze validate`, `cruze check --ci` and `cruze trace --change <ref>`, and quote each result.
14
- - Run `cruze journal list --event checks --item <ref>`. When its last entry's commit has no code changes since, that is when `git diff --quiet <commit> HEAD -- . ':(exclude)docs' ':(exclude).cruze'` succeeds, quote that entry for the format, lint, build and test commands instead of running them again.
15
- - Otherwise run the format, lint and build commands and the full suite, as `.agents/skills/cruze-formats/reference/config.md` defines it under `commands`. When they pass, record them with `cruze journal add checks --set commit=<commit> --item <ref>`, where the commit is the output of `git rev-parse --short HEAD`.
16
- - When one fails, fix the code in a commit and run them again. When a fix needs design, stop and offer `rethink`.
17
- 3. **Check the dependencies.** Follow "Checking" in `.agents/skills/cruze-dependency-approval/SKILL.md` against the merge base. An unrecorded dependency is a blocker.
18
- 4. **Run the verifier.** Run the verifier role from `.agents/skills/cruze-roles/SKILL.md` for the change, with the mechanical checks' results and the commit they ran at. It must be able to build and run the system. When it runs through `cruze review`, check that `review.command` in `.cruze/config.yaml` allows the project's build and run commands, and add them with the user's agreement. Done when you have its report, with a result for every delivered scenario.
19
- 5. **Review the code.** Run the review in the roles skill with the code reviewer, `review=code`, `--item <ref>` and the code review's base. Fix each finding whose disposition is `fixed` in a commit, through a detail amendment when it needs one, running the tests each fix touches, and `test` when `commands:` has a `test_full`. Then check the fixes yourself, as round 2 in the roles skill says.
20
- 6. **Write the manual test script** as `manual-test.md` in the change folder, following [manual-test.md](manual-test.md). Commit it.
21
- 7. **Check the fixes once.** When code changed since the last `checks` entry, run the format, lint and build commands and the full suite once more, and record them as in step 2. Done when the last `checks` entry is at a commit with no code changes since.
22
- 8. **Report.** Show the user four things:
23
- - The mechanical checks, one line each.
24
- - The verifier's result for every scenario.
25
- - The review's outcome, with each finding's disposition.
26
- - The path to `manual-test.md`, with a recommendation to run it before accepting.
27
- 9. **Ask for the decision:** "Do you accept this change, or send it back?"
28
- 10. **Record it** with `cruze journal add verification --set result=<accepted|sent-back> --set summary="<one line>" --set commit=<commit> --item <ref>`, where the commit is the output of `git rev-parse --short HEAD`, adding `--set back_to=<build|rethink>` when it is sent back. On sent-back, say what goes back:
29
- - A defect in code a task already built: `back_to=build`, then `cruze task reopen T<n> "<reason>"`.
30
- - A design that can't deliver the scenario: `back_to=rethink`. Leave the tasks to the rethink, which reopens the ones its amendment changes, and `cruze next` names `rethink` until it is recorded.
31
-
32
- Done when the decision is recorded.
33
-
34
- ## Rules
35
-
36
- - A scenario the verifier couldn't run isn't passed. Mark it clearly in the report and in `manual-test.md`.
37
- - Findings from the review are dispositions in the journal. They edit `change.md` only through a detail amendment.
38
- - Fixes made during verify are code commits that stay within the tasks' files. When a fix needs a new file or owner, or a detail, handle it exactly as build does.
39
-
40
- ## Next step
41
-
42
- - Accepted: `land`.
43
- - Sent back: `build` for reopened tasks, or `rethink` for design.
44
-
45
- Start it in a new session, so its context starts empty.
@@ -1,23 +0,0 @@
1
- # The manual test script
2
-
3
- `manual-test.md` in the change folder is what a person runs to see the change work with their own eyes. Write it for someone who knows the product but not the code.
4
-
5
- ```markdown
6
- # Manual test: <change title>
7
-
8
- Setup, once:
9
- 1. <build or install command, from the project's commands>
10
- 2. <any configuration file to create, with its full content>
11
-
12
- ## SCN-<id>: <scenario title>
13
- 1. <exact command or action>
14
- 2. <next action>
15
- Expected: <what they see, quoted from the scenario's THEN>
16
- Verifier: pass | fail | could not run (<what it needs>)
17
- ```
18
-
19
- - One section per scenario the change delivers, in the order of the spec delta.
20
- - Every step is a command they can paste or an action they can take, with real values.
21
- - Expected results are quoted from the scenario.
22
- - Scenarios the verifier couldn't run come first, since they carry the most risk. Say what each needs, such as a serial device or a jump host.
23
- - Finish with how to clean up anything the setup created.
@@ -1,29 +0,0 @@
1
- import { mkdir, readFile, writeFile } from "node:fs/promises";
2
- import { dirname, resolve } from "node:path";
3
- import { UsageError } from "./cli_context.ts";
4
-
5
- // Files a person or agent names on the command line, such as a scratch file of review blockers or
6
- // the path for an exported bundle. They may be anywhere, unlike project files, and relative paths
7
- // resolve against the working directory.
8
-
9
- export async function readArgumentFile(path: string): Promise<string> {
10
- try {
11
- return await readFile(resolve(path), "utf8");
12
- } catch (error) {
13
- throw new UsageError(`cannot read ${path}: ${reason(error)}`);
14
- }
15
- }
16
-
17
- export async function writeArgumentFile(path: string, text: string): Promise<void> {
18
- try {
19
- await mkdir(dirname(resolve(path)), { recursive: true });
20
- await writeFile(resolve(path), text, "utf8");
21
- } catch (error) {
22
- throw new UsageError(`cannot write ${path}: ${reason(error)}`);
23
- }
24
- }
25
-
26
- function reason(error: unknown): string {
27
- if (error instanceof Error && "code" in error && typeof error.code === "string") return error.code;
28
- return error instanceof Error ? error.message : String(error);
29
- }
@@ -1,23 +0,0 @@
1
- import { upstreamDiff, type UpstreamDiffReport } from "../../../../app/use_cases/upstream_diff.ts";
2
- import { requireArgs, type CliContext, type CommandResult } from "../cli_context.ts";
3
-
4
- export async function runDiff(context: CliContext, args: string[]): Promise<CommandResult> {
5
- requireArgs(args, ["ref"]);
6
- const report = await upstreamDiff(context, args[0] ?? "");
7
- return { json: report, human: render(report) };
8
- }
9
-
10
- function render(report: UpstreamDiffReport): string {
11
- const lines = [`${report.artifact}: ${report.state}`];
12
- if (report.changes.length === 0) lines.push("Nothing it cites changed since its approval.");
13
- for (const change of report.changes) {
14
- lines.push("", `${change.key}${change.path === undefined ? "" : ` in ${change.path}`}:`);
15
- lines.push(...(change.diff ?? [change.note ?? ""]).map((line) => ` ${line}`));
16
- }
17
- if (report.rebase.length > 0) {
18
- lines.push("", `Its delta changes ${report.rebase.join(", ")}: rewrite the delta to keep the new text, then approve with ${report.rebase.map((id) => `--rebase ${id}`).join(" ")}.`);
19
- } else if (report.ref !== undefined && report.state === "upstream-changed") {
20
- lines.push("", `No rebase needed. When the work still holds against this, re-stamp it with cruze approve ${report.ref} --restamp-unchanged.`);
21
- }
22
- return lines.join("\n");
23
- }
@@ -1,32 +0,0 @@
1
- import { exportFeedback, listEvents, recordEvent } from "../../../../app/use_cases/journal_events.ts";
2
- import { writeArgumentFile } from "../argument_files.ts";
3
- import { UsageError, requireArgs, type CliContext, type CommandResult, type Options } from "../cli_context.ts";
4
-
5
- export async function runJournal(context: CliContext, args: string[], options: Options): Promise<CommandResult> {
6
- requireArgs(args, ["add|list"]);
7
- if (args[0] === "add") {
8
- requireArgs(args, ["add", "event"]);
9
- const fields = Object.fromEntries(options.set.map((pair) => {
10
- const at = pair.indexOf("=");
11
- if (at < 1) throw new UsageError(`--set takes key=value, not "${pair}"`);
12
- return [pair.slice(0, at), pair.slice(at + 1)];
13
- }));
14
- const entry = await recordEvent(context, args[1] ?? "", fields, options.item);
15
- return { json: entry, human: `Recorded ${entry.event}.` };
16
- }
17
- if (args[0] === "list") {
18
- const entries = await listEvents(context, options.event, options.item, options.all);
19
- return { json: entries, human: entries.map((e) => `${e.at} ${e.event} ${JSON.stringify({ ...e, at: undefined, event: undefined, by: undefined })}`).join("\n") || "No entries." };
20
- }
21
- throw new UsageError(`cruze journal takes add or list, not "${args[0]}"`);
22
- }
23
-
24
- export async function runFeedback(context: CliContext, args: string[], options: Options): Promise<CommandResult> {
25
- if (args[0] !== "export") throw new UsageError("usage: cruze feedback export [--out <file>] [--no-redact]");
26
- const bundle = await exportFeedback(context, options.redact);
27
- if (options.out !== undefined) {
28
- await writeArgumentFile(options.out, `${JSON.stringify(bundle, null, 2)}\n`);
29
- return { json: { out: options.out, entries: bundle.entries.length }, human: `Wrote ${bundle.entries.length} entries to ${options.out}.` };
30
- }
31
- return { json: bundle, human: `${bundle.entries.length} journal entries${bundle.redacted ? ", redacted" : ""}.` };
32
- }
@@ -1,55 +0,0 @@
1
- import { addFutureFeature, completeTask, dropFutureFeature, updateFeatureSummary, pruneRoadmap, recordDeviation, reopenTask } from "../../../../app/use_cases/record_progress.ts";
2
- import { abandonWork } from "../../../../app/use_cases/abandon_work.ts";
3
- import { UsageError, requireArgs, requireOption, type CliContext, type CommandResult, type Options } from "../cli_context.ts";
4
-
5
- export async function runTask(context: CliContext, args: string[], options: Options): Promise<CommandResult> {
6
- requireArgs(args, ["done|deviation|reopen", "task"]);
7
- const change = options.change === undefined ? {} : { change: options.change };
8
- if (args[0] === "done") {
9
- const report = await completeTask(context, args[1] ?? "", { ...change, ...(options.commit === undefined ? {} : { commit: options.commit }) });
10
- return { json: report, human: `${report.task} done in ${report.change} (${report.done}/${report.total}).` };
11
- }
12
- if (args[0] === "deviation") {
13
- requireArgs(args, ["deviation", "task", "text"]);
14
- const report = await recordDeviation(context, args[1] ?? "", args.slice(2).join(" "), change);
15
- return { json: report, human: `Recorded a deviation on ${report.task} in ${report.change}.` };
16
- }
17
- if (args[0] === "reopen") {
18
- requireArgs(args, ["reopen", "task", "reason"]);
19
- const report = await reopenTask(context, args[1] ?? "", args.slice(2).join(" "), change);
20
- return { json: report, human: `Reopened ${report.task} in ${report.change}.` };
21
- }
22
- throw new UsageError(`cruze task takes done, deviation or reopen, not "${args[0]}"`);
23
- }
24
-
25
- export async function runFeatures(context: CliContext, args: string[], options: Options): Promise<CommandResult> {
26
- requireArgs(args, ["add|update|drop", "slug"]);
27
- if (args[0] === "add") {
28
- const goals = (options.goals ?? "").split(",").map((g) => g.trim()).filter((g) => g !== "");
29
- const report = await addFutureFeature(context, args[1] ?? "", requireOption(options.summary, "--summary"), goals);
30
- return { json: report, human: `Added ${report.feature} to the future list.` };
31
- }
32
- if (args[0] === "update") {
33
- const report = await updateFeatureSummary(context, args[1] ?? "", requireOption(options.summary, "--summary"));
34
- return { json: report, human: `Updated the summary of ${report.feature} on the ${report.list === "Future" ? "future" : "implemented"} list.` };
35
- }
36
- if (args[0] === "drop") {
37
- const report = await dropFutureFeature(context, args[1] ?? "", requireOption(options.reason, "--reason"));
38
- return { json: report, human: `Dropped ${report.feature} from the future list.` };
39
- }
40
- throw new UsageError(`cruze features takes add, update or drop, not "${args[0]}"`);
41
- }
42
-
43
- export async function runRoadmap(context: CliContext, args: string[]): Promise<CommandResult> {
44
- requireArgs(args, ["prune"]);
45
- if (args[0] !== "prune") throw new UsageError(`cruze roadmap takes prune, not "${args[0]}"`);
46
- const report = await pruneRoadmap(context);
47
- return { json: report, human: report.removed.length === 0 ? "No landed items to clear." : `Cleared ${report.removed.join(", ")} from the roadmap status.` };
48
- }
49
-
50
- export async function runAbandon(context: CliContext, args: string[], options: Options): Promise<CommandResult> {
51
- requireArgs(args, ["ref"]);
52
- const report = await abandonWork(context, args[0] ?? "", requireOption(options.reason, "--reason"));
53
- const back = report.futureList === undefined ? "" : ` ${report.futureList} is back on the future list.`;
54
- return { json: report, human: `Abandoned ${report.ref}; archived to ${report.archivedTo}.${back}` };
55
- }
@@ -1,28 +0,0 @@
1
- import { realignDone } from "../../../../app/use_cases/realign_done.ts";
2
- import { realignStatus } from "../../../../app/use_cases/realign_status.ts";
3
- import { UsageError, requireArgs, type CliContext, type CommandResult, type Options } from "../cli_context.ts";
4
-
5
- export async function runRealign(context: CliContext, args: string[], options: Options): Promise<CommandResult> {
6
- requireArgs(args, ["status|done"]);
7
- if (args[0] === "status") {
8
- const report = await realignStatus(context, { full: options.full });
9
- const lines = [`Code meets Cruze ${report.standards}; the installed skills are ${report.target}${report.full ? " (full audit)" : ""}.`];
10
- for (const note of report.notes) {
11
- const open = note.findings.filter((f) => !f.waived);
12
- const how = note.detect !== undefined ? `${open.length} open finding(s) from ${note.detect}` : `for the auditor: ${note.ask ?? ""}`;
13
- lines.push(`${note.version} ${note.id}: ${note.title} (${how})`);
14
- for (const f of open) lines.push(` ${f.path}${f.line === undefined ? "" : `:${f.line}`} ${f.message}`);
15
- }
16
- if (report.newWorkOnly.length > 0) lines.push(`New work only, nothing to move: ${report.newWorkOnly.join(", ")}`);
17
- if (report.notes.length === 0) lines.push("Nothing to realign.");
18
- return { json: report, human: lines.join("\n") };
19
- }
20
- if (args[0] === "done") {
21
- const report = await realignDone(context, options.change === undefined ? {} : { change: options.change });
22
- const lines = [report.from === report.to ? `The code already meets Cruze ${report.to}.` : `The code now meets Cruze ${report.to}, up from ${report.from}.`];
23
- if (report.scheduled !== undefined) lines.push(`${report.scheduled} fixes the rest.`);
24
- if (report.excepted.length > 0) lines.push(`Listed under check.exceptions until it lands: ${report.excepted.join(", ")}`);
25
- return { json: report, human: lines.join("\n") };
26
- }
27
- throw new UsageError(`cruze realign takes status or done, not "${args[0]}"`);
28
- }
@@ -1,96 +0,0 @@
1
- import { showElements, showIndex } from "../../../../app/use_cases/show_elements.ts";
2
- import { showNotes } from "../../../../app/use_cases/show_notes.ts";
3
- import type { Note, NotesFor } from "../../../../domain/project/notes.ts";
4
- import { showScope, type ScopeReport } from "../../../../app/use_cases/show_scope.ts";
5
- import { isId } from "../../../../domain/ids.ts";
6
- import type { ElementIndex } from "../../../../domain/project/element_lookup.ts";
7
- import type { ShownElement } from "../../../../domain/project/scope_view.ts";
8
- import { UsageError, requireArgs, type CliContext, type CommandResult, type Options } from "../cli_context.ts";
9
-
10
- export async function runShow(context: CliContext, args: string[], options: Options): Promise<CommandResult> {
11
- if (options.notes) {
12
- if (args.length > 1 || options.index) throw new UsageError("--notes takes at most one target: project, a step, a feature slug or a context");
13
- const report = await showNotes(context, args[0]);
14
- return { json: report, human: "target" in report ? renderNotesFor(report).join("\n").trimEnd() : renderNotes(report.notes) };
15
- }
16
- if (options.index) {
17
- if (args.length > 1) throw new UsageError("--index takes at most one context or capability");
18
- const index = await showIndex(context, args[0]);
19
- return { json: index, human: renderIndex(index) };
20
- }
21
- requireArgs(args, ["ref"]);
22
- if (args.every(isId)) {
23
- const report = await showElements(context, args);
24
- return { json: report, human: blocks(report.elements).join("\n").trimEnd() };
25
- }
26
- if (args.length > 1) throw new UsageError("show takes one feature or change, or one or more element IDs");
27
- const report = await showScope(context, args[0] ?? "");
28
- return { json: report, human: render(report) };
29
- }
30
-
31
- /** The index as a list an agent scans for what exists, before fetching bodies with `cruze show <ID>`. */
32
- function renderIndex(index: ElementIndex): string {
33
- const { architecture, specs } = index.words;
34
- const lines = [`# Elements`, "", `docs/architecture.md holds ${architecture} words, and docs/specs/ ${specs}. Read an element's body with \`cruze show <ID>\`.`];
35
- let source = "";
36
- for (const entry of [...index.entries].sort((a, b) => Number(a.addedBy !== undefined) - Number(b.addedBy !== undefined))) {
37
- const heading = entry.addedBy === undefined ? entry.source : "In flight, not yet merged";
38
- if (heading !== source) lines.push("", `## ${heading}`, "");
39
- source = heading;
40
- const facts = [
41
- entry.status,
42
- entry.place,
43
- entry.scenarios === undefined ? undefined : `${entry.scenarios} scenarios`,
44
- entry.addedBy === undefined ? undefined : `added by ${entry.addedBy}`,
45
- entry.changedBy.length === 0 ? undefined : `changed by ${entry.changedBy.join(", ")}`,
46
- ].filter((f) => f !== undefined);
47
- lines.push(`- ${entry.id}: ${entry.title}${facts.length === 0 ? "" : ` (${facts.join("; ")})`}`);
48
- }
49
- return lines.join("\n");
50
- }
51
-
52
- /** The scope as a document an agent reads in place of the living docs. */
53
- function render(report: ScopeReport): string {
54
- const lines = [`# ${report.ref}${report.feature === undefined ? "" : ` (feature ${report.feature})`}`, ""];
55
- lines.push("## Elements", "", ...blocks(report.elements), "## Scenarios", "", ...blocks(report.scenarios));
56
- lines.push("## Cited, not shown", "");
57
- lines.push(...(report.cited.length === 0 ? ["None."] : report.cited.map((c) => `- ${c.id}: ${c.title} (${c.source})`)), "");
58
- if (report.feature === undefined) lines.push("## Settled decisions", "", ...(report.decisions.length === 0 ? ["None."] : report.decisions), "");
59
- else {
60
- lines.push("## Settled decisions of the feature", "", ...(report.decisions.length === 0 ? ["None."] : report.decisions), "");
61
- lines.push("## Settled decisions of the change", "", ...(report.changeDecisions.length === 0 ? ["None."] : report.changeDecisions), "");
62
- }
63
- lines.push("## Dispositions", "");
64
- lines.push(...(report.dispositions.length === 0 ? ["None."] : report.dispositions.map((d) => `- [${d.disposition}, ${d.review}] ${d.finding}: ${d.reason}`)), "");
65
- if (report.references.length > 0) lines.push("## References", "", ...report.references.map((r) => `- \`${r.path}\`: ${r.use}`), "");
66
- lines.push(...renderNotesFor(report.notes));
67
- if (report.missing.length > 0) lines.push("## Missing", "", ...report.missing.map((id) => `- ${id} is defined nowhere`), "");
68
- return lines.join("\n").trimEnd();
69
- }
70
-
71
- function blocks(elements: ShownElement[]): string[] {
72
- if (elements.length === 0) return ["None.", ""];
73
- return elements.flatMap((element) => [`<!-- ${element.source} -->`, ...element.body.join("\n").trimEnd().split("\n"), ""]);
74
- }
75
-
76
- /** The notes about a piece of work and the parts of the user's input handed to it; nothing when there are none. */
77
- function renderNotesFor(found: NotesFor): string[] {
78
- if (found.notes.length === 0 && found.handoffs.length === 0) return [];
79
- return [
80
- "## Notes",
81
- "",
82
- ...found.notes.map((n) => `- \`${n.path}\`: ${n.title}${n.kind === undefined ? "" : ` (${n.kind})`}`),
83
- ...found.handoffs.map((h) => `- ${h.part}, from ${h.source} (${h.status}, handed off in \`${h.note}\`)`),
84
- "",
85
- ];
86
- }
87
-
88
- function renderNotes(notes: Note[]): string {
89
- if (notes.length === 0) return "No notes.";
90
- return notes
91
- .flatMap((n) => [
92
- `- \`${n.path}\`: ${n.title} (${n.kind ?? "no kind"}; about ${n.about.length === 0 ? "nothing named" : n.about.join(", ")})`,
93
- ...n.handoff.map((h) => ` - ${h.part} goes to ${h.goesTo} (${h.status})`),
94
- ])
95
- .join("\n");
96
- }
@@ -1,42 +0,0 @@
1
- import { CruzeError } from "../../domain/cruze_error.ts";
2
- import { readRows, upsertRow } from "../../domain/edits/managed_tables.ts";
3
- import { resolveArtifact } from "../../domain/project/artifact_ref.ts";
4
- import { PATHS } from "../../domain/project/layout.ts";
5
- import { appendJournal, loadView, type ProjectDeps } from "../project_context.ts";
6
-
7
- export interface AbandonReport {
8
- ref: string;
9
- archivedTo: string;
10
- /** The feature-map slug it went back to, for a feature that may return. */
11
- futureList?: string;
12
- }
13
-
14
- /**
15
- * Stops a feature or standalone change for good: archives its folder with the reason, marks it
16
- * abandoned on the roadmap, and puts a feature back on the future list. Changes that already
17
- * landed stay in the living docs.
18
- */
19
- export async function abandonWork(deps: ProjectDeps, ref: string, reason: string): Promise<AbandonReport> {
20
- if (reason.trim() === "") throw new CruzeError("missing-reason", "abandoning work needs a reason");
21
- const view = await loadView(deps.files);
22
- const { item } = resolveArtifact(view, ref);
23
- if (item === undefined || item.archived) throw new CruzeError("not-found", `"${ref}" is not an active feature or standalone change`);
24
- if (item.kind === "change") throw new CruzeError("not-abandonable", `${item.ref} belongs to a feature; remove it from the feature's Changes table through rethink instead`);
25
-
26
- const slug = typeof item.doc.frontmatter?.["roadmap"] === "string" ? item.doc.frontmatter["roadmap"] : undefined;
27
- const roadmap = view.snapshot.get(PATHS.roadmap);
28
- if (slug !== undefined && roadmap !== undefined && roadmap.includes("## Status")) {
29
- await deps.files.writeText(PATHS.roadmap, upsertRow(roadmap, "Status", [slug, item.folderName, "abandoned"]));
30
- }
31
- const report: AbandonReport = { ref: item.ref, archivedTo: `${PATHS.archiveDir}/${item.folderName}` };
32
- const vision = view.snapshot.get(PATHS.vision);
33
- if (item.kind === "feature" && slug !== undefined && vision !== undefined && vision.includes("### Future")) {
34
- if (!readRows(vision, "Future").some((row) => row[0] === slug)) {
35
- await deps.files.writeText(PATHS.vision, upsertRow(vision, "Future", [slug, String(item.doc.frontmatter?.["title"] ?? slug), ""]));
36
- }
37
- report.futureList = slug;
38
- }
39
- await appendJournal(deps, item.folder, "abandon", { item: item.ref, reason: reason.trim() });
40
- await deps.files.move(item.folder, report.archivedTo);
41
- return report;
42
- }
@@ -1,197 +0,0 @@
1
- import { requirePlanReviewClosed, requireUnchangedSinceApproval, type AgentBasis } from "../../domain/approvals/agent_approval.ts";
2
- import { evaluateApproval } from "../../domain/approvals/evaluate.ts";
3
- import { fingerprint, isApprovable } from "../../domain/approvals/fingerprint.ts";
4
- import { rebaseNeeded } from "../../domain/approvals/rebase.ts";
5
- import { serializeApprovals, withApproval } from "../../domain/approvals/records.ts";
6
- import { CruzeError } from "../../domain/cruze_error.ts";
7
- import { updateProgress } from "../../domain/edits/progress_block.ts";
8
- import { setStatus, stampMissingStatuses } from "../../domain/edits/status_lines.ts";
9
- import { statusOf } from "../../domain/elements.ts";
10
- import { elementHash } from "../../domain/hashing.ts";
11
- import { parseMarkdown } from "../../domain/markdown.ts";
12
- import { resolveArtifact } from "../../domain/project/artifact_ref.ts";
13
- import { readDelta } from "../../domain/project/deltas.ts";
14
- import { PATHS } from "../../domain/project/layout.ts";
15
- import { isPlanned, readChangesTable } from "../../domain/project/plan_parts.ts";
16
- import { readProgress } from "../../domain/project/progress.ts";
17
- import { approvalFolder, approvalsPath, type ProjectView } from "../../domain/project/project_view.ts";
18
- import { featureOf, type WorkItem } from "../../domain/project/work_items.ts";
19
- import type { Problem } from "../../domain/validation/problem.ts";
20
- import { SIZE_RULES } from "../../domain/validation/size_rules.ts";
21
- import { validateProject } from "../../domain/validation/validate_project.ts";
22
- import { appendJournal, loadView, withFiles, type ProjectDeps } from "../project_context.ts";
23
-
24
- export interface ApproveReport {
25
- artifact: string;
26
- hash: string;
27
- upstream: string[];
28
- /** Elements that received a `planned` status. */
29
- stamped: string[];
30
- /** The branch a change was bound to, when this approval bound it. */
31
- bound?: string;
32
- /** Built elements this approval set back to planned, because a rethink changed them. */
33
- replanned: string[];
34
- /** Elements whose current living text this feature accepted after a land conflict. */
35
- rebased: string[];
36
- /** Why the agent gave this approval for the user, when it did. */
37
- basis?: AgentBasis;
38
- /** For a re-stamp: the upstream IDs or documents that had changed. */
39
- restamped?: string[];
40
- }
41
-
42
- export interface ApproveOptions {
43
- /** Architecture elements to set back to `planned`: their design changed and their code must follow. */
44
- replan?: string[];
45
- /** Elements this work's delta changes whose living text changed since its last approval; the approval accepts the current text. */
46
- rebase?: string[];
47
- /** Approve for the user: a change whose plan review closed, or work a rethink left unchanged. */
48
- agent?: AgentBasis;
49
- }
50
-
51
- /**
52
- * Records a person's approval of a document: its design hash and the hashes of everything
53
- * upstream it relies on. Refuses invalid documents and documents whose upstream isn't approved.
54
- */
55
- export async function approveArtifact(deps: ProjectDeps, ref: string, options: ApproveOptions = {}): Promise<ApproveReport> {
56
- let view = await loadView(deps.files);
57
- const { path, item } = resolveArtifact(view, ref);
58
- if (!isApprovable(view, path)) throw new CruzeError("not-approvable", `${path} is not something you approve; it changes through approved work`);
59
- requireUpstreamApproved(view, path);
60
- if (item?.kind === "feature" && readChangesTable(item.doc).length === 0) {
61
- throw new CruzeError("not-designed", `${path} has no changes yet; architect designs and splits it before approval`);
62
- }
63
- if (item !== undefined && item.kind !== "feature" && !isPlanned(item.doc)) {
64
- throw new CruzeError("not-planned", `${path} has no test plan or tasks yet; plan it before approving`);
65
- }
66
- const before = evaluateApproval(view, path);
67
- if (options.agent === "plan-review") requirePlanReviewClosed(view.journal, item ?? notWork(path));
68
- if (options.agent === "rethink") requireUnchangedSinceApproval(view.journal, item, path, before);
69
-
70
- const edits = new Map<string, string>();
71
- let text = view.snapshot.get(path) ?? "";
72
- let stamped: string[] = [];
73
- if (path === PATHS.architecture) ({ text, stamped } = stampMissingStatuses(text));
74
- const replanned = options.replan ?? [];
75
- if (replanned.length > 0 && path !== PATHS.architecture) throw new CruzeError("replan-architecture-only", "--replan applies to the architecture");
76
- for (const id of replanned) {
77
- const element = view.living.elements.get(id);
78
- if (element?.path !== PATHS.architecture || statusOf(element.doc, element.element) !== "built") {
79
- throw new CruzeError("not-built", `${id} is not a built element of ${PATHS.architecture}`);
80
- }
81
- text = setStatus(text, id, "planned");
82
- }
83
- let bound: string | undefined;
84
- if (item !== undefined && item.kind !== "feature" && readProgress(parseMarkdown(text)).branch === undefined) {
85
- bound = (await deps.repository.currentBranch()) ?? undefined;
86
- text = updateProgress(text, (progress) => {
87
- if (bound !== undefined) progress.branch = bound;
88
- });
89
- }
90
- if (text !== view.snapshot.get(path)) edits.set(path, text);
91
- view = withFiles(view, edits);
92
-
93
- const problems = validateProject(view).filter((p) => p.path === path);
94
- const errors = problems.filter((p) => p.severity === "error");
95
- if (errors.length > 0) {
96
- const listing = errors.map((p) => ` ${p.path}:${p.line ?? ""} ${p.message}`).join("\n");
97
- throw new CruzeError("invalid", `${path} has ${errors.length} problem(s) to fix before approval:\n${listing}`);
98
- }
99
- requireSizesAccepted(problems, path);
100
- if (item !== undefined) requireNoDecisionCitations(problems, path);
101
-
102
- const { hash, upstream } = fingerprint(view, path, parseMarkdown(text));
103
- const folder = approvalFolder(view, path);
104
- const current = view.approvals.get(folder) ?? { version: 1, approvals: [], merged: {} };
105
- const rebased = options.rebase ?? [];
106
- const deltaIds = item === undefined || item.kind === "change" ? new Set<string>() : readDelta(item.doc).ids;
107
- if (item !== undefined) requireRebased(view, item, rebased);
108
- const merged = { ...current.merged };
109
- for (const id of rebased) {
110
- const living = view.living.elements.get(id);
111
- if (living === undefined || (merged[id] === undefined && !deltaIds.has(id))) {
112
- throw new CruzeError("not-in-delta", `${id} is not a living element that ${item?.ref ?? path} changes or merged`);
113
- }
114
- if (merged[id] !== undefined) merged[id] = elementHash(living.doc, living.element);
115
- }
116
- const approvals = withApproval({ ...current, merged }, {
117
- artifact: path,
118
- hash,
119
- upstream,
120
- approvedAt: deps.clock.now().toISOString(),
121
- by: (await deps.repository.userName()) ?? "unknown",
122
- ...(options.agent === undefined ? {} : { basis: options.agent }),
123
- });
124
- for (const [changed, content] of edits) await deps.files.writeText(changed, content);
125
- await deps.files.writeText(approvalsPath(folder), serializeApprovals(approvals));
126
- const restamped = options.agent === "rethink" ? before.changed : undefined;
127
- await appendJournal(deps, folder, "approve", {
128
- artifact: path,
129
- hash,
130
- ...(replanned.length > 0 ? { replanned } : {}),
131
- ...(rebased.length > 0 ? { rebased } : {}),
132
- ...(options.agent === undefined ? {} : { basis: options.agent }),
133
- ...(restamped === undefined ? {} : { restamped }),
134
- });
135
- const report: ApproveReport = { artifact: path, hash, upstream: Object.keys(upstream), stamped, replanned, rebased };
136
- if (bound !== undefined) report.bound = bound;
137
- if (options.agent !== undefined) report.basis = options.agent;
138
- if (restamped !== undefined) report.restamped = restamped;
139
- return report;
140
- }
141
-
142
- /**
143
- * A feature or standalone change whose delta changes an element must have been written against
144
- * that element's current text. When the living element changed since the last approval, through
145
- * a rethink or another land, the delta would overwrite that edit, so approval waits until the
146
- * delta is reconciled and the element is named with --rebase.
147
- */
148
- function requireRebased(view: ProjectView, item: WorkItem, rebased: string[]): void {
149
- const moved = rebaseNeeded(view, item).filter((id) => !rebased.includes(id));
150
- if (moved.length > 0) {
151
- throw new CruzeError(
152
- "rebase-required",
153
- `the living text of ${moved.join(", ")} changed since the last approval, and this work's delta changes ${moved.length === 1 ? "it" : "them"}. Rewrite the delta to keep that change, then approve with ${moved.map((id) => `--rebase ${id}`).join(" ")}`,
154
- );
155
- }
156
- }
157
-
158
- /**
159
- * A size warning stands until the work is split or the user accepts it under `changes.exceptions`.
160
- * A 28-task change approved over its warning went on to 26 deviations, so approval waits for one or the other.
161
- */
162
- function requireSizesAccepted(problems: Problem[], path: string): void {
163
- const standing = problems.filter((p) => SIZE_RULES.includes(p.rule));
164
- if (standing.length === 0) return;
165
- const listing = standing.map((p) => ` ${p.message} [${p.rule}]`).join("\n");
166
- throw new CruzeError(
167
- "size-unaccepted",
168
- `${path} has ${standing.length} size warning(s). Split the work through rethink, or record the user's acceptance under changes.exceptions in .cruze/config.yaml, as the item's ref and their reason:\n${listing}`,
169
- );
170
- }
171
-
172
- /**
173
- * A delta that cites decisions by number would merge them into the living docs, where they dangle once
174
- * the work is archived. Existing docs only warn, so approval of new or redesigned work is where it stops.
175
- */
176
- function requireNoDecisionCitations(problems: Problem[], path: string): void {
177
- const standing = problems.filter((p) => p.rule === "decision-citation");
178
- if (standing.length === 0) return;
179
- const listing = standing.map((p) => ` ${p.path}:${p.line ?? ""} ${p.message}`).join("\n");
180
- throw new CruzeError("decision-cited", `${path} cites decisions by number in its delta, which merges into the living docs:\n${listing}`);
181
- }
182
-
183
- function notWork(path: string): never {
184
- throw new CruzeError("needs-user", `${path} is a project document, so the user approves it`);
185
- }
186
-
187
- /** A change needs its feature approved; a feature or standalone change needs the architecture approved. */
188
- function requireUpstreamApproved(view: ProjectView, path: string): void {
189
- const item = view.items.find((i) => i.path === path);
190
- if (item === undefined) return;
191
- const upstream = item.kind === "change" ? featureOf(view.items, item)?.path : view.snapshot.has(PATHS.architecture) ? PATHS.architecture : undefined;
192
- if (upstream === undefined) return;
193
- const status = evaluateApproval(view, upstream);
194
- if (status.state !== "approved") {
195
- throw new CruzeError("upstream-not-approved", `approve ${upstream} first; it is ${status.state}${status.changed.length > 0 ? ` (${status.changed.join(", ")})` : ""}`);
196
- }
197
- }