@printpractical/cruze 0.0.6 → 0.0.8

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 (82) hide show
  1. package/README.md +9 -7
  2. package/dist/adapters/inbound/cli/commands/journal.js +1 -1
  3. package/dist/adapters/inbound/cli/commands/show.js +27 -0
  4. package/dist/adapters/inbound/cli/commands/status.js +19 -3
  5. package/dist/adapters/inbound/cli/commands.js +3 -1
  6. package/dist/app/use_cases/approve_artifact.js +19 -1
  7. package/dist/app/use_cases/check_code.js +10 -3
  8. package/dist/app/use_cases/journal_events.js +8 -5
  9. package/dist/app/use_cases/run_review.js +1 -1
  10. package/dist/app/use_cases/show_scope.js +35 -0
  11. package/dist/domain/check/budget_text.js +24 -0
  12. package/dist/domain/check/check_source.js +6 -3
  13. package/dist/domain/config.js +11 -1
  14. package/dist/domain/journal.js +2 -2
  15. package/dist/domain/project/scope_view.js +118 -0
  16. package/dist/domain/status/work_status.js +13 -2
  17. package/dist/domain/validation/element_rules.js +6 -1
  18. package/dist/domain/validation/size_rules.js +14 -5
  19. package/dist/domain/validation/work_item_rules.js +25 -3
  20. package/dist/domain/versions/rule_versions.js +1 -0
  21. package/dist/main.js +6 -0
  22. package/package.json +1 -1
  23. package/skills/architect/SKILL.md +2 -0
  24. package/skills/architect/feature.md +1 -1
  25. package/skills/architect/project.md +1 -1
  26. package/skills/architect/walkthrough.md +7 -4
  27. package/skills/build/SKILL.md +9 -8
  28. package/skills/dependency-approval/SKILL.md +8 -1
  29. package/skills/envision/SKILL.md +2 -0
  30. package/skills/envision/feature.md +1 -1
  31. package/skills/explore/SKILL.md +1 -1
  32. package/skills/formats/reference/architecture.md +1 -1
  33. package/skills/formats/reference/config.md +5 -1
  34. package/skills/formats/reference/specs.md +1 -1
  35. package/skills/formats/reference/work-items.md +1 -1
  36. package/skills/formats/templates/architecture.md +2 -2
  37. package/skills/grilling/SKILL.md +19 -7
  38. package/skills/grilling/illustrating.md +37 -0
  39. package/skills/hexagonal-design/SKILL.md +10 -3
  40. package/skills/hexagonal-design/languages/rust.md +3 -2
  41. package/skills/hexagonal-design/languages/swift.md +46 -0
  42. package/skills/hexagonal-design/shapes.md +49 -0
  43. package/skills/improve/SKILL.md +51 -0
  44. package/skills/land/SKILL.md +2 -2
  45. package/skills/next/SKILL.md +2 -2
  46. package/skills/plan/SKILL.md +3 -3
  47. package/skills/plan/tasks.md +9 -5
  48. package/skills/realign/SKILL.md +1 -1
  49. package/skills/realign/notes/0.0.7.md +11 -0
  50. package/skills/realign/notes/0.0.8.md +97 -0
  51. package/skills/realign/notes.md +2 -0
  52. package/skills/research/SKILL.md +2 -1
  53. package/skills/rethink/SKILL.md +2 -2
  54. package/skills/roadmap/SKILL.md +2 -0
  55. package/skills/roles/SKILL.md +9 -10
  56. package/skills/roles/code-reviewer.md +8 -8
  57. package/skills/roles/design-reviewer.md +10 -12
  58. package/skills/roles/surveyor.md +40 -0
  59. package/skills/roles/verifier.md +6 -5
  60. package/skills/triage/SKILL.md +2 -0
  61. package/skills/verify/SKILL.md +3 -1
  62. package/src/adapters/inbound/cli/commands/journal.ts +1 -1
  63. package/src/adapters/inbound/cli/commands/show.ts +27 -0
  64. package/src/adapters/inbound/cli/commands/status.ts +19 -3
  65. package/src/adapters/inbound/cli/commands.ts +3 -1
  66. package/src/app/use_cases/approve_artifact.ts +19 -1
  67. package/src/app/use_cases/check_code.ts +10 -3
  68. package/src/app/use_cases/journal_events.ts +8 -5
  69. package/src/app/use_cases/run_review.ts +1 -1
  70. package/src/app/use_cases/show_scope.ts +35 -0
  71. package/src/domain/check/budget_text.ts +24 -0
  72. package/src/domain/check/check_source.ts +6 -3
  73. package/src/domain/config.ts +11 -1
  74. package/src/domain/journal.ts +2 -2
  75. package/src/domain/project/scope_view.ts +118 -0
  76. package/src/domain/status/work_status.ts +13 -2
  77. package/src/domain/validation/element_rules.ts +6 -1
  78. package/src/domain/validation/size_rules.ts +14 -5
  79. package/src/domain/validation/work_item_rules.ts +25 -3
  80. package/src/domain/versions/rule_versions.ts +1 -0
  81. package/src/main.ts +6 -0
  82. package/templates/init/AGENTS.md +1 -1
@@ -12,17 +12,25 @@ export function changeLimits(view ) {
12
12
  return view.config?.config?.changes ?? DEFAULT_CHANGE_LIMITS;
13
13
  }
14
14
 
15
+ /** The rules a size warning comes from. An item stays outside the band only with the user's recorded acceptance. */
16
+ export const SIZE_RULES = ["feature-size", "change-too-small", "change-too-large", "plan-too-large"];
17
+
18
+ /** Whether the user accepted this feature or change outside the band, under `changes.exceptions`. */
19
+ export function sizeAccepted(view , ref ) {
20
+ return changeLimits(view).exceptions.some((exception) => exception.item === ref);
21
+ }
22
+
15
23
  /** 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 ) {
24
+ export function changeRowSizeProblems(view , feature , rows , landed ) {
17
25
  const limits = changeLimits(view);
18
26
  const problems = [];
19
- for (const row of rows.filter((r) => !landed.has(r.change))) {
27
+ for (const row of rows.filter((r) => !landed.has(r.change) && !sizeAccepted(view, `${feature.ref}/${r.change}`))) {
20
28
  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`));
29
+ problems.push(warning(feature.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
30
  }
23
31
  const scenarios = row.delivers.filter((id) => kindOf(id) === "SCN").length;
24
32
  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`));
33
+ problems.push(warning(feature.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
34
  }
27
35
  }
28
36
  return problems;
@@ -31,6 +39,7 @@ export function changeRowSizeProblems(view , path , rows
31
39
  /** A plan with more tasks than one code review and one verify can hold. */
32
40
  export function planSizeProblems(view , path , tasks ) {
33
41
  const { maxTasks } = changeLimits(view);
34
- if (tasks <= maxTasks) return [];
42
+ const ref = view.items.find((item) => item.path === path)?.ref ?? path;
43
+ if (tasks <= maxTasks || sizeAccepted(view, ref)) return [];
35
44
  return [warning(path, undefined, "plan-too-large", `the plan has ${tasks} tasks, over ${maxTasks}; split the change through rethink at feature level`)];
36
45
  }
@@ -11,7 +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
+ import { changeRowSizeProblems, sizeAccepted } from "./size_rules.js";
15
15
 
16
16
  const REQUIRED_SECTIONS = {
17
17
  feature: ["Intent", "Spec delta", "Architecture delta", "Changes"],
@@ -45,6 +45,10 @@ function itemProblems(view , item ) {
45
45
  for (const cited of findIds(text).filter((cited) => !available.has(cited))) {
46
46
  problems.push(error(path, lineOf(text, cited), "unknown-id", `${cited} is cited but defined neither in the living docs nor in this work's deltas`));
47
47
  }
48
+ const dropped = droppedScenarios(view, delta);
49
+ for (const cited of findIds(text).filter((cited) => dropped.has(cited))) {
50
+ problems.push(error(path, lineOf(text, cited), "cites-dropped", `${cited} is cited, but ${dropped.get(cited)} in this work's delta leaves it out, so it won't exist once the work lands; describe it in words instead`));
51
+ }
48
52
  if (item.kind !== "change") problems.push(...deltaProblems(view, item));
49
53
  if (item.kind === "feature") problems.push(...featureScopeProblems(view, item, delta));
50
54
  // A landed change is history: its scope named what it built, which is now built.
@@ -69,10 +73,10 @@ function featureScopeProblems(view , feature , delta
69
73
  );
70
74
  const names = rows.map((row) => row.change);
71
75
  const extra = rows[MAX_CHANGES];
72
- if (extra !== undefined) {
76
+ if (extra !== undefined && !sizeAccepted(view, feature.ref)) {
73
77
  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`));
74
78
  }
75
- problems.push(...changeRowSizeProblems(view, feature.path, rows, landedChanges));
79
+ problems.push(...changeRowSizeProblems(view, feature, rows, landedChanges));
76
80
  for (const row of rows) {
77
81
  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`));
78
82
  if (names.indexOf(row.change) !== names.lastIndexOf(row.change)) problems.push(error(feature.path, row.line, "change-name", `change ${row.change} is listed twice`));
@@ -151,3 +155,21 @@ function scopeIds(item ) {
151
155
  const scope = readScope(item.doc);
152
156
  return scope === null ? [] : [...scope.delivers, ...scope.builds, ...scope.removes];
153
157
  }
158
+
159
+ /**
160
+ * Living scenarios that a MODIFIED requirement in the delta leaves out, each with that requirement.
161
+ * Land drops them, so a citation of one passes validation now and breaks only at land.
162
+ */
163
+ function droppedScenarios(view , delta ) {
164
+ const dropped = new Map ();
165
+ for (const entry of delta.entries.filter((e) => e.element.op === "MODIFIED" && kindOf(e.element.id) === "REQ")) {
166
+ const living = view.living.elements.get(entry.element.id);
167
+ if (living === undefined) continue;
168
+ const kept = new Set(entry.scenarios.map((s) => s.id));
169
+ for (const scenario of view.living.elements.values()) {
170
+ const nested = scenario.doc === living.doc && scenario.element.start > living.element.start && scenario.element.start < living.element.end;
171
+ if (nested && kindOf(scenario.id) === "SCN" && !kept.has(scenario.id) && !delta.ids.has(scenario.id)) dropped.set(scenario.id, entry.element.id);
172
+ }
173
+ }
174
+ return dropped;
175
+ }
@@ -16,6 +16,7 @@ export const RULE_SINCE = {
16
16
  "validate:change-too-small": "0.0.5",
17
17
  "validate:change-too-large": "0.0.5",
18
18
  "validate:plan-too-large": "0.0.5",
19
+ "validate:module-path-glob": "0.0.8",
19
20
  };
20
21
 
21
22
  /** Whether a rule arrived after the version the project's code meets, so it waits for `realign`. */
package/dist/main.js CHANGED
@@ -9,6 +9,12 @@ import { ProcessAgentRunner } from "./adapters/outbound/process_agent_runner.js"
9
9
  import { SystemClock } from "./adapters/outbound/system_clock.js";
10
10
  import { DefaultAnswerPrompter, TerminalPrompter } from "./adapters/outbound/terminal_prompter.js";
11
11
 
12
+ // A reader that stops early, such as `cruze journal list | head`, closes the pipe; that is not an error worth a stack trace.
13
+ process.stdout.on("error", (error ) => {
14
+ if (error.code === "EPIPE") process.exit(0);
15
+ throw error;
16
+ });
17
+
12
18
  const cwd = process.cwd();
13
19
  const exitCode = await runCli(
14
20
  process.argv.slice(2),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@printpractical/cruze",
3
- "version": "0.0.6",
3
+ "version": "0.0.8",
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",
@@ -41,3 +41,5 @@ Architect decides the how and the how big. It designs the domain, the ports and
41
41
  - Project scope: `plan` for the walking-skeleton change.
42
42
  - Feature scope: `plan` for the feature's first change.
43
43
  - Tweak scope: `plan` for the standalone change.
44
+
45
+ Start it in a new session, so its context starts empty.
@@ -33,6 +33,6 @@ The result is a `feature.md` with a complete architecture delta and an ordered l
33
33
 
34
34
  The thresholds are the defaults under `changes:` in `.cruze/config.yaml`; a project can tune them.
35
35
 
36
- Done when `cruze validate` reports no `change-too-small`, `change-too-large` or `feature-size` warning, or the user has accepted one and a `D<n>` line under the feature's `## Decisions` records why. After approval, only a rethink can split a change.
36
+ Done when `cruze validate` reports no `change-too-small`, `change-too-large` or `feature-size` warning. When the user accepts one, record it twice: a `D<n>` line under the feature's `## Settled decisions` saying why, and the item under `changes.exceptions` in `.cruze/config.yaml` with the same reason, which is what stops the warning. `cruze approve` refuses an item with a warning still standing (`size-unaccepted`). After approval, only a rethink can split a change.
37
37
 
38
38
  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.
@@ -16,7 +16,7 @@ The result has four parts, reviewed together and shown at one walkthrough:
16
16
  5. **Ports (`PORT`).** Define every capability the use cases need from outside, named by capability. Write each operation's contract, following `.agents/skills/cruze-hexagonal-design/contracts.md`. Add driving ports only where the hexagonal-design skill calls for them.
17
17
  6. **Adapters (`ADP`).** Name the technology behind each driven port and each inbound adapter that drives use cases. Run the researcher role on every adapter's technology first, and record the result in the adapter's `Adopts` fact. Take every new dependency to the user.
18
18
  7. **Flows (`FLOW`).** Write a `sequenceDiagram` for each of the 2 to 5 most important interactions, including their failure paths. Leave out `Serves` until scenarios exist.
19
- 8. **Modules (`MOD`).** Give each context its hexagonal modules, and give the shared CLI shell and the composition root the `system` scope. Add a `system` module in the `composition` layer for the build and CI files, such as the package manifest and `.github/workflows/`, so the tasks that write them have an owner. Every source file must fall inside some module's `Path`, including namespace files that join a context's modules, such as `src/inventory/mod.rs` or `index.ts`; list them in a `composition` module's `Path` when no other module fits. Every module gets a `Path` and a `Layer`, and every element above gets its `Module` and `File`.
19
+ 8. **Modules (`MOD`).** Name the kind of system in the Overview, and choose the layout that is normal for that kind and the language, following `.agents/skills/cruze-hexagonal-design/shapes.md` and the language file. Give each context the fewest modules that keep its layer rules, usually one `core` module and its adapters, and give the shared CLI shell and the composition root the `system` scope. Add a `system` module in the `composition` layer for the build and CI files, such as the package manifest and `.github/workflows/`, so the tasks that write them have an owner. Every source file must fall inside some module's `Path`, including namespace files that join a context's modules, such as `src/inventory/mod.rs` or `index.ts`; list them in a `composition` module's `Path` when no other module fits. Every module gets a `Path` and a `Layer`, and every element above gets its `Module` and `File`.
20
20
  9. **Dependency rules (`RULE`).** State what may import what, in one or two sentences each.
21
21
  10. **Cross-cutting concerns (`XC`).** Cover errors, configuration, logging, concurrency and security, plus build and delivery (CI, formatting, linting). Read `.agents/skills/cruze-hexagonal-design/runtime.md` for concurrency.
22
22
  11. **Decisions.** Write an ADR, with `cruze new adr <slug> --title "<title>"`, for each decision that passes the ADR test. Record the other project decisions as `D<n>` lines under `## Decisions`.
@@ -6,12 +6,15 @@ At feature scope, cover only what the feature adds, changes or removes. At proje
6
6
 
7
7
  ## The overview
8
8
 
9
- Write it in the glossary's terms, as what callers can do and what happens when they do, not as a list of elements. Keep it to what the user can read in a minute.
9
+ Write it in the glossary's terms, as what callers can do and what happens when they do, not as a list of elements. Keep the prose to what the user can read in a minute, and open it with the line that says where things stand, from `.agents/skills/cruze-grilling/illustrating.md`.
10
10
 
11
11
  1. **What it does.** The intent in two sentences. Then one line per use case, or at feature scope per requirement: who asks for what, what they get, and the main way it fails.
12
12
  2. **What it touches.** Each new external system, and each new dependency named with its purpose and the approval behind it. Also each component you chose to build yourself, with the reason.
13
- 3. **The slices.** The changes in order, one line each, saying what each delivers and why the first is first. At project scope, the roadmap's phases and the walking skeleton instead.
14
- 4. **What is yours to decide.** The review findings the roles skill brings to the user, any `cruze validate` warning still standing, such as a change over its size limit, any point where a fresh implementer could still build something different, and any rule you challenged that the user overrode.
13
+ 3. **What it looks like.** Illustrated as `.agents/skills/cruze-grilling/illustrating.md` says:
14
+ - A plain-text diagram of the main scenario crossing the design: the inbound adapter, the use case, the domain elements and the ports, marking what is new.
15
+ - A code sketch, in the project's language, of what the new and changed elements look like: the port operations and use case signatures with their errors, the main entity, and the call an inbound adapter makes.
16
+ 4. **The slices.** The changes in order, one line each, saying what each delivers and why the first is first. At project scope, the roadmap's phases and the walking skeleton instead.
17
+ 5. **What is yours to decide.** The review findings the roles skill brings to the user, any `cruze validate` warning still standing, such as a change over its size limit, any point where a fresh implementer could still build something different, and any rule you challenged that the user overrode.
15
18
 
16
19
  ## The documents
17
20
 
@@ -20,7 +23,7 @@ List each file the user should read in full, with the sections that changed:
20
23
  - Feature scope: the feature's `feature.md`, and the ADRs it adds.
21
24
  - Project scope: `docs/architecture.md`, the `layers:` in `.cruze/config.yaml`, `docs/roadmap.md`, the walking-skeleton `change.md`, and the ADRs in `docs/adr/`.
22
25
 
23
- The diagrams are Mermaid inside those files. Say where the user can render them, such as an editor preview or GitHub, rather than pasting them.
26
+ The documents' diagrams are Mermaid. Say where the user can render them, such as an editor preview or GitHub, rather than pasting them; the overview's plain-text sketches are for reading in the conversation.
24
27
 
25
28
  End the message with: "Read these in full, then tell me what to change, or approve. Do you approve this design?"
26
29
 
@@ -16,11 +16,11 @@ Build turns an approved plan into code, one task at a time. Each task starts wit
16
16
  - `.agents/skills/cruze-behavioural-testing/SKILL.md`
17
17
  - `.agents/skills/cruze-hexagonal-design/SKILL.md`, and its language file when one exists for the project's language
18
18
  - `.agents/skills/cruze-dependency-approval/SKILL.md`
19
- 3. **Read the plan and the design.** Read the change's `change.md` and its feature's `feature.md`. Read the full body of every element the change builds, and every scenario it delivers. Read `commands:` in `.cruze/config.yaml`.
19
+ 3. **Read the plan and the design.** Read the change's `change.md`. Run `cruze show <ref>` and read its output: the full body of every element the change builds, every scenario it delivers, and the settled decisions. Read an element it lists as cited only when the task calls it, from the place it names, never `feature.md` or `docs/architecture.md` whole. Read `commands:` in `.cruze/config.yaml`.
20
20
  4. **Build each open task in order,** following the task loop below. Skip the tasks already ticked in `## Progress`.
21
21
  5. **Finish.** When every task is ticked:
22
22
  - Run the format, lint and build commands and the full suite, as `.agents/skills/cruze-formats/reference/config.md` defines it under `commands`.
23
- - Run `cruze check` and `cruze trace`, and quote their results.
23
+ - Run `cruze check --ci` and `cruze trace`, and quote their results.
24
24
  - When all of them pass, record the commit they ran at with `cruze journal add checks --set commit=<commit> --item <ref>`, where the commit is the output of `git rev-parse --short HEAD`. Verify quotes this instead of running the suite again.
25
25
  - Commit the progress record with a message such as `chore: record progress on <change>`.
26
26
  - Done when all of them pass and the checks are recorded.
@@ -30,26 +30,27 @@ Build turns an approved plan into code, one task at a time. Each task starts wit
30
30
  For task `T<n>`:
31
31
 
32
32
  1. **Read it.** Note its owner, its files, the IDs it proves, and the test plan rows for those IDs.
33
- 2. **Red.** Write the tests for the IDs it proves, at the seams the test plan names, carrying the IDs. Run them, and see them fail for the reason you expect.
33
+ 2. **Red.** Write the tests for the IDs it proves, at the seams the test plan names, carrying the IDs. Run them, and see them fail for the reason you expect. When the task names a break, use it, or any break that compiles and turns the same assertion red.
34
34
  3. **Green.** Write the least code that makes them pass, in the task's files. Code that belongs to another task, or another change, waits for it, even when the next change will need it.
35
- 4. **Check.** Run `cruze check <the task's files>`, the formatter and the linter, and fix what they report. Run the tests the task touched. When `commands:` has a `test_full`, run `test` too, since it is the fast tier. Otherwise run the rest of the suite only when it is quick.
35
+ 4. **Check.** Run `cruze check --ci <the task's files>`, the formatter and the linter, and fix what they report. Meet a budget as the budget rule in `.agents/skills/cruze-hexagonal-design/SKILL.md` says, never by bending the code. Run the tests the task touched. When `commands:` has a `test_full`, run `test` too, since it is the fast tier. Otherwise run the rest of the suite only when it is quick.
36
36
  5. **Commit** with a Conventional Commit message naming the task, such as `feat(access): console session state machine (T2)`.
37
37
  6. **Tick it.** Run `cruze task done T<n>`, which records the commit. Its progress edit rides along in the next commit.
38
38
 
39
39
  A task with no behaviour of its own, such as a package manifest or a plain data type, has no red step. Build it, check it, commit it and tick it, and the first task whose test uses it covers it.
40
40
 
41
- Done for a task when its tests pass, `cruze check` passes for its files, the commit exists and the task is ticked.
41
+ Done for a task when its tests pass, `cruze check --ci` passes for its files, the commit exists and the task is ticked.
42
42
 
43
43
  ## Deviations
44
44
 
45
45
  The plan is design. Within a task you decide only what is local and reversible.
46
46
 
47
- - **Record it and carry on** for a task-level choice: a private helper in another file of the same module, a private type, a fixture or harness added to the test support module that the task's tests need, or two tasks' commits swapped for a reason. The test support module is in every test-writing task's scope, so extend it rather than writing a private helper in a test file. Run `cruze task deviation T<n> "<what you did and why>"`.
47
+ - **Record it and carry on** for a task-level choice: a private helper in another file of the same module, a new file of the owner's module that splits off a responsibility, a `check.exceptions` entry with its reason, a private type, a fixture or harness added to the test support module that the task's tests need, or two tasks' commits swapped for a reason. The test support module is in every test-writing task's scope, so extend it rather than writing a private helper in a test file. Run `cruze task deviation T<n> "<what you did and why>"`.
48
48
  - **Stop and offer `rethink`** when the code needs any of these:
49
49
  - a new element, port operation or scenario;
50
50
  - a file outside the owner's module, other than the test support module;
51
51
  - a change to an approved scenario's behaviour, or to its test;
52
- - a dependency the design doesn't name.
52
+ - a dependency, or a feature of an approved dependency, that the design doesn't name. Ask for it as `.agents/skills/cruze-dependency-approval/SKILL.md` says, and never write around it with hand-written code or a shell command;
53
+ - a limit, timeout, buffer size, message or error case the design doesn't give. Picking one in code hides a gap in the design: the user decides it, at feature level. Two projects settled 35 of them as deviations.
53
54
 
54
55
  Explain what you found, with the evidence, and don't work around it. Completed tasks keep their commits.
55
56
  - Never edit `feature.md`, `docs/architecture.md` or `docs/specs/` during build. Design changes go through `rethink`.
@@ -60,4 +61,4 @@ Never weaken, skip or delete a behaviour, contract or smoke test for an approved
60
61
 
61
62
  ## Next step
62
63
 
63
- `verify`, once every task is ticked and the finishing checks pass.
64
+ `verify`, once every task is ticked and the finishing checks pass. Start it in a new session, so its context starts empty: one session that ran plan, build, verify and land reached 745k tokens.
@@ -9,12 +9,19 @@ Every new dependency needs the user's explicit approval before it is declared or
9
9
 
10
10
  ## Asking
11
11
 
12
- 1. Establish the need: the capability, and why the standard library and the project's already-approved dependencies don't provide it.
12
+ 1. Establish the need: the capability, and what the design looks like with the dependency and without it, using the standard library and the project's already-approved dependencies. Show both as short code sketches, following `.agents/skills/cruze-grilling/illustrating.md`.
13
13
  2. Choose a recommendation, following `.agents/skills/cruze-research/SKILL.md`, which also says where versions come from.
14
14
  3. Say where it will sit: the module that owns it, and the port that keeps it out of the core when it is infrastructure.
15
15
  4. Name the realistic alternatives, including writing it yourself and what owning that code would cost. Never offer a hand-written alternative for cryptography, TLS or a security protocol.
16
16
  5. Ask, then wait. Nothing is declared, installed or written against it until the user answers.
17
17
 
18
+ ## Weighing
19
+
20
+ - Recommend the option that gives the cleanest design: the one an experienced developer in the language would write, with the least code to own. A dependency, a new crate or package, or a feature of an approved dependency is not a cost in itself. Weigh its concrete costs: maintenance and release health, security record, licence, and build time or binary size where a stated constraint makes them matter.
21
+ - "No new dependency" is never the reason for a choice. Name what the chosen option does better.
22
+ - A structure the language requires for an idiom, such as a separate crate for a derive macro, is part of that idiom, not an extra cost.
23
+ - When the clean design needs a dependency or feature nobody approved yet, ask for it. Hand-written code, a shell command or a less idiomatic construct that exists only to avoid asking is a design flaw.
24
+
18
25
  ```markdown
19
26
  **Dependency: `russh` for SSH hops**
20
27
  - Why not std or existing: the standard library has no SSH client, and no approved dependency provides one.
@@ -27,3 +27,5 @@ Envision decides the what: the problem, who has it, and the behaviour that solve
27
27
 
28
28
  - After project scope: run `architect` at project scope.
29
29
  - After feature scope: run `architect "<feature>"`.
30
+
31
+ Start it in a new session, so its context starts empty.
@@ -18,6 +18,6 @@ The result is a feature folder whose `feature.md` holds the intent, the settled
18
18
  - Name every ID with glossary terms. An ID is never reused.
19
19
  - Done when every requirement has a scenario for its main case and one for each failure a user can meet.
20
20
  8. **Leave the design to architect.** Set `## Architecture delta` to `None.` and leave the Changes table with its header row only. Remove the remaining template guides.
21
- 9. **Confirm.** Summarize the intent and each requirement with its scenarios, in plain words, and ask the user to confirm them. Apply any corrections.
21
+ 9. **Confirm.** Summarize the intent and each requirement with its scenarios, in plain words, showing each requirement's main scenario as the user would see it, and ask the user to confirm them. Apply any corrections.
22
22
 
23
23
  Done when the user has confirmed, `cruze validate` reports no errors for the feature, only its `not-designed` warning, and the feature is committed.
@@ -12,7 +12,7 @@ Explore is the optional thinking partner that can come before any other Cruze st
12
12
  1. **Frame the problem.** Ask what prompted the idea, who it affects, and what outcome would make it worthwhile. If the user starts from a solution, restate the problem underneath it and confirm it. Done when you can state the problem in one sentence the user agrees with.
13
13
  2. **Read what exists.** Read whichever of these exist: `docs/vision.md`, `docs/glossary.md`, `docs/architecture.md`, `docs/roadmap.md`, and the code the idea touches. Run `cruze status` to see work in flight. Use the glossary's words from here on, following `.agents/skills/cruze-domain-language/SKILL.md`.
14
14
  3. **Separate facts from guesses.** List what is known evidence, what is a hypothesis, and what is a preference. Look up any fact you can rather than asking the user for it.
15
- 4. **Diverge.** Lay out two to four genuinely different approaches, including leaving things as they are when that is credible. For each, give what it changes, what it leaves alone, its benefits, its costs and failure modes, the assumptions it rests on, and what evidence would tell it apart from the others. Sketch an interaction as a Mermaid `sequenceDiagram` or `flowchart` when that makes a difference clearer.
15
+ 4. **Diverge.** Lay out two to four genuinely different approaches, including leaving things as they are when that is credible. For each, give what it changes, what it leaves alone, its benefits, its costs and failure modes, the assumptions it rests on, and what evidence would tell it apart from the others. Show what each approach looks like, following `.agents/skills/cruze-grilling/illustrating.md`.
16
16
  5. **Converge.** Compare the strongest options against the user's outcome, the vision's goals and constraints, and the architecture. Challenge assumptions and accidental scope growth, following the challenge rules in `.agents/skills/cruze-grilling/SKILL.md`. Recommend one option only when its trade-offs are understood. Otherwise recommend the smallest step that would settle the question, such as a spike, a measurement or a research question.
17
17
  6. **Offer to capture.** Summarize the problem, the options, the trade-offs, your recommendation and the open questions, then ask whether to capture them. When the user declines, stop here: the discussion itself was the result.
18
18
 
@@ -35,7 +35,7 @@ Facts are `- Key: value` items. Keys marked required must be present; the others
35
35
  | `PORT` | `Direction` (`driven` or `driving`), `Operations`, `Module`, `File` | `Implemented by`, `Future` |
36
36
  | `ADP` | `Implements` (a driven port) or `Drives` (use cases), `Technology`, `Module`, `File` | `Adopts` (library and adopt-or-build reason), `Format` (the stored or wire format it owns, or where its schema lives) |
37
37
  | `FLOW` | `Elements`, a Mermaid `sequenceDiagram` | `Serves` (SCN IDs, once they exist), `Failure paths` |
38
- | `MOD` | `Path`, `Layer` (`domain`, `application`, `adapter` or `composition`) | |
38
+ | `MOD` | `Path` (a directory ending in `/`, or one file; list several in backticks, never a glob), `Layer` (`core`, `domain`, `application`, `adapter` or `composition`) | |
39
39
  | `RULE` | one or two sentences of rule | `Enforced by` (config layer names) |
40
40
  | `XC` | prose | |
41
41
 
@@ -33,6 +33,9 @@ changes:
33
33
  min_builds: 3
34
34
  max_scenarios: 30
35
35
  max_tasks: 25
36
+ exceptions:
37
+ - item: 2026-09-25-open-console/02-ssh-hops
38
+ reason: every scenario runs through one reconcile, which can't be split without building it twice
36
39
 
37
40
  layers:
38
41
  - name: inventory-domain
@@ -52,7 +55,7 @@ layers:
52
55
  | `tracker` | Where work items live. `markdown` is the only value in V1. |
53
56
  | `source` | Globs of the source files `cruze check` inspects. |
54
57
  | `tests` | Globs of the files `cruze trace` searches for scenario IDs. |
55
- | `check.max_lines` | Line budget per source file. Test files outside `source` have no budget. |
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. |
56
59
  | `check.max_types` | Budget of top-level types per source file. |
57
60
  | `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. |
58
61
  | `layers` | Each layer has a `name`, the `paths` it covers, and `may_import`, the layers it may depend on. |
@@ -62,6 +65,7 @@ layers:
62
65
  | `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
66
  | `changes.max_scenarios` | A change that delivers more scenarios than this gets a `change-too-large` warning: split it. Default `30`. |
64
67
  | `changes.max_tasks` | A plan with more tasks than this gets a `plan-too-large` warning. Default `25`. |
68
+ | `changes.exceptions` | Features and changes the user accepted outside the band, each with the `item` (a feature's ID, `<feature>/<change>`, or a standalone change's ID) and the `reason`. The item's size warning stops. `cruze approve` refuses an item whose size warning still stands (`size-unaccepted`) until it is listed here or split. |
65
69
 
66
70
  Rules for layers:
67
71
 
@@ -60,6 +60,6 @@ The full new requirement text.
60
60
 
61
61
  - `ADDED CAPABILITY` creates a new spec file, with its title and purpose paragraph. It is only needed for a capability that has no file yet.
62
62
  - `ADDED` introduces a new requirement with all its scenarios.
63
- - `MODIFIED` replaces a requirement's whole block, including every scenario it keeps. A scenario left out is removed.
63
+ - `MODIFIED` replaces a requirement's whole block, including every scenario it keeps. A scenario left out is removed, so no part of the work may cite it by ID (`cites-dropped`); describe it in words.
64
64
  - `REMOVED` retires a requirement and its scenarios, and needs a `Reason` and a `Migration`.
65
65
  - `land` shifts heading levels to fit the spec file and keeps the managed status of scenarios that were already built.
@@ -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. 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`.
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`. `cruze approve` refuses an item with one of these warnings standing (`size-unaccepted`) until the work is split or the user's acceptance is listed under `changes.exceptions`.
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
 
@@ -2,7 +2,7 @@
2
2
 
3
3
  ## Overview
4
4
 
5
- <One paragraph: what the system is, how it is used, and the architectural style.>
5
+ <One paragraph: what the system is and what kind (a library, a daemon or service, a mobile or desktop app, a CLI, or several deliverables), how it is used, and the architectural style.>
6
6
 
7
7
  ### VIEW-context: System context
8
8
 
@@ -81,7 +81,7 @@ sequenceDiagram
81
81
 
82
82
  ### MOD-<context>.<name>: <Module title>
83
83
  - Path: `<directory or file>`
84
- - Layer: <domain | application | adapter | composition>
84
+ - Layer: <core | domain | application | adapter | composition>
85
85
 
86
86
  ## Dependency rules
87
87
 
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: cruze-grilling
3
- description: How to interview the user until design decisions are settled. Questions go in rounds over a tree of decisions, facts are looked up rather than asked, each question carries a recommendation, and challenges the user overrides are recorded. Use when a design step needs decisions from the user, when shaping an idea, vision, feature, architecture or plan with them, when the user steers toward something that breaks a rule, or when the user asks to be grilled or to stress-test a plan.
3
+ description: How to interview the user until design decisions are settled. Questions go in rounds over a tree of decisions, facts are looked up rather than asked, each question carries a recommendation and shows what each option looks like, and challenges the user overrides are recorded. Use when a design step needs decisions from the user, when shaping an idea, vision, feature, architecture or plan with them, when the user steers toward something that breaks a rule, or when the user asks to be grilled or to stress-test a plan, and when showing the user review findings or a design to decide on.
4
4
  ---
5
5
 
6
6
  # Grilling
@@ -12,25 +12,37 @@ Interview the user until you both understand the design the same way. Decisions
12
12
  1. List the open decisions this step must settle, as a tree in which each decision hangs off the ones it depends on. Take them from the sections of the document the step writes and from what the user has already said. Done when every section the step writes has its decisions listed.
13
13
  2. Settle the facts yourself. Anything the repository, the living docs, a `cruze` command or a primary source can answer is a fact, so look it up rather than asking. Where the agent can run a helper in parallel, send it to look while you ask about everything that doesn't depend on the answer.
14
14
  3. Find the frontier: every open decision whose prerequisites are settled. A question that depends on another open question waits for a later round.
15
- 4. Ask the frontier as one round, in the format below. Every question carries your recommended answer and the reason for it.
15
+ 4. Ask the frontier as one round, in the format below. Every question shows what each option looks like and carries your recommended answer and the reason for it. Read [illustrating.md](illustrating.md) before the first round.
16
16
  5. Wait for the answers. Record each settled decision as described under Recording, then recompute the frontier.
17
17
  6. Repeat until the frontier is empty: every branch visited, and nothing silently assumed.
18
- 7. Write the sections the step produces from the settled decisions, then summarize them and ask the user to confirm that they match their understanding. Done when the user confirms. Decisions and glossary terms are recorded as they settle, and nothing is approved before the confirmation.
18
+ 7. Write the sections the step produces from the settled decisions, then summarize them, illustrated as [illustrating.md](illustrating.md) says, and ask the user to confirm that they match their understanding. Done when the user confirms. Decisions and glossary terms are recorded as they settle, and nothing is approved before the confirmation.
19
19
 
20
20
  ## A round
21
21
 
22
- ```markdown
22
+ ````markdown
23
+ Where we are: architect for 2026-09-25-open-console, round 2. Settled: D1 (a console session may cross jump hosts), D2 (credentials come from the vault).
24
+
23
25
  **Q4. Where the jump-host list lives**
24
26
  Per device in the device config, or as named hosts that devices reference. Named hosts avoid repeating a host's address and credentials across devices, but add a second lookup.
27
+
28
+ ```yaml
29
+ # A: per device # B: named hosts (recommended)
30
+ devices: jump_hosts:
31
+ core-sw-01: lab-gw: { address: 10.0.0.5 }
32
+ jump: { address: 10.0.0.5 } devices:
33
+ core-sw-02: core-sw-01: { jump: lab-gw }
34
+ jump: { address: 10.0.0.5 } core-sw-02: { jump: lab-gw }
35
+ ```
36
+
25
37
  Recommended: named hosts, because the lab config already shares two jump hosts across 30 devices.
26
38
 
27
39
  **Q5. What a refused second hop reports**
28
40
  ...
29
- ```
41
+ ````
30
42
 
31
43
  - Number questions across the whole session, not per round, so an answer such as "Q7: yes" is never ambiguous.
32
- - Keep a round to what the user can answer in one reply, about 3 to 7 questions. When the frontier is bigger, ask first the questions that others depend on.
33
- - Ask about behaviour and trade-offs in the user's terms and the glossary's words, not about implementation mechanics.
44
+ - Keep a round to what the user can answer in one reply, about 3 to 5 questions. When the frontier is bigger, ask first the questions that others depend on.
45
+ - Ask about behaviour and trade-offs in the user's terms and the glossary's words, not about implementation mechanics. Where the trade-off is in the code's shape, show the shape.
34
46
  - When the user answers "you decide" or "I don't know", take your recommendation and record it with "(recommended; the user deferred)".
35
47
  - When an answer contradicts an earlier decision, point to that decision and ask which one stands.
36
48
 
@@ -0,0 +1,37 @@
1
+ # Illustrating a question
2
+
3
+ The user decides from what they can picture. A question about a port's errors or a module's place is hard to answer from words alone, and the user is often coming back from other work. So every question, review exception and confirmation shows what each option looks like, and reads on its own without the conversation before it.
4
+
5
+ ## Reading cold
6
+
7
+ - Open each round, and each message of review exceptions, with one line saying where things stand: the work item, the step, and the earlier decisions this round builds on, such as `Where we are: architect for 2026-10-08-subscriptions, round 3. Settled: D2 (one stream per session), D4 (events carry revisions).`
8
+ - Say what an ID means in plain words the first time a message uses it, such as `SCN-configuration.reject-taken-priority (a second set asking for a priority already taken is refused)`.
9
+ - Each question says what it decides and why it matters now, without pointing back to an earlier message.
10
+
11
+ ## Choosing the illustration
12
+
13
+ Show each option, side by side, with the form that fits what the question decides:
14
+
15
+ | The question decides | Show |
16
+ | --- | --- |
17
+ | What a user does or sees | The command they type and its exact output, or a plain-text sketch of the screen, using the scenario's concrete values |
18
+ | A port, a use case's input and errors, or a public API | A code sketch in the project's language: the types and signatures, and the call a caller writes |
19
+ | Where something lives: contexts, modules, dependency direction | A box-and-arrow diagram, or the directory tree with the files the option adds |
20
+ | An order of steps, or what happens on failure | A numbered sequence or a plain-text sequence diagram, with the failure branch |
21
+ | States and what moves between them | The states with their transitions, one per line |
22
+ | A stored format or a config file | A sample of the file |
23
+ | A dependency | The code with it and the code without it |
24
+
25
+ Leave out the illustration only when the question is about a name, a number or the wording of a sentence, where it would repeat the words.
26
+
27
+ ## Writing it
28
+
29
+ - Use the design's names and the glossary's words, and the values of a real scenario, so the user sees this project's code, not a generic example.
30
+ - A code sketch shows shape: types, signatures, the error variants, and one call site. Leave bodies out, writing `...` or the language's placeholder. Keep each option to about 15 lines.
31
+ - Draw diagrams in plain text, so they read in a terminal, where Mermaid shows only as source. The documents keep their Mermaid diagrams; the illustration is for the conversation.
32
+ - At envision, show behaviour only: interactions and screens, never code, since envision keeps implementation out.
33
+ - Mark your recommended option in the illustration itself, and show the failure case when the options differ in how they fail.
34
+
35
+ ## After the answer
36
+
37
+ A shape the user chose from a sketch is part of the decision. Write it into the document in that document's format: the port's `Operations`, the entity's facts, the module's `Path`, or the scenario's values. Build follows the documents, so the code comes out as the user saw it.
@@ -37,6 +37,7 @@ When two rules pull apart, the earlier one wins. Prefer the simplest design that
37
37
 
38
38
  | Layer | Owns | May depend on |
39
39
  | --- | --- | --- |
40
+ | `core` | A context's concepts, use cases and ports together (`ENT`, `UC`, `PORT`), when its domain needs no boundary of its own | The standard library, and approved libraries with no infrastructure in them |
40
41
  | `domain` | Concepts, state, rules, invariants and domain errors (`ENT`) | Domain code, the standard library, and approved libraries with no infrastructure in them, such as an error-derive crate |
41
42
  | `application` | Use cases (`UC`) and the ports they define (`PORT`) | The domain |
42
43
  | `adapter` | Inbound adapters that drive use cases, and outbound adapters that implement driven ports (`ADP`) | Application, domain and third-party libraries |
@@ -44,6 +45,10 @@ When two rules pull apart, the earlier one wins. Prefer the simplest design that
44
45
 
45
46
  `cruze check` enforces imports between the project's own layers. It allows every third-party import, so review checks that infrastructure libraries appear only in adapters.
46
47
 
48
+ Use the fewest layers that keep the rules. A context starts with `core`, its adapters and the composition root. The boundary that matters most is between the core and infrastructure, because it is what lets tests fake the outside world. Split a context's core into `domain` and `application` when its domain has rules and invariants that several use cases share and a workflow could break, and record the split as a `D<n>` decision. A `core` module follows the domain and the application rules below together.
49
+
50
+ Layers are roles, not folders. Choose the layout that is normal for the kind of system and its language, following [shapes.md](shapes.md), and give each module the layer of what it holds.
51
+
47
52
  Layers are dependency boundaries, not runtime hops. A port can be a plain function call. Add a task, thread, channel or process only for a concrete ownership, concurrency, isolation or backpressure need, and read [runtime.md](runtime.md) first.
48
53
 
49
54
  ### Domain
@@ -83,13 +88,14 @@ Layers are dependency boundaries, not runtime hops. A port can be a plain functi
83
88
 
84
89
  ## Many small modules
85
90
 
86
- - Give each responsibility its own module, grouped by domain concept, with the hexagonal roles inside. A file may hold a few closely related types.
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.
87
92
  - When a file passes its budget (`check:` in `.cruze/config.yaml`, enforced by `cruze check`) or gains a second responsibility, split it into a submodule named for what it owns.
93
+ - A budget is a prompt to look for a second 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.
88
94
  - Entry-point and namespace files, such as `main`, `lib.rs`, `mod.rs` and `index.ts`, hold only wiring and declarations.
89
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.
90
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.
91
- - Structure follows the module map, never a per-language template. Write idiomatic code for the language and use its standard tooling. [languages/rust.md](languages/rust.md) and [languages/cpp.md](languages/cpp.md) add rules for those languages.
92
- - In an existing codebase, its established layout and naming win over these structure rules. Ownership and dependency direction still apply.
97
+ - Structure follows the module map, never a per-language template. Write idiomatic code for the language and use its standard tooling. [languages/rust.md](languages/rust.md), [languages/swift.md](languages/swift.md) and [languages/cpp.md](languages/cpp.md) add rules for those languages.
98
+ - In an existing codebase, its established layout and naming win over these structure rules until the user restructures it with the improve skill. Ownership and dependency direction still apply.
93
99
 
94
100
  ## Representations and errors
95
101
 
@@ -119,6 +125,7 @@ These are review signals, not automatic failures. When one could be checked mech
119
125
 
120
126
  ## Further reading
121
127
 
128
+ - [shapes.md](shapes.md): choosing the layout for a library, a daemon or service, a mobile or desktop app, or a CLI.
122
129
  - [contracts.md](contracts.md): writing a port's `Operations`, a public API, an IPC protocol or a stored format.
123
130
  - [runtime.md](runtime.md): concurrency, long-lived resources (connections, sessions, devices, subprocesses), state machines, cancellation or a hot path.
124
131
  - [fundamentals.md](fundamentals.md): choosing a data structure, a state representation or an abstraction mechanism; defining equality or ordering; bounding loops, retries and fan-out.
@@ -6,7 +6,7 @@ These add to the hexagonal design rules for Rust projects. The module map decide
6
6
 
7
7
  - `main.rs` is the composition root and stays thin. `lib.rs` and every `mod.rs` hold only module declarations, visibility and deliberate re-exports, and `lib.rs` exposes the use cases, ports and domain types that tests drive.
8
8
  - Name modules for the concept they own. Don't put domain definitions in a `mod.rs`, and don't create `domain.rs`, `services.rs`, `models.rs` or `utils.rs` catch-alls.
9
- - Where the layer rules need a hard boundary, a Cargo workspace with one crate per layer makes the compiler enforce it. The walking-skeleton change decides this.
9
+ - Split crates by deliverable, such as the library, a client, a daemon and a CLI, in a Cargo workspace when there is more than one. Inside a crate, modules carry the concepts and the layers, and `cruze check` enforces the layer rules between them. A crate per layer is unusual in Rust and puts a public API between the layers: use one only when the user wants the compiler to enforce a boundary, and record it as a decision.
10
10
 
11
11
  ## Modelling
12
12
 
@@ -40,6 +40,7 @@ These add to the hexagonal design rules for Rust projects. The module map decide
40
40
  - No `unsafe` unless the user approves it or an unavoidable low-level boundary needs it, with a `// SAFETY:` comment giving the reason.
41
41
  - Format with `cargo fmt`, lint with `cargo clippy -- -D warnings`, and test with `cargo test`.
42
42
  - Behaviour, contract and smoke tests live in the crate's `tests/` directory and reach the code through the library's public API. Domain tests sit in a `#[cfg(test)] mod tests` beside the code and need no fakes.
43
- - When a module's inline tests would take its file past the budget, move them to the crate's `tests/`, named for the module: `tests/<module>.rs`, or `tests/<module>/main.rs` declaring one file per topic, such as `tests/generation/add_generation.rs`. Cargo builds each `tests/*.rs` and each `tests/*/main.rs` as a test binary, and nothing deeper. Never move them to `src/<module>/tests.rs` or `src/<module>/tests/`: `cruze check` reports those as `test-placement`. A test that needs a private item is testing structure, so drive the public API instead. When a private seam really is the contract, such as a filesystem trait a crash test injects, list the file under `check.exceptions` with that reason.
43
+ - `cruze check` leaves a file's `#[cfg(test)]` modules out of its budgets, so domain tests stay inline however long they grow. Never move them to `src/<module>/tests.rs` or `src/<module>/tests/`: `cruze check` reports those as `test-placement`.
44
+ - Name test files in `tests/` for what they cover: `tests/<module>.rs`, or `tests/<module>/main.rs` declaring one file per topic, such as `tests/generation/add_generation.rs`. Cargo builds each `tests/*.rs` and each `tests/*/main.rs` as a test binary, and nothing deeper.
44
45
  - Group integration tests into a few binaries by area, `tests/<area>/main.rs` with one module per topic, since every binary links the crate again.
45
46
  - Share fakes, fixtures and harnesses within a crate through `tests/support/mod.rs`, which each test binary declares with `mod support;`. In a workspace, put what several crates' tests share in one dev-only crate, such as `<project>-test-support`, listed under `[dev-dependencies]`. Never share them with `#[path]` includes.
@@ -0,0 +1,46 @@
1
+ # Swift
2
+
3
+ These add to the hexagonal design rules for Swift projects, such as iOS and macOS apps and Swift packages. The module map decides the layout; this file never does.
4
+
5
+ ## Packages and targets
6
+
7
+ - Every target is a Swift module, and anything another target uses must be `public`. Each target boundary costs access-control boilerplate and build time, so use few:
8
+ - one core target for the app's contexts, with a folder per feature or concept;
9
+ - adapter targets only where a framework or package must stay out of the core, or one adapters target for all of them;
10
+ - the app target, holding the screens, their models and the composition root, or a feature target per feature once the app is large enough to build and preview features alone;
11
+ - one test-support target with the fakes and fixtures every test target shares.
12
+ - Keep `internal`, the default, for everything used inside its target. Use `package` for what the package's own targets share, and `public` only for what the app target or another package needs.
13
+ - A target can import only the targets and packages it declares in `Package.swift`, so declared dependencies enforce those boundaries. They don't stop a target importing a platform framework, such as `CoreLocation` or `SwiftUI`, which any target can import. Review checks framework imports against the layer rules, or a short CI script lists them per target.
14
+ - Keep the core free of UIKit, SwiftUI and other platform frameworks. Its tests then build and run without the app, and with a macOS platform in `Package.swift`, without a simulator.
15
+
16
+ ## Modelling
17
+
18
+ - Use structs for values and entities, enums for closed alternatives and states, and classes only where identity needs reference semantics. Wrap primitives with meaning, such as `struct OfficeName { let value: String }`, with a failable or throwing initializer that enforces the invariant.
19
+ - Use concrete types for single-strategy behaviour. Use protocols for ports and for strategies that really vary.
20
+
21
+ ## Ports and injection
22
+
23
+ - Ports are protocols owned by the core, named for the capability. Platform types, such as `CLLocation` or `UNNotificationRequest`, never appear in their signatures.
24
+ - Inject through initializers. The composition root at the app's entry point builds the adapters and the use cases, and hands each screen's model what it calls.
25
+
26
+ ## Screens
27
+
28
+ - A SwiftUI view and its `@Observable` model are the screen's inbound adapter. The model calls use cases and maps their results to what the view shows. Domain rules stay out of both.
29
+ - Test the model with fakes behind its use cases' ports. Keep UI tests for what only the running screen can show.
30
+
31
+ ## Errors and logging
32
+
33
+ - Throw a typed error, `throws(OfficeChangeError)`, where callers handle its cases differently, and use plain `throws` elsewhere. Never `try!` or `fatalError` for a condition that can happen in production. Tests may use them.
34
+ - Log with `os.Logger` in adapters, use cases and screen models. The domain doesn't log.
35
+
36
+ ## Concurrency
37
+
38
+ - Use the Swift 6 language mode. Domain values are `Sendable` structs and enums. A use case is a struct, or an actor when it owns mutable state that several callers reach.
39
+ - `@MainActor` belongs on views and their models, not on the core.
40
+ - Don't mark a type `@unchecked Sendable` without a comment giving the reason.
41
+
42
+ ## Tooling and tests
43
+
44
+ - Format with `swift format format --in-place --recursive`, and lint with `swift format lint --strict --recursive`.
45
+ - Package tests live in `Tests/<Target>Tests/`. They import the target with `@testable import`, which makes its `internal` declarations visible, so nothing is made `public` only for tests. UI tests live in the app's UI test target.
46
+ - Put the package's tests in the `test` command and the app's UI tests in `test_full`, as `.agents/skills/cruze-formats/reference/config.md` describes, since UI tests run through the simulator and are by far the slowest.