@printpractical/cruze 0.0.3 → 0.0.5

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 (71) hide show
  1. package/README.md +10 -10
  2. package/dist/adapters/inbound/cli/argument_files.js +29 -0
  3. package/dist/adapters/inbound/cli/cli_context.js +3 -0
  4. package/dist/adapters/inbound/cli/command_options.js +20 -0
  5. package/dist/adapters/inbound/cli/commands/documents.js +5 -1
  6. package/dist/adapters/inbound/cli/commands/journal.js +3 -2
  7. package/dist/adapters/inbound/cli/commands/progress.js +7 -3
  8. package/dist/adapters/inbound/cli/commands/review.js +3 -2
  9. package/dist/adapters/inbound/cli/commands/status.js +7 -1
  10. package/dist/adapters/inbound/cli/commands.js +36 -20
  11. package/dist/adapters/inbound/cli/parse_command.js +12 -2
  12. package/dist/adapters/inbound/cli/run_cli.js +9 -5
  13. package/dist/adapters/outbound/node_project_files.js +2 -1
  14. package/dist/app/use_cases/approve_artifact.js +26 -1
  15. package/dist/app/use_cases/journal_events.js +12 -4
  16. package/dist/app/use_cases/project_status.js +8 -2
  17. package/dist/app/use_cases/record_progress.js +17 -0
  18. package/dist/domain/approvals/agent_approval.js +68 -0
  19. package/dist/domain/config.js +30 -2
  20. package/dist/domain/journal.js +5 -0
  21. package/dist/domain/status/verification.js +3 -2
  22. package/dist/domain/status/work_status.js +1 -3
  23. package/dist/domain/validation/plan_rules.js +2 -0
  24. package/dist/domain/validation/size_rules.js +36 -0
  25. package/dist/domain/validation/work_item_rules.js +2 -0
  26. package/dist/domain/versions/rule_versions.js +3 -0
  27. package/package.json +1 -1
  28. package/skills/about/SKILL.md +1 -1
  29. package/skills/architect/feature.md +7 -2
  30. package/skills/envision/SKILL.md +1 -1
  31. package/skills/envision/feature.md +1 -0
  32. package/skills/formats/SKILL.md +1 -1
  33. package/skills/formats/reference/config.md +10 -0
  34. package/skills/formats/reference/durable-docs.md +1 -1
  35. package/skills/formats/reference/work-items.md +1 -1
  36. package/skills/land/SKILL.md +1 -0
  37. package/skills/plan/SKILL.md +3 -3
  38. package/skills/realign/notes/0.0.4.md +27 -0
  39. package/skills/realign/notes/0.0.5.md +38 -0
  40. package/skills/realign/notes.md +2 -0
  41. package/skills/rethink/SKILL.md +5 -1
  42. package/skills/rethink/levels.md +4 -4
  43. package/skills/roles/SKILL.md +17 -7
  44. package/skills/roles/code-reviewer.md +1 -1
  45. package/skills/roles/design-reviewer.md +2 -2
  46. package/skills/verify/SKILL.md +1 -1
  47. package/src/adapters/inbound/cli/argument_files.ts +29 -0
  48. package/src/adapters/inbound/cli/cli_context.ts +3 -0
  49. package/src/adapters/inbound/cli/command_options.ts +20 -0
  50. package/src/adapters/inbound/cli/commands/documents.ts +5 -1
  51. package/src/adapters/inbound/cli/commands/journal.ts +3 -2
  52. package/src/adapters/inbound/cli/commands/progress.ts +7 -3
  53. package/src/adapters/inbound/cli/commands/review.ts +3 -2
  54. package/src/adapters/inbound/cli/commands/status.ts +7 -1
  55. package/src/adapters/inbound/cli/commands.ts +36 -20
  56. package/src/adapters/inbound/cli/parse_command.ts +12 -2
  57. package/src/adapters/inbound/cli/run_cli.ts +9 -5
  58. package/src/adapters/outbound/node_project_files.ts +2 -1
  59. package/src/app/use_cases/approve_artifact.ts +26 -1
  60. package/src/app/use_cases/journal_events.ts +12 -4
  61. package/src/app/use_cases/project_status.ts +8 -2
  62. package/src/app/use_cases/record_progress.ts +17 -0
  63. package/src/domain/approvals/agent_approval.ts +68 -0
  64. package/src/domain/config.ts +30 -2
  65. package/src/domain/journal.ts +5 -0
  66. package/src/domain/status/verification.ts +3 -2
  67. package/src/domain/status/work_status.ts +1 -3
  68. package/src/domain/validation/plan_rules.ts +2 -0
  69. package/src/domain/validation/size_rules.ts +36 -0
  70. package/src/domain/validation/work_item_rules.ts +2 -0
  71. package/src/domain/versions/rule_versions.ts +3 -0
@@ -58,6 +58,23 @@ export async function addFutureFeature(deps , slug , summary
58
58
  return { feature: slug };
59
59
  }
60
60
 
61
+ /**
62
+ * Replaces a feature's summary on the feature map, in the future list or the implemented list,
63
+ * when a decision changed what the feature delivers. Its goals or release stay as they are.
64
+ */
65
+ export async function updateFeatureSummary(deps , slug , summary ) {
66
+ if (summary.trim() === "") throw new CruzeError("missing-summary", "a feature needs a summary");
67
+ const vision = await requireVision(deps);
68
+ const future = readRows(vision, "Future").find((row) => row[0] === slug);
69
+ const implemented = readRows(vision, "Implemented").find((row) => row[0] === slug);
70
+ if (future === undefined && implemented === undefined) throw new CruzeError("not-found", `${slug} is not on the feature map`);
71
+ const list = future !== undefined ? "Future" : "Implemented";
72
+ const cells = future !== undefined ? [slug, summary.trim(), future[2] ?? ""] : [slug, implemented?.[1] ?? "", summary.trim()];
73
+ await deps.files.writeText(PATHS.vision, upsertRow(vision, list, cells));
74
+ await appendJournal(deps, ".cruze", "feature-map", { action: "update", feature: slug, summary: summary.trim() });
75
+ return { feature: slug, list };
76
+ }
77
+
61
78
  export async function dropFutureFeature(deps , slug , reason ) {
62
79
  const vision = await requireVision(deps);
63
80
  const { text, removed } = removeRow(vision, "Future", slug);
@@ -0,0 +1,68 @@
1
+ import { CruzeError } from "../cruze_error.js";
2
+
3
+
4
+
5
+
6
+ /**
7
+ * Why an approval was given without the user: a plan whose review closed, or work that a
8
+ * rethink left unchanged while something it cites moved. The user can still object, and
9
+ * editing the document makes the approval stale as usual.
10
+ */
11
+
12
+
13
+ const byTime = (a , b ) => a.at.localeCompare(b.at);
14
+
15
+ /**
16
+ * A feature's change may be approved by the agent once its plan review has closed: a review
17
+ * recorded since the change was last approved, no blocker left open in its last round, and a
18
+ * disposition for every finding of its first round. The feature's design, and a standalone
19
+ * change's, stay the user's to approve.
20
+ */
21
+ export function requirePlanReviewClosed(journal , item ) {
22
+ if (item.kind !== "change") {
23
+ throw new CruzeError("needs-user", `${item.ref} carries design, so the user approves it; only a feature's change can be approved after its plan review`);
24
+ }
25
+ const entries = journal.filter((e) => e["item"] === item.ref || e["artifact"] === item.path).sort(byTime);
26
+ const lastApproval = entries.filter((e) => e.event === "approve").at(-1);
27
+ const reviews = entries.filter((e) => e.event === "review" && e["review"] === "plan" && (lastApproval === undefined || e.at > lastApproval.at));
28
+ const last = reviews.at(-1);
29
+ if (last === undefined) {
30
+ const since = lastApproval === undefined ? "" : " since the change was last approved";
31
+ throw new CruzeError("plan-review-open", `no plan review is recorded for ${item.ref}${since}; run the plan review first`);
32
+ }
33
+ const blockers = Number(last["blockers"] ?? 0);
34
+ if (blockers > 0 && Number(last["round"]) === 1) {
35
+ throw new CruzeError("plan-review-open", `the plan review's round 1 found ${blockers} blocker(s); check their fixes in round 2`);
36
+ }
37
+ if (blockers > 0) {
38
+ throw new CruzeError("plan-review-open", `round 2 left ${blockers} blocker(s) open; the user decides them and approves the plan`);
39
+ }
40
+ const first = reviews.filter((e) => Number(e["round"]) === 1).at(-1) ?? last;
41
+ const findings = Number(first["blockers"] ?? 0) + Number(first["concerns"] ?? 0);
42
+ const decided = entries.filter((e) => e.event === "disposition" && e["review"] === "plan" && e.at > first.at).length;
43
+ if (decided < findings) {
44
+ throw new CruzeError("plan-review-open", `the plan review raised ${findings} finding(s) but ${decided} have a disposition; record one for each`);
45
+ }
46
+ }
47
+
48
+ /**
49
+ * A rethink may re-approve, for the user, a feature or change it didn't edit: one whose own
50
+ * design is unchanged and that is stale only because something it cites changed. A change
51
+ * with a task reopened since its approval goes back to the user.
52
+ */
53
+ export function requireUnchangedSinceApproval(journal , item , path , status ) {
54
+ if (item === undefined) {
55
+ throw new CruzeError("needs-user", `${path} is a project document, so the user re-approves it; a rethink re-stamps only features and changes`);
56
+ }
57
+ if (status.state === "edited") {
58
+ throw new CruzeError("needs-user", `${item.ref} itself changed since its approval, so the user re-approves it`);
59
+ }
60
+ if (status.state !== "upstream-changed") {
61
+ throw new CruzeError("not-stale", `${item.ref} is ${status.state}; only work whose upstream changed can be re-stamped`);
62
+ }
63
+ const approvedAt = journal.filter((e) => e.event === "approve" && e["artifact"] === path).sort(byTime).at(-1)?.at ?? "";
64
+ const reopened = journal.filter((e) => e.event === "reopen" && e["change"] === item.ref && e.at > approvedAt).map((e) => String(e["task"]));
65
+ if (reopened.length > 0) {
66
+ throw new CruzeError("needs-user", `${item.ref} had ${reopened.join(", ")} reopened since its approval, so the user re-approves it`);
67
+ }
68
+ }
@@ -23,9 +23,25 @@ import { parse as parseYaml } from "yaml";
23
23
 
24
24
 
25
25
 
26
-
26
+
27
+
28
+
29
+
30
+
31
+ /** Which review findings the user decides: only the exceptions, or every one. */
32
+
33
+
34
+
35
+
36
+
37
+
38
+
39
+
40
+
27
41
 
28
42
 
43
+ export const DEFAULT_CHANGE_LIMITS = { minBuilds: 3, maxScenarios: 30, maxTasks: 25 };
44
+
29
45
 
30
46
 
31
47
 
@@ -97,6 +113,14 @@ export function parseConfig(text ) {
97
113
  const review = asRecord(root["review"]) ?? {};
98
114
  const reviewCommand = review["command"] === undefined ? undefined : stringList(review["command"], "review.command", problems);
99
115
  if (reviewCommand?.length === 0) problems.push("review.command needs at least the program to run");
116
+ const decide = review["decide"] ?? "exceptions";
117
+ if (decide !== "exceptions" && decide !== "all") problems.push("review.decide must be exceptions or all");
118
+ const limits = asRecord(root["changes"]) ?? {};
119
+ const changes = {
120
+ minBuilds: positiveInt(limits["min_builds"], "changes.min_builds", problems) ?? DEFAULT_CHANGE_LIMITS.minBuilds,
121
+ maxScenarios: positiveInt(limits["max_scenarios"], "changes.max_scenarios", problems) ?? DEFAULT_CHANGE_LIMITS.maxScenarios,
122
+ maxTasks: positiveInt(limits["max_tasks"], "changes.max_tasks", problems) ?? DEFAULT_CHANGE_LIMITS.maxTasks,
123
+ };
100
124
 
101
125
  const config = {
102
126
  version: 1,
@@ -109,7 +133,11 @@ export function parseConfig(text ) {
109
133
  check: { maxLines: maxLines ?? 250, maxTypes: maxTypes ?? 5, exceptions },
110
134
  layers,
111
135
  commands,
112
- review: reviewCommand === undefined || reviewCommand.length === 0 ? {} : { command: reviewCommand },
136
+ review: {
137
+ ...(reviewCommand === undefined || reviewCommand.length === 0 ? {} : { command: reviewCommand }),
138
+ decide: decide === "all" ? "all" : "exceptions",
139
+ },
140
+ changes,
113
141
  };
114
142
  return { config, problems };
115
143
  }
@@ -21,6 +21,7 @@ export const RECORDED_EVENTS = {
21
21
  const OPTIONAL_FIELDS = {
22
22
  rethink: ["missed_by"],
23
23
  review: ["nits"],
24
+ disposition: ["decided"],
24
25
  };
25
26
 
26
27
  /** The workflow steps and checks that find or miss a problem, so a retro can count them. */
@@ -30,6 +31,7 @@ export const FIELD_VALUES = {
30
31
  level: ["task", "change", "feature", "architecture", "vision"],
31
32
  kind: ["defect", "discovery"],
32
33
  disposition: ["fixed", "waived", "deferred", "rejected"],
34
+ decided: ["agent", "user"],
33
35
  review: ["design", "plan", "code", "realign"],
34
36
  result: ["accepted", "sent-back"],
35
37
  found_by: STEPS,
@@ -77,6 +79,9 @@ export function recordedEventProblems(event , fields
77
79
  if (allowed !== undefined && !allowed.includes(value)) problems.push(`${field} must be one of ${allowed.join(", ")}`);
78
80
  if (COUNT_FIELDS.includes(field) && !/^\d+$/.test(value)) problems.push(`${field} must be a whole number`);
79
81
  }
82
+ if (event === "disposition" && fields["decided"] === "agent" && fields["disposition"] !== "fixed") {
83
+ problems.push("the agent decides only fixes; the user waives, defers or rejects a finding (decided=user)");
84
+ }
80
85
  return problems;
81
86
  }
82
87
 
@@ -4,13 +4,14 @@
4
4
  /**
5
5
  * Whether the user accepted a change at verify. The latest verification recorded for the change
6
6
  * counts. A later approval keeps it only when the change's own design is unchanged, as when it is
7
- * re-approved because something upstream moved; a changed plan means verifying again.
7
+ * re-approved because something upstream moved; a changed plan means verifying again. Land's
8
+ * re-stamps don't count, since they follow the merge; an approval the agent gave does.
8
9
  */
9
10
  export function isVerified(journal , change ) {
10
11
  const byTime = (a , b ) => a.at.localeCompare(b.at);
11
12
  const verification = journal.filter((e) => e.event === "verification" && e["item"] === change.ref).sort(byTime).at(-1);
12
13
  if (verification?.["result"] !== "accepted") return false;
13
- const approvals = journal.filter((e) => e.event === "approve" && e["artifact"] === change.path && e["basis"] === undefined).sort(byTime);
14
+ const approvals = journal.filter((e) => e.event === "approve" && e["artifact"] === change.path && !String(e["basis"] ?? "").startsWith("land ")).sort(byTime);
14
15
  const verifiedHash = approvals.filter((e) => e.at < verification.at).at(-1)?.["hash"];
15
16
  return approvals.filter((e) => e.at > verification.at).every((e) => e["hash"] === verifiedHash);
16
17
  }
@@ -1,5 +1,4 @@
1
1
  import { evaluateApproval, } from "../approvals/evaluate.js";
2
- import { findIds } from "../ids.js";
3
2
  import { PATHS } from "../project/layout.js";
4
3
  import { readDelta } from "../project/deltas.js";
5
4
  import { readScope } from "../project/plan_parts.js";
@@ -89,13 +88,12 @@ export function buildGateReasons(view , branch )
89
88
  });
90
89
  }
91
90
 
92
- /** The elements a change touches: its scope plus every ID its documents cite. */
91
+ /** The elements a change touches: its scope and its delta, not the elements it only cites, such as a test seam. */
93
92
  export function touchedIds(view , change ) {
94
93
  const feature = featureOf(view.items, change);
95
94
  const scope = readScope(change.doc);
96
95
  const ids = new Set([...(scope?.delivers ?? []), ...(scope?.builds ?? []), ...(scope?.removes ?? [])]);
97
96
  const deltaDoc = feature?.doc ?? change.doc;
98
97
  for (const id of readDelta(deltaDoc).ids) ids.add(id);
99
- for (const id of findIds(change.doc.lines.join("\n"))) ids.add(id);
100
98
  return new Set([...ids].filter((id) => !id.startsWith("RULE-")));
101
99
  }
@@ -7,6 +7,7 @@ import { findIds, kindOf } from "../ids.js";
7
7
  import { isPlanned, readScope, readTasks, readTestPlan, } from "../project/plan_parts.js";
8
8
 
9
9
  import { error, warning, } from "./problem.js";
10
+ import { planSizeProblems } from "./size_rules.js";
10
11
 
11
12
  const TEST_KINDS = ["behaviour", "contract", "domain", "smoke"];
12
13
 
@@ -31,6 +32,7 @@ export function changePlanProblems(
31
32
  problems.push(error(path, line, "task-format", "task lines read: - T<n>: `<owner>` in `<path>`[, `<path>`], proves <ID>[, <ID>]"));
32
33
  }
33
34
  if (tasks.length === 0 && malformed.length === 0) problems.push(error(path, undefined, "missing-tasks", "the change has no tasks"));
35
+ problems.push(...planSizeProblems(view, path, tasks.length));
34
36
 
35
37
  const proved = new Set(tasks.flatMap((task) => task.proves));
36
38
  const taskPaths = tasks.flatMap((task) => task.paths);
@@ -0,0 +1,36 @@
1
+ import { DEFAULT_CHANGE_LIMITS, } from "../config.js";
2
+ import { kindOf } from "../ids.js";
3
+
4
+
5
+ import { warning, } from "./problem.js";
6
+
7
+ /**
8
+ * Every change pays for its own plan review, code review, verifier run and land, so a change
9
+ * should carry enough work to be worth them, and no more than one review and one run can hold.
10
+ */
11
+ export function changeLimits(view ) {
12
+ return view.config?.config?.changes ?? DEFAULT_CHANGE_LIMITS;
13
+ }
14
+
15
+ /** A feature's changes not yet landed that fall outside the size band. A feature of one change has nothing to merge into. */
16
+ export function changeRowSizeProblems(view , path , rows , landed ) {
17
+ const limits = changeLimits(view);
18
+ const problems = [];
19
+ for (const row of rows.filter((r) => !landed.has(r.change))) {
20
+ if (rows.length > 1 && row.builds.length < limits.minBuilds) {
21
+ problems.push(warning(path, row.line, "change-too-small", `${row.change} builds ${row.builds.length} element(s), under ${limits.minBuilds}; merge it into the change it continues, since each change pays for its own plan, review and verify`));
22
+ }
23
+ const scenarios = row.delivers.filter((id) => kindOf(id) === "SCN").length;
24
+ if (scenarios > limits.maxScenarios) {
25
+ problems.push(warning(path, row.line, "change-too-large", `${row.change} delivers ${scenarios} scenarios, over ${limits.maxScenarios}; split it, since one verifier run has to go through them all`));
26
+ }
27
+ }
28
+ return problems;
29
+ }
30
+
31
+ /** A plan with more tasks than one code review and one verify can hold. */
32
+ export function planSizeProblems(view , path , tasks ) {
33
+ const { maxTasks } = changeLimits(view);
34
+ if (tasks <= maxTasks) return [];
35
+ return [warning(path, undefined, "plan-too-large", `the plan has ${tasks} tasks, over ${maxTasks}; split the change through rethink at feature level`)];
36
+ }
@@ -11,6 +11,7 @@ import { deltaProblems } from "./delta_rules.js";
11
11
  import { malformedIdHeadings, lineOf } from "./living_rules.js";
12
12
  import { changePlanProblems } from "./plan_rules.js";
13
13
  import { error, warning, } from "./problem.js";
14
+ import { changeRowSizeProblems } from "./size_rules.js";
14
15
 
15
16
  const REQUIRED_SECTIONS = {
16
17
  feature: ["Intent", "Spec delta", "Architecture delta", "Changes"],
@@ -71,6 +72,7 @@ function featureScopeProblems(view , feature , delta
71
72
  if (extra !== undefined) {
72
73
  problems.push(warning(feature.path, extra.line, "feature-size", `the feature has ${rows.length} changes, over ${MAX_CHANGES}; split it into features a single design review can hold`));
73
74
  }
75
+ problems.push(...changeRowSizeProblems(view, feature.path, rows, landedChanges));
74
76
  for (const row of rows) {
75
77
  if (!/^\d\d-[a-z0-9]+(?:-[a-z0-9]+)*$/.test(row.change)) problems.push(error(feature.path, row.line, "change-name", `change "${row.change}" must be named NN-slug`));
76
78
  if (names.indexOf(row.change) !== names.lastIndexOf(row.change)) problems.push(error(feature.path, row.line, "change-name", `change ${row.change} is listed twice`));
@@ -13,6 +13,9 @@ export const RULE_SINCE = {
13
13
  "validate:outside-module-map": "0.0.2",
14
14
  "validate:readme-overview": "0.0.2",
15
15
  "validate:feature-size": "0.0.2",
16
+ "validate:change-too-small": "0.0.5",
17
+ "validate:change-too-large": "0.0.5",
18
+ "validate:plan-too-large": "0.0.5",
16
19
  };
17
20
 
18
21
  /** Whether a rule arrived after the version the project's code meets, so it waits for `realign`. */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@printpractical/cruze",
3
- "version": "0.0.3",
3
+ "version": "0.0.5",
4
4
  "description": "Spec-driven development for coding agents: one living model of the system, small deltas, convergent review.",
5
5
  "license": "Apache-2.0",
6
6
  "author": "Chris Buchter",
@@ -9,7 +9,7 @@ Cruze is a spec-driven development framework. It keeps one living model of the s
9
9
 
10
10
  This installation is an early preview. It has:
11
11
 
12
- - The `cruze` CLI: `cruze init` sets up a repository, `cruze install` updates the skills after an upgrade, and `cruze help` lists the commands that validate, approve, check, trace and land work.
12
+ - The `cruze` CLI: `cruze init` sets up a repository, `cruze install` updates the skills after an upgrade, and `cruze help` lists the commands that validate, approve, check, trace and land work. Each prints its result on stdout, as JSON when stdout isn't a terminal, or as text with `--text`. A command refuses an option it doesn't take, so check `cruze help` when one is refused.
13
13
  - `cruze-formats`, the format of every Cruze document.
14
14
  - Knowledge skills holding the standards to design and code to: `cruze-hexagonal-design`, `cruze-behavioural-testing`, `cruze-grilling`, `cruze-domain-language`, `cruze-dependency-approval` and `cruze-research`.
15
15
  - The workflow: `explore` (optional), `envision` for the what, `architect` for the how, `roadmap` for the order of work, then `plan`, `build`, `verify` and `land` for each change. `triage` handles bugs, `rethink` steps back from anywhere, and `next` says what to do now.
@@ -21,11 +21,16 @@ The result is a `feature.md` with a complete architecture delta and an ordered l
21
21
 
22
22
  ## Split it into changes
23
23
 
24
- 9. **Slice vertically.** Each change delivers one or more scenarios end to end, through every layer they need, and can land on its own. The first change is the thinnest slice that proves the design, a tracer bullet. Later changes widen it.
24
+ 9. **Slice vertically.** Each change delivers scenarios end to end, through every layer they need, and can land on its own. Every change pays for its own plan review, code review, verifier run and land, so slice by what is worth that cost, not by the smallest piece that could land. The feature's first change proves the design end to end; later changes widen it in steps of real work.
25
25
  10. **Fill the Changes table.** Give each row a number and a slug, such as `01-local-serial`. List the scenario and requirement IDs it `Delivers`, the architecture IDs it `Builds`, any IDs it `Removes`, and the changes it `Depends on`.
26
26
  - A change builds an element only when its scenarios prove the whole element. Each delta element is built by exactly one change, so when an element's duties arrive in different changes, split it into elements that each arrive whole, such as frame limits split out of a concurrency rule.
27
27
  - `Depends on` names every earlier change that builds something this change's scenarios need.
28
28
  11. **Cover everything exactly once.** Every delta ID appears in exactly one change. A module the delta adds, or one still `planned` in the living docs, is built by the first change that writes a file in it. A module already `built` isn't listed. Done when `cruze validate` reports no errors for the feature, and no `not-designed` warning.
29
- 12. **Size each change, and the feature.** A change that would need more than about 12 tasks, or touches more than one context's domain, splits in two. A feature of more than 6 changes is more than one design review can hold: split it into features that each serve part of its goals, and run `cruze features add` for the later ones. `cruze validate` warns `feature-size` until it is split.
29
+ 12. **Size each change, and the feature.** Aim for changes of about 8 to 25 tasks, each one a sitting's worth of review and verify:
30
+ - **Merge** a change that builds fewer than 3 elements into the change it continues, when they share adapters or screens, or form a chain where each depends on the one before. Several small changes to the same screens are one change. `cruze validate` warns `change-too-small`.
31
+ - **Split** a change that delivers more than 30 scenarios, or would need more than 25 tasks, or touches more than one context's domain. One verifier run and one code review can't hold more. `cruze validate` warns `change-too-large`, and `plan-too-large` once a plan passes 25 tasks.
32
+ - A feature of more than 6 changes is more than one design review can hold: split it into features that each serve part of its goals, and run `cruze features add` for the later ones. `cruze validate` warns `feature-size` until it is split.
33
+
34
+ The thresholds are the defaults under `changes:` in `.cruze/config.yaml`; a project can tune them.
30
35
 
31
36
  Then continue with the review and walkthrough steps of the skill. When the user approves, run `cruze approve <feature>` and quote its result. The command takes the feature's full ID, such as `2026-09-25-open-console`, or its slug when no other active item shares it.
@@ -21,7 +21,7 @@ Envision decides the what: the problem, who has it, and the behaviour that solve
21
21
  - Requirements and scenarios follow `.agents/skills/cruze-formats/reference/specs.md`. Every scenario uses concrete values: real names, real inputs, exact output. The scenarios become the tests, so their values are the expected results.
22
22
  - Keep implementation out: no modules, types, libraries or file formats unless the user names one as a constraint. When the user raises one, record it as a constraint or a settled decision and move on.
23
23
  - The vision stays within one page, excluding the feature map.
24
- - Only the CLI writes the feature map. Use `cruze features add` and `cruze features drop`.
24
+ - Only the CLI writes the feature map. Use `cruze features add`, `cruze features update` and `cruze features drop`.
25
25
 
26
26
  ## Next step
27
27
 
@@ -13,6 +13,7 @@ The result is a feature folder whose `feature.md` holds the intent, the settled
13
13
  - **Requirements:** each behaviour the feature adds, changes or removes, and the capability it belongs to. It goes in an existing capability when the behaviour fits one; otherwise it starts a new capability.
14
14
  - **Scenarios:** for each requirement, the main case, each edge and failure case a user will meet, and what they observe in each. Invent concrete edge cases to test the boundaries.
15
15
  6. **Grill in rounds** until every open decision is settled. Record decisions under `## Settled decisions` and terms in the glossary as they settle.
16
+ - When a decision changes something the feature's summary on the feature map promises, such as a policy it names, correct the summary with `cruze features update <slug> --summary <text>`.
16
17
  7. **Write the spec delta.** Use `ADDED` for new requirements, `MODIFIED` for a requirement whose text or scenarios change (writing its full new body with every scenario it keeps), `REMOVED` with a `Reason` and `Migration` for retired behaviour, and `ADDED CAPABILITY` for a new capability.
17
18
  - Name every ID with glossary terms. An ID is never reused.
18
19
  - Done when every requirement has a scenario for its main case and one for each failure a user can meet.
@@ -66,7 +66,7 @@ The CLI owns some content so that routine bookkeeping never changes a design:
66
66
  - A block between `<!-- cruze:managed -->` and `<!-- /cruze:managed -->` is written only by the CLI.
67
67
  - A `- Status: planned` or `- Status: built` item in an element body is written only by the CLI. Elements that have code (`ENT`, `UC`, `PORT`, `ADP`, `FLOW`, `MOD`) and scenarios (`SCN`) carry one.
68
68
  - Both are excluded from every hash. Read them freely; change them only through `cruze` commands:
69
- - `cruze approve` adds missing statuses to the architecture, sets built elements back to planned with `--replan`, and binds a change to its branch.
69
+ - `cruze approve` adds missing statuses to the architecture, sets built elements back to planned with `--replan`, and binds a change to its branch. With `--by-agent` it approves a change whose plan review closed, and with `--restamp-unchanged` it re-stamps work a rethink left unchanged, recording that the agent gave the approval.
70
70
  - `cruze task` records progress, and `cruze features` edits the feature map.
71
71
  - `cruze land` merges work, marks it built and fills the Commands and Layout sections of `AGENTS.md`.
72
72
  - `cruze roadmap prune` and `cruze abandon` update the roadmap's status.
@@ -27,6 +27,12 @@ commands:
27
27
 
28
28
  review:
29
29
  command: ["claude", "-p", "--allowedTools", "Read Grep Glob Bash(git diff:*) Bash(git log:*) Bash(git show:*) Bash(cruze:*) Bash(cargo:*)"]
30
+ decide: exceptions
31
+
32
+ changes:
33
+ min_builds: 3
34
+ max_scenarios: 30
35
+ max_tasks: 25
30
36
 
31
37
  layers:
32
38
  - name: inventory-domain
@@ -52,6 +58,10 @@ layers:
52
58
  | `layers` | Each layer has a `name`, the `paths` it covers, and `may_import`, the layers it may depend on. |
53
59
  | `commands` | The project's commands by name, such as `test` or `run`. The walking-skeleton change fills them in, and `cruze land` copies them into the Commands section of `AGENTS.md`. |
54
60
  | `review.command` | The program and arguments `cruze review` runs to start an agent in a fresh context, with the prompt on standard input. Optional. `cruze init` sets it for Claude Code to run in print mode, allowed to read the project and run `git diff`, `git log`, `git show` and `cruze`. The verifier runs the system, so add the project's build and run commands. When it is unset, or its program isn't installed, `cruze review` hands the role's prompt back to the calling agent to run in a helper. |
61
+ | `review.decide` | Which review findings the user decides. `exceptions`, the default: only findings whose fix changes behaviour, a designed port or contract, or another decision the user owns, and any the agent wouldn't simply fix as proposed. The agent fixes the rest and lists them. `all`: the user decides every finding. |
62
+ | `changes.min_builds` | A feature's change that builds fewer elements than this gets a `change-too-small` warning: merge it into the change it continues. Default `3`. |
63
+ | `changes.max_scenarios` | A change that delivers more scenarios than this gets a `change-too-large` warning: split it. Default `30`. |
64
+ | `changes.max_tasks` | A plan with more tasks than this gets a `plan-too-large` warning. Default `25`. |
55
65
 
56
66
  Rules for layers:
57
67
 
@@ -16,7 +16,7 @@ What the product is for. At most one page, excluding the feature map.
16
16
  | `## Feature map` | Managed. `### Implemented` is a table of Feature, Release and Summary. `### Future` is a table of Feature, Summary and Goals |
17
17
 
18
18
  - Feature-map entries are named by slug, such as `jump-hosts`. A future entry gets a folder ID only when its feature starts.
19
- - Only the CLI writes the feature map. `cruze features add` puts an entry on the future list, `cruze features drop` removes one, and `land` moves a finished feature to implemented, tagged with the release in progress.
19
+ - Only the CLI writes the feature map. `cruze features add` puts an entry on the future list, `cruze features update` corrects an entry's summary on either list, `cruze features drop` removes one from the future list, and `land` moves a finished feature to implemented, tagged with the release in progress.
20
20
 
21
21
  ## docs/glossary.md
22
22
 
@@ -35,7 +35,7 @@ Templates: [../templates/feature.md](../templates/feature.md), [../templates/cha
35
35
 
36
36
  A standalone change adds `## Intent`, `## Adopt or build`, `## Spec delta` and `## Architecture delta` before its `## Scope`, in the same format as a feature's. Its scope covers every ID in its own deltas, plus any `planned` elements from the living docs that it builds.
37
37
 
38
- A feature is designed in two steps. Feature-scope `envision` writes the intent, settled decisions and spec delta, and leaves `## Architecture delta` as `None.` and the Changes table empty. `architect` writes the architecture delta and the changes. Until the Changes table has a row, `cruze validate` reports the feature as `not-designed` (a warning), skips the coverage rules, and `cruze approve` refuses it. A feature of more than 6 changes gets a `feature-size` warning: split it, because one design review can't hold it.
38
+ A feature is designed in two steps. Feature-scope `envision` writes the intent, settled decisions and spec delta, and leaves `## Architecture delta` as `None.` and the Changes table empty. `architect` writes the architecture delta and the changes. Until the Changes table has a row, `cruze validate` reports the feature as `not-designed` (a warning), skips the coverage rules, and `cruze approve` refuses it. A feature of more than 6 changes gets a `feature-size` warning: split it, because one design review can't hold it. A change outside the size band in `changes:` of `.cruze/config.yaml` gets `change-too-small` (it builds fewer than 3 elements, in a feature of several changes) or `change-too-large` (it delivers more than 30 scenarios), and a plan of more than 25 tasks gets `plan-too-large`.
39
39
 
40
40
  `architect` writes a standalone change's design sections and its scope, and leaves the test plan table empty and the tasks section blank. Until `plan` fills them, `cruze validate` reports the change as `not-planned` (a warning), skips the task and test plan rules, and `cruze approve` refuses it.
41
41
 
@@ -20,6 +20,7 @@ Land makes finished work part of the living model. `cruze land` merges the chang
20
20
  - When they don't hold, stop and offer `rethink`.
21
21
  4. **Run `cruze land`** and quote its report: what it merged, what is now built, and whether the work finished and was archived.
22
22
  - A `merge-conflict` means someone changed an element this feature merged earlier. Stop and run `rethink`, which reconciles it.
23
+ - When the work finished, read the feature's row under `### Implemented` in `docs/vision.md` against its settled decisions and spec delta. When the summary no longer describes what landed, correct it with `cruze features update <slug> --summary <text>`.
23
24
  - A `merge-invalid` means the merged docs, or the feature and changes being landed, wouldn't validate, such as a decision citing an ID the change removes. Nothing was written. Stop and run `rethink`.
24
25
  5. **Update the changelog.** Under `## [Unreleased]` in `CHANGELOG.md`, add one line per user-visible behaviour the change delivers, in the user's words, under `### Added`, `### Changed` or `### Removed`. Name the behaviour, not the code, including any rule a user can now run into that no scenario covers yet.
25
26
  6. **Update the README** when the change altered how someone installs, runs or uses the product. Run every command you add, to check it works.
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: cruze-plan
3
- description: Turns the next change into a test plan and ordered tasks with file targets, reviews the plan in a fresh context, and records your approval on the branch that will build it.
3
+ description: Turns the next change into a test plan and ordered tasks with file targets, reviews the plan in a fresh context, and approves it on the branch that will build it once the review closes, leaving you to object or go straight to build.
4
4
  ---
5
5
 
6
6
  # Plan
@@ -26,7 +26,7 @@ Plan turns one designed change into work an implementer can do without guessing:
26
26
  10. **Record decisions and risks.** Put task-level choices you made under `## Settled decisions`. Under `## Risks`, name the riskiest task and how its test catches the failure. Remove every template guide.
27
27
  11. **Validate.** Run `cruze validate`. Done when it reports no errors for the change.
28
28
  12. **Review the plan.** Commit the plan, then run the review in `.agents/skills/cruze-roles/SKILL.md` with the design reviewer and its plan rubric, `review=plan`, and `--item <ref>`.
29
- 13. **Approve.** Show the user the test plan table, the tasks in order and the risks. When they approve, run `cruze approve <ref>` on this branch and quote its result. The approval binds the change to the branch.
29
+ 13. **Approve.** Run `cruze approve <ref> --by-agent` on this branch and quote its result. The plan carries no design of its own, so once its review has closed, with no blocker open and a disposition for every finding, you approve it for the user. The approval binds the change to the branch. When it refuses with `plan-review-open`, do what it names. A blocker still open after round 2 goes to the user, and once they decide it, they approve the plan themselves with `cruze approve <ref>`.
30
30
  14. **Commit** with a message such as `docs: plan <change>`.
31
31
 
32
32
  ## When the plan needs design
@@ -49,4 +49,4 @@ The walking-skeleton change also makes CI and the project's tooling real. Its pl
49
49
 
50
50
  ## Next step
51
51
 
52
- `build`, on this branch.
52
+ Show the user the test plan table, the tasks in order and the risks, and say the plan is approved. They can object, and you amend the plan, run its review on the diff, and approve again. Otherwise the next step is `build`, on this branch.
@@ -0,0 +1,27 @@
1
+ # Realign notes: 0.0.4
2
+
3
+ ## New work only
4
+
5
+ ### argument-files-anywhere: Files named on the command line may be outside the project
6
+ - Applies to: new work only
7
+ - Rule: `.agents/skills/cruze-roles/SKILL.md`, "Running a role"
8
+
9
+ ### cli-output-format: The CLI prints one format on stdout, JSON for agents and text with --text
10
+ - Applies to: new work only
11
+ - Rule: `.agents/skills/cruze-about/SKILL.md`
12
+
13
+ ### overlap-scope: Overlap counts only in-flight changes, and only the elements their scope and delta name
14
+ - Applies to: new work only
15
+ - Rule: `.agents/skills/cruze-plan/SKILL.md`, "Steps", step 7
16
+
17
+ ### refused-options: The CLI refuses an option its command doesn't take
18
+ - Applies to: new work only
19
+ - Rule: `.agents/skills/cruze-about/SKILL.md`
20
+
21
+ ### feature-map-summary: A feature's summary on the feature map follows its decisions
22
+ - Applies to: new work only
23
+ - Rule: `.agents/skills/cruze-land/SKILL.md`, "Landing a change", step 4
24
+
25
+ ### journal-list-item: Journal list narrows to one feature or change with --item
26
+ - Applies to: new work only
27
+ - Rule: `.agents/skills/cruze-about/SKILL.md`
@@ -0,0 +1,38 @@
1
+ # Realign notes: 0.0.5
2
+
3
+ ## New work only
4
+
5
+ ### review-exceptions: The user decides only the review findings that are theirs
6
+ - Applies to: new work only
7
+ - Rule: `.agents/skills/cruze-roles/SKILL.md`, "Running a review", round 1, steps 3 to 5
8
+
9
+ ### plan-approved-by-agent: A plan is approved once its review closes, without stopping for the user
10
+ - Applies to: new work only
11
+ - Rule: `.agents/skills/cruze-plan/SKILL.md`, "Steps", step 13
12
+
13
+ ### rethink-restamps: A rethink re-stamps the work it didn't change
14
+ - Applies to: new work only
15
+ - Rule: `.agents/skills/cruze-rethink/SKILL.md`, "Steps", step 6
16
+
17
+ ### change-too-small: A feature's change builds at least 3 elements
18
+ - Applies to: new work only
19
+ - Rule: `.agents/skills/cruze-architect/feature.md`, step 12
20
+ - Detect: validate:change-too-small
21
+
22
+ ### change-too-large: A change delivers at most 30 scenarios
23
+ - Applies to: new work only
24
+ - Rule: `.agents/skills/cruze-architect/feature.md`, step 12
25
+ - Detect: validate:change-too-large
26
+
27
+ ### plan-too-large: A plan has at most 25 tasks
28
+ - Applies to: new work only
29
+ - Rule: `.agents/skills/cruze-architect/feature.md`, step 12
30
+ - Detect: validate:plan-too-large
31
+
32
+ ### concerns-per-size: Reviewers list concerns in proportion to the work
33
+ - Applies to: new work only
34
+ - Rule: `.agents/skills/cruze-roles/code-reviewer.md`, "Findings"
35
+
36
+ ### reverify-changed-plan: A plan changed after verify needs verifying again, whoever approved it
37
+ - Applies to: new work only
38
+ - Rule: `.agents/skills/cruze-verify/SKILL.md`
@@ -6,6 +6,8 @@ Each release that changes what Cruze expects of a project ships a notes file, `n
6
6
 
7
7
  - [0.0.2](notes/0.0.2.md)
8
8
  - [0.0.3](notes/0.0.3.md)
9
+ - [0.0.4](notes/0.0.4.md)
10
+ - [0.0.5](notes/0.0.5.md)
9
11
 
10
12
  ## Format
11
13
 
@@ -18,7 +18,11 @@ Finding a design flaw mid-build is information, not failure. Rethink amends the
18
18
  3. **Load the standards.** Read `.agents/skills/cruze-grilling/SKILL.md`. At feature or architecture level, also read `.agents/skills/cruze-hexagonal-design/SKILL.md`. Also read the format reference for the document you will amend: `.agents/skills/cruze-formats/reference/architecture.md`, `specs.md`, `work-items.md` or `durable-docs.md`.
19
19
  4. **Amend that level,** following the level's section in [levels.md](levels.md). Settle any decision the amendment needs with the user, and record it as that level records decisions.
20
20
  5. **Keep completed work.** A ticked task stays ticked unless the amendment changes its owner, its files, or what it proves. Reopen each task that changed with `cruze task reopen T<n> "<what changed>"`. Its commits stay in history, and build redoes the task.
21
- 6. **Re-approve what went stale.** Run `cruze status` and work from the top down: the amended document first, then each document it lists as `upstream changed`. For each one, show the user the diff of the elements it names, and re-approve it with `cruze approve <ref>` once they agree. A change's verification carries over when its own design didn't change.
21
+ 6. **Re-approve what went stale.** Run `cruze status` and work from the top down. The amended document comes first, and the user approves it as its level says. Then take each feature or change it lists as `upstream changed`, whose own text you didn't edit:
22
+ - Read the diff of the elements it names, and check the work still holds against them. When it doesn't, amend it too, and the user approves it as an amended document.
23
+ - When it holds, re-stamp it with `cruze approve <ref> --restamp-unchanged`. The CLI refuses work whose own text changed, a project document, and a change with a task reopened since its approval; the user re-approves those.
24
+
25
+ Tell the user, in one line each, what you re-stamped and why it still holds. They can object, and that document goes to them. A change's verification carries over when its own design didn't change.
22
26
  7. **Record the rethink.** Draft the entry and show it to the user in one line. When they confirm, run:
23
27
 
24
28
  ```sh
@@ -5,14 +5,14 @@
5
5
  1. Edit the change's `## Test plan` or `## Tasks` so they are right. The scope doesn't change at this level; a scope change is a feature-level rethink.
6
6
  2. Run `cruze validate`. Done when it reports no errors for the change.
7
7
  3. Run the plan review from `.agents/skills/cruze-roles/SKILL.md` on the diff alone, with the design reviewer's plan rubric and `review=plan`.
8
- 4. Re-approve with `cruze approve <change>`.
8
+ 4. Re-approve with `cruze approve <change> --by-agent` once the review has closed, as plan does.
9
9
 
10
10
  ## Feature
11
11
 
12
12
  1. Edit the feature's `## Spec delta`, `## Architecture delta` or `## Changes`. For a standalone change, edit its own deltas and scope. Write complete bodies, as `architect` does, and keep the scope rules.
13
13
  2. Run `cruze validate`. Done when it reports no errors for the feature or its changes.
14
14
  3. Run the design review from the roles skill on the diff alone, with `review=design`. Walk the user through the changed parts, following `.agents/skills/cruze-architect/walkthrough.md`.
15
- 4. Re-approve the feature. For each of its changes that shows as stale, update the change's scope, test plan and tasks to match. Reopen touched tasks, and re-approve the change.
15
+ 4. Re-approve the feature. For each of its changes that shows as stale, check it against the diff. When the amendment touches the change's scope, test plan or tasks, update them to match, reopen touched tasks, run the plan review on the diff, and re-approve it with `cruze approve <change> --by-agent`. When it doesn't, re-stamp the change as the skill's step 6 describes.
16
16
  5. When a change hasn't been created yet, only the feature's Changes row changes; `plan` picks it up.
17
17
 
18
18
  ## Architecture
@@ -26,11 +26,11 @@
26
26
  - **A prefactor**, otherwise. From an up-to-date main, run `architect "<prefactor>"`, which designs it as a standalone change, then `plan`, `build`, `verify` and `land` it before the current change continues.
27
27
  6. Run `cruze status`. Every document citing a changed element shows `upstream changed`:
28
28
  - When the document's own delta changes that element, rewrite the delta so it keeps your amendment. `cruze approve` refuses it with `rebase-required` until you do. Then approve it with `--rebase <ID>`.
29
- - Otherwise, show the user the element's diff, and re-approve the document as the skill's step 6 describes.
29
+ - Otherwise, check the document against the element's diff, and re-stamp it or amend it as the skill's step 6 describes.
30
30
 
31
31
  ## Vision
32
32
 
33
- 1. Edit `docs/vision.md` with the user: goals, non-goals or constraints. The feature map changes only through `cruze features add` and `cruze features drop`. When the problem, users or goals changed, update the overview under the title of `README.md` to match.
33
+ 1. Edit `docs/vision.md` with the user: goals, non-goals or constraints. The feature map changes only through `cruze features add`, `cruze features update` and `cruze features drop`. When the problem, users or goals changed, update the overview under the title of `README.md` to match.
34
34
  2. Update `docs/roadmap.md` to match, following `.agents/skills/cruze-roadmap/SKILL.md`.
35
35
  3. For each feature in progress that served a dropped goal, the user decides: keep it, or stop it with `cruze abandon <ref> --reason "<why>"`. Abandoning archives the folder and puts the feature back on the future list; changes that already landed stay.
36
36
  4. Re-approve with `cruze approve vision`, then `cruze approve roadmap`.