devflow-kit 3.6.0 → 3.7.0

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 (80) hide show
  1. package/CHANGELOG.md +80 -1
  2. package/README.md +6 -2
  3. package/dist/agents/code.md +7 -11
  4. package/dist/agents/fix.md +5 -6
  5. package/dist/agents/refactor.md +5 -6
  6. package/dist/agents/review.md +2 -0
  7. package/dist/cli/commands/gates.js +276 -0
  8. package/dist/cli/commands/init-seed.js +9 -1
  9. package/dist/cli/commands/init.js +31 -8
  10. package/dist/cli/commands/memory.js +18 -0
  11. package/dist/cli.js +2 -0
  12. package/dist/commands/bug-analysis.md +2 -2
  13. package/dist/commands/code-review.md +52 -44
  14. package/dist/commands/debug.md +2 -2
  15. package/dist/commands/dynamic-build.md +53 -61
  16. package/dist/commands/dynamic-plan.md +2 -2
  17. package/dist/commands/dynamic-profile.md +2 -2
  18. package/dist/commands/dynamic-tickets.md +2 -2
  19. package/dist/commands/explore.md +2 -2
  20. package/dist/commands/implement.md +26 -31
  21. package/dist/commands/plan.md +15 -25
  22. package/dist/commands/release.md +2 -2
  23. package/dist/commands/research.md +2 -2
  24. package/dist/commands/resolve.md +2 -2
  25. package/dist/commands/self-review.md +2 -2
  26. package/dist/core/echo-value.js +42 -0
  27. package/dist/core/evidence-policy.js +17 -5
  28. package/dist/core/gate-level.js +213 -0
  29. package/dist/core/manifest.js +26 -10
  30. package/dist/core/model-discovery.js +7 -1
  31. package/dist/core/tracker.js +5 -33
  32. package/dist/learning-off/agents/code.md +7 -11
  33. package/dist/learning-off/agents/fix.md +5 -6
  34. package/dist/learning-off/agents/refactor.md +5 -6
  35. package/dist/learning-off/agents/review.md +2 -0
  36. package/dist/learning-off/commands/code-review.md +52 -44
  37. package/dist/learning-off/commands/debug.md +2 -2
  38. package/dist/learning-off/commands/dynamic-build.md +53 -61
  39. package/dist/learning-off/commands/explore.md +2 -2
  40. package/dist/learning-off/commands/implement.md +26 -31
  41. package/dist/learning-off/commands/plan.md +15 -25
  42. package/dist/learning-off/commands/resolve.md +2 -2
  43. package/dist/learning-off/commands/self-review.md +2 -2
  44. package/dist/skills/git/references/tracker/github/ensure-pr-ready.md +7 -9
  45. package/dist/skills/git/references/tracker/jira/ensure-pr-ready.md +7 -15
  46. package/dist/skills/git/references/tracker/linear/ensure-pr-ready.md +7 -15
  47. package/dist/targets/claude-code/learning-install.js +15 -3
  48. package/package.json +1 -1
  49. package/src/assets/agents/_partials/_code.mds +5 -6
  50. package/src/assets/agents/code.mds +2 -5
  51. package/src/assets/agents/curate.md +4 -6
  52. package/src/assets/agents/review.mds +2 -0
  53. package/src/assets/agents/synthesize.md +2 -2
  54. package/src/assets/commands/_partials/_settings.mds +2 -2
  55. package/src/assets/commands/_partials/_wave.mds +10 -17
  56. package/src/assets/commands/code-review.mds +50 -42
  57. package/src/assets/commands/dynamic-build.mds +41 -42
  58. package/src/assets/commands/implement.mds +24 -29
  59. package/src/assets/commands/plan.mds +14 -24
  60. package/src/assets/mds/git/_ops_steps.mds +3 -3
  61. package/src/assets/mds/tracker/_common.mds +23 -12
  62. package/src/assets/mds/tracker/_github.mds +2 -9
  63. package/src/assets/mds/tracker/_jira.mds +2 -15
  64. package/src/assets/mds/tracker/_linear.mds +2 -15
  65. package/src/assets/scripts/ci-wait.cjs +22 -4
  66. package/src/assets/scripts/claude-md-audit.cjs +66 -51
  67. package/src/assets/scripts/hooks/background-memory-update +404 -95
  68. package/src/assets/scripts/hooks/json-helper.cjs +11 -0
  69. package/src/assets/scripts/hooks/json-parse +18 -3
  70. package/src/assets/scripts/hooks/memory-permission +358 -0
  71. package/src/assets/scripts/hooks/session-start-context +2 -2
  72. package/src/assets/scripts/hooks/session-start-memory +26 -5
  73. package/src/assets/scripts/lib/project-config.cjs +19 -4
  74. package/src/assets/scripts/resolve-settings.cjs +200 -32
  75. package/src/assets/skills/agent-references/SKILL.md +6 -2
  76. package/src/assets/skills/agent-references/references/commands/code-review-levels.md +19 -0
  77. package/src/assets/skills/agent-references/references/commands/implement-revalidate.md +2 -2
  78. package/src/assets/skills/agent-references/references/commands/plan-levels.md +40 -0
  79. package/src/assets/skills/agent-references/references/synthesize/planning.md +9 -22
  80. package/src/assets/skills/agent-references/references/synthesize/review.md +11 -11
package/CHANGELOG.md CHANGED
@@ -5,6 +5,84 @@ All notable changes to Devflow will be documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [3.7.0] - 2026-10-16
9
+
10
+ ### Upgrade notes — Gate level
11
+
12
+ The settings line has a new last field for the gate level ([#472](https://github.com/dean0x/devflow/issues/472)).
13
+ Do the step below after you update.
14
+
15
+ **What you must do:**
16
+ - Run `devflow init` after you update. A full init installs the commands that accept the new settings line.
17
+ - After `devflow init`, a user with no level gets `standard` in `/code-review`: no TypeScript focus, and no Dependencies focus on a code diff that changes no dependency manifest. Run `devflow gates --set full` to keep today's set.
18
+ - A user with no level also gets the lean `/plan`: fewer agents, while the security gap analysis and the design review still run. Run `devflow gates --set full` to keep the full `/plan`.
19
+
20
+ **Stale-install window:**
21
+ - The settings line now ends with `GATE_LEVEL=<full|standard|lean>`. An installed command from an
22
+ older version accepts only the old fields, so it does not accept the longer line.
23
+ - `devflow init --hud-only` and a plugin-only update refresh the resolver script. They do not
24
+ refresh the installed prompts.
25
+ - Until you run a full `devflow init`, every installed command uses its fail-closed settings value.
26
+ - Until you run `devflow init`, the installed `/code-review` and `/plan` ignore the level.
27
+
28
+ **Known limits:**
29
+ - A corrupt manifest, or one over 1 MiB, reads as the `standard` level. `devflow gates --status`
30
+ warns that `devflow init` will replace the file.
31
+ - `devflow init` still overwrites a corrupt or rejected manifest. The stored level is lost with it.
32
+ - Two commands that write the manifest at the same moment can lose one update.
33
+ - There is no team floor. A repository that declares compliance can run a lower personal level.
34
+ `/code-review` and `/plan` read the level, so a personal `lean` level narrows the review and the
35
+ planning run even where compliance is declared. The Compliance focus still runs on its trigger,
36
+ and in `/plan` the security gap focus and the design review run at every level.
37
+ Issue [#488](https://github.com/dean0x/devflow/issues/488) (the compliance lens as a floor on
38
+ the level) is open.
39
+
40
+ ### Upgrade notes — Working memory
41
+
42
+ The memory worker's write is limited to its staged file. Read the items below after you update.
43
+
44
+ **What you must do:**
45
+ - Update Claude Code to 2.1.269 or newer. On an older version, working memory does not update and Claude Code shows no message. The worker log has one line that says why.
46
+ - If a worker `env` value lives only in your project or local Claude Code settings, move it to your user settings file. The worker loads only the user settings.
47
+
48
+ **Your own settings:**
49
+ - Allow rules in your own Claude Code settings also apply to the worker's run. A user-level Edit allow rule that covers other paths covers the worker too.
50
+
51
+ **Known limits:**
52
+ - A project path that contains any of `* ? [ ] \ " { }` or a control character gets no working-memory updates. The permission rule carries the path without quoting. The worker skips the project, logs the reason and shows one message at session start. Parentheses and spaces in the path are carried as they are.
53
+
54
+ ### Added
55
+
56
+ - **A gate level on the settings line, and the `devflow gates` command** ([#472](https://github.com/dean0x/devflow/issues/472)). The settings line ends with `GATE_LEVEL=<full|standard|lean>`. The level comes from a personal `gateLevel` key in `.devflow/config.json`, then from the machine default `features.gateLevel` in `~/.devflow/manifest.json`, then from `standard`. The personal key may raise or lower the level. It is not a narrow-only switch, and a later change can make it narrow-only. A `gateLevel` in `project.json` is ignored, and no command writes the personal key. A malformed value reads as `full`. `devflow gates --set <level>` writes the machine default and nothing else. `devflow gates --status` shows the level in effect, the layer that decided it and the machine default. `/code-review` reads the level ([#474](https://github.com/dean0x/devflow/issues/474)) and so does `/plan` ([#473](https://github.com/dean0x/devflow/issues/473)); [#480](https://github.com/dean0x/devflow/issues/480) adds more readers.
57
+ - **`devflow init --hud-only` prints a line when the installed prompts come from another devflow version** ([#472](https://github.com/dean0x/devflow/issues/472)). It tells you to run `devflow init`.
58
+ - **A message at session start when working memory stops for a cause you can fix.** If the worker cannot write its staged file at the project path (for example, a path with a character the permission rule cannot carry), or Claude Code refuses the worker's write in two runs with no successful update between them, the next session start shows one message that points to the log. Each cause is shown once.
59
+
60
+ ### Changed
61
+
62
+ - **`/code-review` picks its focus set by gate level** ([#474](https://github.com/dean0x/devflow/issues/474)). `full` runs every focus, as before. `standard`, the default, drops TypeScript, and drops Dependencies on a code diff that changes no dependency manifest (a lockfile alone does not count). `lean` runs Security, Testing and Reliability on a code diff. The Compliance focus keeps its own trigger at every level. A docs-only, tests-only or lockfile-only diff keeps its reduced set, and at `lean` a tests-only diff drops Consistency. A mixed non-code diff runs the union of its classes' sets. Security runs at every level and in every class. `/code-review` reads a new level reference only at `standard` or `lean`. If it cannot read the reference, it says so and runs the full sets. The review summary now shows the gate level and the focuses, and a review with no security report gives CHANGES REQUESTED.
63
+ - **`/plan` runs a lean shape at `standard` and `lean`** ([#473](https://github.com/dean0x/devflow/issues/473)). `full` runs the phases as before. `standard`, the default, and `lean` run one Explore round of two agents (three for several issues), or none when a `/research` report from the same conversation covers the scope; no Synthesize step; two gap-analysis agents, one for security and one for completeness, architecture and performance (plus consistency and dependencies for several issues); one Plan agent; and the design review. The security gap agent and the design review run at every level, the Compliance agent keeps its own trigger, and Gates 0, 1 and 2 run at every level. Every agent spawn in `/plan` names its agent type; the planning spawns use Claude Code's built-in `Plan` agent. If `/plan` cannot read its level reference, it says so and runs the full shape. The run report names the gate level. Run `devflow gates --set full` to keep the full shape.
64
+ - **`/implement` splits long plans into sequential Code phases; a run with no plan stays single** ([#452](https://github.com/dean0x/devflow/issues/452)).
65
+ - **Code agents read large files once, by range, and cap printed output** ([#452](https://github.com/dean0x/devflow/issues/452)).
66
+ - **Polish, Harden, Evaluate and Test get the full branch diff as `FILES_CHANGED`** ([#452](https://github.com/dean0x/devflow/issues/452)).
67
+ - **`/implement` stops when the base branch name has a character outside ASCII letters, digits and `._/+@,#-`, or is longer than 200** ([#452](https://github.com/dean0x/devflow/issues/452)). The check also covers the re-validation path. Git accepts some refused names, such as `café`. For them, `/implement` stops and tells the user which characters are allowed.
68
+ - **The token audit counts files written through Bash** ([#442](https://github.com/dean0x/devflow/issues/442)).
69
+ - **The memory worker's write is limited to its staged file.** The `claude -p` run loads only your own Claude Code settings plus two permission rules, an allow rule for `.devflow/memory/WORKING-MEMORY.md.new` and a deny rule for every path under it, and a permission mode that refuses every call that no allow rule covers. Allow rules in your own Claude Code settings also apply to the run, so a user-level Edit allow rule that covers other paths covers the worker too. A folder or a link at the staged path is removed by one guarded delete of exactly that path; when it cannot be removed, the run fails and removes nothing. The worker ignores a project's or a local Claude Code settings file.
70
+ - **Working memory needs Claude Code 2.1.269 or newer.** `devflow init` states the version at its working-memory step. On an older Claude Code the worker does not run, keeps the queue and writes one log line that says why, so working memory does not update until you update Claude Code.
71
+ - **The memory log gets one `Run:` line for each worker run.** `.background-memory-update.log` under `~/.devflow/logs/`, in the folder for the session that ran the worker (`<session-directory-slug>`), ends every run with `Run: cli=<version|unknown> argv=<lean|legacy|none> result=<updated|conflict|failed|skip|below-floor> denials=<count|unknown|n/a> removed=<none|dir|link>`. `cli=unknown` means the worker read no version, `denials=unknown` means the result reported no count, and `denials=n/a` means no run started. A below-floor line adds `floor=<version>`. The line holds keywords and digits only, never turn or prompt text.
72
+ - **A project inside a `.git` folder gets no working-memory update.** The worker skips it, logs the reason and shows the path message once at session start. A checkout under `.claude/worktrees` is updated like any other checkout.
73
+ - **PR links and `/dynamic-build` ticket branches follow stricter rules** ([#486](https://github.com/dean0x/devflow/issues/486)). `/implement` prints `TRACEABILITY: DEGRADED (ambiguous issue reference)` when it has an issue number and no link line. `/code-review` and `/bug-analysis` PRs no longer get a closing line from the branch name. A PR body has no `## Related Issues` heading when neither an issue number nor a link line is given. In a `/dynamic-build` wave, the ticket branch must be a local branch name that passes the allowlist. A name with a leading `#` or `-`, a revision name such as `HEAD`, a tag and a SHA are all refused, and the ticket is quarantined. The wave aborts the merge only when the merge began. On Jira and Linear, the PR link line always uses the default reference form (`Refs {KEY}-{n}`). A custom `## Reference Rendering` section no longer shapes that line. It still shapes the release Shipped Issues section. Follow-up [#511](https://github.com/dean0x/devflow/issues/511) tracks applying it to the PR link line.
74
+
75
+ ### Fixed
76
+
77
+ - **Prompts resolve the Claude config directory with the same rule as the installer** ([#517](https://github.com/dean0x/devflow/pull/517)). `CLAUDE_CONFIG_DIR` applies when it is an absolute path, else `~/.claude`.
78
+ - **A tracked `.devflow/config.json` no longer applies as personal config** ([#489](https://github.com/dean0x/devflow/pull/489)). devflow ignores it and shows a warning.
79
+ - **`/dynamic-build` wave merges are safer, and a supplied PR link line is used as given** ([#486](https://github.com/dean0x/devflow/issues/486)). A wave merge uses `git merge --no-ff` and records the pre-merge SHA. The ticket branch name is checked before the merge, and a refused name quarantines that ticket. The wave halts when the merge or base SHA is not valid. A ticket whose branch adds no commit stops the wave before the post-merge build, and no wave PR opens. A merge whose post-merge build fails is undone by a reset to the recorded SHA. The run summary names why the wave halted. A PR gets no closing reference from a bare number or the branch name.
80
+ - **Model discovery waits up to 6 s for the routing runtime** ([#519](https://github.com/dean0x/devflow/pull/519)). The wait was 2 s. On a loaded machine, the first start of node with the routing runtime can take more than 2 s. The agents TUI then reported an installed runtime as unknown. `devflow proxy --enable` then did not refresh the model cache. A runtime that does not answer within 6 s still falls back to the stale cache.
81
+ - **The memory worker keeps the queue when it finds neither jq nor node** ([#523](https://github.com/dean0x/devflow/issues/523)). The hooks that start the worker already stop without a parser. A worker that ran with a different PATH deleted the queued turns. Now the worker stops before it reads the queue, and the queue stays for the next run. A parser that exits with an error also leaves the queue and the batch in place, and a queue that cannot be added to a leftover batch stays in its file.
82
+ - **A malformed queue line no longer stops the worker from reading the other lines** ([#528](https://github.com/dean0x/devflow/issues/528)). With jq, the worker read the queue as one stream, so one malformed line stopped the parse. The valid turns after it were lost, or the whole batch was deleted. Now the worker reads each line alone, skips only the malformed line, and logs how many lines in the read window it skipped. A row cut off before its line break runs into the next row, so those two rows are one malformed line. Both parsers read a line as a turn only when its role is user, assistant or qa, so jq and node log the same skip count. A batch with no readable line is still removed.
83
+
84
+ ---
85
+
8
86
  ## [3.6.0] - 2026-10-14
9
87
 
10
88
  ### Upgrade notes — Agent rename (6 agents)
@@ -1673,7 +1751,8 @@ devflow init
1673
1751
 
1674
1752
  ---
1675
1753
 
1676
- [Unreleased]: https://github.com/dean0x/devflow/compare/v2.0.0...HEAD
1754
+ [Unreleased]: https://github.com/dean0x/devflow/compare/v3.7.0...HEAD
1755
+ [3.7.0]: https://github.com/dean0x/devflow/compare/v3.6.0...v3.7.0
1677
1756
  [3.6.0]: https://github.com/dean0x/devflow/compare/v3.5.0...v3.6.0
1678
1757
  [3.5.0]: https://github.com/dean0x/devflow/compare/v3.4.0...v3.5.0
1679
1758
  [3.4.0]: https://github.com/dean0x/devflow/compare/v3.3.0...v3.4.0
package/README.md CHANGED
@@ -56,7 +56,7 @@ This is the **orchestrated flow** — you stay in the loop between every step. W
56
56
 
57
57
  **A staffed agent roster.** 20 specialized agents with explicit model assignments — Opus for analysis, Sonnet for execution, Haiku for I/O. Reassign any agent's model with `devflow agents`, including GPT models through external model routing (`devflow proxy`).
58
58
 
59
- **Up to 20 parallel Review agents.** Security, architecture, performance, complexity, consistency, regression, testing, and more. Each produces findings with severity, confidence scoring, and concrete fixes. Conditional Review agents activate when relevant (TypeScript for `.ts` files, database for schema changes, compliance when regulated surface detected in the diff). Every finding gets validated and resolved automatically.
59
+ **Up to 20 parallel Review agents.** Security, architecture, performance, complexity, consistency, regression, testing, and more. Each produces findings with severity, confidence scoring, and concrete fixes. Conditional Review agents activate when relevant (language focuses such as TypeScript for `.ts` files, database for schema changes, compliance when regulated surface detected in the diff). The gate level narrows these sets: the default `standard` level drops the TypeScript focus, and `lean` runs fewer focuses still. Every finding gets validated and resolved automatically.
60
60
 
61
61
  **Memory that persists.** Session context survives restarts, `/clear`, and context compaction. Your agent picks up exactly where it left off.
62
62
 
@@ -200,7 +200,9 @@ A repository can commit `.devflow/project.json` to settle team-wide choices. Eve
200
200
 
201
201
  Each key is checked on its own, so one bad value never disables the rest: a bad `evidence` resolves to `required`, a bad `reviewPublication` to `off`, a bad `compliance` list to the generic lens. A `project.json` or `config.json` that exists but is not a JSON object is never read as absent: the settings fail closed (publication off, knowledge write-back off, the tracker reported as invalid). The compliance lens is the exception, because it only adds scrutiny: it keeps every framework a readable layer declares, and an unreadable `project.json` counts as the generic lens. Memory and learning capture fail open instead: the hooks read an unreadable file as narrowing nothing, so capture runs as your machine's switch says.
202
202
 
203
- `config.json` is personal, so it must not be committed. A `config.json` that git tracks is ignored as if it were absent — otherwise a branch could commit `"reviewPublication":"full"` for whoever reviews it — and `devflow tracker|memory|learning|knowledge --status` say so, with the fix: `git rm --cached .devflow/config.json`. Commands never read the file themselves — one local resolver folds it with your `config.json` and the machine settings, without touching the network.
203
+ `config.json` is personal, so it must not be committed. A `config.json` that git tracks is ignored as if it were absent — otherwise a branch could commit `"reviewPublication":"full"` for whoever reviews it — and `devflow tracker|memory|learning|knowledge|gates --status` say so, with the fix: untrack the tracked entry with `git rm --cached`, then check or delete the file, because untracking leaves the committed bytes on disk as your personal file. Commands never read the file themselves — one local resolver folds it with your `config.json` and the machine settings, without touching the network.
204
+
205
+ **Gate level.** The gate level (`full`, `standard` or `lean`) is not a narrow-only switch: your personal `.devflow/config.json` may carry a top-level `"gateLevel"` key that raises or lowers the machine default, `devflow gates --set <level>` sets the machine default in `~/.devflow/manifest.json`, and `standard` applies when neither is set. A `gateLevel` in `project.json` is ignored, and no command writes the personal key. A malformed value reads as `full`. `/code-review` and `/plan` read the level: `standard` and `lean` run fewer review focuses and fewer planning agents. The security checks and, in `/plan`, the design review run at every level. The compliance gap agent runs on its own trigger at every level. Run `devflow gates --set full` to keep the full `/plan` shape; issue #480 adds more readers. After updating, run `devflow init`. See the [CLI reference](https://github.com/dean0x/devflow/blob/main/docs/cli-reference.md#gate-level).
204
206
 
205
207
  ## Evidence policy
206
208
 
@@ -286,6 +288,8 @@ npx devflow-kit compliance --status # Show compliance state and this repo's
286
288
  npx devflow-kit tracker --set jira # Pick the machine's default tracker (github | jira | linear)
287
289
  npx devflow-kit tracker --status # Show provider, this repo's effective one, learned conventions, mechanics
288
290
  npx devflow-kit tracker --servers default # Tracker servers the agents may call (comma-separated names, or default)
291
+ npx devflow-kit gates --set lean # Set the machine's default gate level (full | standard | lean)
292
+ npx devflow-kit gates --status # Show the gate level in effect here and which layer decided it
289
293
  npx devflow-kit rules --status # Show installed rules
290
294
  npx devflow-kit security --status # Show / manage the security deny list
291
295
  npx devflow-kit safe-delete --enable # Install rm -> trash safe-delete
@@ -67,7 +67,7 @@ You receive from orchestrator:
67
67
  - Read those relevant files to understand interfaces, types, naming conventions, error handling, and testing patterns established by prior work
68
68
  - If PRIOR_PHASE_SUMMARY is provided, use it to validate your understanding — actual code is authoritative, summaries are supplementary
69
69
  - If `DECISIONS_CONTEXT` is provided, follow `devflow:apply-decisions` on it. An absent key or `(none)` means no decisions context. State every decision or pitfall you apply in words in code, comments, tests and commit messages, never by its ID.
70
- - If `HANDOFF_FILE` is provided, read only the `## Phase {N} Implementation Summary` section of the phase immediately before yours — list its `##` headings with `command grep -n '^## ' "$HANDOFF_FILE"`, then Read that range with `offset` and `limit` — never the whole file. Cross-reference against actual code — code is authoritative, handoff is supplementary.
70
+ - If `HANDOFF_FILE` is provided, read only the `## Phase {N} Implementation Summary` section of the phase immediately before yours — list its `##` headings with `command grep -n '^## ' "$HANDOFF_FILE"`, then Read that range with `offset` and `limit` — never the whole file.
71
71
 
72
72
  2. **Implement the plan**: Work through execution steps systematically, creating and modifying files. Follow existing patterns. Type everything. Use Result types if codebase uses them.
73
73
 
@@ -93,7 +93,6 @@ When `OPERATION: issue-fix`, you are fixing pre-classified issues assigned FIX_N
93
93
  - **Standard scope**: Fix directly following existing patterns
94
94
  - **Careful scope**: systematic protocol — understand (50+ lines context, callers/consumers) → plan → write failing regression test → implement → verify tests pass → commit
95
95
  3. **Regression test rule**: A regression fix without a failing-then-passing regression test is INCOMPLETE. Report BLOCKED rather than commit an unverified fix.
96
- 4. **Self-verification scope**: Run compile + the fix's regression test only. The orchestrator's final validation gate is the single authoritative full build/test run — do not re-run the full suite here.
97
96
 
98
97
  **Return report** (a Return block in the spawn replaces this shape):
99
98
  - Status: COMPLETE | PARTIAL | BLOCKED
@@ -120,9 +119,7 @@ When `OPERATION: issue-fix`, you are fixing pre-classified issues assigned FIX_N
120
119
  - {Decision with rationale}
121
120
 
122
121
  ### Integration Points for Next Phase
123
- - {Interfaces to implement against}
124
- - {Functions to call}
125
- - {Types to import}
122
+ - `path:line` of each interface, function or type the next phase builds on
126
123
  ```
127
124
 
128
125
  ## Running commands
@@ -140,11 +137,11 @@ Run builds, typechecks, lints and tests in the foreground, each with an explicit
140
137
 
141
138
  ## Principles
142
139
 
143
- 1. **Work on feature branch** - All operations happen on the current feature branch
144
- 2. **Orient, then match patterns** - Before writing code, orient on branch state and find similar implementations; match their conventions, don't invent new ones
145
- 3. **Be decisive** - Make confident implementation choices. Don't present alternatives or ask permission for tactical decisions
146
- 4. **Small, focused changes** - Don't scope creep beyond the plan
147
- 5. **Fail honestly** - If blocked, report clearly with what was completed
140
+ 1. **Read once** - Never re-read an unchanged file. For a file over 400 lines, or a second look, `grep -n` first, then Read only that range (`offset`, `limit`)
141
+ 2. **Small, focused changes** - Don't scope creep beyond the plan
142
+ 3. **Fail honestly** - If blocked, report clearly with what was completed
143
+
144
+ - Never print a large file whole; page it with offset and limit, and cap any output you print. Before printing search matches, list or count them.
148
145
 
149
146
  ## Output
150
147
 
@@ -184,4 +181,3 @@ Report cap: final message at most about 1,500 tokens; longer material goes to a
184
181
  - Switch branches during implementation
185
182
  - Push to branches other than your feature branch
186
183
  - Merge PRs (orchestrator handles this)
187
- - Trust handoff summaries without reading actual code
@@ -118,11 +118,11 @@ Run builds, typechecks, lints and tests in the foreground, each with an explicit
118
118
 
119
119
  ## Principles
120
120
 
121
- 1. **Work on feature branch** - All operations happen on the current feature branch
122
- 2. **Orient, then match patterns** - Before writing code, orient on branch state and find similar implementations; match their conventions, don't invent new ones
123
- 3. **Be decisive** - Make confident implementation choices. Don't present alternatives or ask permission for tactical decisions
124
- 4. **Small, focused changes** - Don't scope creep beyond the plan
125
- 5. **Fail honestly** - If blocked, report clearly with what was completed
121
+ 1. **Read once** - Never re-read an unchanged file. For a file over 400 lines, or a second look, `grep -n` first, then Read only that range (`offset`, `limit`)
122
+ 2. **Small, focused changes** - Don't scope creep beyond the plan
123
+ 3. **Fail honestly** - If blocked, report clearly with what was completed
124
+
125
+ - Never print a large file whole; page it with offset and limit, and cap any output you print. Before printing search matches, list or count them.
126
126
 
127
127
  ## Output
128
128
 
@@ -162,4 +162,3 @@ Report cap: final message at most about 1,500 tokens; longer material goes to a
162
162
  - Switch branches during implementation
163
163
  - Push to branches other than your feature branch
164
164
  - Merge PRs (orchestrator handles this)
165
- - Trust handoff summaries without reading actual code
@@ -86,11 +86,11 @@ Run builds, typechecks, lints and tests in the foreground, each with an explicit
86
86
 
87
87
  ## Principles
88
88
 
89
- 1. **Work on feature branch** - All operations happen on the current feature branch
90
- 2. **Orient, then match patterns** - Before writing code, orient on branch state and find similar implementations; match their conventions, don't invent new ones
91
- 3. **Be decisive** - Make confident implementation choices. Don't present alternatives or ask permission for tactical decisions
92
- 4. **Small, focused changes** - Don't scope creep beyond the plan
93
- 5. **Fail honestly** - If blocked, report clearly with what was completed
89
+ 1. **Read once** - Never re-read an unchanged file. For a file over 400 lines, or a second look, `grep -n` first, then Read only that range (`offset`, `limit`)
90
+ 2. **Small, focused changes** - Don't scope creep beyond the plan
91
+ 3. **Fail honestly** - If blocked, report clearly with what was completed
92
+
93
+ - Never print a large file whole; page it with offset and limit, and cap any output you print. Before printing search matches, list or count them.
94
94
 
95
95
  ## Output
96
96
 
@@ -130,4 +130,3 @@ Report cap: final message at most about 1,500 tokens; longer material goes to a
130
130
  - Switch branches during implementation
131
131
  - Push to branches other than your feature branch
132
132
  - Merge PRs (orchestrator handles this)
133
- - Trust handoff summaries without reading actual code
@@ -229,3 +229,5 @@ use the same prefix so values are not double-masked.
229
229
  | java | If .java files changed |
230
230
  | python | If .py files changed |
231
231
  | rust | If .rs files changed |
232
+
233
+ The gate level (`GATE_LEVEL`, `/code-review` Step 1.2b) removes focuses from this table at `standard` and `lean`.
@@ -0,0 +1,276 @@
1
+ /**
2
+ * devflow gates — Show or set the machine's default gate level.
3
+ *
4
+ * The gate level is one of `full`, `standard` or `lean`. `--set` writes the MACHINE
5
+ * default, `features.gateLevel` in `~/.devflow/manifest.json`, and nothing else: not
6
+ * the repository's `project.json`, not the personal `.devflow/config.json` (no
7
+ * command writes that key), and no sentinel, re-arm or install step. `--status` shows
8
+ * the level that applies in the current directory, which layer decided it and what
9
+ * the machine records; it writes nothing.
10
+ *
11
+ * Three parts, so no business logic throws or prints:
12
+ * gatherGateStatus resolves the settings ONCE and reads the manifest raw, and
13
+ * returns a {@link GateStatus} that is a discriminated union
14
+ * renderGateStatus pure: one {@link GateStatus} to lines and warnings
15
+ * gatesCommand the thin shell: parse, call, print, set the exit code
16
+ *
17
+ * No code here chooses a level from a task or a diff: the level comes from the
18
+ * resolver (personal key, machine key, default) or from the word typed after `--set`.
19
+ */
20
+ import { Command } from 'commander';
21
+ import * as path from 'path';
22
+ import * as p from '@clack/prompts';
23
+ import color from 'picocolors';
24
+ import { DEFAULT_GATE_LEVEL, GATE_LEVELS, machineGateLevel, parseGateLevel, readGateManifest, writeMachineGateLevel, } from '../../core/gate-level.js';
25
+ import { describeEchoedValue } from '../../core/echo-value.js';
26
+ import { loadSettingsModule, settingsSourceFile, trackedConfigWarning, } from '../../core/evidence-policy.js';
27
+ import { getDevFlowDirectory } from '../../targets/claude-code/claude-paths.js';
28
+ // ── Wording ────────────────────────────────────────────────────────────────────
29
+ /**
30
+ * What each level means, in one line. Each line says what the word stands for and
31
+ * not what a command does with it.
32
+ */
33
+ export const GATE_LEVEL_MEANINGS = Object.freeze({
34
+ full: 'the most checking; any value that is not a level also reads as full',
35
+ standard: 'the default when no level is set',
36
+ lean: 'the least checking',
37
+ });
38
+ /** What reads the level today; each later reader updates this text. */
39
+ export const GATE_LEVEL_EFFECT = '/code-review and /plan read the level: standard and lean run fewer review focuses and fewer planning agents. Other commands read it once issue #480 lands.';
40
+ const MACHINE_MANIFEST_NAME = 'manifest.json';
41
+ const MACHINE_INVALID_LINE = `invalid (${MACHINE_MANIFEST_NAME}; reads as full)`;
42
+ const VALID_LEVELS = GATE_LEVELS.join(', ');
43
+ const VALID_LEVELS_OR = `${GATE_LEVELS.slice(0, -1).join(', ')}, or ${GATE_LEVELS[GATE_LEVELS.length - 1]}`;
44
+ /** The resolved settings, or null when the resolver is unusable or throws despite its contract. */
45
+ function resolveOnce(module, dir) {
46
+ try {
47
+ return module.resolveSettings({ dir });
48
+ }
49
+ catch {
50
+ return null;
51
+ }
52
+ }
53
+ function manifestState(read) {
54
+ switch (read.kind) {
55
+ case 'absent': return 'absent';
56
+ case 'object': return 'readable';
57
+ case 'corrupt':
58
+ case 'rejected': return 'unreadable';
59
+ default: {
60
+ const exhaustive = read;
61
+ return exhaustive;
62
+ }
63
+ }
64
+ }
65
+ /** Why a fail-closed result failed, or null when the resolver names no reason. */
66
+ function failClosedReason(settings) {
67
+ return settings.unreadable === null ? null : `${settingsSourceFile(settings.unreadable)} is unreadable`;
68
+ }
69
+ /**
70
+ * Gather one {@link GateStatus}. Never throws, never writes.
71
+ *
72
+ * The settings resolve ONCE, and that one result feeds the level, the machine value
73
+ * and the tracked-config warning. The manifest is read with the gate reader, never
74
+ * with `readManifest()`, which heal-writes and returns null for a manifest it
75
+ * rejects: a read-only status must do neither. The manifest is read twice, once by
76
+ * the gate reader here and once by the resolver; a write between the two reads can
77
+ * mix two versions, which is acceptable because the result is display only and no
78
+ * action follows it.
79
+ */
80
+ export async function gatherGateStatus(deps) {
81
+ const manifestPath = path.join(deps.devflowDir, MACHINE_MANIFEST_NAME);
82
+ const read = await deps.readManifest(deps.devflowDir);
83
+ const base = { manifest: manifestState(read), manifestPath };
84
+ // The TypeScript reader's machine value, for a result that carries none of its own.
85
+ const stored = read.kind === 'object' || read.kind === 'rejected' ? machineGateLevel(read.raw) : null;
86
+ const loaded = deps.loadSettings();
87
+ const settings = loaded.ok ? resolveOnce(loaded.value, deps.dir) : null;
88
+ if (settings === null) {
89
+ return { ...base, kind: 'unloadable', machine: stored };
90
+ }
91
+ const trackedWarning = trackedConfigWarning(settings);
92
+ if (!settings.ok || settings.gateLevelSource === null) {
93
+ // Only a result that names an unreadable file carries the manifest's value; a
94
+ // fail-closed result without one (git did not answer) never read the manifest.
95
+ const machine = settings.unreadable !== null ? settings.gateLevelMachine : stored;
96
+ return { ...base, kind: 'fail-closed', reason: failClosedReason(settings), machine, trackedWarning };
97
+ }
98
+ return {
99
+ ...base,
100
+ kind: 'resolved',
101
+ level: settings.gateLevel,
102
+ source: settings.gateLevelSource,
103
+ invalid: settings.gateLevelInvalid,
104
+ machine: settings.gateLevelMachine,
105
+ trackedWarning,
106
+ };
107
+ }
108
+ /** The machine line: `none` when the manifest records no level or cannot be read (its own warning says which). */
109
+ function machineLine(machine) {
110
+ if (machine === 'invalid')
111
+ return `Machine: ${MACHINE_INVALID_LINE}`;
112
+ if (machine === null)
113
+ return 'Machine: none';
114
+ return `Machine: ${machine}`;
115
+ }
116
+ function manifestWarning(status) {
117
+ if (status.manifest !== 'unreadable')
118
+ return null;
119
+ return `Manifest at ${status.manifestPath} cannot be read as a manifest; devflow init will replace this file.`;
120
+ }
121
+ /**
122
+ * Render a {@link GateStatus}. Pure and total over every `kind`.
123
+ *
124
+ * A malformed level is reported by the layer that holds it and its state, never by
125
+ * its value: a raw config value never reaches a terminal.
126
+ */
127
+ export function renderGateStatus(status) {
128
+ const warnings = [];
129
+ const lines = [];
130
+ switch (status.kind) {
131
+ case 'resolved': {
132
+ lines.push(`Effective: ${status.level} (${status.source}${status.invalid ? ', invalid' : ''})`);
133
+ lines.push(machineLine(status.machine));
134
+ if (status.invalid && status.source === 'personal') {
135
+ warnings.push(`gateLevel in ${settingsSourceFile('personal')} is not one of ${VALID_LEVELS}, so ${status.level} is in effect. ` +
136
+ 'The value is not shown.');
137
+ }
138
+ if (status.trackedWarning !== null)
139
+ warnings.push(status.trackedWarning);
140
+ break;
141
+ }
142
+ case 'fail-closed': {
143
+ lines.push(`Effective: full (fail-closed: ${status.reason ?? 'reason unknown'})`);
144
+ lines.push(machineLine(status.machine));
145
+ if (status.trackedWarning !== null)
146
+ warnings.push(status.trackedWarning);
147
+ break;
148
+ }
149
+ case 'unloadable': {
150
+ lines.push('Effective: level unknown');
151
+ lines.push(machineLine(status.machine));
152
+ warnings.push('The settings resolver could not be loaded, so the level that applies is unknown.');
153
+ break;
154
+ }
155
+ default: {
156
+ const exhaustive = status;
157
+ return exhaustive;
158
+ }
159
+ }
160
+ const fromManifest = manifestWarning(status);
161
+ if (fromManifest !== null)
162
+ warnings.push(fromManifest);
163
+ return { lines, warnings };
164
+ }
165
+ /**
166
+ * Map the writer's result to a message and an exit code. Pure and total. Every
167
+ * refusal exits 1 and says nothing was written, except a failed write, which names
168
+ * the path and the error code.
169
+ */
170
+ export function describeGateWrite(result, level, manifestPath) {
171
+ switch (result.kind) {
172
+ case 'written':
173
+ return { kind: 'success', message: `Gate level: ${level}`, exitCode: 0 };
174
+ case 'unchanged':
175
+ return { kind: 'info', message: `Gate level: ${level} (unchanged)`, exitCode: 0 };
176
+ case 'not-installed':
177
+ return { kind: 'error', message: `No manifest found at ${manifestPath}. Run devflow init first.`, exitCode: 1 };
178
+ case 'unreadable':
179
+ return {
180
+ kind: 'error',
181
+ message: `Manifest at ${result.path} exists but cannot be read; nothing changed. Move it aside, then run devflow init.`,
182
+ exitCode: 1,
183
+ };
184
+ case 'write-failed':
185
+ return {
186
+ kind: 'error',
187
+ message: `Could not write the manifest at ${result.path} (${result.reason}); nothing changed.`,
188
+ exitCode: 1,
189
+ };
190
+ case 'invalid-level':
191
+ return {
192
+ kind: 'error',
193
+ message: `Unknown gate level: "${describeEchoedValue(level)}"; nothing changed. Valid levels: ${VALID_LEVELS}`,
194
+ exitCode: 1,
195
+ };
196
+ default: {
197
+ const exhaustive = result;
198
+ return exhaustive;
199
+ }
200
+ }
201
+ }
202
+ /**
203
+ * Write the machine default and describe the result. Never throws. `now` is injected
204
+ * so a test fixes the `updatedAt` stamp.
205
+ */
206
+ export async function runGatesSet(devflowDir, level, now = () => new Date().toISOString()) {
207
+ const result = await writeMachineGateLevel(devflowDir, level, now);
208
+ return describeGateWrite(result, level, path.join(devflowDir, MACHINE_MANIFEST_NAME));
209
+ }
210
+ function printOutcome(outcome) {
211
+ switch (outcome.kind) {
212
+ case 'success':
213
+ p.log.success(outcome.message);
214
+ break;
215
+ case 'info':
216
+ p.log.info(outcome.message);
217
+ break;
218
+ case 'error':
219
+ p.log.error(outcome.message);
220
+ break;
221
+ default: {
222
+ const exhaustive = outcome.kind;
223
+ return exhaustive;
224
+ }
225
+ }
226
+ }
227
+ function levelsHelp() {
228
+ const width = Math.max(...GATE_LEVELS.map(level => level.length));
229
+ return GATE_LEVELS.map(level => ` ${level.padEnd(width)} ${GATE_LEVEL_MEANINGS[level]}`).join('\n');
230
+ }
231
+ export const gatesCommand = new Command('gates')
232
+ .description('Show or set the machine\'s default gate level')
233
+ .option('--status', 'Show the gate level in effect here, which layer decided it, and the machine default')
234
+ .option('--set <level>', `Set the machine's default gate level: ${VALID_LEVELS_OR}`)
235
+ .addHelpText('after', `\nGate levels:\n${levelsHelp()}\n\n` +
236
+ `The default is ${DEFAULT_GATE_LEVEL}. --set writes only the machine default. A personal .devflow/config.json ` +
237
+ '"gateLevel" overrides it in one repository; no command writes that key.\n' +
238
+ GATE_LEVEL_EFFECT)
239
+ .action(async (options) => {
240
+ const devflowDir = getDevFlowDirectory();
241
+ if (!options.status && options.set === undefined) {
242
+ p.intro(color.bgCyan(color.white(' Gates ')));
243
+ p.note(`${color.cyan('devflow gates --status')} Show the gate level in effect here and the machine default\n` +
244
+ `${color.cyan('devflow gates --set <level>')} Set the machine default\n\n` +
245
+ `Valid levels:\n${levelsHelp()}`, 'Usage');
246
+ p.outro(color.dim(GATE_LEVEL_EFFECT));
247
+ return;
248
+ }
249
+ // Validate --set before any I/O, even beside --status: a bad word is rejected,
250
+ // never repaired, and exits 1 as it does in `devflow tracker` and `devflow compliance`.
251
+ const parsed = options.set === undefined ? undefined : parseGateLevel(options.set);
252
+ if (parsed !== undefined && !parsed.ok) {
253
+ p.log.error(parsed.error);
254
+ process.exitCode = 1;
255
+ return;
256
+ }
257
+ // --status wins over a valid --set and writes nothing, mirroring `devflow tracker`.
258
+ // With neither flag the usage printed above, so no --set here means --status alone.
259
+ if (options.status || parsed === undefined) {
260
+ const status = await gatherGateStatus({
261
+ devflowDir,
262
+ dir: process.cwd(),
263
+ loadSettings: loadSettingsModule,
264
+ readManifest: readGateManifest,
265
+ });
266
+ const rendered = renderGateStatus(status);
267
+ p.note(rendered.lines.join('\n'), 'Gate Level Status');
268
+ for (const warning of rendered.warnings)
269
+ p.log.warn(warning);
270
+ return;
271
+ }
272
+ const outcome = await runGatesSet(devflowDir, parsed.value);
273
+ printOutcome(outcome);
274
+ process.exitCode = outcome.exitCode;
275
+ });
276
+ //# sourceMappingURL=gates.js.map
@@ -27,6 +27,7 @@ export const FEATURE_DEFAULTS = {
27
27
  behavior: false,
28
28
  compliance: { enabled: false, frameworks: [] },
29
29
  tracker: { provider: DEFAULT_TRACKER_PROVIDER },
30
+ gateLevel: undefined,
30
31
  };
31
32
  // ── Helpers ───────────────────────────────────────────────────────────────────
32
33
  /**
@@ -60,7 +61,11 @@ export function resolveSeedFeatures(manifest) {
60
61
  const memory = manifest?.features.memory ?? FEATURE_DEFAULTS.memory;
61
62
  const knowledge = manifest?.features.knowledge ?? FEATURE_DEFAULTS.knowledge;
62
63
  const learning = manifest?.features.learning ?? FEATURE_DEFAULTS.learning;
63
- return { ambient, memory, hud, knowledge, learning, rules, proxy, behavior, compliance, tracker };
64
+ // The raw stored level, with no default operator: `??` would turn a stored `null`
65
+ // into the default, and init would then write the default over a value the
66
+ // fold reads as the strictest level.
67
+ const gateLevel = manifest?.features.gateLevel;
68
+ return { ambient, memory, hud, knowledge, learning, rules, proxy, behavior, compliance, tracker, gateLevel };
64
69
  }
65
70
  /**
66
71
  * Resolve the flag record for the init seed.
@@ -318,6 +323,9 @@ export function applyCliToggles(base, toggles) {
318
323
  behavior: toggles.behavior ?? base.behavior,
319
324
  compliance: toggles.compliance ?? base.compliance,
320
325
  tracker: toggles.tracker ?? base.tracker,
326
+ // No command-line flag names the gate level, so the seed's raw value passes
327
+ // through. Only `undefined` means "not specified": `null` is a value, not a gap.
328
+ gateLevel: toggles.gateLevel !== undefined ? toggles.gateLevel : base.gateLevel,
321
329
  };
322
330
  }
323
331
  //# sourceMappingURL=init-seed.js.map