@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,106 +0,0 @@
1
- import { CruzeError } from "../../domain/cruze_error.js";
2
-
3
- import { PATHS } from "../../domain/project/layout.js";
4
-
5
- import { validateProject } from "../../domain/validation/validate_project.js";
6
- import { notesBetween, parseRealignNotes, } from "../../domain/versions/realign_notes.js";
7
- import { FIRST_RELEASE } from "../../domain/versions/rule_versions.js";
8
-
9
- import { loadView, } from "../project_context.js";
10
- import { checkCode } from "./check_code.js";
11
-
12
- export const REALIGN_SKILL = "realign";
13
-
14
-
15
-
16
-
17
-
18
-
19
-
20
-
21
-
22
-
23
-
24
-
25
-
26
-
27
-
28
-
29
-
30
-
31
-
32
-
33
-
34
-
35
-
36
-
37
-
38
-
39
-
40
- /**
41
- * What realigning the project to its installed Cruze involves: the notes of every release
42
- * since `standards:`, or of all releases with `full`, and what their CLI rules find now.
43
- */
44
- export async function realignStatus(deps , options ) {
45
- const view = await loadView(deps.files);
46
- const config = requireInstalled(view, deps.bundle.version);
47
- const standards = config.standards ?? FIRST_RELEASE;
48
- const target = deps.bundle.version;
49
- const notes = notesBetween(await bundledNotes(deps.bundle), options.full ? "0.0.0" : standards, target);
50
- const waived = waivedFindings(view);
51
- const check = (await checkCode(deps, { ci: false })).findings;
52
- const problems = validateProject(view);
53
- const report = notes.filter((n) => n.appliesTo === "existing").map((note) => {
54
- const [command, rule] = (note.detect ?? ":").split(":");
55
- const found = command === "check" ? check.filter((f) => f.rule === rule) : command === "validate" ? problems.filter((p) => p.rule === rule) : [];
56
- const findings = found.map((f) => ({
57
- path: f.path,
58
- ...(f.line === undefined ? {} : { line: f.line }),
59
- message: f.message,
60
- // A check finding is waived by listing the file under check.exceptions, which also keeps CI green.
61
- waived: command === "validate" && waived.has(findingKey(note.id, f.path)),
62
- }));
63
- return { ...note, findings };
64
- });
65
- return {
66
- standards,
67
- target,
68
- full: options.full,
69
- notes: report,
70
- newWorkOnly: notes.filter((n) => n.appliesTo === "new").map((n) => n.id),
71
- open: report.flatMap((n) => n.findings).filter((f) => !f.waived).length,
72
- };
73
- }
74
-
75
- /** The key a realign disposition records for a mechanical finding, so a waiver can be matched later. */
76
- export function findingKey(note , path ) {
77
- return `${note}: ${path}`;
78
- }
79
-
80
- /** The project's config, once its skills come from this CLI; realign works from the installed notes. */
81
- export function requireInstalled(view , version ) {
82
- const config = view.config?.config;
83
- if (config === null || config === undefined) throw new CruzeError("config", `fix ${PATHS.config} first; run cruze validate for details`);
84
- if (config.cruze !== version) {
85
- throw new CruzeError("install-first", `the project's skills are from Cruze ${config.cruze ?? "an unrecorded version"}, and this CLI is ${version}; run cruze install first`);
86
- }
87
- return config;
88
- }
89
-
90
- async function bundledNotes(bundle ) {
91
- const skill = (await bundle.skills()).find((s) => s.folder === REALIGN_SKILL);
92
- return (skill?.files ?? [])
93
- .flatMap((file) => {
94
- const version = /^notes\/(\d+\.\d+\.\d+)\.md$/.exec(file.path)?.[1];
95
- return version === undefined ? [] : parseRealignNotes(version, file.text).notes;
96
- });
97
- }
98
-
99
- /** Findings earlier realigns waived, by key. */
100
- function waivedFindings(view ) {
101
- return new Set(
102
- view.journal
103
- .filter((e) => e.event === "disposition" && e["review"] === "realign" && e["disposition"] === "waived")
104
- .map((e) => String(e["finding"])),
105
- );
106
- }
@@ -1,118 +0,0 @@
1
- import { CruzeError } from "../../domain/cruze_error.js";
2
- import { readRows, removeRow, upsertRow } from "../../domain/edits/managed_tables.js";
3
- import { updateProgress } from "../../domain/edits/progress_block.js";
4
- import { resolveChange } from "../../domain/project/artifact_ref.js";
5
- import { PATHS } from "../../domain/project/layout.js";
6
- import { readTasks } from "../../domain/project/plan_parts.js";
7
- import { requireSlug } from "../../domain/project/new_items.js";
8
- import { activeChange } from "../../domain/status/work_status.js";
9
-
10
- import { appendJournal, loadView, } from "../project_context.js";
11
-
12
- /** Commands that write the CLI-owned progress records: task ticks, deviations and the feature map. */
13
-
14
-
15
-
16
-
17
-
18
-
19
-
20
-
21
- /** Marks a task done, recording the commit that completed it (HEAD by default). */
22
- export async function completeTask(deps , task , options ) {
23
- const change = await changeFor(deps, options.change);
24
- requireTask(change, task);
25
- const commit = options.commit ?? (await deps.repository.headCommit()) ?? "";
26
- const text = updateProgress(change.doc.lines.join("\n"), (progress) => progress.tasks.set(task, commit));
27
- await deps.files.writeText(change.path, text);
28
- const ticks = [...text.matchAll(/^- \[([ x])\] T\d+/gm)];
29
- return { change: change.ref, task, done: ticks.filter((m) => m[1] === "x").length, total: ticks.length };
30
- }
31
-
32
- /** Reopens a done task whose design a rethink changed, so build does it again. */
33
- export async function reopenTask(deps , task , reason , options ) {
34
- const change = await changeFor(deps, options.change);
35
- requireTask(change, task);
36
- if (reason.trim() === "") throw new CruzeError("missing-reason", "say why the task reopens");
37
- await deps.files.writeText(change.path, updateProgress(change.doc.lines.join("\n"), (progress) => progress.tasks.set(task, null)));
38
- await appendJournal(deps, change.folder, "reopen", { change: change.ref, task, reason: reason.trim() });
39
- return { change: change.ref, task };
40
- }
41
-
42
- /** Records a task-level choice the agent made on its own. Anything larger is a rethink. */
43
- export async function recordDeviation(deps , task , text , options ) {
44
- const change = await changeFor(deps, options.change);
45
- requireTask(change, task);
46
- if (text.trim() === "") throw new CruzeError("missing-text", "describe the deviation");
47
- await deps.files.writeText(change.path, updateProgress(change.doc.lines.join("\n"), (progress) => progress.deviations.push(`${task}: ${text.trim()}`)));
48
- await appendJournal(deps, change.folder, "deviation", { change: change.ref, task, text: text.trim() });
49
- return { change: change.ref, task };
50
- }
51
-
52
- /** Adds a feature to the future list of the feature map, or replaces its entry. */
53
- export async function addFutureFeature(deps , slug , summary , goals ) {
54
- requireSlug(slug);
55
- const vision = await requireVision(deps);
56
- await deps.files.writeText(PATHS.vision, upsertRow(vision, "Future", [slug, summary, goals.join(", ")]));
57
- await appendJournal(deps, ".cruze", "feature-map", { action: "add", feature: slug, summary, goals });
58
- return { feature: slug };
59
- }
60
-
61
- /**
62
- * Replaces a feature's summary on the feature map, in the future list or the implemented list,
63
- * when a decision changed what the feature delivers. Its goals or release stay as they are.
64
- */
65
- export async function updateFeatureSummary(deps , slug , summary ) {
66
- if (summary.trim() === "") throw new CruzeError("missing-summary", "a feature needs a summary");
67
- const vision = await requireVision(deps);
68
- const future = readRows(vision, "Future").find((row) => row[0] === slug);
69
- const implemented = readRows(vision, "Implemented").find((row) => row[0] === slug);
70
- if (future === undefined && implemented === undefined) throw new CruzeError("not-found", `${slug} is not on the feature map`);
71
- const list = future !== undefined ? "Future" : "Implemented";
72
- const cells = future !== undefined ? [slug, summary.trim(), future[2] ?? ""] : [slug, implemented?.[1] ?? "", summary.trim()];
73
- await deps.files.writeText(PATHS.vision, upsertRow(vision, list, cells));
74
- await appendJournal(deps, ".cruze", "feature-map", { action: "update", feature: slug, summary: summary.trim() });
75
- return { feature: slug, list };
76
- }
77
-
78
- export async function dropFutureFeature(deps , slug , reason ) {
79
- const vision = await requireVision(deps);
80
- const { text, removed } = removeRow(vision, "Future", slug);
81
- if (removed === null) throw new CruzeError("not-found", `${slug} is not on the future list`);
82
- if (reason.trim() === "") throw new CruzeError("missing-reason", "dropping a feature needs a reason");
83
- await deps.files.writeText(PATHS.vision, text);
84
- await appendJournal(deps, ".cruze", "feature-map", { action: "drop", feature: slug, reason });
85
- return { feature: slug };
86
- }
87
-
88
- /** Clears the landed rows from the roadmap's Status table, when a release closes. */
89
- export async function pruneRoadmap(deps ) {
90
- const roadmap = await deps.files.readText(PATHS.roadmap);
91
- if (roadmap === undefined) throw new CruzeError("not-found", `${PATHS.roadmap} does not exist yet`);
92
- const landed = readRows(roadmap, "Status").filter((row) => row[2] === "landed").map((row) => row[0] ?? "");
93
- let text = roadmap;
94
- for (const item of landed) text = removeRow(text, "Status", item).text;
95
- if (landed.length > 0) {
96
- await deps.files.writeText(PATHS.roadmap, text);
97
- await appendJournal(deps, ".cruze", "roadmap-prune", { items: landed });
98
- }
99
- return { removed: landed };
100
- }
101
-
102
- async function changeFor(deps , ref ) {
103
- const view = await loadView(deps.files);
104
- if (ref !== undefined) return resolveChange(view, ref);
105
- const change = activeChange(view, await deps.repository.currentBranch());
106
- if (change === undefined) throw new CruzeError("no-active-change", "no change is bound to this branch; name one with --change");
107
- return change;
108
- }
109
-
110
- function requireTask(change , task ) {
111
- if (!readTasks(change.doc).tasks.some((t) => t.number === task)) throw new CruzeError("unknown-task", `${change.ref} has no task ${task}`);
112
- }
113
-
114
- async function requireVision(deps ) {
115
- const vision = await deps.files.readText(PATHS.vision);
116
- if (vision === undefined) throw new CruzeError("not-found", `${PATHS.vision} does not exist yet`);
117
- return vision;
118
- }
@@ -1,18 +0,0 @@
1
- import { CruzeError } from "../../domain/cruze_error.js";
2
- import { elementIndex, lookupElements, } from "../../domain/project/element_lookup.js";
3
-
4
- import { loadView, } from "../project_context.js";
5
-
6
- /** The bodies of the named elements, living and in each in-flight delta that changes them. */
7
- export async function showElements(deps , ids ) {
8
- const { elements, missing } = lookupElements(await loadView(deps.files), ids);
9
- if (missing.length > 0) throw new CruzeError("unknown-id", `${missing.join(", ")} ${missing.length === 1 ? "is" : "are"} defined nowhere in the living docs or the work in flight; cruze show --index lists every element`);
10
- return { elements };
11
- }
12
-
13
- /** One line per element of the living architecture and specs, with what work in flight adds or changes. */
14
- export async function showIndex(deps , scope ) {
15
- const index = elementIndex(await loadView(deps.files), scope);
16
- if (scope !== undefined && index.entries.length === 0) throw new CruzeError("unknown-scope", `no element belongs to "${scope}"; name a context or capability, such as the part of an ID before its dot`);
17
- return scope === undefined ? index : { ...index, scope };
18
- }
@@ -1,11 +0,0 @@
1
- import { notesFor, readNotes, } from "../../domain/project/notes.js";
2
- import { loadView, } from "../project_context.js";
3
-
4
- /**
5
- * Every note with what it is about and the hand-off it holds, or, given a target such as
6
- * `project`, `architect` or a feature slug, the notes and hand-off rows for it.
7
- */
8
- export async function showNotes(deps , target ) {
9
- const notes = readNotes((await loadView(deps.files)).snapshot);
10
- return target === undefined ? { notes } : { target, ...notesFor(notes, [target]) };
11
- }
@@ -1,51 +0,0 @@
1
- import { CruzeError } from "../../domain/cruze_error.js";
2
- import { resolveArtifact } from "../../domain/project/artifact_ref.js";
3
- import { scopeOf } from "../../domain/ids.js";
4
- import { notesFor, readNotes, } from "../../domain/project/notes.js";
5
- import { scopeView, } from "../../domain/project/scope_view.js";
6
-
7
- import { loadView, } from "../project_context.js";
8
- import { listEvents } from "./journal_events.js";
9
-
10
-
11
-
12
-
13
-
14
-
15
-
16
-
17
-
18
-
19
-
20
-
21
-
22
-
23
-
24
-
25
-
26
- /**
27
- * The design a change builds, or a feature's whole delta, with the elements it cites and the
28
- * item's settled ledger: what plan, build and the reviewers read instead of the whole living docs.
29
- */
30
- export async function showScope(deps , ref ) {
31
- const view = await loadView(deps.files);
32
- const { item } = resolveArtifact(view, ref);
33
- if (item === undefined) throw new CruzeError("not-work", `"${ref}" is not a feature or change; show takes a feature or change ID`);
34
- const scope = scopeView(view, item);
35
- const dispositions = (await listEvents(deps, "disposition", scope.feature ?? item.ref, true)).map((entry) => ({
36
- finding: String(entry["finding"] ?? ""),
37
- disposition: String(entry["disposition"] ?? ""),
38
- reason: String(entry["reason"] ?? ""),
39
- review: String(entry["review"] ?? ""),
40
- }));
41
- const feature = scope.feature === undefined ? item : (view.items.find((i) => i.ref === scope.feature) ?? item);
42
- const contexts = [...scope.elements, ...scope.scenarios].flatMap((e) => scopeOf(e.id) ?? []);
43
- const notes = notesFor(readNotes(view.snapshot), [...slugsOf(feature), ...contexts]);
44
- return { ...scope, dispositions, references: view.config?.config?.references ?? [], notes };
45
- }
46
-
47
- /** The names a note or a hand-off may use for a feature or standalone change: its roadmap slug, and its folder name without the date. */
48
- export function slugsOf(item ) {
49
- const roadmap = item.doc.frontmatter?.["roadmap"];
50
- return [...new Set([...(typeof roadmap === "string" ? [roadmap] : []), item.folderName.replace(/^\d{4}-\d{2}-\d{2}-/, "")])];
51
- }
@@ -1,55 +0,0 @@
1
- import { evaluateApproval, } from "../../domain/approvals/evaluate.js";
2
- import { rebaseNeeded } from "../../domain/approvals/rebase.js";
3
- import { upstreamLines, upstreamPath } from "../../domain/approvals/upstream_text.js";
4
- import { lineDiff } from "../../domain/line_diff.js";
5
- import { resolveArtifact } from "../../domain/project/artifact_ref.js";
6
- import { approvalFolder, approvalsPath } from "../../domain/project/project_view.js";
7
- import { loadView, } from "../project_context.js";
8
-
9
-
10
-
11
-
12
-
13
-
14
-
15
-
16
-
17
-
18
-
19
-
20
-
21
-
22
-
23
-
24
-
25
-
26
-
27
- /**
28
- * What changed upstream of a document since its approval, element by element, so a person can
29
- * judge a re-stamp from the text rather than the IDs. The approved text comes from the commit
30
- * that recorded the approval. One project went to a rethink for two file-list merges that
31
- * changed no behaviour, because status named only the IDs.
32
- */
33
- export async function upstreamDiff(deps , ref ) {
34
- const view = await loadView(deps.files);
35
- const { path, item } = resolveArtifact(view, ref);
36
- const status = evaluateApproval(view, path);
37
- const report = { artifact: path, state: status.state, changes: [], rebase: item === undefined ? [] : rebaseNeeded(view, item) };
38
- if (item !== undefined) report.ref = item.ref;
39
- const record = status.record;
40
- if (record === undefined || status.changed.length === 0) return report;
41
- const commit = await deps.repository.firstCommitAdding(approvalsPath(approvalFolder(view, path)), record.approvedAt);
42
- for (const key of status.changed) {
43
- const where = upstreamPath(view, key);
44
- if (where === undefined) {
45
- report.changes.push({ key, note: "it no longer exists" });
46
- continue;
47
- }
48
- const now = upstreamLines(key, where, view.snapshot.get(where) ?? "") ?? [];
49
- const then = commit === null ? null : await deps.repository.fileAt(commit, where);
50
- const approved = then === null ? undefined : upstreamLines(key, where, then, record.upstream[key]);
51
- if (approved !== undefined) report.changes.push({ key, path: where, diff: lineDiff(approved, now) });
52
- else report.changes.push({ key, path: where, note: commit === null ? "the approval isn't committed yet; compare with git diff" : `its approved text isn't in ${where} at ${commit}, the commit that recorded the approval` });
53
- }
54
- return report;
55
- }
@@ -1,68 +0,0 @@
1
- import { CruzeError } from "../cruze_error.js";
2
-
3
-
4
-
5
-
6
- /**
7
- * Why an approval was given without the user: a plan whose review closed, or work that a
8
- * rethink left unchanged while something it cites moved. The user can still object, and
9
- * editing the document makes the approval stale as usual.
10
- */
11
-
12
-
13
- const byTime = (a , b ) => a.at.localeCompare(b.at);
14
-
15
- /**
16
- * A feature's change may be approved by the agent once its plan review has closed: a review
17
- * recorded since the change was last approved, no blocker left open in its last round, and a
18
- * disposition for every finding of its first round. The feature's design, and a standalone
19
- * change's, stay the user's to approve.
20
- */
21
- export function requirePlanReviewClosed(journal , item ) {
22
- if (item.kind !== "change") {
23
- throw new CruzeError("needs-user", `${item.ref} carries design, so the user approves it; only a feature's change can be approved after its plan review`);
24
- }
25
- const entries = journal.filter((e) => e["item"] === item.ref || e["artifact"] === item.path).sort(byTime);
26
- const lastApproval = entries.filter((e) => e.event === "approve").at(-1);
27
- const reviews = entries.filter((e) => e.event === "review" && e["review"] === "plan" && (lastApproval === undefined || e.at > lastApproval.at));
28
- const last = reviews.at(-1);
29
- if (last === undefined) {
30
- const since = lastApproval === undefined ? "" : " since the change was last approved";
31
- throw new CruzeError("plan-review-open", `no plan review is recorded for ${item.ref}${since}; run the plan review first`);
32
- }
33
- const blockers = Number(last["blockers"] ?? 0);
34
- if (blockers > 0 && Number(last["round"]) === 1) {
35
- throw new CruzeError("plan-review-open", `the plan review's round 1 found ${blockers} blocker(s); check their fixes in round 2`);
36
- }
37
- if (blockers > 0) {
38
- throw new CruzeError("plan-review-open", `round 2 left ${blockers} blocker(s) open; the user decides them and approves the plan`);
39
- }
40
- const first = reviews.filter((e) => Number(e["round"]) === 1).at(-1) ?? last;
41
- const findings = Number(first["blockers"] ?? 0) + Number(first["concerns"] ?? 0);
42
- const decided = entries.filter((e) => e.event === "disposition" && e["review"] === "plan" && e.at > first.at).length;
43
- if (decided < findings) {
44
- throw new CruzeError("plan-review-open", `the plan review raised ${findings} finding(s) but ${decided} have a disposition; record one for each`);
45
- }
46
- }
47
-
48
- /**
49
- * A rethink may re-approve, for the user, a feature or change it didn't edit: one whose own
50
- * design is unchanged and that is stale only because something it cites changed. A change
51
- * with a task reopened since its approval goes back to the user.
52
- */
53
- export function requireUnchangedSinceApproval(journal , item , path , status ) {
54
- if (item === undefined) {
55
- throw new CruzeError("needs-user", `${path} is a project document, so the user re-approves it; a rethink re-stamps only features and changes`);
56
- }
57
- if (status.state === "edited") {
58
- throw new CruzeError("needs-user", `${item.ref} itself changed since its approval, so the user re-approves it`);
59
- }
60
- if (status.state !== "upstream-changed") {
61
- throw new CruzeError("not-stale", `${item.ref} is ${status.state}; only work whose upstream changed can be re-stamped`);
62
- }
63
- const approvedAt = journal.filter((e) => e.event === "approve" && e["artifact"] === path).sort(byTime).at(-1)?.at ?? "";
64
- const reopened = journal.filter((e) => e.event === "reopen" && e["change"] === item.ref && e.at > approvedAt).map((e) => String(e["task"]));
65
- if (reopened.length > 0) {
66
- throw new CruzeError("needs-user", `${item.ref} had ${reopened.join(", ")} reopened since its approval, so the user re-approves it`);
67
- }
68
- }
@@ -1,36 +0,0 @@
1
- import { parseMarkdown } from "../markdown.js";
2
- import { approvalFolder, } from "../project/project_view.js";
3
- import { currentUpstreamHash, fingerprint } from "./fingerprint.js";
4
- import { latestApproval, } from "./records.js";
5
-
6
-
7
-
8
-
9
-
10
-
11
-
12
-
13
-
14
-
15
-
16
-
17
-
18
- /**
19
- * Computes an artifact's approval state from content, never from stored state:
20
- * editing an upstream document is all it takes to make downstream work stale.
21
- */
22
- export function evaluateApproval(view , artifact ) {
23
- const record = latestApproval(view.approvals.get(approvalFolder(view, artifact)) ?? { version: 1, approvals: [], merged: {} }, artifact);
24
- if (record === undefined) return { artifact, state: "unapproved", changed: [] };
25
- const journaled = view.journal.some((e) => e.event === "approve" && e.artifact === artifact && e.hash === record.hash);
26
- if (!journaled) return { artifact, state: "unapproved", changed: [], record, unjournaled: true };
27
-
28
- const text = view.snapshot.get(artifact);
29
- if (text === undefined) return { artifact, state: "unapproved", changed: [], record };
30
- const { hash } = fingerprint(view, artifact, parseMarkdown(text));
31
- if (hash !== record.hash) return { artifact, state: "edited", changed: [], record };
32
- const changed = Object.entries(record.upstream)
33
- .filter(([key, pinned]) => currentUpstreamHash(view, key) !== pinned)
34
- .map(([key]) => key);
35
- return { artifact, state: changed.length > 0 ? "upstream-changed" : "approved", changed, record };
36
- }
@@ -1,52 +0,0 @@
1
- import { headingElements, rowElements } from "../elements.js";
2
- import { designHash, elementHash } from "../hashing.js";
3
- import { findIds } from "../ids.js";
4
-
5
- import { PATHS } from "../project/layout.js";
6
-
7
- import { featureOf } from "../project/work_items.js";
8
-
9
- /** What an approval pins: the document's design hash and the hash of everything upstream it relies on. */
10
-
11
-
12
-
13
-
14
-
15
- /** Documents a person can approve. Specs, the glossary and ADRs change only through approved work. */
16
- export function isApprovable(view , path ) {
17
- return [PATHS.vision, PATHS.architecture, PATHS.roadmap].includes(path ) || view.items.some((i) => i.path === path && !i.archived);
18
- }
19
-
20
- export function fingerprint(view , path , doc ) {
21
- const upstream = {};
22
- const ownIds = new Set([...headingElements(doc), ...rowElements(doc)].filter((e) => e.op !== "ADDED").map((e) => e.id));
23
- const isLivingDoc = !view.items.some((item) => item.path === path);
24
- const designText = doc.lines.filter((_, i) => !doc.inManaged[i]).join("\n");
25
-
26
- for (const id of findIds(designText)) {
27
- if (isLivingDoc && ownIds.has(id)) continue;
28
- const living = view.living.elements.get(id);
29
- if (living !== undefined) upstream[id] = elementHash(living.doc, living.element);
30
- }
31
- const item = view.items.find((i) => i.path === path);
32
- if (item !== undefined) {
33
- // Work always depends on the dependency rules, cited or not.
34
- for (const [id, living] of view.living.elements) {
35
- if (id.startsWith("RULE-")) upstream[id] = elementHash(living.doc, living.element);
36
- }
37
- const feature = item.kind === "change" ? featureOf(view.items, item) : undefined;
38
- if (feature !== undefined) upstream[`doc:${feature.path}`] = designHash(feature.doc);
39
- }
40
- return { hash: designHash(doc), upstream };
41
- }
42
-
43
- /** The current hash of one upstream key, or undefined when it no longer exists. */
44
- export function currentUpstreamHash(view , key ) {
45
- if (key.startsWith("doc:")) {
46
- const path = key.slice("doc:".length);
47
- const item = view.items.find((i) => i.path === path);
48
- return item === undefined ? undefined : designHash(item.doc);
49
- }
50
- const living = view.living.elements.get(key);
51
- return living === undefined ? undefined : elementHash(living.doc, living.element);
52
- }
@@ -1,39 +0,0 @@
1
- import { elementHash } from "../hashing.js";
2
-
3
- import { readDelta } from "../project/deltas.js";
4
- import { approvalFolder, } from "../project/project_view.js";
5
-
6
- import { latestApproval } from "./records.js";
7
-
8
- /**
9
- * Delta elements a feature or standalone change merged and hasn't changed since. The merge did
10
- * their job, so the living text is the one to keep: an edit there by a rethink, a realign or
11
- * another land asks nothing of the delta. One project's delta went stale this way, and every
12
- * later edit of an element its first change had merged forced a rebase of text with no work left in it.
13
- */
14
- export function settledIds(doc , merged ) {
15
- const settled = new Set ();
16
- for (const entry of readDelta(doc).entries) {
17
- for (const element of [entry.element, ...entry.scenarios]) {
18
- if (merged[element.id] === elementHash(doc, element)) settled.add(element.id);
19
- }
20
- }
21
- return settled;
22
- }
23
-
24
- /**
25
- * Elements the work's delta still changes whose living text moved since its last approval:
26
- * approving the delta as it stands would overwrite that edit. A change's own approval pins no delta.
27
- */
28
- export function rebaseNeeded(view , item ) {
29
- if (item.kind === "change") return [];
30
- const file = view.approvals.get(approvalFolder(view, item.path));
31
- const previous = file === undefined ? undefined : latestApproval(file, item.path);
32
- if (previous === undefined) return [];
33
- const settled = settledIds(item.doc, file?.merged ?? {});
34
- return [...readDelta(item.doc).ids].filter((id) => {
35
- const living = view.living.elements.get(id);
36
- const before = previous.upstream[id];
37
- return !settled.has(id) && living !== undefined && before !== undefined && before !== elementHash(living.doc, living.element);
38
- });
39
- }
@@ -1,50 +0,0 @@
1
- /** What `approvals.json` holds. Only the CLI writes it. */
2
-
3
-
4
-
5
-
6
-
7
-
8
-
9
-
10
-
11
-
12
-
13
-
14
-
15
-
16
-
17
-
18
-
19
-
20
-
21
-
22
-
23
- export function emptyApprovals() {
24
- return { version: 1, approvals: [], merged: {} };
25
- }
26
-
27
- export function parseApprovals(text ) {
28
- if (text === undefined) return { file: emptyApprovals() };
29
- try {
30
- const raw = JSON.parse(text) ;
31
- if (raw.version !== 1 || !Array.isArray(raw.approvals)) return { file: emptyApprovals(), problem: "approvals.json has an unknown format" };
32
- return { file: { version: 1, approvals: raw.approvals, merged: raw.merged ?? {} } };
33
- } catch {
34
- return { file: emptyApprovals(), problem: "approvals.json is not valid JSON" };
35
- }
36
- }
37
-
38
- export function serializeApprovals(file ) {
39
- return `${JSON.stringify(file, null, 2)}\n`;
40
- }
41
-
42
- /** The latest approval of an artifact, if any. */
43
- export function latestApproval(file , artifact ) {
44
- return file.approvals.filter((record) => record.artifact === artifact).at(-1);
45
- }
46
-
47
- /** Replaces the artifact's approval: one current approval per artifact; history lives in the journal. */
48
- export function withApproval(file , record ) {
49
- return { ...file, approvals: [...file.approvals.filter((r) => r.artifact !== record.artifact), record] };
50
- }
@@ -1,36 +0,0 @@
1
- import { isStatusLine } from "../elements.js";
2
- import { designHash, elementHash } from "../hashing.js";
3
- import { parseMarkdown, } from "../markdown.js";
4
- import { buildLivingModel } from "../project/living_model.js";
5
-
6
-
7
- /** The document that holds an upstream key now: an element's living document, or the path of a `doc:` key. */
8
- export function upstreamPath(view , key ) {
9
- if (key.startsWith("doc:")) return view.snapshot.has(key.slice("doc:".length)) ? key.slice("doc:".length) : undefined;
10
- return view.living.elements.get(key)?.path;
11
- }
12
-
13
- /**
14
- * The design lines behind an upstream key in one version of the document at `path`, without the
15
- * managed blocks and status lines no hash covers. With `pinned`, only when they hash as pinned.
16
- */
17
- export function upstreamLines(key , path , text , pinned ) {
18
- if (key.startsWith("doc:")) {
19
- const doc = parseMarkdown(text);
20
- if (pinned !== undefined && designHash(doc) !== pinned) return undefined;
21
- return designLines(doc, 0, doc.lines.length);
22
- }
23
- const living = buildLivingModel(new Map([[path, text]])).elements.get(key);
24
- if (living === undefined) return undefined;
25
- if (pinned !== undefined && elementHash(living.doc, living.element) !== pinned) return undefined;
26
- return designLines(living.doc, living.element.start, living.element.end);
27
- }
28
-
29
- function designLines(doc , start , end ) {
30
- const lines = [];
31
- for (let i = start; i < end; i++) {
32
- const line = doc.lines[i] ?? "";
33
- if (!doc.inManaged[i] && (doc.inFence[i] || !isStatusLine(line))) lines.push(line);
34
- }
35
- return lines;
36
- }