@printpractical/cruze 0.0.8 → 0.0.9
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.
- package/README.md +4 -4
- package/dist/adapters/inbound/cli/cli_context.js +2 -0
- package/dist/adapters/inbound/cli/commands/code.js +2 -1
- package/dist/adapters/inbound/cli/commands/show.js +6 -1
- package/dist/adapters/outbound/git_repository.js +40 -7
- package/dist/adapters/outbound/project_root.js +13 -0
- package/dist/app/use_cases/project_status.js +4 -1
- package/dist/app/use_cases/show_scope.js +3 -1
- package/dist/domain/check/budget_text.js +43 -6
- package/dist/domain/check/check_source.js +2 -2
- package/dist/domain/config.js +12 -0
- package/dist/domain/edits/agents_file.js +6 -1
- package/dist/domain/journal.js +13 -2
- package/dist/domain/project/scope_view.js +20 -4
- package/dist/domain/status/next_step.js +25 -2
- package/dist/domain/status/verification.js +12 -0
- package/dist/domain/validation/plan_rules.js +1 -1
- package/dist/main.js +8 -4
- package/package.json +1 -1
- package/skills/architect/SKILL.md +4 -1
- package/skills/build/SKILL.md +2 -1
- package/skills/formats/reference/architecture.md +1 -1
- package/skills/formats/reference/config.md +9 -4
- package/skills/hexagonal-design/SKILL.md +2 -2
- package/skills/plan/SKILL.md +3 -3
- package/skills/plan/tasks.md +1 -1
- package/skills/realign/SKILL.md +1 -1
- package/skills/realign/notes/0.0.8.md +1 -1
- package/skills/realign/notes/0.0.9.md +59 -0
- package/skills/realign/notes.md +1 -0
- package/skills/rethink/SKILL.md +2 -2
- package/skills/rethink/levels.md +2 -2
- package/skills/roles/SKILL.md +1 -1
- package/skills/roles/code-reviewer.md +3 -2
- package/skills/roles/design-reviewer.md +13 -1
- package/skills/roles/verifier.md +2 -0
- package/skills/verify/SKILL.md +3 -3
- package/src/adapters/inbound/cli/cli_context.ts +2 -0
- package/src/adapters/inbound/cli/commands/code.ts +2 -1
- package/src/adapters/inbound/cli/commands/show.ts +6 -1
- package/src/adapters/outbound/git_repository.ts +40 -7
- package/src/adapters/outbound/project_root.ts +13 -0
- package/src/app/use_cases/project_status.ts +4 -1
- package/src/app/use_cases/show_scope.ts +3 -1
- package/src/domain/check/budget_text.ts +43 -6
- package/src/domain/check/check_source.ts +2 -2
- package/src/domain/config.ts +12 -0
- package/src/domain/edits/agents_file.ts +6 -1
- package/src/domain/journal.ts +13 -2
- package/src/domain/project/scope_view.ts +20 -4
- package/src/domain/status/next_step.ts +25 -2
- package/src/domain/status/verification.ts +12 -0
- package/src/domain/validation/plan_rules.ts +1 -1
- package/src/main.ts +8 -4
- package/templates/init/config.yaml +5 -4
|
@@ -13,8 +13,8 @@ source: ["src/**/*.rs"]
|
|
|
13
13
|
tests: ["src/**/*.rs", "tests/**/*.rs"]
|
|
14
14
|
|
|
15
15
|
check:
|
|
16
|
-
max_lines:
|
|
17
|
-
max_types:
|
|
16
|
+
max_lines: 600
|
|
17
|
+
max_types: 12
|
|
18
18
|
exceptions:
|
|
19
19
|
- path: src/cli/args.rs
|
|
20
20
|
reason: the clap derive struct lists every flag in one place
|
|
@@ -37,6 +37,10 @@ changes:
|
|
|
37
37
|
- item: 2026-09-25-open-console/02-ssh-hops
|
|
38
38
|
reason: every scenario runs through one reconcile, which can't be split without building it twice
|
|
39
39
|
|
|
40
|
+
references:
|
|
41
|
+
- path: third_party/rfc4254.txt
|
|
42
|
+
use: the SSH connection protocol every hop speaks; channels and their failures must follow it
|
|
43
|
+
|
|
40
44
|
layers:
|
|
41
45
|
- name: inventory-domain
|
|
42
46
|
paths: ["src/inventory/domain/**"]
|
|
@@ -55,9 +59,10 @@ layers:
|
|
|
55
59
|
| `tracker` | Where work items live. `markdown` is the only value in V1. |
|
|
56
60
|
| `source` | Globs of the source files `cruze check` inspects. |
|
|
57
61
|
| `tests` | Globs of the files `cruze trace` searches for scenario IDs. |
|
|
58
|
-
| `check.max_lines` | Line budget per source file. Test files outside `source` have no budget, and a Rust file's `#[cfg(test)]` modules don't count. |
|
|
59
|
-
| `check.max_types` | Budget of top-level types per source file. |
|
|
62
|
+
| `check.max_lines` | Line budget per source file, a backstop against a file that has outgrown one responsibility. `cruze init` sets `600`. Test files outside `source` have no budget, and a Rust file's `#[cfg(test)]` modules don't count. |
|
|
63
|
+
| `check.max_types` | Budget of top-level types per source file. `cruze init` sets `12`. |
|
|
60
64
|
| `check.exceptions` | Files allowed past a budget, or Rust test files allowed under `src/` (`test-placement`), each with a `path` and a `reason`. `realign` adds entries whose reason ends `scheduled in <ref>`, and landing that change removes them. |
|
|
65
|
+
| `references` | External documents the design must comply with, such as a protocol specification or a standard, each with its `path` in the repository and its `use`: what it governs. Optional. `cruze show` lists them, and architect, plan, build and the reviewers read the parts their scope relies on and cite them, such as `RFC 4254 §5.2`, in the design. |
|
|
61
66
|
| `layers` | Each layer has a `name`, the `paths` it covers, and `may_import`, the layers it may depend on. |
|
|
62
67
|
| `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`. When the suite has a slow tier, such as UI, device or end-to-end tests, `test` runs only the fast tier and `test_full` runs everything. The full suite is `test_full` when it is set, and `test` otherwise. |
|
|
63
68
|
| `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. |
|
|
@@ -89,8 +89,8 @@ Layers are dependency boundaries, not runtime hops. A port can be a plain functi
|
|
|
89
89
|
## Many small modules
|
|
90
90
|
|
|
91
91
|
- Give each responsibility its own module, grouped by domain concept, with the hexagonal roles inside. A context's core is usually one module, with each port beside the use cases that define it, and grows submodules by concept, not by layer. A file may hold a few closely related types.
|
|
92
|
-
- When a file
|
|
93
|
-
-
|
|
92
|
+
- When a file gains a second responsibility, split it into a submodule named for what it owns. Never split a file by length: one that grows with its one responsibility stays whole.
|
|
93
|
+
- The budgets (`check:` in `.cruze/config.yaml`, enforced by `cruze check`) are a backstop against a file that has outgrown one responsibility, never a shape to write code to. Write the code as you would with no budget: no type alias, string where an error type belongs, merged types or function moved away from its owner to stay under a limit. When a file over budget holds one responsibility, such as the related types of one grammar, list it under `check.exceptions` with that reason.
|
|
94
94
|
- Entry-point and namespace files, such as `main`, `lib.rs`, `mod.rs` and `index.ts`, hold only wiring and declarations.
|
|
95
95
|
- Don't create generic modules such as `utils`, `helpers`, `common`, `services`, `models`, `types` or `manager`. Shared behaviour belongs with the concept that owns it.
|
|
96
96
|
- Duplication is semantic. Two implementations of one rule are duplication even when the text differs, so consolidate them in the owner, not in a helper.
|
package/skills/plan/SKILL.md
CHANGED
|
@@ -19,14 +19,14 @@ Plan turns one designed change into work an implementer can do without guessing:
|
|
|
19
19
|
- `.agents/skills/cruze-behavioural-testing/SKILL.md`
|
|
20
20
|
- `.agents/skills/cruze-hexagonal-design/SKILL.md`, and its language file when one exists for the project's language
|
|
21
21
|
- `.agents/skills/cruze-formats/reference/work-items.md`
|
|
22
|
-
6. **Read the design it builds.** Run `cruze show <ref>` and read its output: the full body of every element in the scope, every scenario it delivers, the settled decisions and the
|
|
22
|
+
6. **Read the design it builds.** Run `cruze show <ref>` and read its output: the full body of every element in the scope, every scenario it delivers, the settled decisions, the ledger and the references. Read an element it lists as cited only when a task will call it, from the place it names. Don't read `docs/architecture.md` or `feature.md` whole: two projects read a 40k-word architecture about 150 times each.
|
|
23
23
|
7. **Check for overlap.** Run `cruze status --overlap --change <ref>`. For each change on another branch that touches the same elements, tell the user which elements are shared and which change should land first. Record the answer under `## Risks`.
|
|
24
24
|
8. **Write the test plan.** Follow [test-plan.md](test-plan.md). Done when every delivered scenario has a `behaviour` row and every port a built adapter implements has a `contract` row.
|
|
25
25
|
9. **Write the tasks.** Follow [tasks.md](tasks.md). Done when every scope ID is proved by a task and every task line matches the grammar.
|
|
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.**
|
|
29
|
+
13. **Approve.** For a feature's change, 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. A standalone change, such as the walking skeleton or a prefactor, carries its own design, so show the user the plan and ask them to approve it with `cruze approve <ref>`. The approval binds the change to the branch. When it refuses with `plan-review-open`, do what it names. When it refuses with `size-unaccepted`, the plan has more tasks than `changes.max_tasks`: split the change through `rethink` at feature level, or, when the user accepts the size, list the change under `changes.exceptions` in `.cruze/config.yaml` with their reason and approve again. 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
|
-
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, in a new session so its context starts empty.
|
|
52
|
+
Show the user the test plan table, the tasks in order and the risks, and say the plan is approved, or, for a standalone change, ask them to approve it. 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, in a new session so its context starts empty.
|
package/skills/plan/tasks.md
CHANGED
|
@@ -27,7 +27,7 @@ A task is one owner, the files it writes, and the IDs it proves. Build does them
|
|
|
27
27
|
4. **Find the owners.** For each rule, text, format or conversion a task's element applies, such as a message, a path format, a rounding or a name check, search the code and the other tasks for every place that applies it. Give it one owner: when an earlier task's owner already holds it, the later task's line names that file as a callee rather than building the rule again. Done when no rule is written in two places. Two projects wrote 20 rules twice and moved them at code review.
|
|
28
28
|
5. **Find the callers.** For each signature, default or setting a task changes, search the code for every use of it, including tests that start the real binary or read the setting. Add each file that must change to the task. Done when no task will need a file it doesn't list.
|
|
29
29
|
6. **Size each task.** A task writes one owner, with its tests, in one sitting. When an owner would take several steps, such as a use case with many scenarios, split it into tasks that each prove a subset of the scenarios.
|
|
30
|
-
-
|
|
30
|
+
- When a task would give an existing file a second responsibility, name the new file in the task, named for what it owns. Never plan a split by line or type count: a file that grows with its one responsibility stays whole, and when it passes a budget, build lists it under `check.exceptions` as the budget rule in `.agents/skills/cruze-hexagonal-design/SKILL.md` says.
|
|
31
31
|
- The budgets apply to source files, not tests. Split a test file by the behaviour it covers, one use case or port per file, never to meet a budget. Tests that leave a source file go where the hexagonal-design language file you loaded says.
|
|
32
32
|
7. **Check coverage.**
|
|
33
33
|
- Every `Builds` ID is proved by a task, except a module, which is proved by any task that writes inside its `Path`.
|
package/skills/realign/SKILL.md
CHANGED
|
@@ -15,7 +15,7 @@ Each Cruze release can expect more of a project's code and documents than the la
|
|
|
15
15
|
- `.agents/skills/cruze-hexagonal-design/SKILL.md`, and its language file when one exists for the project's language
|
|
16
16
|
- `.agents/skills/cruze-behavioural-testing/SKILL.md`
|
|
17
17
|
- `.agents/skills/cruze-formats/reference/architecture.md`
|
|
18
|
-
4. **Audit.** Run the auditor role from `.agents/skills/cruze-roles/SKILL.md` on every note with an `Ask`. In a full audit, give it the standards instead, and run one auditor per module in the module map. Check its evidence against the files. Done when every note with an `Ask` has an answer with evidence, and every note with a `Detect` has its findings from the status report.
|
|
18
|
+
4. **Audit.** Run the auditor role from `.agents/skills/cruze-roles/SKILL.md` on every note with an `Ask`. A note whose `Rule` is an item of the design reviewer's rubric is about designs, not code: run the design reviewer instead, on each feature in flight, with that item only, since those designs were reviewed before the item existed. In a full audit, give it the standards instead, and run one auditor per module in the module map. Check its evidence against the files. Done when every note with an `Ask` has an answer with evidence, and every note with a `Detect` has its findings from the status report.
|
|
19
19
|
5. **Decide each finding** with the user, a group at a time, with your recommendation:
|
|
20
20
|
- `fixed`: a prefactor change will fix it.
|
|
21
21
|
- `waived`: the project keeps its own convention, with the reason. For a `check:` finding, list the file under `check.exceptions` with that reason, which keeps CI green. For the rest, the journal holds the waiver.
|
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
- Applies to: existing code
|
|
19
19
|
- Rule: `.agents/skills/cruze-dependency-approval/SKILL.md`, "Weighing"
|
|
20
20
|
- Ask: does any code hand-write, shell out for, or use a less idiomatic construct for something a dependency or a feature of an approved dependency does cleanly, where the design or a deviation gives avoiding the dependency as the reason?
|
|
21
|
-
- Migrate: ask the user for the dependency as the dependency-approval skill says, then replace the workaround through `rethink` at feature level.
|
|
21
|
+
- Migrate: ask the user for the dependency as the dependency-approval skill says, then replace the workaround through `rethink`: at architecture level when the workaround is in an `XC` rule or an import allowlist, at feature level otherwise.
|
|
22
22
|
|
|
23
23
|
### size-exceptions: An accepted size warning is recorded under changes.exceptions
|
|
24
24
|
- Applies to: existing code
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
# Realign notes: 0.0.9
|
|
2
|
+
|
|
3
|
+
## Existing code
|
|
4
|
+
|
|
5
|
+
### budget-backstop: File budgets are a backstop, and files split by responsibility, never by length
|
|
6
|
+
- Applies to: existing code
|
|
7
|
+
- Rule: `.agents/skills/cruze-hexagonal-design/SKILL.md`, "Many small modules"
|
|
8
|
+
- Ask: does `.cruze/config.yaml` still set the old defaults, `check.max_lines: 250` and `check.max_types: 5`? Is any `check.exceptions` entry, or any split of one owner across files, there only to meet them?
|
|
9
|
+
- Migrate: raise the budgets to `max_lines: 600` and `max_types: 12`, unless the user chose the old ones, and remove each exception whose file is now within budget. A split made only for length, whose files share one responsibility, merges back in a prefactor; waive it when the files read well apart.
|
|
10
|
+
|
|
11
|
+
### flow-control-review: A design says how its work flows, waits and recovers
|
|
12
|
+
- Applies to: existing code
|
|
13
|
+
- Rule: `.agents/skills/cruze-roles/design-reviewer.md`, "Rubric", item 11
|
|
14
|
+
- Ask: for each feature in flight, does its design leave out any check in rubric item 11 that a scenario or an `XC` rule depends on, such as what a sender does when a queue is full, or what happens when a reconnect fails?
|
|
15
|
+
- Migrate: settle each gap through `rethink` at feature level before the feature's next change is planned. Code a landed change built goes to a prefactor.
|
|
16
|
+
|
|
17
|
+
### references: External specifications the design must follow are listed under references
|
|
18
|
+
- Applies to: existing code
|
|
19
|
+
- Rule: `.agents/skills/cruze-formats/reference/config.md`, "references"
|
|
20
|
+
- Ask: does the project rely on an external document it must comply with, such as a protocol specification in the repository, that `references:` in `.cruze/config.yaml` doesn't list?
|
|
21
|
+
- Migrate: list each under `references:` with its path and what it governs, as the user confirms.
|
|
22
|
+
|
|
23
|
+
## New work only
|
|
24
|
+
|
|
25
|
+
### plan-splits-by-responsibility: Plan splits a file for a second responsibility, never for its length
|
|
26
|
+
- Applies to: new work only
|
|
27
|
+
- Rule: `.agents/skills/cruze-plan/tasks.md`, step 6
|
|
28
|
+
|
|
29
|
+
### name-clash-review: The design reviewer checks new names against the ones a caller already reaches
|
|
30
|
+
- Applies to: new work only
|
|
31
|
+
- Rule: `.agents/skills/cruze-roles/design-reviewer.md`, "Rubric", item 8
|
|
32
|
+
|
|
33
|
+
### architecture-rethink-review: An architecture rethink that changes a contract gets a design review of the diff
|
|
34
|
+
- Applies to: new work only
|
|
35
|
+
- Rule: `.agents/skills/cruze-rethink/levels.md`, "Architecture", step 2
|
|
36
|
+
|
|
37
|
+
### missed-by-design-review: A gap the design review's rubric covers is missed by design-review
|
|
38
|
+
- Applies to: new work only
|
|
39
|
+
- Rule: `.agents/skills/cruze-rethink/SKILL.md`, "Steps", step 7
|
|
40
|
+
|
|
41
|
+
### sent-back-to: A sent-back verification says whether the change goes back to build or to rethink
|
|
42
|
+
- Applies to: new work only
|
|
43
|
+
- Rule: `.agents/skills/cruze-verify/SKILL.md`, "Steps", step 10
|
|
44
|
+
|
|
45
|
+
### standalone-plan-approval: The user approves the plan of a standalone change
|
|
46
|
+
- Applies to: new work only
|
|
47
|
+
- Rule: `.agents/skills/cruze-plan/SKILL.md`, "Steps", step 13
|
|
48
|
+
|
|
49
|
+
### deviation-read-back: Build reads a deviation back, and one that names a value the design didn't give is a rethink
|
|
50
|
+
- Applies to: new work only
|
|
51
|
+
- Rule: `.agents/skills/cruze-build/SKILL.md`, "Deviations"
|
|
52
|
+
|
|
53
|
+
### prefactor-verify: The verifier runs no suite again for a change that delivers no scenarios
|
|
54
|
+
- Applies to: new work only
|
|
55
|
+
- Rule: `.agents/skills/cruze-roles/verifier.md`, "Work", step 3
|
|
56
|
+
|
|
57
|
+
### design-on-main: The approved design reaches main before plan branches from it
|
|
58
|
+
- Applies to: new work only
|
|
59
|
+
- Rule: `.agents/skills/cruze-architect/SKILL.md`, "Next step"
|
package/skills/realign/notes.md
CHANGED
package/skills/rethink/SKILL.md
CHANGED
|
@@ -32,7 +32,7 @@ Finding a design flaw mid-build is information, not failure. Rethink amends the
|
|
|
32
32
|
--set agent="<the agent you run in>" [--item <ref>]
|
|
33
33
|
```
|
|
34
34
|
|
|
35
|
-
`found_by` is the step that found it. A defect also needs `missed_by`, the earliest step whose job was to catch it. Both take one of `envision`, `architect`, `research`, `design-review`, `walkthrough`, `plan`, `plan-review`, `build`, `code-review`, `verify`, `land`, `improve`, `validate`, `check` or `user`.
|
|
35
|
+
`found_by` is the step that found it. A defect also needs `missed_by`, the earliest step whose job was to catch it. Both take one of `envision`, `architect`, `research`, `design-review`, `walkthrough`, `plan`, `plan-review`, `build`, `code-review`, `verify`, `land`, `realign`, `improve`, `validate`, `check` or `user`. When a design review ran over the element and an item of its rubric in `.agents/skills/cruze-roles/design-reviewer.md` covers the gap, `missed_by` is `design-review`, not `architect`: a retro reads it to know which rubric to fix.
|
|
36
36
|
|
|
37
37
|
Done when `cruze status` shows every document approved and current, or names only work the user chose to leave for later.
|
|
38
38
|
|
|
@@ -43,7 +43,7 @@ Done when `cruze status` shows every document approved and current, or names onl
|
|
|
43
43
|
| Task | A helper belongs in a different file of the same module | Nothing. Record it with `cruze task deviation` | Carry on building. This is not a rethink, so record no rethink entry |
|
|
44
44
|
| Change | A test plan row or a task's file is wrong, with no design change | `change.md`: its test plan or tasks | Plan review of the diff, then re-approve the change |
|
|
45
45
|
| Feature | A use case needs a second port operation; a scenario is wrong | `feature.md`: its deltas and Changes table, or a standalone `change.md`'s deltas | Design review of the diff, walkthrough of the changed parts, then re-approve the feature and its changes |
|
|
46
|
-
| Architecture | An entity boundary is wrong, or a new port is needed | `docs/architecture.md` directly, plus an ADR when it passes the ADR test |
|
|
46
|
+
| Architecture | An entity boundary is wrong, or a new port is needed | `docs/architecture.md` directly, plus an ADR when it passes the ADR test | Design review of the diff when it changes an operation, port, error or `XC` rule, walkthrough of the diff, then `cruze approve architecture`, with `--replan` for built elements whose code must change |
|
|
47
47
|
| Vision | The release's scope was wrong, or a new goal appears | `docs/vision.md`, and `docs/roadmap.md` | Re-approve the vision and the roadmap. Decide what happens to features that served a dropped goal |
|
|
48
48
|
|
|
49
49
|
## After landing
|
package/skills/rethink/levels.md
CHANGED
|
@@ -18,8 +18,8 @@
|
|
|
18
18
|
## Architecture
|
|
19
19
|
|
|
20
20
|
1. Edit `docs/architecture.md` directly. Keep every element's ID, and write complete bodies. Write an ADR with `cruze new adr` when the decision passes the ADR test, and link it under `## Decisions`.
|
|
21
|
-
2. Run `cruze validate
|
|
22
|
-
3. List the elements you changed that are already `built`, whose code must now change. When the user approves, run `cruze approve architecture`, adding `--replan <ID>` for each of them. They go back to `planned` until a change rebuilds them.
|
|
21
|
+
2. Run `cruze validate`. When the amendment adds, renames or changes an operation, a port, a use case's `Errors` or an `XC` rule, run the review in `.agents/skills/cruze-roles/SKILL.md` on the diff with the design reviewer, `review=design`: a realign amendment once named a method that clashed with one the type already had, found only at plan. Then run the walkthrough of the diff only, following `.agents/skills/cruze-architect/walkthrough.md`.
|
|
22
|
+
3. List the elements you changed that are already `built`, whose code must now change. When the user approves, run `cruze approve architecture`, adding `--replan <ID>` for each of them. They go back to `planned` until a change rebuilds them, and only a land sets them back to `built`. A `- Status:` line belongs to the CLI: when an element was replanned by mistake, tell the user, and let the change rebuild or re-check it.
|
|
23
23
|
4. Commit the amendment, and get it onto main on its own, as a pull request or a merge. Every branch and every later change must build on it. Then merge main into the branch you were working on.
|
|
24
24
|
5. Hand each replanned element to a change that rebuilds it:
|
|
25
25
|
- **The current change**, when the rebuild belongs to its work. Add the element to the feature's Changes row for that change and to the change's `Builds`, then re-plan and re-approve the change.
|
package/skills/roles/SKILL.md
CHANGED
|
@@ -20,7 +20,7 @@ A role is a prompt with fixed inputs and a report as its only output. It runs in
|
|
|
20
20
|
|
|
21
21
|
1. Read the role file and gather exactly the inputs it lists. Done when you have every input path, plus any command output the role asks for.
|
|
22
22
|
2. Start a fresh context: a helper agent with no conversation history, where the agent can start one. Give it the role file's full text and the inputs the role lists, including draft documents it names, and nothing else. Your own opinions, the conversation and your reasons for the design stay out.
|
|
23
|
-
3. If the agent can't start a helper, run `cruze review <role> --item <ref>`, where the role is `design-reviewer`, `code-reviewer`, `verifier`, `researcher`, `auditor` or `surveyor`. Add `--base <commit>` for the code under review. It starts the agent configured under `review:` in `.cruze/config.yaml` in a fresh context and prints the role's report. When no agent is configured, or it isn't installed, it reports `status: run-in-helper` with the role's full prompt instead: start a helper, such as a subagent, and give it exactly that prompt. If no fresh context can run, the reviewers, the verifier and the auditor must still run fresh: tell the user, and have them run the role in a new session. The researcher may run in your own context, since its evidence is checkable; say that you did.
|
|
23
|
+
3. If the agent can't start a helper, run `cruze review <role> --item <ref>`, where the role is `design-reviewer`, `code-reviewer`, `verifier`, `researcher`, `auditor` or `surveyor`. Add `--base <commit>` for the code under review. It starts the agent configured under `review:` in `.cruze/config.yaml` in a fresh context and prints the role's report. When no agent is configured, or it isn't installed, it reports `status: run-in-helper` with the role's full prompt instead: start a helper, such as a subagent, and give it exactly that prompt. The configured agent can run only the commands `review.command` allows: before running the verifier, or a design reviewer whose design spells out commands to check against their `--help`, add those commands with the user's agreement. If no fresh context can run, the reviewers, the verifier and the auditor must still run fresh: tell the user, and have them run the role in a new session. The researcher may run in your own context, since its evidence is checkable; say that you did.
|
|
24
24
|
4. Take the report as it comes back. Check its evidence against the files before you act on any finding.
|
|
25
25
|
|
|
26
26
|
## Running a review
|
|
@@ -7,6 +7,7 @@ You review the code a change adds, before it lands. You work in two lanes. The s
|
|
|
7
7
|
- The change's `change.md`, and the output of `cruze show <ref>`: the elements it builds, the scenarios it delivers, the settled decisions and the ledger. Read an element it lists as cited only when the diff calls it.
|
|
8
8
|
- The diff under review, from `git diff <base>` for the base commit you are given.
|
|
9
9
|
- The ADRs in `docs/adr/`, and the specs in `docs/specs/` for the capabilities the change touches.
|
|
10
|
+
- The references `cruze show` lists, at the sections the change's elements cite.
|
|
10
11
|
- The settled ledger, as `.agents/skills/cruze-roles/SKILL.md` defines it, with the output of `cruze journal list --event disposition --item <ref>`.
|
|
11
12
|
- The deviations the change recorded, from `cruze journal list --event deviation --item <ref>`.
|
|
12
13
|
- The output of `cruze check` and `cruze trace --change <ref>`.
|
|
@@ -19,7 +20,7 @@ Review against these items and nothing else. Each finding names one.
|
|
|
19
20
|
### Spec lane
|
|
20
21
|
|
|
21
22
|
1. **Scenarios.** Every scenario the change delivers is implemented, and has a test that carries its ID, drives the seam the test plan names, and takes its expected values from the scenario.
|
|
22
|
-
2. **Failures.** Each failure scenario produces the error it specifies and leaves the state it specifies.
|
|
23
|
+
2. **Failures.** Each failure scenario produces the error it specifies and leaves the state it specifies. Wire formats, codes and behaviour that a reference governs follow the section the design cites.
|
|
23
24
|
3. **Scope.** The code adds no behaviour the change's scope doesn't deliver, and no public item, variant or operation that only a later change uses. Unrequested or early code is a finding, even when it looks useful or the next change needs it.
|
|
24
25
|
4. **Protected tests.** No behaviour, contract or smoke test for an approved scenario, port or flow was weakened, skipped or deleted without a delta that changes its subject.
|
|
25
26
|
|
|
@@ -27,7 +28,7 @@ Review against these items and nothing else. Each finding names one.
|
|
|
27
28
|
|
|
28
29
|
5. **Placement.** Every file the diff adds or changes is where the architecture puts its owner, and matches the task's file targets or a recorded deviation.
|
|
29
30
|
6. **Direction.** Dependencies point inward. No technology type crosses a port. Inbound adapters hold no domain rules or workflow.
|
|
30
|
-
7. **Size.** No file holds two responsibilities
|
|
31
|
+
7. **Size.** No file holds two responsibilities, and none passes its budget without a recorded exception. No file was split by length alone, leaving one responsibility across files, and no code is bent to fit a budget, such as a type alias, a string where an error type belongs, types merged or a function placed away from its owner to stay under a limit.
|
|
31
32
|
8. **Idiom.** The code is idiomatic for the language, uses its standard tooling, and follows the language file's rules, including where tests live. Tests reuse the fixtures and harnesses in the test support module rather than redefining them.
|
|
32
33
|
9. **Libraries.** Nothing hand-writes what a mature library does without a recorded `build` decision, and every new dependency has a recorded approval. No code works around a dependency or a feature: hand-written code, a shell command or a less idiomatic construct chosen because the clean one needs a dependency is a finding.
|
|
33
34
|
10. **Contracts.** Each adapter honours its port's `Operations`, and each fake passes the same contract tests as the real adapter.
|
|
@@ -11,6 +11,7 @@ You review a design, or a change's plan, before any code exists. You find the pl
|
|
|
11
11
|
- The living docs it builds on: `docs/vision.md`, `docs/glossary.md`, the ADRs in `docs/adr/`, the specs in `docs/specs/` that the artifact cites, and, from `docs/architecture.md`, its Overview, Dependency rules, Cross-cutting concerns and Decisions sections plus each element `cruze show` lists as cited. Read the whole architecture only at project scope.
|
|
12
12
|
- The settled ledger, as `.agents/skills/cruze-roles/SKILL.md` defines it, with the output of `cruze journal list --event disposition --item <ref>`.
|
|
13
13
|
- The output of `cruze validate`.
|
|
14
|
+
- The references `cruze show` lists, from `references:` in `.cruze/config.yaml`: read the sections the design cites or relies on.
|
|
14
15
|
- The standards: `.agents/skills/cruze-hexagonal-design/SKILL.md` and `.agents/skills/cruze-formats/SKILL.md`.
|
|
15
16
|
|
|
16
17
|
## Rubric
|
|
@@ -24,15 +25,26 @@ Review against these items and nothing else. Each finding names one.
|
|
|
24
25
|
5. **Adopt or build.** Every component that isn't domain logic has an adopt-or-build decision with a reason, and every new dependency is named with the features the design uses. Every type, trait, macro, framework or tool the design names from outside the project comes from a dependency and feature the design approves, declared by each crate, package or target that uses it, test support included, and allowed by the import rules in `XC` and the `layers:` in `.cruze/config.yaml`. A choice whose reason is avoiding a dependency, a crate or a feature, where the idiomatic design needs one, is a concern: name the idiomatic design. Every command the design spells out, such as a CI step, is spelled as that version's `--help` or source shows it. A claim the design relies on that the research left unverified is a blocker.
|
|
25
26
|
6. **Contracts.** Every new or changed port's `Operations` state what each operation guarantees, how it fails, and who owns cleanup. Check each use case and each port operation for the values build would otherwise have to pick: a named error for every failure a caller can meet, where a panic, an `expect` or "caller error" counts as missing; a number for every timeout, limit, buffer and poll interval; what a caller observes after each state transition, and what happens to queued or in-flight work at it; and one owner for every message a scenario quotes. Each one missing is a blocker: two projects settled 35 of them during build, as deviations.
|
|
26
27
|
7. **Flows.** The key scenarios have a `FLOW` whose participants are all in its `Elements`, including the failure paths the scenarios need.
|
|
27
|
-
8. **Consistency.** The design agrees with `docs/architecture.md`, the accepted ADRs and what earlier changes landed, or changes them explicitly through a `MODIFIED` operation or a new ADR. Its own elements agree with each other. Check every pair, not a sample:
|
|
28
|
+
8. **Consistency.** The design agrees with `docs/architecture.md`, the accepted ADRs, the references and what earlier changes landed, or changes them explicitly through a `MODIFIED` operation or a new ADR. Its own elements agree with each other. Check every pair, not a sample:
|
|
28
29
|
- each use case's `Output` and `Errors` against the `FLOW`s that serve it and the `XC` rules that apply to it;
|
|
29
30
|
- each adapter's duties against the port it implements and the `XC` rules it follows;
|
|
30
31
|
- each guarantee, such as "always fits" or "never returns", against the limits and failures that bound it;
|
|
31
32
|
- each `XC` rule against the other `XC` rules, the `RULE` elements and the `layers:` in `.cruze/config.yaml`, such as a logging rule that every module logs against a build rule that forbids the logging import;
|
|
32
33
|
- each pair of use cases that change the same state, for an interleaving that leaves it wrong, against the concurrency rule in `XC`;
|
|
33
34
|
- at feature scope, the prose in `docs/architecture.md` the delta makes untrue, such as an Overview sentence or a `D<n>` line describing an element the delta splits or replaces.
|
|
35
|
+
- each new or renamed operation, method or builder step against the others the same type, trait or caller reaches, for two names a caller can't tell apart, such as an `events` method beside an `events` trait method the type also implements.
|
|
34
36
|
9. **Slicing.** Each change is a vertical slice that delivers scenarios end to end and can land on its own. The order respects `Depends on`, and the first change proves the design end to end. Each change carries enough work to be worth its own plan review, code review and verify: changes that build one or two elements each on the same adapters or screens, in a chain, are one change split too fine. A change that one verifier run can't hold is split too coarse. A `change-too-small`, `change-too-large` or `feature-size` warning from `cruze validate` is a finding, unless a decision records the user accepting it. A change `Builds` an element only when its delivered scenarios prove the whole body; an element whose duties arrive in several changes is split into elements that each arrive whole. `Depends on` names every earlier change that builds something this change's scenarios need. At project scope, the roadmap starts with the walking skeleton, and every goal in the release is covered.
|
|
35
37
|
10. **Ambiguity.** Name each point where a fresh implementer could reasonably build something different from what the user expects.
|
|
38
|
+
11. **Flow control and recovery.** Follow each message, request and event from its source to its consumer through every task, channel and queue the design names, against `.agents/skills/cruze-hexagonal-design/runtime.md`. Check:
|
|
39
|
+
- each bounded queue or buffer: what a sender does when it is full, and which other work waits meanwhile. One slow peer, link, subscriber or consumer holds up only its own work unless a decision says otherwise, so one queue or one task serving several peers needs a reason. A limit meant to tell a stalled consumer from a burst says how it tells them apart;
|
|
40
|
+
- each place several sources feed one consumer: the order it takes them in, so that none starves;
|
|
41
|
+
- each asynchronous result: how it reaches the request that waits for it, including across a reconnect or a retry;
|
|
42
|
+
- each recovery, such as a reconnect, resubscribe, relist or retry: its schedule and limit, what happens when the recovery itself fails, what is lost between the failure and the recovery and how the consumer learns of it, and the order of steps that loses nothing, such as subscribing before relisting;
|
|
43
|
+
- each loop, task and retry: what stops it, and what shutdown does with the work it holds;
|
|
44
|
+
- each value the design compares with observed state: an adapter can observe it, or the difference is reported for good;
|
|
45
|
+
- each exclusive resource, such as a socket, a device or kernel state: what happens when a second instance or caller claims it.
|
|
46
|
+
|
|
47
|
+
Each one missing that a scenario or an `XC` rule depends on is a blocker: in two projects, 7 of 14 defect rethinks after a design review were one of these, found as late as verify.
|
|
36
48
|
|
|
37
49
|
## Plan rubric
|
|
38
50
|
|
package/skills/roles/verifier.md
CHANGED
|
@@ -18,6 +18,8 @@ You check that a change actually works when someone uses it. The tests passing i
|
|
|
18
18
|
- Compare what you observe with its THEN, word for word where the scenario quotes output.
|
|
19
19
|
|
|
20
20
|
A scenario whose row runs through a use case with fakes is proved by its behaviour test, which passed at the checks commit: give the test's file and name as `Ran:` and the commit as `Observed:`, and don't drive it. Driving every scenario cost one app project 31 minutes a change, for one defect in 28 runs.
|
|
21
|
+
|
|
22
|
+
A change that delivers no scenarios, such as a prefactor that keeps behaviour, is proved by the suite that passed at the checks commit. Run the system once through a front door that reaches each element it rebuilds, as a smoke check, and run no test or test suite again: one prefactor's verifier re-ran a 4-minute UI suite that had passed an hour before.
|
|
21
23
|
4. **Probe one step further.** For each scenario you ran, try the nearest thing a user would plausibly do next or get wrong, such as the same command twice, a typo in the input, or an interrupt. Report surprises; they are not failures of the scenario.
|
|
22
24
|
5. **Clean up** everything you created outside the project.
|
|
23
25
|
|
package/skills/verify/SKILL.md
CHANGED
|
@@ -25,9 +25,9 @@ Verify answers one question: does this change work when someone uses it? Green t
|
|
|
25
25
|
- The review's outcome, with each finding's disposition.
|
|
26
26
|
- The path to `manual-test.md`, with a recommendation to run it before accepting.
|
|
27
27
|
9. **Ask for the decision:** "Do you accept this change, or send it back?"
|
|
28
|
-
10. **Record it** with `cruze journal add verification --set result=accepted --set summary="<one line>" --item <ref>`, or
|
|
29
|
-
- A defect in code a task already built: `cruze task reopen T<n> "<reason>"
|
|
30
|
-
- A design that can't deliver the scenario: `rethink`.
|
|
28
|
+
10. **Record it** with `cruze journal add verification --set result=accepted --set summary="<one line>" --item <ref>`, or `--set result=sent-back --set back_to=<build|rethink>`. On sent-back, say what goes back:
|
|
29
|
+
- A defect in code a task already built: `back_to=build`, then `cruze task reopen T<n> "<reason>"`.
|
|
30
|
+
- A design that can't deliver the scenario: `back_to=rethink`. Leave the tasks to the rethink, which reopens the ones its amendment changes, and `cruze next` names `rethink` until it is recorded.
|
|
31
31
|
|
|
32
32
|
Done when the decision is recorded.
|
|
33
33
|
|
|
@@ -13,6 +13,8 @@ export interface CliContext extends ProjectDeps {
|
|
|
13
13
|
runner: AgentRunner;
|
|
14
14
|
/** Name of the working directory, the default project name. */
|
|
15
15
|
directoryName: string;
|
|
16
|
+
/** The working directory relative to the project root, empty at the root; file arguments are relative to it. */
|
|
17
|
+
workingDirectory: string;
|
|
16
18
|
}
|
|
17
19
|
|
|
18
20
|
/** What a command produces: a JSON result for agents, a summary for people, and whether it passed. */
|
|
@@ -1,9 +1,10 @@
|
|
|
1
|
+
import { posix } from "node:path";
|
|
1
2
|
import { checkCode, traceTests } from "../../../../app/use_cases/check_code.ts";
|
|
2
3
|
import { landChange } from "../../../../app/use_cases/land_change.ts";
|
|
3
4
|
import type { CliContext, CommandResult, Options } from "../cli_context.ts";
|
|
4
5
|
|
|
5
6
|
export async function runCheck(context: CliContext, args: string[], options: Options): Promise<CommandResult> {
|
|
6
|
-
const files = [...options.file, ...args];
|
|
7
|
+
const files = [...options.file, ...args].map((file) => posix.normalize(posix.join(context.workingDirectory, file)));
|
|
7
8
|
const report = await checkCode(context, { ci: options.ci, ...(files.length === 0 ? {} : { files }) });
|
|
8
9
|
const lines = report.findings.map((f) => `${f.severity} ${f.path}${f.line === undefined ? "" : `:${f.line}`} ${f.message} [${f.rule}]${f.pending === true ? " (pending realign)" : ""}`);
|
|
9
10
|
lines.push(`Checked ${report.checked} file(s): ${report.passed ? "passed" : "failed"}.`);
|
|
@@ -14,9 +14,14 @@ function render(report: ScopeReport): string {
|
|
|
14
14
|
lines.push("## Elements", "", ...blocks(report.elements), "## Scenarios", "", ...blocks(report.scenarios));
|
|
15
15
|
lines.push("## Cited, not shown", "");
|
|
16
16
|
lines.push(...(report.cited.length === 0 ? ["None."] : report.cited.map((c) => `- ${c.id}: ${c.title} (${c.source})`)), "");
|
|
17
|
-
lines.push("## Settled decisions", "", ...(report.decisions.length === 0 ? ["None."] : report.decisions), "");
|
|
17
|
+
if (report.feature === undefined) lines.push("## Settled decisions", "", ...(report.decisions.length === 0 ? ["None."] : report.decisions), "");
|
|
18
|
+
else {
|
|
19
|
+
lines.push("## Settled decisions of the feature", "", ...(report.decisions.length === 0 ? ["None."] : report.decisions), "");
|
|
20
|
+
lines.push("## Settled decisions of the change", "", ...(report.changeDecisions.length === 0 ? ["None."] : report.changeDecisions), "");
|
|
21
|
+
}
|
|
18
22
|
lines.push("## Dispositions", "");
|
|
19
23
|
lines.push(...(report.dispositions.length === 0 ? ["None."] : report.dispositions.map((d) => `- [${d.disposition}, ${d.review}] ${d.finding}: ${d.reason}`)), "");
|
|
24
|
+
if (report.references.length > 0) lines.push("## References", "", ...report.references.map((r) => `- \`${r.path}\`: ${r.use}`), "");
|
|
20
25
|
if (report.missing.length > 0) lines.push("## Missing", "", ...report.missing.map((id) => `- ${id} is defined nowhere`), "");
|
|
21
26
|
return lines.join("\n").trimEnd();
|
|
22
27
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { execFile } from "node:child_process";
|
|
1
|
+
import { execFile, spawn } from "node:child_process";
|
|
2
2
|
import { promisify } from "node:util";
|
|
3
3
|
import type { Repository } from "../../app/ports/repository.ts";
|
|
4
4
|
|
|
@@ -37,13 +37,24 @@ export class GitRepository implements Repository {
|
|
|
37
37
|
}
|
|
38
38
|
|
|
39
39
|
async filesOnBranch(branch: string, dir: string): Promise<Map<string, string>> {
|
|
40
|
-
const files = new Map<string, string>();
|
|
41
40
|
const listing = await this.git("ls-tree", "-r", "--name-only", branch, "--", dir);
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
}
|
|
46
|
-
return
|
|
41
|
+
const paths = (listing ?? "").split("\n").filter((p) => p !== "");
|
|
42
|
+
if (paths.length === 0) return new Map();
|
|
43
|
+
// One git process for every file: a process per file took up to two minutes on a project with 26 branches.
|
|
44
|
+
const blobs = await this.catFiles(paths.map((path) => `${branch}:${path}`));
|
|
45
|
+
return new Map(paths.flatMap((path, i) => (blobs[i] === undefined ? [] : [[path, blobs[i]] as [string, string]])));
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/** The contents of each object, in order, through one `git cat-file --batch`; undefined for one that is missing. */
|
|
49
|
+
private catFiles(objects: string[]): Promise<Array<string | undefined>> {
|
|
50
|
+
return new Promise((resolve) => {
|
|
51
|
+
const child = spawn("git", ["cat-file", "--batch"], { cwd: this.root, stdio: ["pipe", "pipe", "ignore"] });
|
|
52
|
+
const chunks: Buffer[] = [];
|
|
53
|
+
child.stdout.on("data", (chunk: Buffer) => chunks.push(chunk));
|
|
54
|
+
child.on("error", () => resolve(objects.map(() => undefined)));
|
|
55
|
+
child.on("close", () => resolve(parseBatch(Buffer.concat(chunks), objects.length)));
|
|
56
|
+
child.stdin.end(`${objects.join("\n")}\n`);
|
|
57
|
+
});
|
|
47
58
|
}
|
|
48
59
|
|
|
49
60
|
private async git(...args: string[]): Promise<string | null> {
|
|
@@ -55,3 +66,25 @@ export class GitRepository implements Repository {
|
|
|
55
66
|
}
|
|
56
67
|
}
|
|
57
68
|
}
|
|
69
|
+
|
|
70
|
+
/** Splits `git cat-file --batch` output: a `<sha> <type> <size>` header, the bytes and a newline, or `<name> missing`. */
|
|
71
|
+
function parseBatch(output: Buffer, count: number): Array<string | undefined> {
|
|
72
|
+
const results: Array<string | undefined> = [];
|
|
73
|
+
let at = 0;
|
|
74
|
+
while (results.length < count && at < output.length) {
|
|
75
|
+
const headerEnd = output.indexOf(10, at);
|
|
76
|
+
if (headerEnd === -1) break;
|
|
77
|
+
const header = output.subarray(at, headerEnd).toString("utf8");
|
|
78
|
+
const size = /^[0-9a-f]+ \w+ (\d+)$/.exec(header)?.[1];
|
|
79
|
+
if (size === undefined) {
|
|
80
|
+
results.push(undefined);
|
|
81
|
+
at = headerEnd + 1;
|
|
82
|
+
continue;
|
|
83
|
+
}
|
|
84
|
+
const start = headerEnd + 1;
|
|
85
|
+
results.push(output.subarray(start, start + Number(size)).toString("utf8"));
|
|
86
|
+
at = start + Number(size) + 1;
|
|
87
|
+
}
|
|
88
|
+
while (results.length < count) results.push(undefined);
|
|
89
|
+
return results;
|
|
90
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { existsSync } from "node:fs";
|
|
2
|
+
import { dirname, join } from "node:path";
|
|
3
|
+
|
|
4
|
+
/** The nearest directory at or above `start` that holds `.cruze/config.yaml`, or undefined when none does. */
|
|
5
|
+
export function findProjectRoot(start: string): string | undefined {
|
|
6
|
+
let dir = start;
|
|
7
|
+
for (;;) {
|
|
8
|
+
if (existsSync(join(dir, ".cruze", "config.yaml"))) return dir;
|
|
9
|
+
const parent = dirname(dir);
|
|
10
|
+
if (parent === dir) return undefined;
|
|
11
|
+
dir = parent;
|
|
12
|
+
}
|
|
13
|
+
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { CruzeError } from "../../domain/cruze_error.ts";
|
|
2
|
+
import { PATHS } from "../../domain/project/layout.ts";
|
|
2
3
|
import { readProgress } from "../../domain/project/progress.ts";
|
|
3
4
|
import { buildProjectView } from "../../domain/project/project_view.ts";
|
|
4
5
|
import { findWorkItem, type WorkItem } from "../../domain/project/work_items.ts";
|
|
@@ -59,7 +60,9 @@ export async function findOverlaps(deps: ProjectDeps, ref?: string): Promise<{ c
|
|
|
59
60
|
const mine = touchedIds(view, change);
|
|
60
61
|
const overlaps: Overlap[] = [];
|
|
61
62
|
for (const other of await deps.repository.otherBranches()) {
|
|
62
|
-
|
|
63
|
+
// Landed work is archived, so a branch's work in flight is under features/ and changes/ only.
|
|
64
|
+
const inFlight = [...(await deps.repository.filesOnBranch(other, PATHS.featuresDir)), ...(await deps.repository.filesOnBranch(other, PATHS.changesDir))];
|
|
65
|
+
const theirs = buildProjectView(new Map([...view.snapshot].filter(([p]) => p.startsWith("docs/")).concat(inFlight)));
|
|
63
66
|
// Work in flight on a branch is the change bound to it that hasn't landed, there or here.
|
|
64
67
|
const boundThere = theirs.items.filter((i) => {
|
|
65
68
|
const bound = readProgress(i.doc).branch;
|
|
@@ -14,6 +14,8 @@ export interface Disposition {
|
|
|
14
14
|
export interface ScopeReport extends ScopeView {
|
|
15
15
|
/** The settled ledger of the item: the feature's dispositions and its changes', or a standalone change's. */
|
|
16
16
|
dispositions: Disposition[];
|
|
17
|
+
/** The external documents the design must comply with, from `references:` in `.cruze/config.yaml`. */
|
|
18
|
+
references: Array<{ path: string; use: string }>;
|
|
17
19
|
}
|
|
18
20
|
|
|
19
21
|
/**
|
|
@@ -31,5 +33,5 @@ export async function showScope(deps: ProjectDeps, ref: string): Promise<ScopeRe
|
|
|
31
33
|
reason: String(entry["reason"] ?? ""),
|
|
32
34
|
review: String(entry["review"] ?? ""),
|
|
33
35
|
}));
|
|
34
|
-
return { ...scope, dispositions };
|
|
36
|
+
return { ...scope, dispositions, references: view.config?.config?.references ?? [] };
|
|
35
37
|
}
|
|
@@ -1,24 +1,61 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* The part of a source file its budgets measure. Budgets apply to source, not tests, so a Rust
|
|
3
|
-
* file's
|
|
3
|
+
* file's test-only modules are left out: rustfmt closes a top-level module with `}` in column 0.
|
|
4
|
+
* A module is test-only when its `cfg` needs `test`, as `#[cfg(test)]` or
|
|
5
|
+
* `#[cfg(all(test, feature = "schema"))]` do; other attributes may sit between the two.
|
|
4
6
|
*/
|
|
5
7
|
export function budgetedText(path: string, text: string): string {
|
|
6
8
|
if (!path.endsWith(".rs")) return text;
|
|
7
9
|
const lines = text.split("\n");
|
|
8
10
|
const kept: string[] = [];
|
|
9
11
|
for (let i = 0; i < lines.length; i++) {
|
|
10
|
-
const
|
|
11
|
-
if (
|
|
12
|
+
const start = testModuleStart(lines, i);
|
|
13
|
+
if (start === null) {
|
|
12
14
|
kept.push(lines[i] ?? "");
|
|
13
15
|
continue;
|
|
14
16
|
}
|
|
15
|
-
if (
|
|
16
|
-
i
|
|
17
|
+
if ((lines[start] ?? "").trimEnd().endsWith(";")) {
|
|
18
|
+
i = start;
|
|
17
19
|
continue;
|
|
18
20
|
}
|
|
19
|
-
let end =
|
|
21
|
+
let end = start + 1;
|
|
20
22
|
while (end < lines.length && lines[end] !== "}") end++;
|
|
21
23
|
i = end;
|
|
22
24
|
}
|
|
23
25
|
return kept.join("\n");
|
|
24
26
|
}
|
|
27
|
+
|
|
28
|
+
/** The line of the `mod` a test-only `cfg` at `index` gates, or null when it gates no module. */
|
|
29
|
+
function testModuleStart(lines: string[], index: number): number | null {
|
|
30
|
+
if (!needsTest(lines[index]?.trim() ?? "")) return null;
|
|
31
|
+
let next = index + 1;
|
|
32
|
+
while (/^\s*#\[/.test(lines[next] ?? "")) next++;
|
|
33
|
+
return /^(?:pub(?:\([^)]*\))?\s+)?mod\s+\w+\s*[{;]/.test(lines[next] ?? "") ? next : null;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function needsTest(attribute: string): boolean {
|
|
37
|
+
const predicate = /^#\[cfg\((.*)\)\]$/.exec(attribute)?.[1]?.trim();
|
|
38
|
+
if (predicate === undefined) return false;
|
|
39
|
+
if (predicate === "test") return true;
|
|
40
|
+
const all = /^all\((.*)\)$/.exec(predicate)?.[1];
|
|
41
|
+
return all !== undefined && topLevelArguments(all).includes("test");
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/** Splits `a, b(c, d), e` at its top-level commas. */
|
|
45
|
+
function topLevelArguments(text: string): string[] {
|
|
46
|
+
const parts: string[] = [];
|
|
47
|
+
let depth = 0;
|
|
48
|
+
let current = "";
|
|
49
|
+
for (const char of text) {
|
|
50
|
+
if (char === "," && depth === 0) {
|
|
51
|
+
parts.push(current.trim());
|
|
52
|
+
current = "";
|
|
53
|
+
continue;
|
|
54
|
+
}
|
|
55
|
+
if (char === "(") depth++;
|
|
56
|
+
if (char === ")") depth--;
|
|
57
|
+
current += char;
|
|
58
|
+
}
|
|
59
|
+
parts.push(current.trim());
|
|
60
|
+
return parts;
|
|
61
|
+
}
|
|
@@ -55,11 +55,11 @@ export function checkSource(input: CheckInput): CheckFinding[] {
|
|
|
55
55
|
const lines = budgeted.split("\n").length - (budgeted.endsWith("\n") ? 1 : 0);
|
|
56
56
|
if (!excepted && lines > config.check.maxLines) {
|
|
57
57
|
const measured = budgeted === text ? "" : ", not counting its inline tests";
|
|
58
|
-
findings.push({ path, rule: "max-lines", severity: "warning", message: `${lines} lines${measured}, over the budget of ${config.check.maxLines}; split it
|
|
58
|
+
findings.push({ path, rule: "max-lines", severity: "warning", message: `${lines} lines${measured}, over the budget of ${config.check.maxLines}; split it if it holds a second responsibility, or list it under check.exceptions` });
|
|
59
59
|
}
|
|
60
60
|
const types = countTypes(path, budgeted);
|
|
61
61
|
if (!excepted && types !== null && types > config.check.maxTypes) {
|
|
62
|
-
findings.push({ path, rule: "max-types", severity: "warning", message: `${types} top-level types, over the budget of ${config.check.maxTypes}` });
|
|
62
|
+
findings.push({ path, rule: "max-types", severity: "warning", message: `${types} top-level types, over the budget of ${config.check.maxTypes}; split it if it holds a second responsibility, or list it under check.exceptions` });
|
|
63
63
|
}
|
|
64
64
|
if (!excepted && isRustTestFileInSource(path)) {
|
|
65
65
|
findings.push({ path, rule: "test-placement", severity: "warning", message: "Rust tests that leave their inline module go in the crate's tests/ directory, not under src/" });
|
package/src/domain/config.ts
CHANGED
|
@@ -26,6 +26,8 @@ export interface CruzeConfig {
|
|
|
26
26
|
review: { command?: string[]; decide: ReviewDecide };
|
|
27
27
|
/** The size band of a change, so each one is worth its own plan, review and verify. */
|
|
28
28
|
changes: ChangeLimits;
|
|
29
|
+
/** External documents the design must comply with, such as a protocol specification, each with what it governs. */
|
|
30
|
+
references: Array<{ path: string; use: string }>;
|
|
29
31
|
}
|
|
30
32
|
|
|
31
33
|
/** Which review findings the user decides: only the exceptions, or every one. */
|
|
@@ -132,6 +134,15 @@ export function parseConfig(text: string): ConfigResult {
|
|
|
132
134
|
}),
|
|
133
135
|
};
|
|
134
136
|
|
|
137
|
+
const references = (Array.isArray(root["references"]) ? root["references"] : root["references"] === undefined ? [] : [null]).flatMap((entry: unknown, i: number) => {
|
|
138
|
+
const record = asRecord(entry);
|
|
139
|
+
if (typeof record?.["path"] !== "string" || record["path"].trim() === "" || typeof record["use"] !== "string" || record["use"].trim() === "") {
|
|
140
|
+
problems.push(`references[${i}] needs a path and a non-empty use`);
|
|
141
|
+
return [];
|
|
142
|
+
}
|
|
143
|
+
return [{ path: record["path"], use: record["use"] }];
|
|
144
|
+
});
|
|
145
|
+
|
|
135
146
|
const config: CruzeConfig = {
|
|
136
147
|
version: 1,
|
|
137
148
|
...(typeof cruze === "string" && cruze.trim() !== "" ? { cruze } : {}),
|
|
@@ -148,6 +159,7 @@ export function parseConfig(text: string): ConfigResult {
|
|
|
148
159
|
decide: decide === "all" ? "all" : "exceptions",
|
|
149
160
|
},
|
|
150
161
|
changes,
|
|
162
|
+
references,
|
|
151
163
|
};
|
|
152
164
|
return { config, problems };
|
|
153
165
|
}
|