@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,137 +0,0 @@
1
- import { settledIds } from "../approvals/rebase.js";
2
- import { sectionForKind } from "../architecture_sections.js";
3
- import { CruzeError } from "../cruze_error.js";
4
- import { setStatus } from "../edits/status_lines.js";
5
- import { headingElements, statusOf, } from "../elements.js";
6
- import { elementHash } from "../hashing.js";
7
- import { STATUS_KINDS, kindOf, scopeOf } from "../ids.js";
8
- import { findSection, parseMarkdown } from "../markdown.js";
9
- import { readDelta, } from "../project/deltas.js";
10
- import { PATHS, specPath } from "../project/layout.js";
11
- import { readScope } from "../project/plan_parts.js";
12
-
13
-
14
- import { REMOVED_MARK, capabilityKey } from "../validation/delta_rules.js";
15
- import { appendBlock, relevel, removeElement, replaceElement } from "./text_ops.js";
16
-
17
-
18
-
19
-
20
-
21
-
22
-
23
-
24
-
25
-
26
-
27
- /**
28
- * Merges a change's work into the living docs. The first change of a feature to land merges
29
- * the whole delta; elements it doesn't build arrive as planned, and later changes flip what
30
- * they build to built. Text is replaced only when nobody else changed it since this work merged it,
31
- * and an element merged and unchanged in the delta since keeps its living text, whoever edited it.
32
- */
33
- export function planMerge(view , change , source ) {
34
- const scope = readScope(change.doc);
35
- const inScope = new Set([...(scope?.delivers ?? []), ...(scope?.builds ?? [])]);
36
- const delta = readDelta(source.doc);
37
- const merged = { ...(view.approvals.get(source.folder)?.merged ?? {}) };
38
- const files = new Map ();
39
- const text = (path ) => files.get(path) ?? view.snapshot.get(path) ?? "";
40
- const plan = { files, merged, mergedIds: [], built: [], removed: [] };
41
- const settled = settledIds(source.doc, merged);
42
-
43
- for (const op of delta.capabilities) {
44
- const key = capabilityKey(op.capability);
45
- if (merged[key] !== undefined || view.snapshot.has(specPath(op.capability))) continue;
46
- const body = source.doc.lines.slice(op.start + 1, op.end).join("\n").trim();
47
- files.set(specPath(op.capability), `# ${op.title}\n\n${body}\n`);
48
- merged[key] = "created";
49
- }
50
-
51
- for (const entry of delta.entries) {
52
- const { id } = entry.element;
53
- const path = entry.section === "spec" ? specPath(scopeOf(id) ?? "") : PATHS.architecture;
54
- if (entry.element.op === "REMOVED") {
55
- if (scope?.removes.includes(id) === true) {
56
- files.set(path, removeElement(text(path), id));
57
- merged[id] = REMOVED_MARK;
58
- plan.removed.push(id);
59
- }
60
- continue;
61
- }
62
- const kept = settled.has(id) && view.living.elements.has(id);
63
- if (!kept) mergeEntry(view, source, entry, path, text, files, merged, plan);
64
- for (const target of [entry.element, ...entry.scenarios].filter((e) => STATUS_KINDS.includes(e.kind))) {
65
- if (kept && !inScope.has(target.id)) continue;
66
- files.set(path, setStatus(text(path), target.id, statusAfterMerge(view, source, target, inScope)));
67
- if (inScope.has(target.id)) plan.built.push(target.id);
68
- }
69
- }
70
-
71
- for (const id of [...inScope].filter((id) => !delta.ids.has(id) && STATUS_KINDS.includes(kindOf(id)))) {
72
- const living = view.living.elements.get(id);
73
- if (living === undefined) continue;
74
- files.set(living.path, setStatus(text(living.path), id, "built"));
75
- plan.built.push(id);
76
- }
77
- verifyMerged(source, delta.entries, text, plan);
78
- return plan;
79
- }
80
-
81
- function mergeEntry(
82
- view ,
83
- source ,
84
- entry ,
85
- path ,
86
- text ,
87
- files ,
88
- merged ,
89
- plan ,
90
- ) {
91
- const { id } = entry.element;
92
- const block = relevel(source.doc, entry.element.start, entry.element.end, entry.section === "spec" ? 2 : 3);
93
- const deltaHash = elementHash(source.doc, entry.element);
94
- const doc = parseMarkdown(text(path));
95
- const living = headingElements(doc).find((e) => e.id === id);
96
- const previous = merged[id];
97
- if (living !== undefined) {
98
- const livingHash = elementHash(doc, living);
99
- if (previous !== undefined && previous !== REMOVED_MARK && livingHash !== previous) {
100
- throw new CruzeError("merge-conflict", `${id} in ${path} changed since ${source.ref} merged it; rethink before landing`);
101
- }
102
- if (livingHash !== deltaHash) {
103
- files.set(path, replaceElement(text(path), id, block));
104
- plan.mergedIds.push(id);
105
- }
106
- } else {
107
- if (previous !== undefined && previous !== REMOVED_MARK) {
108
- throw new CruzeError("merge-conflict", `${id} was removed from ${path} after ${source.ref} merged it; rethink before landing`);
109
- }
110
- const section = entry.section === "spec" ? undefined : sectionForKind(entry.element.kind);
111
- if (section !== undefined && findSection(doc, section) === null) throw new CruzeError("merge-conflict", `${path} has no "## ${section}" section for ${id}`);
112
- files.set(path, appendBlock(text(path), block, section));
113
- plan.mergedIds.push(id);
114
- }
115
- merged[id] = deltaHash;
116
- for (const scenario of entry.scenarios) merged[scenario.id] = elementHash(source.doc, scenario);
117
- }
118
-
119
- /** Built when this change builds it, or when it was built and its text is unchanged; planned otherwise. */
120
- function statusAfterMerge(view , source , target , inScope ) {
121
- if (inScope.has(target.id)) return "built";
122
- const before = view.living.elements.get(target.id);
123
- if (before === undefined || statusOf(before.doc, before.element) !== "built") return "planned";
124
- return elementHash(before.doc, before.element) === elementHash(source.doc, target) ? "built" : "planned";
125
- }
126
-
127
- /** After merging, every merged element must hash exactly as it did in the delta. */
128
- function verifyMerged(source , entries , text , plan ) {
129
- for (const entry of entries.filter((e) => plan.mergedIds.includes(e.element.id))) {
130
- const path = entry.section === "spec" ? specPath(scopeOf(entry.element.id) ?? "") : PATHS.architecture;
131
- const doc = parseMarkdown(text(path));
132
- const living = headingElements(doc).find((e) => e.id === entry.element.id);
133
- if (living === undefined || elementHash(doc, living) !== elementHash(source.doc, entry.element)) {
134
- throw new Error(`merge check failed: ${entry.element.id} in ${path} does not match the delta`);
135
- }
136
- }
137
- }
@@ -1,66 +0,0 @@
1
- import { headingElements, isStatusLine } from "../elements.js";
2
- import { parseMarkdown, } from "../markdown.js";
3
-
4
- /** Line-level edits land makes to living documents. Every function takes and returns whole text. */
5
-
6
- /**
7
- * A delta block ready for a living document: headings shifted so the element's own heading
8
- * sits at `level`, the operation prefix dropped, status lines removed, trailing blanks trimmed.
9
- */
10
- export function relevel(doc , start , end , level ) {
11
- const own = /^(#{1,6}) /.exec(doc.lines[start] ?? "")?.[1]?.length ?? level;
12
- const shift = level - own;
13
- const out = [];
14
- for (let i = start; i < end; i++) {
15
- let line = doc.lines[i] ?? "";
16
- if (!doc.inFence[i]) {
17
- if (isStatusLine(line)) continue;
18
- const heading = /^(#{1,6}) (.*)$/.exec(line);
19
- if (heading?.[1] !== undefined && heading[2] !== undefined) {
20
- const text = i === start ? heading[2].replace(/^(ADDED|MODIFIED|REMOVED) /, "") : heading[2];
21
- line = `${"#".repeat(heading[1].length + shift)} ${text}`;
22
- }
23
- }
24
- out.push(line);
25
- }
26
- while (out.length > 0 && out[out.length - 1]?.trim() === "") out.pop();
27
- return out;
28
- }
29
-
30
- /** Replaces an element's whole block, keeping one blank line before whatever follows. */
31
- export function replaceElement(text , id , block ) {
32
- const doc = parseMarkdown(text);
33
- const element = headingElements(doc).find((e) => e.id === id);
34
- if (element === undefined) throw new Error(`${id} is not in the document`);
35
- const lines = [...doc.lines];
36
- const follows = element.end < lines.length;
37
- lines.splice(element.start, element.end - element.start, ...block, ...(follows ? [""] : []));
38
- return lines.join("\n");
39
- }
40
-
41
- export function removeElement(text , id ) {
42
- const doc = parseMarkdown(text);
43
- const element = headingElements(doc).find((e) => e.id === id);
44
- if (element === undefined) return text;
45
- const lines = [...doc.lines];
46
- lines.splice(element.start, element.end - element.start);
47
- return lines.join("\n");
48
- }
49
-
50
- /** Appends a block at the end of a `##` section, or at the end of the document when none is named. */
51
- export function appendBlock(text , block , section ) {
52
- const doc = parseMarkdown(text);
53
- const lines = [...doc.lines];
54
- let at = lines.length;
55
- if (section !== undefined) {
56
- const heading = doc.headings.find((h) => h.level === 2 && h.text === section);
57
- if (heading === undefined) throw new Error(`the document has no "## ${section}" section`);
58
- at = doc.headings.find((h) => h.line > heading.line && h.level <= 2)?.line ?? lines.length;
59
- }
60
- let insertAt = at;
61
- while (insertAt > 0 && lines[insertAt - 1]?.trim() === "") insertAt--;
62
- const trailing = at < lines.length ? [""] : [];
63
- lines.splice(insertAt, at - insertAt, "", ...block, ...trailing);
64
- const joined = lines.join("\n");
65
- return joined.endsWith("\n") ? joined : `${joined}\n`;
66
- }
@@ -1,56 +0,0 @@
1
- /**
2
- * A line diff of two texts as unified-diff lines: " " for a kept line, "- " removed, "+ " added.
3
- * Only the changes and `context` kept lines around each are shown; "…" marks the lines left out.
4
- */
5
- export function lineDiff(before , after , context = 2) {
6
- const ops = diffOps(before, after);
7
- const near = new Set ();
8
- ops.forEach((op, i) => {
9
- if (op.kind === " ") return;
10
- for (let j = Math.max(0, i - context); j <= Math.min(ops.length - 1, i + context); j++) near.add(j);
11
- });
12
- const lines = [];
13
- let skipped = false;
14
- ops.forEach((op, i) => {
15
- if (!near.has(i)) {
16
- skipped = true;
17
- return;
18
- }
19
- if (skipped && lines.length > 0) lines.push("…");
20
- skipped = false;
21
- lines.push(`${op.kind} ${op.line}`);
22
- });
23
- if (skipped && lines.length > 0) lines.push("…");
24
- return lines;
25
- }
26
-
27
-
28
-
29
-
30
-
31
-
32
- /** The edit script of a longest common subsequence: documents are small enough for the quadratic table. */
33
- function diffOps(a , b ) {
34
- const width = b.length + 1;
35
- const table = new Int32Array((a.length + 1) * width);
36
- const at = (i , j ) => table[i * width + j] ?? 0;
37
- for (let i = a.length - 1; i >= 0; i--) {
38
- for (let j = b.length - 1; j >= 0; j--) {
39
- table[i * width + j] = a[i] === b[j] ? at(i + 1, j + 1) + 1 : Math.max(at(i + 1, j), at(i, j + 1));
40
- }
41
- }
42
- const ops = [];
43
- let i = 0;
44
- let j = 0;
45
- while (i < a.length || j < b.length) {
46
- if (i < a.length && j < b.length && a[i] === b[j]) {
47
- ops.push({ kind: " ", line: a[i++] ?? "" });
48
- j++;
49
- } else if (j >= b.length || (i < a.length && at(i + 1, j) >= at(i, j + 1))) {
50
- ops.push({ kind: "-", line: a[i++] ?? "" });
51
- } else {
52
- ops.push({ kind: "+", line: b[j++] ?? "" });
53
- }
54
- }
55
- return ops;
56
- }
@@ -1,42 +0,0 @@
1
- import { CruzeError } from "../cruze_error.js";
2
- import { PATHS } from "./layout.js";
3
-
4
- import { findWorkItem, } from "./work_items.js";
5
-
6
- const NAMED = { vision: PATHS.vision, architecture: PATHS.architecture, roadmap: PATHS.roadmap };
7
-
8
- /**
9
- * Resolves what a person or skill names on the command line: `vision`, `architecture`,
10
- * `roadmap`, a work item ref such as `2026-09-25-open-console/01-local-serial`, the same ref
11
- * without its date (`open-console/local-serial`) when that names one active item, or a path.
12
- */
13
- export function resolveArtifact(view , ref ) {
14
- const named = NAMED[ref];
15
- if (named !== undefined) {
16
- if (!view.snapshot.has(named)) throw new CruzeError("not-found", `${named} does not exist yet`);
17
- return { path: named };
18
- }
19
- const item = findWorkItem(view.items, ref.replace(/\/$/, "")) ?? bySlug(view.items, ref.replace(/\/$/, ""));
20
- if (item !== undefined) return { path: item.path, item };
21
- if (view.snapshot.has(ref)) return { path: ref };
22
- throw new CruzeError("not-found", `nothing called "${ref}"; use vision, architecture, roadmap, a feature or change ID, or a path`);
23
- }
24
-
25
- /** An active item named by its slug, without the date or the change number. */
26
- function bySlug(items , ref ) {
27
- const [head = "", change] = ref.split("/");
28
- const undated = (name ) => name.replace(/^\d{4}-\d{2}-\d{2}-/, "");
29
- const matches = items.filter((i) => !i.archived && i.kind !== "change" && undated(i.folderName) === head);
30
- if (matches.length > 1) throw new CruzeError("ambiguous", `"${head}" names ${matches.map((m) => m.ref).join(" and ")}; use the full ID`);
31
- const base = matches[0];
32
- if (base === undefined || change === undefined) return base;
33
- return items.find((i) => i.featureRef === base.ref && (i.folderName === change || i.folderName.replace(/^\d\d-/, "") === change));
34
- }
35
-
36
- /** Resolves a change: a feature's change or a standalone change, never archived. */
37
- export function resolveChange(view , ref ) {
38
- const { item } = resolveArtifact(view, ref);
39
- if (item === undefined || item.kind === "feature") throw new CruzeError("not-a-change", `"${ref}" is not a change`);
40
- if (item.archived) throw new CruzeError("archived", `${item.ref} has already landed`);
41
- return item;
42
- }
@@ -1,49 +0,0 @@
1
- import { capabilityOps, headingElements, } from "../elements.js";
2
- import { findSection, } from "../markdown.js";
3
-
4
- /** One delta operation on a living element, with its nested scenarios when it is a requirement. */
5
-
6
-
7
-
8
-
9
-
10
-
11
-
12
-
13
-
14
-
15
-
16
-
17
-
18
-
19
- export function readDelta(doc ) {
20
- const entries = [];
21
- const capabilities = [];
22
- for (const [title, section] of [["Spec delta", "spec"], ["Architecture delta", "architecture"]] ) {
23
- const range = findSection(doc, title);
24
- if (range === null) continue;
25
- capabilities.push(...capabilityOps(doc, range.start, range.end));
26
- const elements = headingElements(doc, range.start, range.end);
27
- for (const element of elements.filter((e) => e.op !== undefined)) {
28
- const scenarios = elements.filter((e) => e.op === undefined && e.start > element.start && e.start < element.end);
29
- entries.push({ element, section, scenarios });
30
- }
31
- }
32
- const ids = new Set(entries.flatMap((entry) => [entry.element.id, ...entry.scenarios.map((s) => s.id)]));
33
- return { entries, capabilities, ids };
34
- }
35
-
36
- /** Scenario elements that sit under no requirement operation: always a format error. */
37
- export function orphanDeltaElements(doc ) {
38
- const orphans = [];
39
- for (const title of ["Spec delta", "Architecture delta"]) {
40
- const range = findSection(doc, title);
41
- if (range === null) continue;
42
- const elements = headingElements(doc, range.start, range.end);
43
- const ops = elements.filter((e) => e.op !== undefined);
44
- for (const element of elements.filter((e) => e.op === undefined)) {
45
- if (!ops.some((op) => element.start > op.start && element.start < op.end)) orphans.push(element);
46
- }
47
- }
48
- return orphans;
49
- }
@@ -1,124 +0,0 @@
1
- import { elementLines, factsOf, statusOf, } from "../elements.js";
2
- import { findIds, kindOf, scopeOf, } from "../ids.js";
3
-
4
- import { readDelta } from "./deltas.js";
5
- import { PATHS } from "./layout.js";
6
-
7
-
8
-
9
-
10
- /**
11
- * Elements one at a time, and an index of all of them, so a step that needs a few elements of a
12
- * large architecture fetches those instead of reading the whole file. One project's architecture
13
- * held 52k words, and a single architect run read all of it to find what it would extend.
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
- const DOCS_INDEXED = (path ) => path === PATHS.architecture || path.startsWith(`${PATHS.specsDir}/`);
40
-
41
- /**
42
- * Every element of the living architecture and specs, one entry each, with what in-flight work
43
- * adds. Without a scope, scenarios are counted under their requirement; with a context or
44
- * capability name, only its elements are listed, scenarios included.
45
- */
46
- export function elementIndex(view , scope ) {
47
- const inScope = (id ) => scope === undefined || scopeOf(id) === scope || id === `CTX-${scope}`;
48
- const entries = [];
49
- const byId = new Map ();
50
- for (const living of view.living.elements.values()) {
51
- if (!DOCS_INDEXED(living.path) || !inScope(living.id) || (scope === undefined && kindOf(living.id) === "SCN")) continue;
52
- const entry = entryOf(living.id, living.element, living.doc, living.path);
53
- if (kindOf(living.id) === "REQ") entry.scenarios = scenarioCount(view, living.id);
54
- entries.push(entry);
55
- byId.set(entry.id, entry);
56
- }
57
- for (const item of inFlight(view)) {
58
- for (const element of deltaElements(item)) {
59
- if (!inScope(element.element.id) || (scope === undefined && element.element.kind === "SCN")) continue;
60
- const known = byId.get(element.element.id);
61
- if (known !== undefined) {
62
- known.changedBy.push(item.ref);
63
- continue;
64
- }
65
- const added = { ...entryOf(element.element.id, element.element, item.doc, item.ref), addedBy: item.ref };
66
- entries.push(added);
67
- byId.set(added.id, added);
68
- }
69
- }
70
- return { words: livingWords(view), entries };
71
- }
72
-
73
- /** The bodies of the named elements: the living one, then each in-flight delta that changes it. */
74
- export function lookupElements(view , ids ) {
75
- const elements = [];
76
- const missing = [];
77
- for (const id of ids) {
78
- const found = [];
79
- const living = view.living.elements.get(id);
80
- if (living !== undefined) found.push(shown(living.doc, living.element, living.path));
81
- for (const item of inFlight(view)) {
82
- const delta = deltaElements(item).find((d) => d.element.id === id);
83
- if (delta !== undefined) found.push({ ...shown(item.doc, delta.element, item.path), ...(delta.op === undefined ? {} : { op: delta.op }) });
84
- }
85
- if (found.length === 0) missing.push(id);
86
- elements.push(...found);
87
- }
88
- return { elements, missing };
89
- }
90
-
91
- function entryOf(id , element , doc , source ) {
92
- const facts = factsOf(doc, element);
93
- const place = (facts.get("Module") ?? []).flatMap(findIds)[0] ?? (facts.get("Path") ?? [])[0];
94
- const status = statusOf(doc, element);
95
- return { id, title: element.title, kind: kindOf(id), source, changedBy: [], ...(status === undefined ? {} : { status }), ...(place === undefined ? {} : { place }) };
96
- }
97
-
98
- function shown(doc , element , path ) {
99
- return { id: element.id, title: element.title, source: `${path}:${element.start + 1}`, body: elementLines(doc, element) };
100
- }
101
-
102
- function inFlight(view ) {
103
- return view.items.filter((item) => !item.archived && item.kind !== "change");
104
- }
105
-
106
- /** Each element a work item's delta defines or changes, scenarios under a requirement included. */
107
- function deltaElements(item ) {
108
- return readDelta(item.doc).entries.flatMap((entry) => [
109
- { element: entry.element, ...(entry.element.op === undefined ? {} : { op: entry.element.op }) },
110
- ...entry.scenarios.map((scenario) => ({ element: scenario })),
111
- ]);
112
- }
113
-
114
- function scenarioCount(view , requirement ) {
115
- const living = view.living.elements.get(requirement);
116
- if (living === undefined) return 0;
117
- return [...view.living.elements.values()].filter((e) => e.path === living.path && e.id.startsWith("SCN-") && e.element.start > living.element.start && e.element.start < living.element.end).length;
118
- }
119
-
120
- function livingWords(view ) {
121
- const words = (path ) => (view.snapshot.get(path) ?? "").split(/\s+/).filter((w) => w !== "").length;
122
- const specs = [...view.living.docs.keys()].filter((path) => path.startsWith(`${PATHS.specsDir}/`));
123
- return { architecture: words(PATHS.architecture), specs: specs.reduce((sum, path) => sum + words(path), 0) };
124
- }
@@ -1,55 +0,0 @@
1
- import { headingElements, rowElements, } from "../elements.js";
2
- import { VISION_KINDS } from "../ids.js";
3
- import { parseMarkdown, } from "../markdown.js";
4
- import { PATHS, childFiles, } from "./layout.js";
5
-
6
- /** An element of the living docs, with the document it lives in. */
7
-
8
-
9
-
10
-
11
-
12
-
13
-
14
- /** The living docs: vision, architecture, specs and ADRs, indexed by element ID. */
15
-
16
-
17
-
18
-
19
-
20
-
21
-
22
- export function livingDocPaths(snapshot ) {
23
- const fixed = [PATHS.vision, PATHS.architecture].filter((path) => snapshot.has(path));
24
- return [...fixed, ...childFiles(snapshot, PATHS.specsDir, ".md"), ...childFiles(snapshot, PATHS.adrDir, ".md")];
25
- }
26
-
27
- export function buildLivingModel(snapshot ) {
28
- const docs = new Map ();
29
- const elements = new Map ();
30
- const places = new Map ();
31
-
32
- for (const path of livingDocPaths(snapshot)) {
33
- const doc = parseMarkdown(snapshot.get(path) ?? "");
34
- docs.set(path, doc);
35
- for (const element of elementsOf(path, doc)) {
36
- places.set(element.id, [...(places.get(element.id) ?? []), `${path}:${element.start + 1}`]);
37
- if (!elements.has(element.id)) elements.set(element.id, { id: element.id, path, doc, element });
38
- }
39
- }
40
- const duplicates = [...places].filter(([, at]) => at.length > 1).map(([id, at]) => ({ id, places: at }));
41
- return { docs, elements, duplicates };
42
- }
43
-
44
- /** The ID an ADR file defines: `ADR-<file stem>`. */
45
- export function adrId(path ) {
46
- return `ADR-${path.slice(path.lastIndexOf("/") + 1, -".md".length)}`;
47
- }
48
-
49
- function elementsOf(path , doc ) {
50
- if (path.startsWith(`${PATHS.adrDir}/`)) {
51
- return [{ id: adrId(path), kind: "ADR", title: doc.headings[0]?.text ?? "", form: "heading", level: 1, start: 0, end: doc.lines.length }];
52
- }
53
- if (path === PATHS.vision) return rowElements(doc).filter((element) => VISION_KINDS.includes(element.kind));
54
- return headingElements(doc);
55
- }
@@ -1,48 +0,0 @@
1
- import { CruzeError } from "../cruze_error.js";
2
- import { readChangesTable } from "./plan_parts.js";
3
-
4
-
5
-
6
- const SLUG = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
7
-
8
- export function requireSlug(slug ) {
9
- if (!SLUG.test(slug)) throw new CruzeError("invalid-slug", `"${slug}" must be lowercase words joined by single hyphens`);
10
- }
11
-
12
- /**
13
- * A new feature or standalone change ID: the date plus the slug, with `-2`, `-3`… added
14
- * on a clash with any live or archived work. IDs are never reused.
15
- */
16
- export function allocateId(view , date , slug ) {
17
- requireSlug(slug);
18
- const taken = new Set(view.items.filter((item) => item.kind !== "change").map((item) => item.folderName));
19
- const base = `${date}-${slug}`;
20
- if (!taken.has(base)) return base;
21
- for (let n = 2; ; n++) if (!taken.has(`${base}-${n}`)) return `${base}-${n}`;
22
- }
23
-
24
- /**
25
- * The change folder name for a feature's change: the row of the feature's Changes table it
26
- * names, by full name (`01-local-serial`) or slug (`local-serial`).
27
- */
28
- export function changeRow(view , feature , name ) {
29
- const rows = readChangesTable(feature.doc);
30
- const row = rows.find((r) => r.change === name) ?? rows.find((r) => r.change.replace(/^\d\d-/, "") === name);
31
- if (row === undefined) {
32
- throw new CruzeError("change-not-listed", `${feature.ref} has no change "${name}" in its Changes table; add the row first`);
33
- }
34
- if (view.items.some((item) => item.featureRef === feature.ref && item.folderName === row.change)) {
35
- throw new CruzeError("exists", `${feature.ref}/${row.change} already exists`);
36
- }
37
- return row;
38
- }
39
-
40
- /** Fills the template's frontmatter, title and scope lines for a new document. */
41
- export function fillTemplate(template , values ) {
42
- let text = template;
43
- if (values.roadmap !== undefined) text = text.replace(/^roadmap: .*$/m, `roadmap: ${values.roadmap}`);
44
- else text = text.replace(/^roadmap: <.*\n/m, "");
45
- if (values.delivers !== undefined) text = text.replace(/^- Delivers: .*$/m, `- Delivers: ${values.delivers.join(", ")}`);
46
- if (values.builds !== undefined) text = text.replace(/^- Builds: .*$/m, `- Builds: ${values.builds.join(", ")}`);
47
- return text;
48
- }
@@ -1,74 +0,0 @@
1
- import { findSection, parseMarkdown } from "../markdown.js";
2
- import { tablesIn } from "../markdown_tables.js";
3
- import { PATHS } from "./layout.js";
4
-
5
-
6
- /**
7
- * Notes under `.cruze/notes/`: what the user brought, what explore captured, the evidence research
8
- * gathered, and the hand-off envision wrote from them. Each says what it is about, so a step reads
9
- * the notes for its work rather than guessing from file names: one project held 21 notes, and a
10
- * 9,500-word seed whose design sections no step was told to read.
11
- */
12
-
13
- export const NOTE_KINDS = ["input", "intake", "explore", "research"] ;
14
-
15
-
16
- /** The step an intake row hands a part to, besides a feature on the map. */
17
- export const HANDOFF_STEPS = ["vision", "architect", "roadmap", "user"] ;
18
-
19
-
20
-
21
-
22
-
23
-
24
-
25
-
26
-
27
-
28
-
29
-
30
-
31
-
32
-
33
-
34
-
35
-
36
-
37
-
38
-
39
- export function readNotes(snapshot ) {
40
- return [...snapshot.keys()]
41
- .filter((path) => path.startsWith(`${PATHS.notesDir}/`) && path.endsWith(".md"))
42
- .sort()
43
- .map((path) => readNote(path, snapshot.get(path) ?? ""));
44
- }
45
-
46
- function readNote(path , text ) {
47
- const doc = parseMarkdown(text);
48
- const title = doc.headings.find((h) => h.level === 1)?.text ?? path.slice(path.lastIndexOf("/") + 1, -".md".length);
49
- const firstSection = doc.headings.find((h) => h.level === 2)?.line ?? doc.lines.length;
50
- const fact = (key ) =>
51
- doc.lines.slice(0, firstSection).map((line) => new RegExp(`^- ${key}: (.+)$`).exec(line)?.[1]?.trim()).find((v) => v !== undefined);
52
- const about = (fact("About") ?? "").split(",").map((a) => a.trim()).filter((a) => a !== "");
53
- const kind = fact("Kind");
54
- const section = findSection(doc, "Hand-off");
55
- const rows = section === null ? [] : (tablesIn(doc.lines, doc.inFence, section.start, section.end)[0]?.rows ?? []);
56
- const handoff = rows.map(({ cells }) => ({ part: cells[0] ?? "", source: cells[1] ?? "", goesTo: (cells[2] ?? "").replace(/`/g, ""), status: cells[3] ?? "open" }));
57
- return { path, title, about, handoff, ...(kind === undefined ? {} : { kind }) };
58
- }
59
-
60
-
61
-
62
-
63
-
64
-
65
-
66
-
67
- /** The notes and hand-off rows for a piece of work, named by its targets: `project`, a step, a feature slug or a context. */
68
- export function notesFor(notes , targets ) {
69
- const wanted = new Set(targets);
70
- return {
71
- notes: notes.filter((note) => note.about.some((a) => wanted.has(a))),
72
- handoffs: notes.flatMap((note) => note.handoff.filter((row) => wanted.has(row.goesTo)).map((row) => ({ ...row, note: note.path }))),
73
- };
74
- }