devflow-kit 3.1.0 → 3.3.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 (138) hide show
  1. package/CHANGELOG.md +52 -0
  2. package/README.md +2 -2
  3. package/dist/cli/agents-view/render.js +69 -15
  4. package/dist/cli/agents-view/state.js +40 -14
  5. package/dist/cli/commands/agents.js +135 -45
  6. package/dist/cli/commands/init.js +128 -53
  7. package/dist/cli/commands/learning.js +61 -13
  8. package/dist/cli/commands/memory.js +35 -14
  9. package/dist/cli/commands/uninstall.js +163 -39
  10. package/dist/commands/code-review.md +1 -3
  11. package/dist/commands/debug.md +15 -12
  12. package/dist/commands/dynamic-build.md +172 -135
  13. package/dist/commands/dynamic-plan.md +9 -3
  14. package/dist/commands/explore.md +10 -4
  15. package/dist/commands/implement.md +149 -145
  16. package/dist/commands/plan.md +13 -9
  17. package/dist/commands/release.md +8 -2
  18. package/dist/commands/research.md +8 -2
  19. package/dist/commands/resolve.md +28 -19
  20. package/dist/commands/self-review.md +16 -13
  21. package/dist/core/agent-frontmatter.js +25 -0
  22. package/dist/core/agent-models.js +201 -36
  23. package/dist/core/agent-state.js +27 -5
  24. package/dist/core/assets.js +1 -1
  25. package/dist/core/feature-config.js +68 -10
  26. package/dist/core/flags.js +24 -0
  27. package/dist/core/learning-queue-cleanup.js +10 -11
  28. package/dist/core/learning-tuning-config.js +8 -0
  29. package/dist/core/linked-path.js +46 -0
  30. package/dist/core/plugins.js +16 -5
  31. package/dist/core/queue-drain.js +31 -0
  32. package/dist/hud/components/learning-counts.js +54 -8
  33. package/dist/skills/git/references/tracker/github/create-release.md +2 -2
  34. package/dist/skills/git/references/tracker/github/gather-release-evidence.md +1 -1
  35. package/dist/skills/git/references/tracker/jira/create-release.md +2 -2
  36. package/dist/skills/git/references/tracker/jira/gather-release-evidence.md +1 -1
  37. package/dist/skills/git/references/tracker/linear/create-release.md +2 -2
  38. package/dist/skills/git/references/tracker/linear/gather-release-evidence.md +1 -1
  39. package/dist/targets/claude-code/installer.js +36 -9
  40. package/dist/targets/claude-code/post-install.js +128 -38
  41. package/package.json +1 -1
  42. package/src/assets/agents/code.md +85 -35
  43. package/src/assets/agents/design.md +12 -0
  44. package/src/assets/agents/diagnose.md +18 -11
  45. package/src/assets/agents/evaluate.md +17 -24
  46. package/src/assets/agents/knowledge.md +7 -3
  47. package/src/assets/agents/learning.md +4 -6
  48. package/src/assets/agents/research.md +21 -0
  49. package/src/assets/agents/review.md +12 -0
  50. package/src/assets/agents/scrutinize.md +37 -9
  51. package/src/assets/agents/simplify.md +24 -0
  52. package/src/assets/agents/skim.md +6 -2
  53. package/src/assets/agents/synthesize.md +18 -0
  54. package/src/assets/agents/test.md +19 -11
  55. package/src/assets/agents/triage.md +8 -0
  56. package/src/assets/agents/validate.md +20 -11
  57. package/src/assets/commands/_partials/_engine.mds +36 -55
  58. package/src/assets/commands/_partials/_knowledge.mds +1 -3
  59. package/src/assets/commands/_partials/_plan_contract.mds +1 -1
  60. package/src/assets/commands/_partials/_tracker.mds +1 -1
  61. package/src/assets/commands/_partials/_wave.mds +8 -6
  62. package/src/assets/commands/code-review.mds +1 -3
  63. package/src/assets/commands/debug.mds +13 -8
  64. package/src/assets/commands/dynamic-build.mds +126 -72
  65. package/src/assets/commands/dynamic-plan.mds +7 -1
  66. package/src/assets/commands/explore.mds +9 -1
  67. package/src/assets/commands/implement.mds +147 -141
  68. package/src/assets/commands/plan.mds +12 -8
  69. package/src/assets/commands/release.md +8 -2
  70. package/src/assets/commands/research.mds +8 -2
  71. package/src/assets/commands/resolve.mds +27 -16
  72. package/src/assets/commands/self-review.mds +15 -10
  73. package/src/assets/mds/tracker/_common.mds +1 -1
  74. package/src/assets/mds/tracker/_github.mds +2 -2
  75. package/src/assets/mds/tracker/_jira.mds +2 -2
  76. package/src/assets/mds/tracker/_linear.mds +2 -2
  77. package/src/assets/scripts/ci-wait.cjs +636 -0
  78. package/src/assets/scripts/hooks/assets/orchestrator-charter.md +4 -3
  79. package/src/assets/scripts/hooks/background-memory-update +356 -17
  80. package/src/assets/scripts/hooks/capture-prompt +4 -3
  81. package/src/assets/scripts/hooks/capture-question +4 -3
  82. package/src/assets/scripts/hooks/capture-turn +4 -3
  83. package/src/assets/scripts/hooks/ensure-devflow-init +13 -1
  84. package/src/assets/scripts/hooks/ensure-root-gitignore +122 -10
  85. package/src/assets/scripts/hooks/git-marker +71 -0
  86. package/src/assets/scripts/hooks/json-helper.cjs +12 -145
  87. package/src/assets/scripts/hooks/json-parse +24 -129
  88. package/src/assets/scripts/hooks/lib/learning-store.cjs +169 -64
  89. package/src/assets/scripts/hooks/lib/render-decisions.cjs +1 -1
  90. package/src/assets/scripts/hooks/memory-worker +10 -0
  91. package/src/assets/scripts/hooks/pre-compact-memory +66 -14
  92. package/src/assets/scripts/hooks/preamble +9 -1
  93. package/src/assets/scripts/hooks/queue-append +53 -21
  94. package/src/assets/scripts/hooks/session-start-context +108 -29
  95. package/src/assets/scripts/hooks/session-start-memory +33 -11
  96. package/src/assets/scripts/release-trace.cjs +27 -10
  97. package/src/assets/skills/accessibility/SKILL.md +1 -1
  98. package/src/assets/skills/apply-decisions/SKILL.md +12 -82
  99. package/src/assets/skills/apply-feature-knowledge/SKILL.md +8 -42
  100. package/src/assets/skills/architecture/SKILL.md +1 -1
  101. package/src/assets/skills/boundary-validation/SKILL.md +1 -1
  102. package/src/assets/skills/complexity/SKILL.md +1 -1
  103. package/src/assets/skills/compliance/SKILL.md +1 -1
  104. package/src/assets/skills/consistency/SKILL.md +1 -1
  105. package/src/assets/skills/database/SKILL.md +1 -1
  106. package/src/assets/skills/dependencies/SKILL.md +1 -1
  107. package/src/assets/skills/dependency-research/SKILL.md +3 -6
  108. package/src/assets/skills/design-review/SKILL.md +1 -1
  109. package/src/assets/skills/docs-framework/SKILL.md +1 -1
  110. package/src/assets/skills/documentation/SKILL.md +1 -1
  111. package/src/assets/skills/gap-analysis/SKILL.md +1 -1
  112. package/src/assets/skills/git/SKILL.md +1 -1
  113. package/src/assets/skills/go/SKILL.md +1 -1
  114. package/src/assets/skills/java/SKILL.md +1 -1
  115. package/src/assets/skills/patterns/SKILL.md +1 -1
  116. package/src/assets/skills/performance/SKILL.md +1 -1
  117. package/src/assets/skills/python/SKILL.md +1 -1
  118. package/src/assets/skills/qa/SKILL.md +1 -3
  119. package/src/assets/skills/quality-gates/SKILL.md +9 -12
  120. package/src/assets/skills/quality-gates/references/report-template.md +20 -20
  121. package/src/assets/skills/react/SKILL.md +1 -1
  122. package/src/assets/skills/regression/SKILL.md +1 -1
  123. package/src/assets/skills/reliability/SKILL.md +1 -1
  124. package/src/assets/skills/research-codebase/SKILL.md +1 -1
  125. package/src/assets/skills/research-competitor/SKILL.md +1 -1
  126. package/src/assets/skills/research-external/SKILL.md +1 -1
  127. package/src/assets/skills/research-technology/SKILL.md +1 -1
  128. package/src/assets/skills/review-methodology/SKILL.md +1 -1
  129. package/src/assets/skills/rust/SKILL.md +1 -1
  130. package/src/assets/skills/security/SKILL.md +1 -1
  131. package/src/assets/skills/software-design/SKILL.md +1 -1
  132. package/src/assets/skills/test-driven-development/SKILL.md +15 -33
  133. package/src/assets/skills/testing/SKILL.md +1 -1
  134. package/src/assets/skills/typescript/SKILL.md +1 -1
  135. package/src/assets/skills/ui-design/SKILL.md +1 -1
  136. package/src/assets/skills/worktree-support/SKILL.md +3 -55
  137. package/src/assets/skills/worktree-support/references/discovery.md +48 -0
  138. package/src/assets/skills/worktree-support/references/roots.md +2 -2
package/CHANGELOG.md CHANGED
@@ -5,6 +5,56 @@ 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.3.0] - 2026-10-09
9
+
10
+ ### Changed
11
+
12
+ - **Agents ship chosen tiers, efforts and tool sets** ([#422](https://github.com/dean0x/devflow/issues/422)). Fifteen agents now carry a deliberate model, effort, tool policy and `omitClaudeMd` setting, held by a guard to one table. Skim moves from `sonnet` to `haiku`, the only model change. Every agent except Learning, Git and Tracker now ships an effort and no longer follows your session's effort: `high` for Code, Design, Review and Triage, `medium` for the rest. `devflow agents --set <agent> --effort inherit` restores the old behaviour for one agent. Review ships `opus` at `high` effort: an A/B run of the `/code-review` focus agents over two seeded PRs found every seeded bug at `high` that it found at `xhigh`, for about 43% lower cost. Learning keeps its model, its absent effort and its CLAUDE.md context: the cheaper candidates fell short of the capture bar, and without that context it stops retiring entries as Encoded. Code, Research, Scrutinize and Simplify deny one shared list of orchestration and host-UI tools (Scrutinize and Simplify add `Skill`) and keep their MCP and web access; every other agent holds an allowlist. Synthesize and Skim omit CLAUDE.md. Preloads leave where the body never reads them: `testing` from Validate and Test, `software-design` from Evaluate, the five pattern skills from Diagnose, which now loads its focus's skills on demand, and `apply-decisions` from Learning. `devflow learning --configure` marks as recommended the model the Learning agent ships, read from its frontmatter, so a change of the shipped tier moves the recommendation with it.
13
+ - **The gates run in a new order, and a script waits on CI** ([#421](https://github.com/dean0x/devflow/issues/421)). `/implement` now runs Simplify, Scrutinize, Evaluate, then one Validate over the final HEAD, then Test, then pushes and gates CI; a changed-only Validate follows only when a QA fix committed. The alignment and QA loops no longer spawn a Validate of their own, and `/self-review` validates whenever HEAD moved, so a Simplify-only commit is no longer skipped. `ci-wait.cjs` is a head-bound foreground CI wait that replaces re-spawning the Git agent once a minute: it binds the verdict to the pushed head, waits inside node and prints one line. One wait is capped at 570 seconds, and each CI gate allows at most 3 waits and 2 fixes, spawning Code only on a failing verdict. Scrutinize reports one status vocabulary, PASS, FIXED or BLOCKED (READY is gone), and each gate duty has one owner: Naming and Consistency belong to Simplify. In the dynamic-build engine both Gate 1 passes end with Validate. The Git agent now merges locally and validates nothing; the workflow runs a post-merge Validate after every merge, counts the merge as kept only on PASS, and undoes a red merge with `reset --keep`; a refused undo halts the wave and names the red HEAD, and a first Gate 1 that ends ESCALATED or BLOCKED returns early. `/resolve` waits on CI through the same script.
14
+ - **Skills cost less** ([#423](https://github.com/dean0x/devflow/issues/423)). Code preloads six skills (`git`, `testing`, `test-driven-development`, `worktree-support`, `apply-feature-knowledge` and `apply-decisions`) where it preloaded ten: 25,578 bytes of skill text, down from 53,450. `software-design`, `patterns`, `boundary-validation` and `dependency-research` load on demand from a table with one row per operating mode. Every Code spawn now opens its prompt with `OPERATION: <mode>`, including two new modes, `ci-fix` (fix the failing CI checks the gate names) and `edit` (a mechanical change that adds no behaviour), and the orchestrator charter's direct Code delegations do the same. `apply-decisions` reads an entry by finding its heading with `command grep -nF` and then Reading only that section by offset, instead of opening the whole file. `worktree-support` keeps its discovery algorithm in a reference that `/code-review` and `/resolve` read when they need it, rather than in every agent's preload. Skill descriptions are capped by who reads them, at 90 characters for the agent-internal skills and 200 for the user-trigger ones, and 38 shrank. The Git agent's loaded sets are 1,704 characters smaller.
15
+ - **The memory worker reads `agents.memory` and runs lean** ([#424](https://github.com/dean0x/devflow/issues/424)). `background-memory-update` takes its model and effort from `agents.memory` (`devflow agents --set memory ...`), checking each field on its own and falling back to the shipped default for one it cannot use. A bounded `claude --version` probe picks the argv. On Claude Code 2.1.286 or later the worker runs with `--safe-mode`, `--tools Write`, an empty strict MCP config, `--max-turns 3` and an explicit `--effort`, with any inherited `CLAUDE_CODE_EFFORT_LEVEL` cleared so the effort it names is the one that applies; below that, or when the probe fails, it runs a legacy argv that still limits the worker to Write and keeps CLAUDE.md and auto-memory out, but passes no effort, safe mode, MCP isolation or turn cap. A staged file that cannot be removed stops the run before any model call, which is what makes a Write-only tool set safe.
16
+
17
+ ### Fixed
18
+
19
+ - **Only closing keywords ship an issue in a release.** `Closes`, `Fixes` and `Resolves` ship an issue; a `Refs #N` commit mentions one without closing it, yet it reached the release's Shipped Issues, its back-links and its milestone. It no longer does. The release trace map still counts `Refs` as traced, because that map asks only whether a commit names an issue at all.
20
+
21
+ ---
22
+
23
+ ## [3.2.0] - 2026-10-08
24
+
25
+ ### Added
26
+
27
+ - **`devflow flags` gains `bash-max-timeout-ms`** ([#418](https://github.com/dean0x/devflow/issues/418)). It sets `BASH_MAX_TIMEOUT_MS`, the ceiling on a foreground Bash command's `timeout` (600000 ms upstream), to a whole number from 600000 to 7200000, and it is unset by default. A run that cannot be split under the ceiling is reported BLOCKED with `devflow flags --set bash-max-timeout-ms=<ms>` as the remedy. The registry now holds 30 flags.
28
+
29
+ ### Changed
30
+
31
+ - **`devflow agents` carries a shipped effort, takes an `inherit` effort, and sets the memory worker** ([#420](https://github.com/dean0x/devflow/issues/420)). Nothing changes at shipped defaults: no agent ships an `effort:` line yet and every installed `model:` is as before. This is the plumbing the agent-tier tickets build on.
32
+ - **A shipped effort survives a reapply.** A shipped agent default is now its `model:` and its `effort:` when it has one, read from the same file. A reapply used to remove an `effort:` line from every agent whose mapping had no effort, including one that ships it.
33
+ - **`--effort inherit` drops an agent's effort line** so it follows the session; `--effort default` removes your override and restores the shipped effort. The EFFORT column reads `default (<shipped effort>)` when an agent ships one.
34
+ - **`memory` is settable in `devflow agents`** (`--set memory --model sonnet --effort medium`), listed after the agents with state `worker`. It takes Claude models and effort levels only, is not an agent, and is left out of the agent counts.
35
+ - **A `devflow agents` Learning mapping now decides the Learning model.** The session-start directive used to pass `model="opus"` (or the `learning.json` model) on every spawn, which overrode any mapping. It now takes the first of: a valid project `learning.json` model, the mapping (which sends no model, so the installed frontmatter decides), a valid global `learning.json` model, none. An invalid `learning.json` value falls through instead of becoming `opus`. `devflow learning --configure` says that a mapping outranks the global file.
36
+ - **A user's `validate` override now reaches the five Validate spawns** in `/implement` and `/resolve`, which passed `model="haiku"`. A new guard fails on any spawn in the prompt sources that names a model.
37
+ - **Commands carry less context** ([#419](https://github.com/dean0x/devflow/issues/419)). Claude Code copies a command's arguments into the prompt at every placeholder, so each command now binds them once, in a `<command-input>` block, and names the bound text `COMMAND_INPUT` afterwards: the compiled commands went from 21 placeholders to 8, and a guard holds the count. A plan handoff now invokes `devflow:implement` with no arguments, because the plan is already in the conversation; with no input, `/implement` names the branch from the plan's title. The orchestrator charter gains a bounded-inline exception (one `git`, `gh` or script command whose output stays under about 40 lines, never a diff, log or test run) and a delegation report cap of about 1,500 tokens. `/implement`, `/code-review`, `/debug` and `/plan` stop loading companion skills their main thread never uses, and the plan and code-review plugins stop requiring the skills only those lines needed. The built-in Explore and Plan spawns in `/explore`, `/debug` and `/plan` ask for a report of at most about 1,500 tokens.
38
+ - **Agents run builds and tests in the foreground, and the full test suite has one owner** ([#418](https://github.com/dean0x/devflow/issues/418)). The Code, Validate and Test agents ran any build that might exceed two minutes in the background and polled it with Monitor, and the dynamic-build engine and prompts repeated the procedure. A silent 250-second foreground run survives inside a Workflow sub-agent, and the platform's stall watchdog fires on model silence after a tool returns, never while a command runs, so polling only added turns. The three bodies now share one `## Running commands` block: an explicit Bash timeout, capture-then-tail in one call, no backgrounding or polling, a scoped command, and BLOCKED with duration and log path on an overrun. A parity guard holds the copies identical, and the engine and the nine dynamic-build prompt sites name the block. Each of the six gate agents carries one `**Gate ownership:**` row: Validate runs the full suite once per HEAD and no other agent does, Code runs targeted tests plus one affected-tests run, and the TDD skill defines "affected tests". `docs/reference/platform-assumptions.md` records what was measured, with the stall investigation.
39
+ - **Every roster agent caps its final message at about 1,500 tokens** ([#418](https://github.com/dean0x/devflow/issues/418)). The 14 roster agents end their Output section with a `Report cap:` line. Longer material goes to a file whose path the message gives, and the fields a command or test parses, or an orchestrator hands to another agent, stay inline in full: Validate's `HEAD:` line and command table, Test's plan evidence and scenario table, Triage's whole ledger and the rest. Validate cuts failure output to 30 lines per failing command and then names the log. The Git agent keeps its output unchanged until its split.
40
+ - **The memory worker runs on Sonnet 5.5 at high effort.** `background-memory-update` now spawns `claude -p --model claude-sonnet-5-5` (it was `claude-sonnet-4-6`), and the `memory` row in `devflow agents` ships `claude-sonnet-5-5` at `high` effort (it was `haiku`), so the shipped default and the model the worker runs are the same value; a test holds the two equal. Memory quality was judged worth more than the saving from Haiku. The worker does not read an `agents.memory` override or pass `--effort` yet. `devflow agents --list` widens its DEFAULT column to the longest shipped default, so a full model identifier is no longer cut.
41
+
42
+ ### Fixed
43
+
44
+ - **`devflow init` no longer reports removing skills that were never installed** ([#432](https://github.com/dean0x/devflow/issues/432)). The "Removed N skill(s) no selected plugin requires" line now names only skills that were present and actually removed, and is omitted when none were.
45
+ - **`devflow agents` no longer cuts a full model identifier it displays.** `--list` cut the CONFIGURED column at 16 characters, so a mapped `claude-sonnet-4-6` read as `claude-sonnet-4-`; the column now grows to the longest configured value, as DEFAULT does. In the interactive editor, the focused `‹ default (claude-sonnet-5-5) ›` cell on the `memory` row overflowed its column and lost its closing arrow and the unsaved mark; the cell now narrows its own arrows to fit and keeps both.
46
+ - **The Knowledge agent is no longer told to load a skill it already has** ([#419](https://github.com/dean0x/devflow/issues/419)). It preloads `devflow:feature-knowledge` and has no Skill tool, yet the write-back step and `/research` still told it to load that skill.
47
+ - **A re-init reports what it actually did** ([#415](https://github.com/dean0x/devflow/issues/415)). Running `devflow init` again printed `.claudeignore: created` even when nothing changed, and a run without a terminal told you to "Run interactively to auto-install" safe-delete even when it was already set up. The `.claudeignore` row now reads `created`, `already present` or `skipped`, and the safe-delete line reads installed, upgraded or already configured. The hint is gone: a run without a terminal takes the Recommended path, which installs or upgrades the safe-delete block itself.
48
+ - **The Skim agent finds the decisions ledger from a linked worktree** ([#415](https://github.com/dean0x/devflow/issues/415)). It looked for the ledger in the directory it ran in, so in a linked worktree it reported no decisions. It now reads the main worktree's ledger, as the other agents do.
49
+ - **Without `jq`, the hooks' JSON helpers stay silent on stderr, as they already were with it** ([#415](https://github.com/dean0x/devflow/issues/415)). On a machine without `jq`, a hook that read a missing or unparseable file printed a `json-helper error` line, or the shell's own error for a file it could not open, where the `jq` path prints nothing. The `node` fallbacks now discard that output too, and return exactly what they did before.
50
+ - **The captured-turns files stay owner-only after a trim** ([#415](https://github.com/dean0x/devflow/issues/415)). Once a memory or learning queue, or a memory batch a failed refresh left behind, passed 200 turns, the trim to the newest 100 replaced it with a copy made under the hook's umask, so the file came back readable by every user (`0644`) while it holds conversation text. The copy is now made `0600`, the mode the queue is created with.
51
+ - **A corrupt working-memory backup no longer stops the session-start memory hook** ([#415](https://github.com/dean0x/devflow/issues/415)). A truncated or unparseable `.devflow/memory/backup.json` ended the hook before it injected the working memory it had already built. Such a backup is now read as no snapshot, like a missing one.
52
+ - **The working-memory backup is written atomically** ([#415](https://github.com/dean0x/devflow/issues/415)). The pre-compact hook wrote `backup.json` by truncating it and writing it again, so a session start reading it in between could meet a partial file. It now writes a complete copy beside it and renames the copy into place, owner-only (`0600`); a write that fails leaves the previous backup as it was. The copy is created only where nothing already stands, so it never writes through a symbolic link, and each backup first removes any copy a killed run left behind that is over an hour old.
53
+ - **The hooks no longer write, or read into the session, through a symbolic link inside a project's `.devflow` folder** ([#415](https://github.com/dean0x/devflow/issues/415)). The capture and memory hooks skip, and log, any write whose file, or a folder on the way to it, is a link, so a repository cannot point captured conversation text or working memory at a file outside it. They also never read a linked file there into the session or a backup: a linked working memory, backup or decisions file is treated as missing. The learning store no longer reads a linked file either: a linked log, ledger, archive or history file is treated as missing, so nothing of it is listed, shown, backed up or copied into the project. The statusline's learning counts skip a ledger that is a link or larger than 8 MiB, as they skip a missing one. The hooks write nothing under a `.devflow` that is itself a link, and every learning operation that writes, `devflow learning --reset` included, refuses a linked `.devflow` or `.devflow/learning` folder, changing nothing. `devflow init`, the `.gitignore` hook, `devflow learning --configure` and turning learning or memory off hold to the same rule: none of them writes or deletes through a link that leads outside the project, and each says so when it skips one. The session-start hook, the only one that reaches the `.gitignore` step when memory and learning are both off, now writes those skips to its log; it used to record them only in debug mode. `devflow uninstall` holds to the rule too when it removes a legacy project-local install: it deletes and rewrites nothing through a link in the project's `.devflow` or `.claude`, removes the rest, and names each link it skipped. A root `.gitignore` linked to a file inside the project is still updated when no part of that file's path there is named `.git`, in any letter case, so a link into the project's own `.git` folder or a nested repository's is refused.
54
+ - **The release notes name their issue section `Shipped Issues` on every provider** ([#415](https://github.com/dean0x/devflow/issues/415)). The GitHub, Jira and Linear release mechanics told the release agent to append `## Closed Issues`, while the shipped-issues input, the back-link step and every published release say Shipped Issues. All three now name the section `## Shipped Issues`.
55
+
56
+ ---
57
+
8
58
  ## [3.1.0] - 2026-10-06
9
59
 
10
60
  ### Changed
@@ -1482,6 +1532,8 @@ devflow init
1482
1532
  ---
1483
1533
 
1484
1534
  [Unreleased]: https://github.com/dean0x/devflow/compare/v2.0.0...HEAD
1535
+ [3.3.0]: https://github.com/dean0x/devflow/compare/v3.2.0...v3.3.0
1536
+ [3.2.0]: https://github.com/dean0x/devflow/compare/v3.1.0...v3.2.0
1485
1537
  [3.1.0]: https://github.com/dean0x/devflow/compare/v3.0.1...v3.1.0
1486
1538
  [3.0.1]: https://github.com/dean0x/devflow/compare/v3.0.0...v3.0.1
1487
1539
  [3.0.0]: https://github.com/dean0x/devflow/compare/v2.5.0...v3.0.0
package/README.md CHANGED
@@ -30,9 +30,9 @@ you: /implement (hand it the plan)
30
30
 
31
31
  Git branch feat/42-rate-limit-upload
32
32
  Code implements the plan — your learned decisions, pitfalls, and feature knowledge preloaded
33
- Validate build ✓ typecheck ✓ lint ✓ tests ✓
34
33
  Simplify · Scrutinize cleanup pass, then 9-pillar quality gate
35
34
  Evaluate implementation matches the original request ✓
35
+ Validate build ✓ typecheck ✓ lint ✓ tests ✓
36
36
  Test 5/5 QA scenarios pass → PR opened
37
37
 
38
38
  you: /code-review
@@ -51,7 +51,7 @@ This is the **orchestrated flow** — you stay in the loop between every step. W
51
51
 
52
52
  ## What you get
53
53
 
54
- **Ambient orchestration.** Your main session becomes the tech lead: a charter injected at session start turns it into a pure orchestrator that delegates work to specialized agents and keeps only judgment mainline. Plan-mode handoffs auto-run `/implement`. Init and forget.
54
+ **Ambient orchestration.** Your main session becomes the tech lead: a charter injected at session start turns it into an orchestrator that delegates work to specialized agents and keeps judgment mainline, with one bounded inline exception for a single short git, gh or script command. Plan-mode handoffs auto-run `/implement`. Init and forget.
55
55
 
56
56
  **A staffed agent roster.** 17 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`).
57
57
 
@@ -15,17 +15,29 @@
15
15
  * -1 Unsaved count " N unsaved changes" (blank if 0)
16
16
  * 0 Keybinding footer
17
17
  *
18
- * Columns (chars) — total 79 ≤ 80:
18
+ * Columns (chars) — total 80 ≤ 80:
19
19
  * PREFIX : 2 (cursor mark "❯ " or " ")
20
- * AGENT : 18
21
- * MODEL : 32
22
- * EFFORT : 13
20
+ * AGENT : 12
21
+ * MODEL : 30
22
+ * EFFORT : 22
23
23
  * STATE : 14
24
+ *
25
+ * EFFORT is 22 wide so the longest unconfigured cell, "default (medium)" (16),
26
+ * fits whole inside the cursor's "‹ … ›" wrapper with the dirty marker
27
+ * ("‹ default (medium) ● ›", 22) — the shipped effort (D-SHIPPED-EFFORT) and the
28
+ * unsaved mark are never clipped on the cursor row. The agent names the registry
29
+ * holds are at most 10 characters ("Scrutinize"); longer orphan keys are
30
+ * truncated by truncateVisible.
31
+ *
32
+ * MODEL has no such slack for a worker row, whose shipped default is a full
33
+ * model identifier: the focused cell narrows its own wrapper to fit
34
+ * (D-FOCUSED-CELL-FITS, renderFocusedCell) rather than taking a column from
35
+ * the 80-column budget.
24
36
  */
25
- import { bold, dim, green, yellow, cyan, gray, stripAnsi, } from '../../core/ansi.js';
37
+ import { bold, dim, green, yellow, cyan, gray, stripAnsi, truncate, } from '../../core/ansi.js';
26
38
  import { padToVisible, truncateVisible, sanitizeCell } from '../tui/cells.js';
27
39
  import { isDirtyModel, isDirtyEffort, unsavedCount, isOffCycle, rowState, } from './state.js';
28
- import { AGENT_STATE_LABELS } from '../../core/agent-state.js';
40
+ import { AGENT_STATE_LABELS, formatEffortDisplay } from '../../core/agent-state.js';
29
41
  // ---------------------------------------------------------------------------
30
42
  // Layout constants
31
43
  // ---------------------------------------------------------------------------
@@ -39,9 +51,9 @@ const MIN_VIEWPORT = 1;
39
51
  export function computeViewportHeight(termRows) {
40
52
  return Math.max(MIN_VIEWPORT, termRows - FIXED_ROWS);
41
53
  }
42
- const COL_AGENT = 18;
43
- const COL_MODEL = 32;
44
- const COL_EFFORT = 13;
54
+ const COL_AGENT = 12;
55
+ const COL_MODEL = 30;
56
+ const COL_EFFORT = 22;
45
57
  const COL_STATE = 14;
46
58
  // ---------------------------------------------------------------------------
47
59
  // Name formatter — TUI only
@@ -64,6 +76,41 @@ export function formatAgentName(name) {
64
76
  .map(seg => (seg.length === 0 ? seg : seg[0].toUpperCase() + seg.slice(1)))
65
77
  .join('-');
66
78
  }
79
+ // ---------------------------------------------------------------------------
80
+ // Cell renderers (pure, return styled string)
81
+ // ---------------------------------------------------------------------------
82
+ /**
83
+ * Wrap the focused field's value in the cursor arrows, with the unsaved mark
84
+ * after the value when the field is dirty.
85
+ *
86
+ * D-FOCUSED-CELL-FITS: the arrows and the unsaved mark are never clipped. A
87
+ * column is sized for the common value, but a worker row ships a full model
88
+ * identifier ("default (claude-sonnet-5-5)" is 27 characters), so the spaced
89
+ * wrapper "‹ default (claude-sonnet-5-5) ● ›" is 33 in a 30-wide MODEL cell and
90
+ * the closing arrow and the mark would fall off the end. The layout already
91
+ * spends all 80 columns and no other column has slack (EFFORT needs 22 for
92
+ * "‹ default (medium) ● ›", STATE needs 14 for "saved-inactive"), so the cell
93
+ * narrows itself instead. The widest form that fits wins:
94
+ * spaced "‹ value ● ›"
95
+ * tight "‹value●›" — the padding spaces go, the value stays whole
96
+ * clipped "‹valu…●›" — only a value wider than the column; the
97
+ * value gives way, never the wrapper
98
+ *
99
+ * Pure function, no I/O.
100
+ */
101
+ function renderFocusedCell(value, dirty, maxWidth) {
102
+ const valueWidth = stripAnsi(value).length;
103
+ const mark = dirty ? '●' : '';
104
+ const spacedWidth = valueWidth + 4 + (dirty ? 2 : 0);
105
+ if (spacedWidth <= maxWidth) {
106
+ return cyan(`‹ ${value}${dirty ? ` ${mark}` : ''} ›`);
107
+ }
108
+ const tightBudget = Math.max(1, maxWidth - 2 - mark.length);
109
+ if (valueWidth <= tightBudget) {
110
+ return cyan(`‹${value}${mark}›`);
111
+ }
112
+ return cyan(`‹${truncate(stripAnsi(value), tightBudget)}${mark}›`);
113
+ }
67
114
  /**
68
115
  * Render the model cell for a given row, considering cursor/active/dirty state.
69
116
  *
@@ -95,7 +142,10 @@ function renderModelCell({ row, isCursor, isActive, maxWidth, modelCycle, }) {
95
142
  valueStr += ` ${dim(`${safeDormantModel} saved`)}`;
96
143
  }
97
144
  }
98
- else if (isOffCycle(modelCycle, row.configuredModel)) {
145
+ else if (!row.worker && isOffCycle(modelCycle, row.configuredModel)) {
146
+ // A worker row is exempt: its model is never judged against the catalog, and
147
+ // a full claude- identifier, which no cycle lists, is in the worker domain
148
+ // (D-WORKER-AGENTS) — readAgentMapping has already dropped anything outside it.
99
149
  // Off-cycle pin: model was saved but is no longer in the discovered catalog.
100
150
  // The per-row effective cycle (state.ts cycleField) includes it for reachability,
101
151
  // but it renders as unavailable to signal the user should update it.
@@ -109,8 +159,7 @@ function renderModelCell({ row, isCursor, isActive, maxWidth, modelCycle, }) {
109
159
  let cell;
110
160
  if (isCursor && isActive) {
111
161
  // Active field on cursor row: wrap in ‹ ›, put ● after value if dirty
112
- const inner = dirty ? `${valueStr} ●` : valueStr;
113
- cell = cyan(`‹ ${inner} ›`);
162
+ cell = renderFocusedCell(valueStr, dirty, maxWidth);
114
163
  }
115
164
  else if (isCursor && dirty) {
116
165
  cell = `● ${valueStr}`;
@@ -122,14 +171,16 @@ function renderModelCell({ row, isCursor, isActive, maxWidth, modelCycle, }) {
122
171
  }
123
172
  /**
124
173
  * Render the effort cell for a given row, considering cursor/active/dirty state.
174
+ *
175
+ * D-SHIPPED-EFFORT: an unconfigured row shows `default (<shipped effort>)` when
176
+ * the shipped source carries an effort, through the formatter --list shares.
125
177
  */
126
178
  function renderEffortCell(row, isCursor, isActive, maxWidth) {
127
179
  const dirty = isDirtyEffort(row);
128
- const value = row.configuredEffort;
180
+ const value = formatEffortDisplay(row.configuredEffort, row.shippedEffort);
129
181
  let cell;
130
182
  if (isCursor && isActive) {
131
- const inner = dirty ? `${value} ●` : value;
132
- cell = cyan(`‹ ${inner} ›`);
183
+ cell = renderFocusedCell(value, dirty, maxWidth);
133
184
  }
134
185
  else if (isCursor && dirty) {
135
186
  cell = `● ${value}`;
@@ -159,6 +210,9 @@ function renderStateCell(row, proxyEnabled, maxWidth) {
159
210
  case 'unknown':
160
211
  cell = dim(AGENT_STATE_LABELS['unknown']);
161
212
  break;
213
+ case 'worker':
214
+ cell = dim(AGENT_STATE_LABELS['worker']);
215
+ break;
162
216
  default: {
163
217
  const _ = state;
164
218
  void _;
@@ -9,7 +9,9 @@
9
9
  * Picker names: all aliases for each model; canonical id iff
10
10
  * the model has no aliases (zero-maintenance, catalog-driven).
11
11
  * Model cycle (proxy OFF): default → haiku → sonnet → opus → fable → default
12
- * Effort cycle: default → low → medium → high → xhigh → max → default
12
+ * Effort cycle: default → low → medium → high → xhigh → max → inherit → default
13
+ * Worker rows (D-WORKER-AGENTS) cycle only what a worker accepts: the model
14
+ * cycle is default and the Claude aliases, the effort cycle has no inherit.
13
15
  *
14
16
  * Dormancy semantics (plan D5 / Phase 1):
15
17
  * When proxy is off and a row's saved model is a GPT model, configuredModel
@@ -28,7 +30,7 @@
28
30
  * on the state — never reallocated per keypress. The reducer receives it as
29
31
  * state.modelCycle and threads it through without reconstructing.
30
32
  */
31
- import { EFFORT_LEVELS } from '../../core/agent-models.js';
33
+ import { EFFORT_INHERIT, EFFORT_LEVELS, } from '../../core/agent-models.js';
32
34
  import { CLAUDE_MODEL_ALIASES, isDormantExternalModel, } from '../../core/external-models.js';
33
35
  import { classifyAgentState, } from '../../core/agent-state.js';
34
36
  // ---------------------------------------------------------------------------
@@ -125,14 +127,26 @@ export function buildModelCycle(catalog) {
125
127
  return base;
126
128
  return [...base, ...pickerNames(catalog.models)];
127
129
  }
128
- const EFFORT_CYCLE = ['default', ...EFFORT_LEVELS];
129
- function cycleNext(cycle, current) {
130
+ /**
131
+ * The effort cycle of an agent row: default, the levels in order, then the
132
+ * `inherit` sentinel (D-SHIPPED-EFFORT). `inherit` sits after the levels so the
133
+ * forward order of the levels is unchanged.
134
+ */
135
+ export const EFFORT_CYCLE = ['default', ...EFFORT_LEVELS, EFFORT_INHERIT];
136
+ /**
137
+ * The cycles of a worker row (D-WORKER-AGENTS). A worker takes only Claude
138
+ * models and has no session effort to inherit, so neither the catalog's external
139
+ * models nor `inherit` is a stop. Built once at load, never per keypress.
140
+ */
141
+ const WORKER_MODEL_CYCLE = ['default', ...CLAUDE_MODEL_ALIASES];
142
+ const WORKER_EFFORT_CYCLE = ['default', ...EFFORT_LEVELS];
143
+ export function cycleNext(cycle, current) {
130
144
  const idx = cycle.indexOf(current);
131
145
  if (idx === -1)
132
146
  return cycle[0];
133
147
  return cycle[(idx + 1) % cycle.length];
134
148
  }
135
- function cyclePrev(cycle, current) {
149
+ export function cyclePrev(cycle, current) {
136
150
  const idx = cycle.indexOf(current);
137
151
  if (idx === -1)
138
152
  return cycle[cycle.length - 1];
@@ -213,9 +227,14 @@ export function unsavedCount(rows) {
213
227
  * model would keep showing 'saved-inactive' even though 'opus' is what gets
214
228
  * written. Mirroring the merge rule keeps display and persistence in lockstep.
215
229
  *
230
+ * A worker row (D-WORKER-AGENTS) has no installed file and cannot be dormant,
231
+ * so it bypasses classification and is always 'worker'.
232
+ *
216
233
  * Pure function, no I/O.
217
234
  */
218
235
  export function rowState(row, proxyEnabled) {
236
+ if (row.worker)
237
+ return 'worker';
219
238
  return classifyAgentState({
220
239
  configured: persistedModelFor(row),
221
240
  proxyEnabled,
@@ -245,11 +264,13 @@ function replaceRow(rows, cursor, newRow) {
245
264
  */
246
265
  function cycleField(row, field, dir, modelCycle) {
247
266
  if (field === 'model') {
267
+ // A worker row cycles its own Claude-only stops, not the catalog cycle.
268
+ const baseCycle = row.worker ? WORKER_MODEL_CYCLE : modelCycle;
248
269
  // Build effective cycle: splice off-cycle pin at the end if present.
249
270
  // This is the ≤ 1 array allocation case (AC-P6): only allocates when offCyclePin != null.
250
- const effectiveCycle = row.offCyclePin !== null && isOffCycle(modelCycle, row.offCyclePin)
251
- ? [...modelCycle, row.offCyclePin]
252
- : modelCycle;
271
+ const effectiveCycle = row.offCyclePin !== null && isOffCycle(baseCycle, row.offCyclePin)
272
+ ? [...baseCycle, row.offCyclePin]
273
+ : baseCycle;
253
274
  // cycleNext/cyclePrev handle the case where configuredModel is not in effectiveCycle
254
275
  // by falling back to cycle[0] / cycle[last]. This is correct for the off-cycle case
255
276
  // where configuredModel IS in effectiveCycle (we splice it in above).
@@ -259,12 +280,14 @@ function cycleField(row, field, dir, modelCycle) {
259
280
  return { ...row, configuredModel: next };
260
281
  }
261
282
  else {
283
+ const effortCycle = row.worker ? WORKER_EFFORT_CYCLE : EFFORT_CYCLE;
262
284
  const next = dir === 'forward'
263
- ? cycleNext(EFFORT_CYCLE, row.configuredEffort)
264
- : cyclePrev(EFFORT_CYCLE, row.configuredEffort);
265
- // Sound narrowing: EFFORT_CYCLE is ['default', ...EFFORT_LEVELS], so next
266
- // is always EffortLevel | 'default'. cycleNext/cyclePrev return string
267
- // because their signature is intentionally generic (also used for model cycles).
285
+ ? cycleNext(effortCycle, row.configuredEffort)
286
+ : cyclePrev(effortCycle, row.configuredEffort);
287
+ // Sound narrowing: both effort cycles are built from 'default', EFFORT_LEVELS
288
+ // and (agents only) the inherit sentinel, so next is always StoredEffort | 'default'.
289
+ // cycleNext/cyclePrev return string because their signature is intentionally
290
+ // generic (also used for model cycles).
268
291
  return { ...row, configuredEffort: next };
269
292
  }
270
293
  }
@@ -293,8 +316,9 @@ function adjustViewport(cursor, viewportOffset, viewportHeight, rowCount) {
293
316
  * the model remains reachable in the per-row effective cycle.
294
317
  */
295
318
  export function buildRow(input) {
319
+ const worker = input.worker === true;
296
320
  const dormant = isDormantExternalModel(input.savedModel, input.proxyEnabled);
297
- const cycle = input.modelCycle ?? [];
321
+ const cycle = worker ? WORKER_MODEL_CYCLE : (input.modelCycle ?? []);
298
322
  // Normalize stored canonical id to picker name (Fix 1: in-memory only, never
299
323
  // written back to disk). E.g. 'gpt-5.6-sol' → 'sol' when pickerNameMap is known.
300
324
  // This keeps the value in-cycle so it does not become a spurious off-cycle pin.
@@ -316,6 +340,7 @@ export function buildRow(input) {
316
340
  return {
317
341
  name: input.name,
318
342
  shippedDefault: input.shippedDefault,
343
+ shippedEffort: input.shippedEffort,
319
344
  configuredModel,
320
345
  originalModel: configuredModel,
321
346
  configuredEffort,
@@ -324,6 +349,7 @@ export function buildRow(input) {
324
349
  offCyclePin,
325
350
  installed: input.installed,
326
351
  inRegistry: input.inRegistry,
352
+ worker,
327
353
  };
328
354
  }
329
355
  // ---------------------------------------------------------------------------