@printpractical/cruze 0.0.13 → 0.0.14

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (298) hide show
  1. package/README.md +61 -118
  2. package/dist/adapters/inbound/cli/cli_context.js +0 -27
  3. package/dist/adapters/inbound/cli/commands/code.js +5 -18
  4. package/dist/adapters/inbound/cli/commands/documents.js +11 -37
  5. package/dist/adapters/inbound/cli/commands/init.js +1 -1
  6. package/dist/adapters/inbound/cli/commands/review.js +2 -9
  7. package/dist/adapters/inbound/cli/commands/status.js +16 -63
  8. package/dist/adapters/inbound/cli/commands.js +10 -37
  9. package/dist/adapters/inbound/cli/parse_command.js +0 -54
  10. package/dist/adapters/outbound/git_repository.js +2 -71
  11. package/dist/app/ports/repository.js +0 -12
  12. package/dist/app/project_context.js +15 -25
  13. package/dist/app/use_cases/approve_document.js +29 -0
  14. package/dist/app/use_cases/check_code.js +21 -56
  15. package/dist/app/use_cases/new_document.js +25 -0
  16. package/dist/app/use_cases/project_status.js +43 -67
  17. package/dist/app/use_cases/run_review.js +11 -30
  18. package/dist/app/use_cases/validate_project.js +4 -4
  19. package/dist/domain/approvals.js +51 -0
  20. package/dist/domain/check/check_source.js +2 -31
  21. package/dist/domain/config.js +8 -84
  22. package/dist/domain/hashing.js +16 -30
  23. package/dist/domain/ids.js +13 -27
  24. package/dist/domain/next_step.js +41 -0
  25. package/dist/domain/project/features.js +71 -0
  26. package/dist/domain/project/layout.js +11 -27
  27. package/dist/domain/project/project_docs.js +48 -0
  28. package/dist/domain/project/release.js +30 -0
  29. package/dist/domain/scaffold.js +0 -1
  30. package/dist/domain/skill_budget.js +4 -4
  31. package/dist/domain/trace.js +8 -25
  32. package/dist/domain/validation/document_rules.js +70 -0
  33. package/dist/domain/validation/release_rules.js +45 -0
  34. package/dist/domain/validation/validate_project.js +20 -27
  35. package/dist/domain/versions/cruze_version.js +3 -9
  36. package/dist/main.js +1 -1
  37. package/package.json +1 -1
  38. package/skills/asking/SKILL.md +51 -0
  39. package/skills/behavioural-testing/SKILL.md +14 -16
  40. package/skills/behavioural-testing/examples.md +1 -2
  41. package/skills/behavioural-testing/fakes.md +2 -2
  42. package/skills/build/SKILL.md +34 -38
  43. package/skills/dependency-approval/SKILL.md +34 -31
  44. package/skills/envision/SKILL.md +23 -18
  45. package/skills/envision/architecture.md +16 -0
  46. package/skills/envision/breakdown.md +14 -0
  47. package/skills/envision/possibilities.md +6 -7
  48. package/skills/fix/SKILL.md +34 -0
  49. package/skills/formats/SKILL.md +39 -87
  50. package/skills/formats/config.md +47 -0
  51. package/skills/formats/templates/architecture.md +18 -74
  52. package/skills/formats/templates/feature.md +18 -34
  53. package/skills/formats/templates/vision.md +15 -27
  54. package/skills/hexagonal-design/SKILL.md +11 -11
  55. package/skills/hexagonal-design/contracts.md +3 -3
  56. package/skills/hexagonal-design/fundamentals.md +1 -1
  57. package/skills/hexagonal-design/languages/cpp.md +1 -1
  58. package/skills/hexagonal-design/languages/rust.md +1 -1
  59. package/skills/hexagonal-design/languages/swift.md +2 -2
  60. package/skills/hexagonal-design/runtime.md +2 -2
  61. package/skills/research/SKILL.md +4 -4
  62. package/skills/research/evaluation.md +1 -1
  63. package/skills/review/SKILL.md +27 -0
  64. package/skills/roles/SKILL.md +8 -59
  65. package/skills/roles/code-reviewer.md +21 -32
  66. package/skills/roles/researcher.md +3 -3
  67. package/skills/shape/SKILL.md +37 -0
  68. package/skills/shape/self-check.md +12 -0
  69. package/src/adapters/inbound/cli/cli_context.ts +0 -27
  70. package/src/adapters/inbound/cli/commands/code.ts +5 -18
  71. package/src/adapters/inbound/cli/commands/documents.ts +11 -37
  72. package/src/adapters/inbound/cli/commands/init.ts +1 -1
  73. package/src/adapters/inbound/cli/commands/review.ts +2 -9
  74. package/src/adapters/inbound/cli/commands/status.ts +16 -63
  75. package/src/adapters/inbound/cli/commands.ts +10 -37
  76. package/src/adapters/inbound/cli/parse_command.ts +0 -54
  77. package/src/adapters/outbound/git_repository.ts +2 -71
  78. package/src/app/ports/repository.ts +0 -12
  79. package/src/app/project_context.ts +15 -25
  80. package/src/app/use_cases/approve_document.ts +29 -0
  81. package/src/app/use_cases/check_code.ts +21 -56
  82. package/src/app/use_cases/new_document.ts +25 -0
  83. package/src/app/use_cases/project_status.ts +49 -73
  84. package/src/app/use_cases/run_review.ts +11 -30
  85. package/src/app/use_cases/validate_project.ts +4 -4
  86. package/src/domain/approvals.ts +51 -0
  87. package/src/domain/check/check_source.ts +2 -31
  88. package/src/domain/config.ts +9 -85
  89. package/src/domain/hashing.ts +16 -30
  90. package/src/domain/ids.ts +13 -27
  91. package/src/domain/next_step.ts +41 -0
  92. package/src/domain/project/features.ts +71 -0
  93. package/src/domain/project/layout.ts +11 -27
  94. package/src/domain/project/project_docs.ts +48 -0
  95. package/src/domain/project/release.ts +30 -0
  96. package/src/domain/scaffold.ts +0 -1
  97. package/src/domain/skill_budget.ts +4 -4
  98. package/src/domain/trace.ts +8 -25
  99. package/src/domain/validation/document_rules.ts +70 -0
  100. package/src/domain/validation/release_rules.ts +45 -0
  101. package/src/domain/validation/validate_project.ts +21 -28
  102. package/src/domain/versions/cruze_version.ts +3 -9
  103. package/src/main.ts +1 -1
  104. package/templates/init/AGENTS.md +4 -12
  105. package/templates/init/ci.yml +3 -3
  106. package/templates/init/config.yaml +8 -17
  107. package/templates/init/review/claude.yaml +2 -2
  108. package/dist/adapters/inbound/cli/argument_files.js +0 -29
  109. package/dist/adapters/inbound/cli/commands/diff.js +0 -23
  110. package/dist/adapters/inbound/cli/commands/journal.js +0 -32
  111. package/dist/adapters/inbound/cli/commands/progress.js +0 -55
  112. package/dist/adapters/inbound/cli/commands/realign.js +0 -28
  113. package/dist/adapters/inbound/cli/commands/show.js +0 -96
  114. package/dist/app/use_cases/abandon_work.js +0 -42
  115. package/dist/app/use_cases/approve_artifact.js +0 -197
  116. package/dist/app/use_cases/journal_events.js +0 -64
  117. package/dist/app/use_cases/land_change.js +0 -134
  118. package/dist/app/use_cases/new_project_doc.js +0 -48
  119. package/dist/app/use_cases/new_work_item.js +0 -75
  120. package/dist/app/use_cases/realign_done.js +0 -45
  121. package/dist/app/use_cases/realign_status.js +0 -106
  122. package/dist/app/use_cases/record_progress.js +0 -118
  123. package/dist/app/use_cases/show_elements.js +0 -18
  124. package/dist/app/use_cases/show_notes.js +0 -11
  125. package/dist/app/use_cases/show_scope.js +0 -51
  126. package/dist/app/use_cases/upstream_diff.js +0 -55
  127. package/dist/domain/approvals/agent_approval.js +0 -68
  128. package/dist/domain/approvals/evaluate.js +0 -36
  129. package/dist/domain/approvals/fingerprint.js +0 -52
  130. package/dist/domain/approvals/rebase.js +0 -39
  131. package/dist/domain/approvals/records.js +0 -50
  132. package/dist/domain/approvals/upstream_text.js +0 -36
  133. package/dist/domain/architecture_sections.js +0 -23
  134. package/dist/domain/check/decision_comments.js +0 -38
  135. package/dist/domain/decision_citations.js +0 -62
  136. package/dist/domain/edits/agents_file.js +0 -58
  137. package/dist/domain/edits/managed_tables.js +0 -60
  138. package/dist/domain/edits/progress_block.js +0 -33
  139. package/dist/domain/edits/status_lines.js +0 -52
  140. package/dist/domain/elements.js +0 -126
  141. package/dist/domain/journal.js +0 -109
  142. package/dist/domain/land/bookkeeping.js +0 -54
  143. package/dist/domain/land/merge_plan.js +0 -137
  144. package/dist/domain/land/text_ops.js +0 -66
  145. package/dist/domain/line_diff.js +0 -56
  146. package/dist/domain/project/artifact_ref.js +0 -42
  147. package/dist/domain/project/deltas.js +0 -49
  148. package/dist/domain/project/element_lookup.js +0 -124
  149. package/dist/domain/project/living_model.js +0 -55
  150. package/dist/domain/project/new_items.js +0 -48
  151. package/dist/domain/project/notes.js +0 -74
  152. package/dist/domain/project/plan_parts.js +0 -111
  153. package/dist/domain/project/progress.js +0 -43
  154. package/dist/domain/project/project_view.js +0 -72
  155. package/dist/domain/project/roadmap.js +0 -43
  156. package/dist/domain/project/scope_view.js +0 -134
  157. package/dist/domain/project/work_items.js +0 -68
  158. package/dist/domain/status/next_step.js +0 -181
  159. package/dist/domain/status/pending_rethink.js +0 -22
  160. package/dist/domain/status/verification.js +0 -29
  161. package/dist/domain/status/work_status.js +0 -125
  162. package/dist/domain/validation/decision_rules.js +0 -76
  163. package/dist/domain/validation/delta_rules.js +0 -81
  164. package/dist/domain/validation/element_rules.js +0 -104
  165. package/dist/domain/validation/future_rules.js +0 -40
  166. package/dist/domain/validation/living_rules.js +0 -99
  167. package/dist/domain/validation/plan_rules.js +0 -124
  168. package/dist/domain/validation/project_rules.js +0 -115
  169. package/dist/domain/validation/size_rules.js +0 -45
  170. package/dist/domain/validation/vision_rules.js +0 -41
  171. package/dist/domain/validation/work_item_rules.js +0 -175
  172. package/dist/domain/versions/realign_notes.js +0 -72
  173. package/dist/domain/versions/rule_versions.js +0 -29
  174. package/dist/domain/versions/scheduled_exceptions.js +0 -41
  175. package/skills/about/SKILL.md +0 -21
  176. package/skills/architect/SKILL.md +0 -49
  177. package/skills/architect/feature.md +0 -40
  178. package/skills/architect/project.md +0 -71
  179. package/skills/architect/tweak.md +0 -22
  180. package/skills/architect/walkthrough.md +0 -35
  181. package/skills/domain-language/SKILL.md +0 -37
  182. package/skills/envision/feature.md +0 -25
  183. package/skills/envision/intake.md +0 -28
  184. package/skills/envision/project.md +0 -24
  185. package/skills/explore/SKILL.md +0 -39
  186. package/skills/formats/reference/architecture.md +0 -84
  187. package/skills/formats/reference/config.md +0 -80
  188. package/skills/formats/reference/durable-docs.md +0 -74
  189. package/skills/formats/reference/specs.md +0 -65
  190. package/skills/formats/reference/work-items.md +0 -100
  191. package/skills/formats/templates/adr.md +0 -7
  192. package/skills/formats/templates/change.md +0 -29
  193. package/skills/formats/templates/glossary.md +0 -8
  194. package/skills/formats/templates/roadmap.md +0 -32
  195. package/skills/formats/templates/spec.md +0 -11
  196. package/skills/formats/templates/standalone-change.md +0 -46
  197. package/skills/grilling/SKILL.md +0 -90
  198. package/skills/grilling/illustrating.md +0 -38
  199. package/skills/improve/SKILL.md +0 -51
  200. package/skills/land/SKILL.md +0 -48
  201. package/skills/next/SKILL.md +0 -31
  202. package/skills/plan/SKILL.md +0 -62
  203. package/skills/plan/tasks.md +0 -35
  204. package/skills/plan/test-plan.md +0 -14
  205. package/skills/realign/SKILL.md +0 -37
  206. package/skills/realign/notes/0.0.10.md +0 -131
  207. package/skills/realign/notes/0.0.11.md +0 -15
  208. package/skills/realign/notes/0.0.12.md +0 -31
  209. package/skills/realign/notes/0.0.13.md +0 -11
  210. package/skills/realign/notes/0.0.2.md +0 -69
  211. package/skills/realign/notes/0.0.3.md +0 -11
  212. package/skills/realign/notes/0.0.4.md +0 -27
  213. package/skills/realign/notes/0.0.5.md +0 -38
  214. package/skills/realign/notes/0.0.6.md +0 -44
  215. package/skills/realign/notes/0.0.7.md +0 -11
  216. package/skills/realign/notes/0.0.8.md +0 -97
  217. package/skills/realign/notes/0.0.9.md +0 -59
  218. package/skills/realign/notes.md +0 -40
  219. package/skills/rethink/SKILL.md +0 -62
  220. package/skills/rethink/levels.md +0 -54
  221. package/skills/roadmap/SKILL.md +0 -54
  222. package/skills/roles/auditor.md +0 -35
  223. package/skills/roles/design-reviewer.md +0 -43
  224. package/skills/roles/design-rubric.md +0 -41
  225. package/skills/roles/plan-rubric.md +0 -11
  226. package/skills/roles/surveyor.md +0 -41
  227. package/skills/roles/verifier.md +0 -42
  228. package/skills/triage/SKILL.md +0 -37
  229. package/skills/verify/SKILL.md +0 -45
  230. package/skills/verify/manual-test.md +0 -23
  231. package/src/adapters/inbound/cli/argument_files.ts +0 -29
  232. package/src/adapters/inbound/cli/commands/diff.ts +0 -23
  233. package/src/adapters/inbound/cli/commands/journal.ts +0 -32
  234. package/src/adapters/inbound/cli/commands/progress.ts +0 -55
  235. package/src/adapters/inbound/cli/commands/realign.ts +0 -28
  236. package/src/adapters/inbound/cli/commands/show.ts +0 -96
  237. package/src/app/use_cases/abandon_work.ts +0 -42
  238. package/src/app/use_cases/approve_artifact.ts +0 -197
  239. package/src/app/use_cases/journal_events.ts +0 -64
  240. package/src/app/use_cases/land_change.ts +0 -134
  241. package/src/app/use_cases/new_project_doc.ts +0 -48
  242. package/src/app/use_cases/new_work_item.ts +0 -75
  243. package/src/app/use_cases/realign_done.ts +0 -45
  244. package/src/app/use_cases/realign_status.ts +0 -106
  245. package/src/app/use_cases/record_progress.ts +0 -118
  246. package/src/app/use_cases/show_elements.ts +0 -18
  247. package/src/app/use_cases/show_notes.ts +0 -11
  248. package/src/app/use_cases/show_scope.ts +0 -51
  249. package/src/app/use_cases/upstream_diff.ts +0 -55
  250. package/src/domain/approvals/agent_approval.ts +0 -68
  251. package/src/domain/approvals/evaluate.ts +0 -36
  252. package/src/domain/approvals/fingerprint.ts +0 -52
  253. package/src/domain/approvals/rebase.ts +0 -39
  254. package/src/domain/approvals/records.ts +0 -50
  255. package/src/domain/approvals/upstream_text.ts +0 -36
  256. package/src/domain/architecture_sections.ts +0 -23
  257. package/src/domain/check/decision_comments.ts +0 -38
  258. package/src/domain/decision_citations.ts +0 -62
  259. package/src/domain/edits/agents_file.ts +0 -58
  260. package/src/domain/edits/managed_tables.ts +0 -60
  261. package/src/domain/edits/progress_block.ts +0 -33
  262. package/src/domain/edits/status_lines.ts +0 -52
  263. package/src/domain/elements.ts +0 -126
  264. package/src/domain/journal.ts +0 -109
  265. package/src/domain/land/bookkeeping.ts +0 -54
  266. package/src/domain/land/merge_plan.ts +0 -137
  267. package/src/domain/land/text_ops.ts +0 -66
  268. package/src/domain/line_diff.ts +0 -56
  269. package/src/domain/project/artifact_ref.ts +0 -42
  270. package/src/domain/project/deltas.ts +0 -49
  271. package/src/domain/project/element_lookup.ts +0 -124
  272. package/src/domain/project/living_model.ts +0 -55
  273. package/src/domain/project/new_items.ts +0 -48
  274. package/src/domain/project/notes.ts +0 -74
  275. package/src/domain/project/plan_parts.ts +0 -111
  276. package/src/domain/project/progress.ts +0 -43
  277. package/src/domain/project/project_view.ts +0 -72
  278. package/src/domain/project/roadmap.ts +0 -43
  279. package/src/domain/project/scope_view.ts +0 -134
  280. package/src/domain/project/work_items.ts +0 -68
  281. package/src/domain/status/next_step.ts +0 -181
  282. package/src/domain/status/pending_rethink.ts +0 -22
  283. package/src/domain/status/verification.ts +0 -29
  284. package/src/domain/status/work_status.ts +0 -125
  285. package/src/domain/validation/decision_rules.ts +0 -76
  286. package/src/domain/validation/delta_rules.ts +0 -81
  287. package/src/domain/validation/element_rules.ts +0 -104
  288. package/src/domain/validation/future_rules.ts +0 -40
  289. package/src/domain/validation/living_rules.ts +0 -99
  290. package/src/domain/validation/plan_rules.ts +0 -124
  291. package/src/domain/validation/project_rules.ts +0 -115
  292. package/src/domain/validation/size_rules.ts +0 -45
  293. package/src/domain/validation/vision_rules.ts +0 -41
  294. package/src/domain/validation/work_item_rules.ts +0 -175
  295. package/src/domain/versions/realign_notes.ts +0 -72
  296. package/src/domain/versions/rule_versions.ts +0 -29
  297. package/src/domain/versions/scheduled_exceptions.ts +0 -41
  298. package/templates/init/gitattributes +0 -2
@@ -1,44 +1,27 @@
1
- import { headingElements, statusOf } from "./elements.ts";
2
- import { findIds } from "./ids.ts";
3
- import { PATHS } from "./project/layout.ts";
4
- import type { ProjectView } from "./project/project_view.ts";
5
- import { readScope } from "./project/plan_parts.ts";
6
- import { readDelta } from "./project/deltas.ts";
7
- import type { WorkItem } from "./project/work_items.ts";
1
+ import { findScenarioIds } from "./ids.ts";
2
+ import type { Feature } from "./project/features.ts";
8
3
 
9
4
  export interface TraceReport {
10
5
  /** Scenarios that must have a test, and the test files that carry each one. */
11
6
  scenarios: Array<{ id: string; tests: string[] }>;
12
7
  missing: string[];
13
- /** Scenario IDs in tests that no living doc or active delta defines. */
8
+ /** Scenario IDs in tests that no feature document defines. */
14
9
  unknown: Array<{ id: string; path: string; line: number }>;
15
10
  }
16
11
 
17
- /**
18
- * Links scenarios to the tests that prove them. For a change: every scenario it delivers.
19
- * For the whole project: every built scenario in the living specs.
20
- */
21
- export function traceScenarios(view: ProjectView, tests: ReadonlyMap<string, string>, change?: WorkItem): TraceReport {
22
- const required = change === undefined ? builtScenarios(view) : (readScope(change.doc)?.delivers ?? []).filter((id) => id.startsWith("SCN-"));
23
- const known = new Set([...view.living.elements.keys()]);
24
- for (const item of view.items.filter((i) => !i.archived)) for (const id of readDelta(item.doc).ids) known.add(id);
25
-
12
+ /** Links the required features' scenarios to the tests that carry their IDs. */
13
+ export function traceScenarios(features: readonly Feature[], required: readonly Feature[], tests: ReadonlyMap<string, string>): TraceReport {
14
+ const known = new Set(features.flatMap((f) => f.scenarios.map((s) => s.id)));
26
15
  const carriers = new Map<string, string[]>();
27
16
  const unknown: TraceReport["unknown"] = [];
28
17
  for (const [path, text] of tests) {
29
18
  text.split("\n").forEach((line, index) => {
30
- for (const id of findIds(line).filter((id) => id.startsWith("SCN-"))) {
19
+ for (const id of findScenarioIds(line)) {
31
20
  carriers.set(id, [...new Set([...(carriers.get(id) ?? []), path])]);
32
21
  if (!known.has(id)) unknown.push({ id, path, line: index + 1 });
33
22
  }
34
23
  });
35
24
  }
36
- const scenarios = required.map((id) => ({ id, tests: carriers.get(id) ?? [] }));
25
+ const scenarios = required.flatMap((f) => f.scenarios).map((s) => ({ id: s.id, tests: carriers.get(s.id) ?? [] }));
37
26
  return { scenarios, missing: scenarios.filter((s) => s.tests.length === 0).map((s) => s.id), unknown };
38
27
  }
39
-
40
- function builtScenarios(view: ProjectView): string[] {
41
- return [...view.living.docs]
42
- .filter(([path]) => path.startsWith(`${PATHS.specsDir}/`))
43
- .flatMap(([, doc]) => headingElements(doc).filter((e) => e.kind === "SCN" && statusOf(doc, e) === "built").map((e) => e.id));
44
- }
@@ -0,0 +1,70 @@
1
+ import { featureOfScenario } from "../ids.ts";
2
+ import { findSection, type MarkdownDoc } from "../markdown.ts";
3
+ import { FEATURE_SECTIONS, FEATURE_STATUSES, statusLine, strayScenarioIds, type Feature } from "../project/features.ts";
4
+ import { PATHS } from "../project/layout.ts";
5
+ import type { ProjectDocs } from "../project/project_docs.ts";
6
+ import { error, warning, type Problem } from "./problem.ts";
7
+
8
+ /** Each document's required sections, its size, its scenarios, and leftover template guides. */
9
+
10
+ const VISION_SECTIONS = ["Problem", "Users", "Release"];
11
+ const ARCHITECTURE_SECTIONS = ["Overview", "Ports", "Adapters", "Runtime"];
12
+
13
+ /** Words past which a document stops being something a person reads in one sitting. */
14
+ export const WORD_LIMITS = { vision: 1500, architecture: 3000, feature: 2500 } as const;
15
+
16
+ const GUIDE = /(?<!`)<(?!!--|\/|https?:|br\b)[A-Za-z][^<>\n]*>(?!`)/;
17
+ const PLACEHOLDER = /\{\{[a-z_]+\}\}/;
18
+
19
+ export function documentProblems(docs: ProjectDocs): Problem[] {
20
+ const problems: Problem[] = [];
21
+ if (docs.vision !== undefined) problems.push(...shapeOf(PATHS.vision, docs.vision, VISION_SECTIONS, WORD_LIMITS.vision));
22
+ if (docs.architecture !== undefined) problems.push(...shapeOf(PATHS.architecture, docs.architecture, ARCHITECTURE_SECTIONS, WORD_LIMITS.architecture));
23
+ for (const feature of docs.features) {
24
+ problems.push(...shapeOf(feature.path, feature.doc, [...FEATURE_SECTIONS], WORD_LIMITS.feature), ...featureProblems(feature));
25
+ }
26
+ problems.push(...duplicateScenarios(docs.features));
27
+ return problems;
28
+ }
29
+
30
+ function shapeOf(path: string, doc: MarkdownDoc, sections: string[], limit: number): Problem[] {
31
+ const problems: Problem[] = sections.filter((title) => findSection(doc, title) === null).map((title) => error(path, undefined, "missing-section", `needs a ## ${title} section`));
32
+ const words = doc.lines.filter((_, i) => !doc.inFence[i]).join(" ").split(/\s+/).filter((w) => w !== "").length;
33
+ if (words > limit) problems.push(warning(path, undefined, "too-long", `${words} words, over the ${limit} a person reads in one sitting; cut what the agent decided and the user needn't read, or split it`));
34
+ doc.lines.forEach((line, i) => {
35
+ if (doc.inFence[i]) return;
36
+ const withoutCode = line.replace(/`[^`]*`/g, "");
37
+ if (GUIDE.test(withoutCode) || PLACEHOLDER.test(withoutCode)) problems.push(error(path, i, "template-leftover", "replace the template guide or placeholder on this line"));
38
+ });
39
+ return problems;
40
+ }
41
+
42
+ function featureProblems(feature: Feature): Problem[] {
43
+ const problems: Problem[] = [];
44
+ const status = statusLine(feature.doc);
45
+ if (status === undefined) problems.push(error(feature.path, undefined, "status", `needs a "- Status: shaped" line under the title`));
46
+ else if (feature.status === undefined) problems.push(error(feature.path, status.line, "status", `the status is one of ${FEATURE_STATUSES.join(" or ")}`));
47
+ if (feature.scenarios.length === 0 && findSection(feature.doc, "Behaviour") !== null) {
48
+ problems.push(error(feature.path, undefined, "no-scenarios", `## Behaviour needs at least one ### SCN-${feature.slug}.<name>: <title> scenario`));
49
+ }
50
+ for (const scenario of feature.scenarios) {
51
+ if (featureOfScenario(scenario.id) !== feature.slug) problems.push(error(feature.path, scenario.line, "scenario-id", `${scenario.id} belongs to this feature, so it starts SCN-${feature.slug}.`));
52
+ }
53
+ for (const stray of strayScenarioIds(feature)) {
54
+ problems.push(error(feature.path, stray.line, "scenario-heading", `a scenario heading reads exactly "${stray.id}: <title>"`));
55
+ }
56
+ return problems;
57
+ }
58
+
59
+ function duplicateScenarios(features: Feature[]): Problem[] {
60
+ const seen = new Map<string, string>();
61
+ const problems: Problem[] = [];
62
+ for (const feature of features) {
63
+ for (const scenario of feature.scenarios) {
64
+ const first = seen.get(scenario.id);
65
+ if (first !== undefined) problems.push(error(feature.path, scenario.line, "duplicate-scenario", `${scenario.id} is already defined in ${first}`));
66
+ else seen.set(scenario.id, feature.path);
67
+ }
68
+ }
69
+ return problems;
70
+ }
@@ -0,0 +1,45 @@
1
+ import { isSlug } from "../ids.ts";
2
+ import { PATHS } from "../project/layout.ts";
3
+ import type { ProjectDocs } from "../project/project_docs.ts";
4
+ import { error, warning, type Problem } from "./problem.ts";
5
+
6
+ /** The vision's Release table: feature slugs, what each needs, and no cycles. */
7
+ export function releaseProblems(docs: ProjectDocs): Problem[] {
8
+ if (docs.vision === undefined) return [];
9
+ if (docs.release.length === 0) return [warning(PATHS.vision, undefined, "release", "## Release has no Feature | Summary | Needs table with features in it yet")];
10
+ const problems: Problem[] = [];
11
+ const slugs = new Set<string>();
12
+ for (const row of docs.release) {
13
+ if (!isSlug(row.slug)) problems.push(error(PATHS.vision, row.line, "release", `"${row.slug}" must be a feature slug: lowercase words joined by single hyphens`));
14
+ if (slugs.has(row.slug)) problems.push(error(PATHS.vision, row.line, "release", `${row.slug} is listed twice`));
15
+ slugs.add(row.slug);
16
+ }
17
+ for (const row of docs.release) {
18
+ for (const need of row.needs.filter((n) => !slugs.has(n))) {
19
+ problems.push(error(PATHS.vision, row.line, "release-needs", `${row.slug} needs ${need}, which isn't a feature of this release`));
20
+ }
21
+ }
22
+ const cycle = findCycle(docs.release.map((r) => [r.slug, r.needs.filter((n) => slugs.has(n))] as const));
23
+ if (cycle !== undefined) problems.push(error(PATHS.vision, docs.release.find((r) => r.slug === cycle[0])?.line, "release-needs", `features need each other in a circle: ${cycle.join(" → ")}`));
24
+ return problems;
25
+ }
26
+
27
+ function findCycle(graph: ReadonlyArray<readonly [string, readonly string[]]>): string[] | undefined {
28
+ const edges = new Map(graph);
29
+ const done = new Set<string>();
30
+ const visit = (node: string, path: string[]): string[] | undefined => {
31
+ if (path.includes(node)) return [...path.slice(path.indexOf(node)), node];
32
+ if (done.has(node)) return undefined;
33
+ for (const next of edges.get(node) ?? []) {
34
+ const found = visit(next, [...path, node]);
35
+ if (found !== undefined) return found;
36
+ }
37
+ done.add(node);
38
+ return undefined;
39
+ };
40
+ for (const [node] of graph) {
41
+ const found = visit(node, []);
42
+ if (found !== undefined) return found;
43
+ }
44
+ return undefined;
45
+ }
@@ -1,36 +1,29 @@
1
- import type { ProjectView } from "../project/project_view.ts";
2
- import { futureProblems } from "./future_rules.ts";
3
- import { livingProblems } from "./living_rules.ts";
1
+ import { PATHS } from "../project/layout.ts";
2
+ import type { ProjectDocs } from "../project/project_docs.ts";
3
+ import { documentProblems } from "./document_rules.ts";
4
4
  import { error, sortProblems, type Problem } from "./problem.ts";
5
- import { configProblems, readmeProblems, roadmapProblems, templateLeftovers } from "./project_rules.ts";
6
- import { visionProblems } from "./vision_rules.ts";
7
- import { workItemProblems } from "./work_item_rules.ts";
5
+ import { releaseProblems } from "./release_rules.ts";
8
6
 
9
7
  /** Every rule in the cruze-formats skill, applied to the whole project. */
10
- export function validateProject(view: ProjectView): Problem[] {
11
- const approvalProblems = [...view.approvalProblems].map(([path, message]) => error(path, undefined, "approvals-file", message));
12
- const journalProblems = [...view.journalProblems].flatMap(([path, lines]) =>
13
- lines.map((line) => error(path, line - 1, "journal-line", "this journal line is not a valid entry")),
14
- );
15
- return sortProblems([
16
- ...configProblems(view),
17
- ...livingProblems(view),
18
- ...visionProblems(view),
19
- ...futureProblems(view),
20
- ...roadmapProblems(view),
21
- ...workItemProblems(view),
22
- ...templateLeftovers(view),
23
- ...readmeProblems(view),
24
- ...approvalProblems,
25
- ...journalProblems,
26
- ]);
27
- }
28
-
29
- /** Problems that concern one document. */
30
- export function problemsFor(problems: Problem[], path: string): Problem[] {
31
- return problems.filter((problem) => problem.path === path);
8
+ export function validateProject(docs: ProjectDocs): Problem[] {
9
+ const config = docs.config === undefined
10
+ ? [error(PATHS.config, undefined, "config", "the project has no config; run cruze init")]
11
+ : docs.config.problems.map((message) => error(PATHS.config, undefined, "config", message));
12
+ const approvals = approvalsFileProblems(docs.snapshot.get(PATHS.approvals));
13
+ return sortProblems([...config, ...approvals, ...documentProblems(docs), ...releaseProblems(docs)]);
32
14
  }
33
15
 
34
16
  export function hasErrors(problems: Problem[]): boolean {
35
17
  return problems.some((problem) => problem.severity === "error");
36
18
  }
19
+
20
+ function approvalsFileProblems(text: string | undefined): Problem[] {
21
+ if (text === undefined) return [];
22
+ try {
23
+ const parsed: unknown = JSON.parse(text);
24
+ if (!Array.isArray((parsed as { approvals?: unknown }).approvals)) return [error(PATHS.approvals, undefined, "approvals-file", "needs an approvals list; only cruze approve writes this file")];
25
+ return [];
26
+ } catch {
27
+ return [error(PATHS.approvals, undefined, "approvals-file", "not valid JSON; only cruze approve writes this file")];
28
+ }
29
+ }
@@ -36,15 +36,9 @@ export function versionSkew(cli: string, recorded: string | undefined): VersionS
36
36
  * after `version:`, so the file's comments and layout survive.
37
37
  */
38
38
  export function withRecordedVersion(configText: string, version: string): string {
39
- return withVersionLine(configText, "cruze", version);
40
- }
41
-
42
- /** The config text with a top-level version key, such as `cruze:` or `standards:`, set in place. */
43
- export function withVersionLine(configText: string, key: "cruze" | "standards", version: string): string {
44
- const line = `${key}: ${JSON.stringify(version)}`;
45
- const existing = new RegExp(`^${key}:.*$`, "m");
46
- if (existing.test(configText)) return configText.replace(existing, line);
47
- const anchor = key === "standards" ? /^cruze:.*$/m.exec(configText) ?? /^version:.*$/m.exec(configText) : /^version:.*$/m.exec(configText);
39
+ const line = `cruze: ${JSON.stringify(version)}`;
40
+ if (/^cruze:.*$/m.test(configText)) return configText.replace(/^cruze:.*$/m, line);
41
+ const anchor = /^version:.*$/m.exec(configText);
48
42
  if (anchor === null) return `${line}\n${configText}`;
49
43
  const end = anchor.index + anchor[0].length;
50
44
  return `${configText.slice(0, end)}\n${line}${configText.slice(end)}`;
package/src/main.ts CHANGED
@@ -10,7 +10,7 @@ import { ProcessAgentRunner } from "./adapters/outbound/process_agent_runner.ts"
10
10
  import { SystemClock } from "./adapters/outbound/system_clock.ts";
11
11
  import { DefaultAnswerPrompter, TerminalPrompter } from "./adapters/outbound/terminal_prompter.ts";
12
12
 
13
- // A reader that stops early, such as `cruze journal list | head`, closes the pipe; that is not an error worth a stack trace.
13
+ // A reader that stops early, such as `cruze status --text | head`, closes the pipe; that is not an error worth a stack trace.
14
14
  process.stdout.on("error", (error: NodeJS.ErrnoException) => {
15
15
  if (error.code === "EPIPE") process.exit(0);
16
16
  throw error;
@@ -1,28 +1,20 @@
1
1
  # {{project_name}}
2
2
 
3
- Instructions for coding agents working in this repository. Cruze keeps the managed sections current as changes land; edit everything else freely.
3
+ Instructions for coding agents working in this repository.
4
4
 
5
5
  ## Workflow
6
6
 
7
- This project uses [Cruze](https://github.com/PrintPractical/cruze) for spec-driven development. Durable documents live in `docs/`: vision, glossary, architecture, ADRs, specs and roadmap. Work in progress lives in `.cruze/`. Before changing code, read the design the change builds with `cruze show <change>`, once it exists. Each Cruze step runs in a new session, so its context holds what the step reads and nothing else. The Cruze skills run the `cruze` command; when it is missing, install it as https://github.com/PrintPractical/cruze#quick-start describes.
7
+ This project uses [Cruze](https://github.com/PrintPractical/cruze). `docs/vision.md` says what the product is for and which features the release holds; `docs/architecture.md` maps its ports, adapters and runtime; each `docs/features/<slug>.md` holds one feature's behaviour, public API and stack sketch, approved by the user before it is built. Run `cruze next` to see what comes next. The Cruze skills run the `cruze` command; when it is missing, install it as https://github.com/PrintPractical/cruze#quick-start describes.
8
8
 
9
9
  ## Commands
10
10
 
11
- <!-- cruze:managed -->
12
- No build or test commands yet. `cruze land` fills this in when the foundation change lands.
13
- <!-- /cruze:managed -->
11
+ No build or test commands yet. The foundation feature fills this in.
14
12
 
15
13
  ## Layout
16
14
 
17
- <!-- cruze:managed -->
18
- Not defined yet. `cruze land` fills this in from the module map in `docs/architecture.md`.
19
- <!-- /cruze:managed -->
15
+ Not defined yet. The foundation feature fills this in from `docs/architecture.md`.
20
16
 
21
17
  ## Conventions
22
18
 
23
19
  - Commit messages follow [Conventional Commits](https://www.conventionalcommits.org/).
24
20
  - Record notable changes under `[Unreleased]` in `CHANGELOG.md`.
25
-
26
- ## Known pitfalls
27
-
28
- None recorded yet. `land` adds a line here when the same review finding or defect about this project's code happens twice.
@@ -13,11 +13,11 @@ jobs:
13
13
  - uses: actions/setup-node@v5
14
14
  with:
15
15
  node-version: 22
16
- # The foundation change adds the language's format, lint, build and
17
- # test steps here, from `commands:` in .cruze/config.yaml.
16
+ # The foundation feature adds the language's format, lint, build and
17
+ # test steps here, with a coverage gate.
18
18
  - name: Documents match the Cruze formats
19
19
  run: npx --yes --package={{cruze_package}} cruze validate
20
20
  - name: Layer rules and file budgets
21
21
  run: npx --yes --package={{cruze_package}} cruze check --ci
22
22
  - name: Every built scenario has a test
23
- run: npx --yes --package={{cruze_package}} cruze trace --all
23
+ run: npx --yes --package={{cruze_package}} cruze trace
@@ -1,33 +1,24 @@
1
1
  # Cruze project configuration, created by cruze {{cruze_version}}.
2
- # The format is described in .agents/skills/cruze-formats/reference/config.md.
3
- version: 1
2
+ # The format is described in .agents/skills/cruze-formats/config.md.
3
+ version: 2
4
4
  # The Cruze whose skills this project installed. `cruze install` keeps it current.
5
5
  cruze: "{{cruze_version}}"
6
- # The Cruze whose expectations the code meets. `realign` moves it forward.
7
- standards: "{{cruze_version}}"
8
6
  project: {{project_name_quoted}}
9
7
 
10
- # Where work items are tracked. V1 supports markdown in the repository only.
11
- tracker: markdown
12
-
13
- # Source files `cruze check` inspects, and files `cruze trace` searches for
14
- # scenario IDs. Project-scope architecture design sets these.
8
+ # Source files `cruze check` inspects, and test files `cruze trace` searches
9
+ # for scenario IDs. The foundation feature sets these.
15
10
  source: []
16
11
  tests: []
17
12
 
18
- # Budgets enforced by `cruze check`: a backstop against a file that has
19
- # outgrown one responsibility, not a size to design to. List a file under
20
- # exceptions, with a reason, to allow it past a budget.
13
+ # Budgets `cruze check` warns on: a backstop against a file that has outgrown
14
+ # one responsibility, not a size to design to. List a file under exceptions,
15
+ # with a reason, to allow it past a budget.
21
16
  check:
22
17
  max_lines: 600
23
18
  max_types: 12
24
19
  exceptions: []
25
20
 
26
- # Layer rules, written by project-scope architecture design from the module map.
21
+ # Layer rules from docs/architecture.md: which layer may import which.
27
22
  layers: []
28
23
 
29
- # The project's commands, by name, such as `test: cargo test`. The
30
- # foundation change fills them in; `cruze land` copies them into AGENTS.md.
31
- commands: {}
32
-
33
24
  {{review_setting}}
@@ -1,7 +1,7 @@
1
1
  # How `cruze review` starts an agent in a fresh context to run a role, with the
2
2
  # prompt on standard input. This lets Claude Code read the project and run git
3
- # and cruze. The verifier runs the system, so add the project's build and run
4
- # commands, such as "Bash(cargo:*)". Without review.command, your agent runs each
3
+ # and cruze. To let the reviewer run the tests, add the project's test
4
+ # command, such as "Bash(cargo test:*)". Without review.command, your agent runs each
5
5
  # role in a helper of its own.
6
6
  review:
7
7
  command: ["claude", "-p", "--allowedTools", "Read Grep Glob Bash(git diff:*) Bash(git log:*) Bash(git show:*) Bash(cruze:*)"]
@@ -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.js";
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 ) {
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 , text ) {
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 ) {
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, } from "../../../../app/use_cases/upstream_diff.js";
2
- import { requireArgs, } from "../cli_context.js";
3
-
4
- export async function runDiff(context , args ) {
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 ) {
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.js";
2
- import { writeArgumentFile } from "../argument_files.js";
3
- import { UsageError, requireArgs, } from "../cli_context.js";
4
-
5
- export async function runJournal(context , args , options ) {
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 , args , options ) {
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.js";
2
- import { abandonWork } from "../../../../app/use_cases/abandon_work.js";
3
- import { UsageError, requireArgs, requireOption, } from "../cli_context.js";
4
-
5
- export async function runTask(context , args , options ) {
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 , args , options ) {
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 , args ) {
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 , args , options ) {
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.js";
2
- import { realignStatus } from "../../../../app/use_cases/realign_status.js";
3
- import { UsageError, requireArgs, } from "../cli_context.js";
4
-
5
- export async function runRealign(context , args , options ) {
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.js";
2
- import { showNotes } from "../../../../app/use_cases/show_notes.js";
3
-
4
- import { showScope, } from "../../../../app/use_cases/show_scope.js";
5
- import { isId } from "../../../../domain/ids.js";
6
-
7
-
8
- import { UsageError, requireArgs, } from "../cli_context.js";
9
-
10
- export async function runShow(context , args , options ) {
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 ) {
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 ) {
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 ) {
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 ) {
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 ) {
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
- }