devflow-kit 3.9.0 → 3.10.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 (157) hide show
  1. package/CHANGELOG.md +111 -0
  2. package/README.md +1 -1
  3. package/dist/agents/code.md +6 -6
  4. package/dist/agents/fix.md +6 -6
  5. package/dist/agents/harden.md +7 -6
  6. package/dist/agents/refactor.md +6 -6
  7. package/dist/cli/commands/ambient.js +10 -2
  8. package/dist/cli/commands/compliance-prompts.js +9 -4
  9. package/dist/cli/commands/compliance.js +171 -24
  10. package/dist/cli/commands/curation.js +12 -7
  11. package/dist/cli/commands/gate-level-prompts.js +39 -25
  12. package/dist/cli/commands/gates.js +5 -7
  13. package/dist/cli/commands/init.js +142 -138
  14. package/dist/cli/commands/install-report.js +34 -0
  15. package/dist/cli/commands/legacy-sweep.js +64 -0
  16. package/dist/cli/commands/proxy.js +6 -1
  17. package/dist/cli/commands/rules.js +29 -12
  18. package/dist/cli/commands/skills.js +42 -9
  19. package/dist/cli/commands/tracker.js +9 -3
  20. package/dist/cli/commands/uninstall.js +277 -51
  21. package/dist/commands/bug-analysis.md +69 -120
  22. package/dist/commands/dynamic-build.md +23 -23
  23. package/dist/commands/implement.md +46 -32
  24. package/dist/commands/release.md +1 -1
  25. package/dist/commands/resolve.md +35 -31
  26. package/dist/commands/self-review.md +8 -7
  27. package/dist/compliance-off/agents/git.md +120 -0
  28. package/dist/compliance-off/agents/triage.md +180 -0
  29. package/dist/compliance-off/commands/bug-analysis.md +398 -0
  30. package/dist/compliance-off/commands/code-review.md +520 -0
  31. package/dist/compliance-off/commands/dynamic-build.md +1251 -0
  32. package/dist/compliance-off/commands/dynamic-tickets.md +676 -0
  33. package/dist/compliance-off/commands/implement.md +736 -0
  34. package/dist/compliance-off/commands/plan.md +671 -0
  35. package/dist/compliance-off/commands/release.md +309 -0
  36. package/dist/compliance-off/commands/resolve.md +742 -0
  37. package/dist/compliance-off/skills/git/references/decision-markers.md +17 -0
  38. package/dist/compliance-off/skills/git/references/learn-conventions.md +80 -0
  39. package/dist/compliance-off/skills/git/references/pr/check-ci-status.md +33 -0
  40. package/dist/compliance-off/skills/git/references/pr/check-merge-readiness.md +49 -0
  41. package/dist/compliance-off/skills/git/references/pr/fetch-review-threads.md +52 -0
  42. package/dist/compliance-off/skills/git/references/pr/validate-branch.md +47 -0
  43. package/dist/compliance-off/skills/git/references/tracker/_contract.md +33 -0
  44. package/dist/compliance-off/skills/git/references/tracker/_mcp.md +153 -0
  45. package/dist/compliance-off/skills/git/references/tracker/github/create-release.md +35 -0
  46. package/dist/compliance-off/skills/git/references/tracker/github/ensure-pr-ready.md +100 -0
  47. package/dist/compliance-off/skills/git/references/tracker/github/ensure-traceable-issue.md +74 -0
  48. package/dist/compliance-off/skills/git/references/tracker/github/fetch-issue.md +69 -0
  49. package/dist/compliance-off/skills/git/references/tracker/github/fetch-issues-batch.md +65 -0
  50. package/dist/compliance-off/skills/git/references/tracker/github/gather-release-evidence.md +48 -0
  51. package/dist/compliance-off/skills/git/references/tracker/github/setup-task.md +77 -0
  52. package/dist/compliance-off/skills/git/references/tracker/jira/create-release.md +35 -0
  53. package/dist/compliance-off/skills/git/references/tracker/jira/ensure-pr-ready.md +100 -0
  54. package/dist/compliance-off/skills/git/references/tracker/jira/ensure-traceable-issue.md +61 -0
  55. package/dist/compliance-off/skills/git/references/tracker/jira/fetch-issue.md +51 -0
  56. package/dist/compliance-off/skills/git/references/tracker/jira/fetch-issues-batch.md +63 -0
  57. package/dist/compliance-off/skills/git/references/tracker/jira/gather-release-evidence.md +47 -0
  58. package/dist/compliance-off/skills/git/references/tracker/jira/setup-task.md +82 -0
  59. package/dist/compliance-off/skills/git/references/tracker/linear/create-release.md +35 -0
  60. package/dist/compliance-off/skills/git/references/tracker/linear/ensure-pr-ready.md +100 -0
  61. package/dist/compliance-off/skills/git/references/tracker/linear/ensure-traceable-issue.md +61 -0
  62. package/dist/compliance-off/skills/git/references/tracker/linear/fetch-issue.md +51 -0
  63. package/dist/compliance-off/skills/git/references/tracker/linear/fetch-issues-batch.md +63 -0
  64. package/dist/compliance-off/skills/git/references/tracker/linear/gather-release-evidence.md +47 -0
  65. package/dist/compliance-off/skills/git/references/tracker/linear/setup-task.md +83 -0
  66. package/dist/compliance-off/skills/git/references/trust-rule.md +7 -0
  67. package/dist/core/agent-models.js +19 -4
  68. package/dist/core/assets.js +126 -17
  69. package/dist/core/compliance-off-files.js +27 -0
  70. package/dist/core/curation-variants.js +460 -112
  71. package/dist/core/flags.js +2 -2
  72. package/dist/core/installed-variants.js +63 -0
  73. package/dist/core/manifest.js +48 -5
  74. package/dist/core/mds-variants.js +700 -3
  75. package/dist/core/orphan-sweep.js +11 -2
  76. package/dist/core/plugins.js +65 -8
  77. package/dist/curation-compliance-off/agents/triage.md +176 -0
  78. package/dist/curation-compliance-off/commands/bug-analysis.md +349 -0
  79. package/dist/curation-compliance-off/commands/code-review.md +481 -0
  80. package/dist/curation-compliance-off/commands/dynamic-build.md +1211 -0
  81. package/dist/curation-compliance-off/commands/dynamic-tickets.md +634 -0
  82. package/dist/curation-compliance-off/commands/implement.md +689 -0
  83. package/dist/curation-compliance-off/commands/plan.md +631 -0
  84. package/dist/curation-compliance-off/commands/release.md +279 -0
  85. package/dist/curation-compliance-off/commands/resolve.md +692 -0
  86. package/dist/curation-off/agents/code.md +6 -6
  87. package/dist/curation-off/agents/fix.md +6 -6
  88. package/dist/curation-off/agents/harden.md +7 -6
  89. package/dist/curation-off/agents/refactor.md +6 -6
  90. package/dist/curation-off/commands/bug-analysis.md +69 -119
  91. package/dist/curation-off/commands/dynamic-build.md +23 -23
  92. package/dist/curation-off/commands/implement.md +46 -32
  93. package/dist/curation-off/commands/release.md +1 -1
  94. package/dist/curation-off/commands/resolve.md +35 -31
  95. package/dist/curation-off/commands/self-review.md +8 -7
  96. package/dist/skills/git/references/pr/resolve-review-threads.md +1 -1
  97. package/dist/targets/claude-code/behavior-install.js +23 -9
  98. package/dist/targets/claude-code/charter-render.js +374 -0
  99. package/dist/targets/claude-code/compliance-install.js +32 -12
  100. package/dist/targets/claude-code/compliance-off-files.js +245 -0
  101. package/dist/targets/claude-code/curation-install.js +570 -100
  102. package/dist/targets/claude-code/installer.js +467 -62
  103. package/dist/targets/claude-code/language-stamp.js +6 -0
  104. package/dist/targets/claude-code/linked-folder.js +182 -0
  105. package/dist/targets/claude-code/reference-converge.js +118 -0
  106. package/dist/targets/claude-code/tracker-install.js +21 -8
  107. package/dist/targets/claude-code/tracker-servers.js +20 -6
  108. package/dist/targets/claude-code/workflow-folder-install.js +231 -0
  109. package/dist/variant-axes.json +109 -0
  110. package/package.json +1 -1
  111. package/src/assets/agents/_partials/_code.mds +6 -6
  112. package/src/assets/agents/evaluate.md +1 -1
  113. package/src/assets/agents/git.mds +32 -0
  114. package/src/assets/agents/harden.mds +7 -6
  115. package/src/assets/agents/test.md +21 -12
  116. package/src/assets/agents/triage.mds +2 -0
  117. package/src/assets/agents/validate.md +10 -10
  118. package/src/assets/commands/_partials/_compliance.mds +5 -3
  119. package/src/assets/commands/_partials/_engine.mds +9 -8
  120. package/src/assets/commands/_partials/_publication.mds +10 -1
  121. package/src/assets/commands/bug-analysis.mds +80 -126
  122. package/src/assets/commands/code-review.mds +43 -0
  123. package/src/assets/commands/dynamic-build.mds +109 -15
  124. package/src/assets/commands/dynamic-tickets.mds +13 -0
  125. package/src/assets/commands/implement.mds +145 -33
  126. package/src/assets/commands/plan.mds +19 -0
  127. package/src/assets/commands/release.mds +50 -1
  128. package/src/assets/commands/resolve.mds +217 -32
  129. package/src/assets/commands/self-review.mds +8 -7
  130. package/src/assets/mds/git/_ops.mds +23 -0
  131. package/src/assets/mds/git/_ops_steps.mds +17 -0
  132. package/src/assets/mds/git/_pr.mds +1 -1
  133. package/src/assets/mds/git/_references.mds +6 -0
  134. package/src/assets/mds/tracker/_common.mds +4 -0
  135. package/src/assets/mds/tracker/_github.mds +42 -0
  136. package/src/assets/mds/tracker/_jira.mds +43 -0
  137. package/src/assets/mds/tracker/_linear.mds +43 -0
  138. package/src/assets/rules/quality.md +2 -0
  139. package/src/assets/scripts/ci-wait.cjs +204 -126
  140. package/src/assets/scripts/hooks/assets/implement-installed +1 -0
  141. package/src/assets/scripts/hooks/assets/orchestrator-charter.md +13 -11
  142. package/src/assets/scripts/hooks/preamble +18 -8
  143. package/src/assets/scripts/hooks/session-start-orchestrator +4 -2
  144. package/src/assets/scripts/pr-evidence.cjs +41 -2
  145. package/src/assets/scripts/verify-evidence.cjs +408 -56
  146. package/src/assets/skills/agent-references/references/commands/implement-revalidate.md +3 -3
  147. package/src/assets/skills/agent-references/references/commands/plan-levels.md +1 -1
  148. package/src/assets/skills/agent-references/references/synthesize/bug-analysis.md +6 -2
  149. package/src/assets/skills/agent-references/references/validate/passed-alone.md +7 -7
  150. package/src/assets/skills/docs-framework/SKILL.md +5 -2
  151. package/src/assets/skills/git/SKILL.md +3 -3
  152. package/src/assets/skills/git/references/github-api.md +2 -139
  153. package/src/assets/skills/git/references/github-posting.md +158 -0
  154. package/src/assets/skills/review-methodology/SKILL.md +1 -1
  155. package/src/assets/skills/review-methodology/references/patterns.md +1 -1
  156. package/src/assets/skills/review-methodology/references/report-template.md +1 -2
  157. package/src/assets/skills/testing/SKILL.md +2 -4
package/CHANGELOG.md CHANGED
@@ -5,6 +5,116 @@ 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.10.0] - 2026-10-30
9
+
10
+ ### Upgrade notes — Shadowed rules and skills
11
+
12
+ **What you must do:**
13
+ - A quality rule that you keep at `~/.devflow/rules/quality.md` replaces the shipped one, so you do not get its two new bullets. Add them to your copy. Or run `devflow rules unshadow quality`, which deletes your copy, and then run `devflow init`.
14
+ - A testing skill that you keep at `~/.devflow/skills/testing/` replaces the shipped one, so you do not get its new Quality Gates item or its cuts. Add the item to your copy. Or run `devflow skills unshadow testing`, which deletes your copy, and then run `devflow init`.
15
+ - A review-methodology or agent-references skill that you keep under `~/.devflow/skills/` replaces the shipped one, so you do not get the new follow-up text of #690. Add it to your copy. Or run `devflow skills unshadow <name>`, which deletes your copy, and then run `devflow init`.
16
+
17
+ **Stale-install window:**
18
+ - Until you run `devflow init`, the installed quality rule, testing, review-methodology and agent-references skills lack the new text.
19
+
20
+ ### Behaviour change
21
+
22
+ - **With compliance off, devflow installs no step or reference that posts to GitHub or a tracker** ([#648](https://github.com/dean0x/devflow/issues/648)).
23
+ - With compliance off (the default for new installs and upgrades), devflow installs no step and no reference that posts test-plan evidence, review or resolution summaries, backlog entries, review-thread replies, wave reports, plan artifacts, or release back-links to GitHub or a tracker. Results stay in local files under `.devflow/docs/`.
24
+ - The change covers the `/implement`, `/resolve`, `/code-review`, `/bug-analysis`, `/dynamic-build`, `/dynamic-tickets`, `/plan` and `/release` commands, the Git and Triage agents, and the reference files that the Git agent reads. Each command names its local result files and marks them `(local; not posted)`. The commands still open pull requests, link issues and file tickets.
25
+ - The Git agent reads one reference file for each operation. With compliance off, devflow installs none of the files for the operations that post: the review summary, the resolution summary, the test-plan evidence, the backlog entry, the review-thread reply, the wave report and the two release back-link operations. This holds for the pull-request host and for each tracker. Devflow also installs neither the publication-gate document nor the `/implement` reference for the evidence questions. The other operation files stay.
26
+ - The GitHub posting recipes moved out of the GitHub API reference into a separate reference, `github-posting.md`. The recipes are the pull-request comments, the review, and the replies to review threads. That reference installs only with compliance on. The GitHub API reference keeps the rate-limit, fetch and release text.
27
+ - With compliance off, devflow no longer adds a link comment to an existing issue. It still creates a new issue when the task has none.
28
+ - The evidence publish script refuses to post unless the machine setting has compliance on. It reads only the machine install record. It ignores the devflow directory setting in the environment, and it reads no repository file. When compliance is off, it prints `TRACEABILITY: DEGRADED (compliance off)`. When it cannot read the record, it prints `TRACEABILITY: DEGRADED (compliance state unreadable)`. In both cases it exits with code 2 and makes no GitHub call.
29
+ - Where the repository requires evidence, each command prints one line: `TRACEABILITY: DEGRADED (compliance off on this machine; the repository requires evidence)`.
30
+ - Only the machine switch picks the installed build. A repository `compliance` key keeps the review lens on, locally, and never brings a posting step back.
31
+ - `devflow init`, `devflow curation` and `devflow compliance` use one converge. Every change of the compliance switch converges the installed commands, agents and references at once. The result equals a fresh install with the same switches, apart from stamps. With compliance off, the converge removes the omitted references. When compliance is on again, it puts each one back with the same bytes. If `devflow compliance` turns compliance on but cannot refresh an installed reference, it records the switch as on, names the cause, and exits 1. If `devflow compliance --disable` cannot show that no installed file still posts, it records the switch as off, lists the files that still post, and exits 1. A listed file can be a command, an agent or a reference. It also exits 1, with no success line, when it cannot remove the compliance rule. The command writes the switch under the install manifest lock. If it cannot take the lock, it writes nothing and exits 1.
32
+ - `devflow init` prints a one-time notice that names this effect. `devflow compliance --status` has a new `Posting:` line.
33
+ - The wave block that `/dynamic-build` writes on such a machine lists links only: `Refs` lines, never `Closes`. It admits `KEY-N` references as well as `#N` references. This corrects the earlier links-only rule, which admitted only `#N` references.
34
+ - Run `devflow init` after you update to install the new commands and references.
35
+
36
+ ### Changed
37
+
38
+ - **Plan and code review list follow-up work, and file fewer tickets by default** ([#690](https://github.com/dean0x/devflow/issues/690)).
39
+ - At the `standard` and `lean` levels, `/plan` lists a sibling finding on the plan's deferred list, with no proposed ticket title, and files no ticket for it. You file one only if you want it. The `full` level does not change. The rest of the scope rule does not change.
40
+ - The review-methodology skill lists pre-existing problems and does not suggest separate issues for them. Its report template recommends an issue only for data loss, a false pass or a broken install.
41
+ - The plan levels reference is 47 bytes smaller (4,056 to 4,009), and its ceiling falls with it. The review-methodology skill is 24 bytes smaller, and its report template is 26 bytes larger.
42
+
43
+ - **The quality rule and the testing skill target doc drift, duplicated code and vacuous tests** ([#476](https://github.com/dean0x/devflow/issues/476)).
44
+ - The quality rule has two new bullets. When a change alters a name, path or behavior, the agent updates the current tracked docs that state the old fact, and not released changelog entries. It describes a security fix by its effect only. Before it adds new code, the agent finds the existing owner and extends it when the new code shares its job. A trust-boundary or fail-closed check is not a copy.
45
+ - The Quality Gates of the testing skill have a new item: each new assertion would fail, not error, with the change reverted. The RED run counts; else a bad input or a temporary, uncommitted revert of your own edit shows it, never a stash. Three items that repeated other parts of the skill are cut, and a repeated parenthetical is cut from the flaky-pattern item. The skill is 25 bytes smaller, so the Code and Fix preload totals fall by 25 bytes each (Code 25,514 to 25,489, Fix 14,875 to 14,850). The quality rule is not in the preload and loads in every session: it grows by 326 bytes.
46
+ - A guard pins each new line once, with its limits, and pins a byte ceiling for the two bullets.
47
+
48
+ ### Fixed
49
+
50
+ - **A partial install no longer writes through a linked devflow folder** ([#648](https://github.com/dean0x/devflow/issues/648)).
51
+ - If a folder that a partial install writes (commands, rules, agents, a skill folder or its references folder) is a link, the install now writes nothing there. It reports the folder, and you replace it with a real folder and run the install again.
52
+ - **A full install no longer changes files outside its own folder when a skill folder is a link** ([#648](https://github.com/dean0x/devflow/issues/648)).
53
+ - If an installed skill folder is a link, a full install now removes the link and leaves the linked folder as it is. It then installs the skill into a real folder and reports the link.
54
+ - **devflow no longer writes through a linked folder inside its own install folders; it refuses or replaces the link and reports it** ([#648](https://github.com/dean0x/devflow/issues/648)).
55
+ - This applies to the install, to `uninstall --plugin` and to every converge, for example `devflow curation`, `devflow compliance` and `devflow behavior`.
56
+ - **The `/implement` self-review (Harden) states that it never reviews a deleted file or the old path of a rename** ([#571](https://github.com/dean0x/devflow/issues/571)).
57
+ - The file list that Harden gets leaves out deleted files and the old path of a rename. The command now says so.
58
+ - When the prompt lists no file, Harden reports `### Status: PASS` with the line `no files reviewed`. An empty list no longer reads as a completed review, because the final report of `/implement` shows the line, at every gate level. When the prompt has no file list at all, Harden finds the branch changes and reviews them, as before.
59
+ - The docs describe the same rule.
60
+ - **The `/implement` command states that alignment-fix commits get no self-review** ([#572](https://github.com/dean0x/devflow/issues/572)).
61
+ - Phase 4 runs before the alignment fix, so Harden does not review the commits of that fix. The command now says that Phase 6 Validate and `/code-review` check them.
62
+ - The docs describe the same rule.
63
+ - **At `lean`, `/implement` runs the lint pack again after a QA fix** ([#535](https://github.com/dean0x/devflow/issues/535)).
64
+ - The re-validation after a QA fix now gets the gate level and the branch base, as Phase 6 does. A pack match that a QA fix adds is now reported, and the re-validation fails on it, as it does on any other failure.
65
+ - At `lean`, the re-validation labels passed-alone files, and its `Lint pack:` line replaces the one from Phase 6. The pack scans the branch a second time, so a run with QA fixes does that work twice.
66
+ - A pack error after a QA fix reads `UNVERIFIED`, never a pass. So does a re-validation that prints no `Lint pack:` line: it never reads clean, and it never drops an `UNVERIFIED` result from the first validation.
67
+ - At `full` and `standard`, the re-validation prompt does not change.
68
+ - **`/implement` re-validation now follows your compliance, review-publication and PR-title settings, and always runs self-review** ([#544](https://github.com/dean0x/devflow/issues/544)).
69
+ - With compliance on, the fix agents in a re-validation get the compliance guidance, as in a full run. With compliance off, re-validation gives them none, as before.
70
+ - Re-validation now follows your review-publication setting, as a full run does.
71
+ - Re-validation now applies your team's PR-title convention, as a full run does.
72
+ - Re-validation always runs the self-review (Harden) step, at every gate level.
73
+ - **The gate-level step of `devflow init` fits an 80-column terminal** ([#658](https://github.com/dean0x/devflow/issues/658)).
74
+ - Each level hint is now one short line, and no line of the step note is longer than 72 characters.
75
+ - The step note names `/resolve` with the other commands that read the level.
76
+ - The full effect of each level stays in `devflow gates` and in the docs.
77
+ - **`/bug-analysis` no longer reports "no findings" when static analysis could not run** ([#649](https://github.com/dean0x/devflow/issues/649)).
78
+ - When no scanner is installed, it says that static analysis did not run. It no longer reports "no findings" for a run that scanned nothing.
79
+ - A scanner that fails, does not finish or exits with an error is named in the report with its exit status, or says that it had none.
80
+ - File names with spaces or other unusual characters reach the scanners whole.
81
+ - Semgrep and Snyk now run one after the other.
82
+ - **`/bug-analysis` keeps its head marker when static analysis did not cover the commits, and a long scanner result no longer hides findings** ([#652](https://github.com/dean0x/devflow/issues/652), [#661](https://github.com/dean0x/devflow/issues/661)).
83
+ - The marker moves only when every tool that was to run completed, or `--no-static` is set. Otherwise the report keeps the marker and names the way out: fix the tool, or run with `--no-static`. The next run then covers the same commits again.
84
+ - The report shows a status for each tool: completed, not installed, not needed (CodeQL only), no supported language (CodeQL, or Snyk when it finds no supported file), or did not complete with the reason. The summary takes this status from the command, not from the text of a file.
85
+ - Each scanner writes its SARIF to a file, and the session reads counts and a table of the top 50 findings. A long result no longer hides a finding or the exit line. Every finding is saved to a table in the run folder.
86
+ - Snyk keeps each result for a changed file. If its filter fails, the report says so and uses the unfiltered result.
87
+ - CodeQL scans one language, chosen from the changed files: JavaScript or TypeScript, Python, or Ruby. With none of them, the report says that CodeQL had no supported language.
88
+ - Each Semgrep, Snyk and CodeQL call has its Bash timeout at the ceiling: 600000 ms, or `BASH_MAX_TIMEOUT_MS` when set. Devflow sets no time limit of its own on a scanner. A call that reaches the Bash limit counts as not complete.
89
+ - **The orchestrator charter names only installed workflows and routes to more agents.**
90
+ - The charter now names only the workflows that you installed, and it names the Fix agent only when it is installed.
91
+ - It routes to the Test, Evaluate, Harden and Synthesize agents. It routes to the Knowledge agent only when you ask for a new knowledge base. It names the `devflow:bug-analysis` workflow.
92
+ - For debug work, it names `devflow:debug` when that workflow is installed. Otherwise it sends the work to Explore, then Code. It never routes to the Debug agent.
93
+ - A plan handoff no longer points to `devflow:implement` when it is not installed. The prompt gets a generic reminder instead.
94
+ - `devflow ambient --enable` now warns when the ambient agents are not installed. It still registers the hooks.
95
+ - `devflow uninstall --plugin` removes the plan-handoff marker when it removes implement. It does this also when the manifest has no record of the install.
96
+ - `devflow uninstall --plugin` also drops a manifest record that holds the old name of the removed plugin.
97
+ - Run `devflow init` after you update to refresh the charter.
98
+ - **The CI wait and the Validate re-runs no longer stop too early** ([#664](https://github.com/dean0x/devflow/issues/664)). The CI wait waits for CI to start until its 570 s limit, not only 90 s, so a repository with no CI now waits 570 s before the wait reports no CI. When a pull request has only external checks and the repository has no workflow file, the wait ends at once. The CI gates of `/implement` and `/resolve` do not wait again after a full 570 s wait that ends with a head mismatch or no workflow check, so one gate waits at most about 3 x 570 s (28.5 minutes), and only when CI keeps running or its status stays unknown for another cause. Validate no longer stops the re-run of a failed test file alone after 120 s.
99
+ - **Validate waits for a long one-file re-run and reads its result** ([#666](https://github.com/dean0x/devflow/issues/666)). When the re-run of one failed test file runs past its Bash timeout, Validate now waits for it to end and reads its result, so the file gets its label from the real result and no test process stays running with nobody to read it. After one such re-run, Validate re-runs no more files, and the other failed files are REAL.
100
+ - **A run that reaches its Bash timeout is waited for and read, and only the last exit line of the output file that the end notice names sets the result** ([#677](https://github.com/dean0x/devflow/issues/677)).
101
+ - The Code, Fix, Refactor, Validate and Test agents no longer report BLOCKED for a build, test or lint run that passes its Bash timeout. The run moves to the background. The agent makes no tool call, not even a read, until the end notice. Then it reads the last `EXIT=` line of the file that the move names, not the capture log, because a read of the wrong file could report a pass. That line alone sets the result: the end notice and the log do not.
102
+ - A run is BLOCKED only when no `EXIT=` line is read, with its duration and log path. A failed run that is read after the wait gets its re-runs like any failed run. A `LINT_PACK:` line that is read after the wait counts like a line read in time.
103
+ - Splitting a long run into parts is optional. The `bash-max-timeout-ms` flag is optional too, and the agents no longer name it.
104
+ - The one-file re-run in Validate is waited for and read in the same way.
105
+ - `/bug-analysis` waits for a scanner call that reaches the Bash limit, with no tool call until the end notice, and then reads the last `exit=` line of the output file that the notice names, never a scanner file. A scanner call with no `exit=` line read still counts as not complete. Semgrep writes its result to `semgrep.part` and renames it to `semgrep.sarif` when its scan ends, so the SARIF file appears only when the scan ends. The warning for a missing scanner no longer names an install command.
106
+ - `/implement` reports and halts when the Validate report has a Status other than PASS or FAIL, or none. This includes a BLOCKED report, in the validation and in the re-validation. `/resolve` records FAILED and ends its validation loop in the same case. Neither reads it as a pass.
107
+ - Accepted cost: devflow stops no run, so a run that never ends holds its agent until you stop it (Esc). Parallel agents in one wave each wait for their own run.
108
+ - **A test or validation run that gives no result never reads as a pass** ([#679](https://github.com/dean0x/devflow/issues/679), [#680](https://github.com/dean0x/devflow/issues/680), [#681](https://github.com/dean0x/devflow/issues/681)).
109
+ - The Test agent can report `BLOCKED`: no blocking scenario failed, and the command of a blocking scenario gave no result (it could not start, or no `EXIT=` line was read). The report lists each such scenario under `### Blockers`, with its cause, its duration and its log path. A scenario that is skipped because a tool is missing never makes the run BLOCKED. When a blocking scenario is skipped and every blocking scenario that ran passed, the Status line stays `PASS` and names each skip, for example `PASS (1 BLOCKING scenario skipped: no browser tools)`, or `PASS (0 of 3 BLOCKING scenarios ran: all skipped, no browser tools)` when none ran. `/implement`, `/resolve` and the acceptance gate of `/dynamic-build` read that line by its first word, `PASS`; the gate returns the verdict `PASS` without the note. Each test-plan line still gets `PASS`, `FAIL` or `SKIP`, and a line is `PASS` only when at least one scenario covers it and every one passed.
110
+ - `/implement` gives the cause and the log path when it stops on a BLOCKED Validate or Test report. It also stops on a Test report with any other Status or none. It starts no fix agent, and it still records the test-plan claims of that Test report.
111
+ - `/resolve` records a BLOCKED validation as `Final gate: FAILED (Validate blocked)`, with a blocking callout, and starts no fix agent. It still pushes, and the later phases still run. The cause and the log path go only to the report that you see, never to the posted summary. A stale test-plan re-check that is BLOCKED records `Test plan: DEGRADED (Test run blocked)`.
112
+ - In `/dynamic-build`, a BLOCKED, missing or unknown Test result in the acceptance gate keeps the ticket from `PASS`: the run reports `UNVERIFIED`, or `PARTIAL` when the review leaves findings. It starts no fix agent and raises no error. In wave mode such a ticket merges as `UNVERIFIED`, never as `PASS`, and the wave table shows `—` in its Test column. This includes a ticket whose Test reply is missing or empty.
113
+ - The build rules of `/dynamic-build` name all five agents that carry the shared rules for running commands: Code, Fix, Refactor, Validate and Test.
114
+ - **`/self-review` stops when its validation does not pass** ([#695](https://github.com/dean0x/devflow/issues/695)). A Validate report with a Status other than PASS or FAIL, or with no Status, stops the command. This includes a BLOCKED report. The command reports the status, or "none", with its cause and, when the report gives one, its log path. It shows no completion summary. Before, it had steps only for PASS and FAIL, so the summary could read as if validation passed.
115
+
116
+ ---
117
+
8
118
  ## [3.9.0] - 2026-10-22
9
119
 
10
120
  ### Upgrade notes — Evaluate by level
@@ -2228,6 +2338,7 @@ devflow init
2228
2338
  ---
2229
2339
 
2230
2340
  [Unreleased]: https://github.com/dean0x/devflow/compare/v3.8.0...HEAD
2341
+ [3.10.0]: https://github.com/dean0x/devflow/compare/v3.9.0...v3.10.0
2231
2342
  [3.9.0]: https://github.com/dean0x/devflow/compare/v3.8.0...v3.9.0
2232
2343
  [3.8.0]: https://github.com/dean0x/devflow/compare/v3.7.0...v3.8.0
2233
2344
  [3.7.0]: https://github.com/dean0x/devflow/compare/v3.6.0...v3.7.0
package/README.md CHANGED
@@ -52,7 +52,7 @@ This is the **orchestrated flow** — you stay in the loop between every step. W
52
52
 
53
53
  ## What you get
54
54
 
55
- **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
+ **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 run `/implement` when it is installed. Init and forget.
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
 
@@ -126,13 +126,13 @@ When `OPERATION: issue-fix`, you are fixing pre-classified issues assigned FIX_N
126
126
 
127
127
  Run builds, typechecks, lints and tests in the foreground, each with an explicit Bash `timeout` above its expected run time. The ceiling is 600000 ms, or `BASH_MAX_TIMEOUT_MS` when set (`echo ${BASH_MAX_TIMEOUT_MS:-600000}`).
128
128
 
129
- - Capture, then tail, in one Bash call (shell state does not persist): `LOG=$(mktemp); echo "LOG=$LOG"; <command> >"$LOG" 2>&1; rc=$?; tail -n 40 "$LOG"; echo "EXIT=$rc"`. The printed `EXIT=` value is the result; never decide one from a grep count.
130
- - Never background a command and wait on it, and never poll across turns: no `sleep` or `true` turns, no sentinel-file checks, no Monitor.
131
- - Prefer the scoped command for the change (a package, a path or a test file); for the whole set, one workspace-level command over a per-package loop.
132
- - A run that exceeds its timeout is BLOCKED: report its duration and log path. Do not wait on it, poll it or re-run it.
133
- - A run expected to exceed the ceiling is split into parts, each under about 90% of it, run in sequence. If it cannot be split, report BLOCKED with the remedy `devflow flags --set bash-max-timeout-ms=<ms>`.
129
+ - Capture, then tail, in one Bash call: `LOG=$(mktemp); echo "LOG=$LOG"; <command> >"$LOG" 2>&1; rc=$?; tail -n 40 "$LOG"; printf '\nEXIT=%s\n' "$rc"`. The last `EXIT=` line is the result; never decide one from a grep count.
130
+ - Never background a build or test, and never poll across turns: no `sleep` or `true` turns, no sentinel-file checks, no Monitor.
131
+ - Prefer the scoped command for the change; for the whole set, one workspace command over per-package loops.
132
+ - A run past its timeout moves to the background: no tool call, even a Read, until its end notice, then Read the last `EXIT=` line of the file the move names, not `$LOG`; it alone sets the label, not the notice. With none read it is BLOCKED: report its duration and log path. Report only after the end notice.
133
+ - A run expected to exceed the ceiling may be split into parts, run in sequence.
134
134
  - Never re-run a command when nothing it reads has changed.
135
- - Never wrap a build or test command in `sh -c`, `bash -c`, `python3 -c` or `node -e`: permission rules deny wrapped commands they would allow directly.
135
+ - Never wrap a build or test command in `sh -c`, `bash -c`, `python3 -c` or `node -e`: permission rules deny wrapped commands they allow directly.
136
136
  - The same rules hold inside a dynamic Workflow sub-agent.
137
137
 
138
138
  ## Principles
@@ -108,13 +108,13 @@ When `OPERATION: ci-fix`, you are fixing the CI checks the ci-status gate report
108
108
 
109
109
  Run builds, typechecks, lints and tests in the foreground, each with an explicit Bash `timeout` above its expected run time. The ceiling is 600000 ms, or `BASH_MAX_TIMEOUT_MS` when set (`echo ${BASH_MAX_TIMEOUT_MS:-600000}`).
110
110
 
111
- - Capture, then tail, in one Bash call (shell state does not persist): `LOG=$(mktemp); echo "LOG=$LOG"; <command> >"$LOG" 2>&1; rc=$?; tail -n 40 "$LOG"; echo "EXIT=$rc"`. The printed `EXIT=` value is the result; never decide one from a grep count.
112
- - Never background a command and wait on it, and never poll across turns: no `sleep` or `true` turns, no sentinel-file checks, no Monitor.
113
- - Prefer the scoped command for the change (a package, a path or a test file); for the whole set, one workspace-level command over a per-package loop.
114
- - A run that exceeds its timeout is BLOCKED: report its duration and log path. Do not wait on it, poll it or re-run it.
115
- - A run expected to exceed the ceiling is split into parts, each under about 90% of it, run in sequence. If it cannot be split, report BLOCKED with the remedy `devflow flags --set bash-max-timeout-ms=<ms>`.
111
+ - Capture, then tail, in one Bash call: `LOG=$(mktemp); echo "LOG=$LOG"; <command> >"$LOG" 2>&1; rc=$?; tail -n 40 "$LOG"; printf '\nEXIT=%s\n' "$rc"`. The last `EXIT=` line is the result; never decide one from a grep count.
112
+ - Never background a build or test, and never poll across turns: no `sleep` or `true` turns, no sentinel-file checks, no Monitor.
113
+ - Prefer the scoped command for the change; for the whole set, one workspace command over per-package loops.
114
+ - A run past its timeout moves to the background: no tool call, even a Read, until its end notice, then Read the last `EXIT=` line of the file the move names, not `$LOG`; it alone sets the label, not the notice. With none read it is BLOCKED: report its duration and log path. Report only after the end notice.
115
+ - A run expected to exceed the ceiling may be split into parts, run in sequence.
116
116
  - Never re-run a command when nothing it reads has changed.
117
- - Never wrap a build or test command in `sh -c`, `bash -c`, `python3 -c` or `node -e`: permission rules deny wrapped commands they would allow directly.
117
+ - Never wrap a build or test command in `sh -c`, `bash -c`, `python3 -c` or `node -e`: permission rules deny wrapped commands they allow directly.
118
118
  - The same rules hold inside a dynamic Workflow sub-agent.
119
119
 
120
120
  ## Principles
@@ -33,16 +33,18 @@ disallowedTools:
33
33
 
34
34
  # Harden Agent
35
35
 
36
- You are a meticulous self-review specialist. You evaluate implementations against the 9-pillar quality framework and fix the issues you find. You run in a fresh context after the Code and Polish agents complete, ensuring adequate resources for thorough review and fixes.
36
+ You are a meticulous self-review specialist. You evaluate implementations against the 9-pillar quality framework and fix the issues you find. You run in a fresh context after the Code and Polish agents complete.
37
37
 
38
38
  ## Input Context
39
39
 
40
40
  You receive from orchestrator:
41
41
  - **TASK_DESCRIPTION**: What was implemented
42
- - **FILES_CHANGED**: List of modified files from Code agent output
42
+ - **FILES_CHANGED** (optional): The branch diff's file list, with no deleted file or old rename path
43
43
  - **DECISIONS_CONTEXT** (optional): Compact index of active ADR/PF entries for this repository (pre-rendered to `.devflow/curation/index.md` in its main worktree). `(none)` when absent. Use `devflow:apply-decisions` to Read full bodies on demand.
44
44
  - **FEATURE_KNOWLEDGE** (optional): Per KB, the Rules bullets and the KB path, with no heading index, for pattern compliance checking. Check implementation against each bullet's anti-pattern, gotcha or invariant; Read a KB section from its path for more. Follow `devflow:apply-feature-knowledge`.
45
45
 
46
+ **Empty FILES_CHANGED**: If the prompt has a `FILES_CHANGED` line that lists no file, report `### Status: PASS` with the line `no files reviewed`, and review nothing; with no `FILES_CHANGED` line, find the branch changes and review them.
47
+
46
48
  **Worktree Support**: If `WORKTREE_PATH` is provided, follow the `devflow:worktree-support` skill for path resolution. If omitted, use cwd.
47
49
 
48
50
  ## Apply Decisions
@@ -72,10 +74,9 @@ Code-scanning runs in the Code agent; do not scan here.
72
74
  ## Principles
73
75
 
74
76
  1. **Fix, don't report** - Self-review means fixing issues, not generating reports
75
- 2. **Fresh context advantage** - Use your full context for thorough evaluation
76
- 3. **Pillar priority** - P0 issues block, P1 issues should be fixed, P2 covers Documentation only
77
- 4. **Minimal changes** - Fix the issue, don't refactor surrounding code
78
- 5. **Honest assessment** - If P0 issue is unfixable, report BLOCKED immediately
77
+ 2. **Pillar priority** - P0 issues block, P1 issues should be fixed, P2 covers Documentation only
78
+ 3. **Minimal changes** - Fix the issue, don't refactor surrounding code
79
+ 4. **Honest assessment** - If P0 issue is unfixable, report BLOCKED immediately
79
80
 
80
81
  ## Output
81
82
 
@@ -75,13 +75,13 @@ When `OPERATION: edit`, you apply a mechanical change: a rename, a move or boile
75
75
 
76
76
  Run builds, typechecks, lints and tests in the foreground, each with an explicit Bash `timeout` above its expected run time. The ceiling is 600000 ms, or `BASH_MAX_TIMEOUT_MS` when set (`echo ${BASH_MAX_TIMEOUT_MS:-600000}`).
77
77
 
78
- - Capture, then tail, in one Bash call (shell state does not persist): `LOG=$(mktemp); echo "LOG=$LOG"; <command> >"$LOG" 2>&1; rc=$?; tail -n 40 "$LOG"; echo "EXIT=$rc"`. The printed `EXIT=` value is the result; never decide one from a grep count.
79
- - Never background a command and wait on it, and never poll across turns: no `sleep` or `true` turns, no sentinel-file checks, no Monitor.
80
- - Prefer the scoped command for the change (a package, a path or a test file); for the whole set, one workspace-level command over a per-package loop.
81
- - A run that exceeds its timeout is BLOCKED: report its duration and log path. Do not wait on it, poll it or re-run it.
82
- - A run expected to exceed the ceiling is split into parts, each under about 90% of it, run in sequence. If it cannot be split, report BLOCKED with the remedy `devflow flags --set bash-max-timeout-ms=<ms>`.
78
+ - Capture, then tail, in one Bash call: `LOG=$(mktemp); echo "LOG=$LOG"; <command> >"$LOG" 2>&1; rc=$?; tail -n 40 "$LOG"; printf '\nEXIT=%s\n' "$rc"`. The last `EXIT=` line is the result; never decide one from a grep count.
79
+ - Never background a build or test, and never poll across turns: no `sleep` or `true` turns, no sentinel-file checks, no Monitor.
80
+ - Prefer the scoped command for the change; for the whole set, one workspace command over per-package loops.
81
+ - A run past its timeout moves to the background: no tool call, even a Read, until its end notice, then Read the last `EXIT=` line of the file the move names, not `$LOG`; it alone sets the label, not the notice. With none read it is BLOCKED: report its duration and log path. Report only after the end notice.
82
+ - A run expected to exceed the ceiling may be split into parts, run in sequence.
83
83
  - Never re-run a command when nothing it reads has changed.
84
- - Never wrap a build or test command in `sh -c`, `bash -c`, `python3 -c` or `node -e`: permission rules deny wrapped commands they would allow directly.
84
+ - Never wrap a build or test command in `sh -c`, `bash -c`, `python3 -c` or `node -e`: permission rules deny wrapped commands they allow directly.
85
85
  - The same rules hold inside a dynamic Workflow sub-agent.
86
86
 
87
87
  ## Principles
@@ -4,7 +4,7 @@ import * as path from 'path';
4
4
  import * as p from '@clack/prompts';
5
5
  import color from 'picocolors';
6
6
  import { getClaudeDirectory, getDevFlowDirectory } from '../../targets/claude-code/claude-paths.js';
7
- import { syncManifestFeature } from '../../core/manifest.js';
7
+ import { readManifestPlugins, syncManifestFeature } from '../../core/manifest.js';
8
8
  import { writeSettingsFileAtomic } from '../../core/fs-atomic.js';
9
9
  import { HOOKS_DIR_SUFFIX, endsWithAny, ensureHook, hasHook, removeHooks, runHookCommand, runHookSuffix, } from '../../targets/claude-code/hooks.js';
10
10
  const PREAMBLE_HOOK_MARKER = 'preamble';
@@ -219,6 +219,14 @@ export function createAmbientCommand() {
219
219
  // exist, so every prompt would fail.
220
220
  const devflowDir = getDevFlowDirectory();
221
221
  if (options.enable) {
222
+ // The hooks route to the ambient agents and the rendered charter that `devflow init` installs.
223
+ // The manifest is the record of that install. readManifestPlugins is read-only: unlike
224
+ // readManifest it never heals or rewrites the file. The check runs before the already-enabled
225
+ // return so a repeat run still reports it, and it never blocks the registration below.
226
+ const recordedPlugins = await readManifestPlugins(devflowDir);
227
+ if (recordedPlugins === null || !recordedPlugins.includes('devflow-ambient')) {
228
+ p.log.warn('Ambient agents are not installed. Run `devflow init` to install them.');
229
+ }
222
230
  const updated = await addAmbientHook(settingsContent, devflowDir);
223
231
  if (updated === settingsContent) {
224
232
  // Both hooks already present — addAmbientHook purges any legacy rule anyway
@@ -228,7 +236,7 @@ export function createAmbientCommand() {
228
236
  await writeSettingsFileAtomic(settingsPath, updated);
229
237
  await syncManifestFeature(devflowDir, 'ambient', true);
230
238
  p.log.success('Ambient mode enabled — orchestrator hooks registered');
231
- p.log.info(color.dim('Charter at session start, reminder per prompt, plan handoffs auto-run devflow:implement (git repos only)'));
239
+ p.log.info(color.dim('Charter at session start, reminder per prompt, plan handoffs run devflow:implement when it is installed (git repos only)'));
232
240
  }
233
241
  if (options.disable) {
234
242
  const updated = await removeAmbientHook(settingsContent);
@@ -126,12 +126,17 @@ export async function runComplianceStep(opts) {
126
126
  'Stamps the compliance rule with active framework labels and installs\n' +
127
127
  'framework reference files into the compliance skill. Only the\n' +
128
128
  'frameworks you select are included — zero selection = generic controls.\n\n' +
129
+ 'Compliance on also installs the steps that post test-plan evidence, review\n' +
130
+ 'and resolution summaries, backlog entries, review-thread replies, wave\n' +
131
+ 'reports, plan artifacts and release back-links to GitHub or a tracker.\n' +
132
+ 'Compliance off installs none of them; results stay in local files under\n' +
133
+ '.devflow/docs/.\n\n' +
129
134
  formatFrameworkCatalogue(), 'Compliance');
130
135
  const enableOutcome = await prompts.select({
131
136
  message: 'Enable compliance?',
132
137
  options: [
133
- { value: true, label: 'Yes', hint: 'stamps frameworks into the compliance rule and skill' },
134
- { value: false, label: 'No', hint: 'enable later with devflow compliance --enable' },
138
+ { value: true, label: 'Yes', hint: 'stamps frameworks into the rule and skill; installs the steps that post to GitHub or a tracker' },
139
+ { value: false, label: 'No', hint: 'no step that posts is installed; enable later with devflow compliance --enable' },
135
140
  ],
136
141
  initialValue: seed.enabled,
137
142
  });
@@ -145,7 +150,7 @@ export async function runComplianceStep(opts) {
145
150
  state: { enabled: false, frameworks: [...seed.frameworks] },
146
151
  messages: [{
147
152
  level: 'info',
148
- text: 'Compliance: disabled — enable later with devflow compliance --enable',
153
+ text: 'Compliance: disabled — no step that posts is installed; enable later with devflow compliance --enable',
149
154
  }],
150
155
  };
151
156
  }
@@ -164,7 +169,7 @@ export async function runComplianceStep(opts) {
164
169
  return {
165
170
  kind: 'resolved',
166
171
  state: { enabled: true, frameworks },
167
- messages: [{ level: 'success', text: `Compliance: enabled (${fwLabel})` }],
172
+ messages: [{ level: 'success', text: `Compliance: enabled (${fwLabel}) — the steps that post are installed` }],
168
173
  };
169
174
  }
170
175
  //# sourceMappingURL=compliance-prompts.js.map
@@ -2,11 +2,14 @@
2
2
  * devflow compliance — Enable, disable, set, and check status of the compliance feature.
3
3
  *
4
4
  * CLI-layer module; pure helpers in src/core/compliance.ts,
5
- * I/O orchestration in src/targets/claude-code/compliance-install.ts.
5
+ * I/O orchestration in src/targets/claude-code/curation-install.ts (the one converge
6
+ * for both axes) and compliance-install.ts (the compliance artifacts it composes).
6
7
  * Compliance is manifest-group (like proxy), not config.json-gated.
7
8
  * Per-artifact failures are warn-not-throw, so one failed artifact never aborts the rest.
8
- * Enable/disable each converge BOTH artifacts unconditionally, so neither is left
9
- * half-converged.
9
+ * Every change of the switch (enable, disable, --set, interactive enable) writes the switch,
10
+ * runs the one converge, and only after a clean converge records which variants are installed.
11
+ * With compliance off the command fails closed (AC-28): it exits non-zero when an installed
12
+ * prompt may still post, and prints no success line.
10
13
  * The evidence-policy lines (--status, and the --enable/--set suggestion) come
11
14
  * from src/core/evidence-policy.ts, the seam onto the package's own resolvers;
12
15
  * the CLI prints the keys to add to .devflow/project.json and never writes it
@@ -21,7 +24,10 @@ import { COMPLIANCE_FRAMEWORKS, normalizeFrameworks, parseFrameworkList, } from
21
24
  import { frameworkChoices, FRAMEWORK_SELECT_MESSAGE } from './compliance-prompts.js';
22
25
  import { COMPLIANCE_SKILL_TOKENS } from '../../core/compliance-compose.js';
23
26
  import { evidencePolicyStatusLine, evidencePolicySuggestion, formatEvidencePolicyUnavailable, loadEvidencePolicyModule, loadSettingsModule, repoComplianceStatusLines, } from '../../core/evidence-policy.js';
27
+ import { postingStatus, readInstalledVariants } from '../../core/installed-variants.js';
28
+ import { withManifestLock } from '../../core/manifest-lock.js';
24
29
  import { readManifest, writeManifest } from '../../core/manifest.js';
30
+ import { applyVariantToggle, describePostingLeft, describeVariantConverge, readRunningVersion, } from '../../targets/claude-code/curation-install.js';
25
31
  import { convergeFromManifest } from '../../targets/claude-code/compliance-install.js';
26
32
  import { validateRuleShadow, validateSkillShadow } from '../../targets/claude-code/installer.js';
27
33
  import { getClaudeDirectory, getDevFlowDirectory, } from '../../targets/claude-code/claude-paths.js';
@@ -67,7 +73,7 @@ export function resolveComplianceCliAction(current, action, setFrameworks) {
67
73
  messages: [
68
74
  {
69
75
  level: 'success',
70
- text: 'Compliance disabled — rule removed, frameworks remembered for re-enable',
76
+ text: 'Compliance disabled — the rule and every posting step removed, frameworks remembered for re-enable',
71
77
  },
72
78
  ],
73
79
  };
@@ -118,10 +124,12 @@ async function skillInstalled(claudeDir) {
118
124
  return false;
119
125
  }
120
126
  }
127
+ /** The installed compliance rule file. */
128
+ const complianceRuleFile = (claudeDir) => path.join(claudeDir, 'rules', 'devflow', 'compliance.md');
121
129
  /** Returns true if the compliance rule file exists at the install target. */
122
130
  async function ruleInstalled(claudeDir) {
123
131
  try {
124
- await fs.access(path.join(claudeDir, 'rules', 'devflow', 'compliance.md'));
132
+ await fs.access(complianceRuleFile(claudeDir));
125
133
  return true;
126
134
  }
127
135
  catch {
@@ -156,10 +164,146 @@ async function skillShadowState(devflowDir) {
156
164
  return 'none';
157
165
  }
158
166
  }
167
+ /** The `Posting:` line of `--status`: the install record read against the machine's compliance switch (AC-31). */
168
+ export function postingStatusLine(record, complianceEnabled) {
169
+ const status = postingStatus(readInstalledVariants(record), complianceEnabled);
170
+ const shown = status === 'installed' ? color.green(status) : status === 'not installed' ? color.dim(status) : color.yellow(status);
171
+ return `Posting: ${shown}`;
172
+ }
173
+ /**
174
+ * Write the switch under the manifest lock, into the manifest read inside the lock, so a field
175
+ * that another devflow run wrote since this command first read the manifest is kept. A lock that
176
+ * cannot be taken, or is lost before the write, writes nothing. Null when the switch is on disk,
177
+ * otherwise the reason it is not. Never throws.
178
+ */
179
+ async function recordComplianceSwitch(devflowDir, nextState) {
180
+ try {
181
+ const locked = await withManifestLock(devflowDir, {}, async (lock) => {
182
+ const current = await readManifest(devflowDir);
183
+ if (current === null)
184
+ return 'no readable install manifest';
185
+ if (!(await lock.stillHeld()))
186
+ return 'the install manifest lock was lost';
187
+ await writeManifest(devflowDir, {
188
+ ...current,
189
+ features: { ...current.features, compliance: nextState },
190
+ updatedAt: new Date().toISOString(),
191
+ });
192
+ return null;
193
+ });
194
+ return locked.ok ? locked.value : 'the install manifest lock could not be taken';
195
+ }
196
+ catch (err) {
197
+ return String(err);
198
+ }
199
+ }
200
+ /**
201
+ * Apply one change of the compliance switch: write the switch, run the one converge for both
202
+ * axes, and only after a clean converge record which variants are installed (the toggle helper
203
+ * does that last step). Every change of the enabled value converges, `--set` and interactive enable
204
+ * included, and a `--set` that only changes the frameworks converges too, so the stamp follows.
205
+ *
206
+ * Fail closed on the off side: with compliance off, a converge that cannot prove every installed
207
+ * compliance-armed prompt holds the compliance-off file is an error that lists the files, a
208
+ * toggle that cannot converge at all (a version skew, or no manifest) says that posting steps remain
209
+ * installed until `devflow init`, and a compliance rule that is still installed is an error that
210
+ * names the file. None of them prints the success text. A skipped prompt converge still converges
211
+ * the compliance skill and rule, which do not depend on the installed version. On the enable side
212
+ * the skip is a warning, and a rule that is wanted and still not installed is a second warning.
213
+ * A reference that the converge could not refresh, a linked folder for example, is an error that
214
+ * names it, with exit 1, and the switch stays recorded as on.
215
+ *
216
+ * Returns a result and never throws; the caller prints the messages and sets the exit code.
217
+ */
218
+ export async function applyComplianceChange(opts) {
219
+ const { claudeDir, devflowDir, manifest, action, setFrameworks, runningVersion, packageRoot, warn } = opts;
220
+ const resolved = resolveComplianceCliAction(manifest.features.compliance, action, setFrameworks);
221
+ // normalizeFrameworks deduplicates (e.g. --set gdpr,GDPR → [gdpr]).
222
+ const nextState = {
223
+ enabled: resolved.nextState.enabled,
224
+ frameworks: normalizeFrameworks(resolved.nextState.frameworks),
225
+ };
226
+ // The switch comes first: whatever the converge does next, the machine's choice is on disk.
227
+ const notRecorded = await recordComplianceSwitch(devflowDir, nextState);
228
+ if (notRecorded !== null) {
229
+ return {
230
+ nextState: manifest.features.compliance,
231
+ messages: [{ level: 'error', text: `Could not record the compliance switch, so nothing was changed: ${notRecorded}` }],
232
+ exitCode: 1,
233
+ };
234
+ }
235
+ const outcome = await applyVariantToggle({
236
+ claudeDir,
237
+ devflowDir,
238
+ axis: 'compliance',
239
+ enabled: nextState.enabled,
240
+ runningVersion,
241
+ warn,
242
+ packageRoot,
243
+ });
244
+ if (outcome.kind === 'skipped') {
245
+ // The compliance skill and rule do not depend on the installed version, so they converge
246
+ // whatever the prompt converge did. The converge warns per item and never throws.
247
+ await convergeFromManifest({
248
+ claudeDir,
249
+ devflowDir,
250
+ manifest: { features: { compliance: nextState, rules: manifest.features.rules } },
251
+ warn,
252
+ });
253
+ if (!nextState.enabled) {
254
+ return { nextState, messages: [{ level: 'error', text: outcome.message }], exitCode: 1 };
255
+ }
256
+ const ruleNote = manifest.features.rules && !(await ruleInstalled(claudeDir))
257
+ ? [{ level: 'warn', text: 'The compliance rule was not installed. Run `devflow init`.' }]
258
+ : [];
259
+ return { nextState, messages: [...resolved.messages, ...ruleNote, { level: 'warn', text: outcome.message }], exitCode: 0 };
260
+ }
261
+ const { posting } = outcome.result;
262
+ if (!nextState.enabled && posting.kind !== 'clear') {
263
+ return {
264
+ nextState,
265
+ messages: [
266
+ { level: 'error', text: 'Compliance is recorded as off, but the install may still post to GitHub or a tracker.' },
267
+ ...describePostingLeft(posting).map((text) => ({ level: 'error', text })),
268
+ ],
269
+ exitCode: 1,
270
+ };
271
+ }
272
+ // A failed rule removal is only a warning inside the converge. The line that says the rule is
273
+ // removed prints only when it is gone.
274
+ if (!nextState.enabled && (await ruleInstalled(claudeDir))) {
275
+ return {
276
+ nextState,
277
+ messages: [{
278
+ level: 'error',
279
+ text: `Compliance is recorded as off, but the compliance rule could not be removed: ${complianceRuleFile(claudeDir)}. ` +
280
+ 'Fix the cause in the warning above, or delete the file. Then run `devflow compliance --disable` again.',
281
+ }],
282
+ exitCode: 1,
283
+ };
284
+ }
285
+ const referenceFailures = outcome.result.references.failures;
286
+ if (nextState.enabled && referenceFailures.length > 0) {
287
+ return {
288
+ nextState,
289
+ messages: [{
290
+ level: 'error',
291
+ text: 'Compliance is recorded as on, but some installed references were not refreshed: ' +
292
+ `${referenceFailures.join('; ')}. Replace any link in the cause with a real folder. Then run the command again.`,
293
+ }],
294
+ exitCode: 1,
295
+ };
296
+ }
297
+ const messages = [...resolved.messages];
298
+ const changed = describeVariantConverge(outcome.result, 'compliance', nextState.enabled);
299
+ if (changed !== null)
300
+ messages.push({ level: 'info', text: `Installed prompts: ${changed}` });
301
+ return { nextState, messages, exitCode: 0 };
302
+ }
159
303
  export const complianceCommand = new Command('compliance')
160
304
  .description('Enable, disable, or configure the compliance feature')
161
- .option('--enable', 'Enable compliance (restores previously selected frameworks)')
162
- .option('--disable', 'Disable compliance (rule removed; frameworks remembered for re-enable)')
305
+ .option('--enable', 'Enable compliance (restores previously selected frameworks and installs the steps that post evidence and summaries to GitHub or a tracker)')
306
+ .option('--disable', 'Disable compliance (rule and every step that posts evidence or summaries to GitHub or a tracker removed; frameworks remembered for re-enable)')
163
307
  .option('--status', 'Show compliance state: manifest, installed artifacts, shadow presence, and the evidence policy for the current repository')
164
308
  .option('--set <list>', 'Set active frameworks (comma-separated IDs); enables compliance. Use --set "" for zero frameworks (generic controls only)')
165
309
  .action(async (options) => {
@@ -169,8 +313,8 @@ export const complianceCommand = new Command('compliance')
169
313
  if (!hasFlag) {
170
314
  p.intro(color.bgCyan(color.white(' Compliance ')));
171
315
  const validIds = COMPLIANCE_FRAMEWORKS.map(fw => `${fw.id} — ${fw.hint}`).join('\n ');
172
- p.note(`${color.cyan('devflow compliance --enable')} Enable compliance (restores prior frameworks)\n` +
173
- `${color.cyan('devflow compliance --disable')} Disable compliance (frameworks remembered)\n` +
316
+ p.note(`${color.cyan('devflow compliance --enable')} Enable compliance (restores prior frameworks; installs the posting steps)\n` +
317
+ `${color.cyan('devflow compliance --disable')} Disable compliance (removes the posting steps; frameworks remembered)\n` +
174
318
  `${color.cyan('devflow compliance --status')} Show current state and installed artifacts\n` +
175
319
  `${color.cyan('devflow compliance --set <ids>')} Set active frameworks and enable\n\n` +
176
320
  `Valid framework IDs:\n ${validIds}`, 'Usage');
@@ -228,6 +372,7 @@ export const complianceCommand = new Command('compliance')
228
372
  ? color.yellow(' (withheld — rules disabled)')
229
373
  : '') +
230
374
  (isRuleShadowed ? color.green(' [shadowed]') : ''),
375
+ postingStatusLine(manifest.installedVariants, current.enabled),
231
376
  '',
232
377
  // The repository in cwd, resolved by the package's own resolver with the
233
378
  // compliance state already read above (D-POLICY-CJS-SEAM). Bounded: at most
@@ -277,23 +422,18 @@ export const complianceCommand = new Command('compliance')
277
422
  else {
278
423
  action = 'disable';
279
424
  }
280
- const resolved = resolveComplianceCliAction(current, action, setFrameworks);
281
- // Converge artifacts (always converge, never short-circuit)
282
- await convergeFromManifest({
425
+ // Write the switch, converge both axes, record the installed variants (applyComplianceChange).
426
+ const applied = await applyComplianceChange({
283
427
  claudeDir,
284
428
  devflowDir,
285
- manifest: { features: { compliance: resolved.nextState, rules: manifest.features.rules } },
429
+ manifest,
430
+ action,
431
+ setFrameworks,
432
+ runningVersion: await readRunningVersion(),
286
433
  warn: (msg) => p.log.warn(msg),
287
434
  });
288
- // Persist to manifest — normalizeFrameworks deduplicates (e.g. --set gdpr,GDPR → [gdpr])
289
- manifest.features.compliance = {
290
- enabled: resolved.nextState.enabled,
291
- frameworks: normalizeFrameworks(resolved.nextState.frameworks),
292
- };
293
- manifest.updatedAt = new Date().toISOString();
294
- await writeManifest(devflowDir, manifest);
295
435
  // Emit messages
296
- for (const msg of resolved.messages) {
436
+ for (const msg of applied.messages) {
297
437
  switch (msg.level) {
298
438
  case 'success':
299
439
  p.log.success(msg.text);
@@ -301,18 +441,25 @@ export const complianceCommand = new Command('compliance')
301
441
  case 'warn':
302
442
  p.log.warn(msg.text);
303
443
  break;
444
+ case 'error':
445
+ p.log.error(msg.text);
446
+ break;
304
447
  default:
305
448
  p.log.info(msg.text);
306
449
  break;
307
450
  }
308
451
  }
309
- if (resolved.nextState.enabled && !manifest.features.rules) {
452
+ if (applied.exitCode !== 0) {
453
+ process.exitCode = applied.exitCode;
454
+ return;
455
+ }
456
+ if (applied.nextState.enabled && !manifest.features.rules) {
310
457
  p.log.info(color.dim('Note: compliance rule withheld (rules disabled) — ' +
311
458
  'run `devflow rules --enable` to install the stamped rule'));
312
459
  }
313
460
  // Suggest the team file compliance now implies. Printed, never written:
314
461
  // .devflow/project.json is team-owned (D-POLICY-NO-WRITE).
315
- if (resolved.nextState.enabled) {
462
+ if (applied.nextState.enabled) {
316
463
  const policyModule = loadEvidencePolicyModule();
317
464
  const settingsModule = loadSettingsModule();
318
465
  if (!policyModule.ok) {
@@ -322,7 +469,7 @@ export const complianceCommand = new Command('compliance')
322
469
  p.log.warn(formatEvidencePolicyUnavailable(settingsModule.error));
323
470
  }
324
471
  else {
325
- const suggestion = evidencePolicySuggestion(resolved.nextState, policyModule.value, settingsModule.value);
472
+ const suggestion = evidencePolicySuggestion(applied.nextState, policyModule.value, settingsModule.value);
326
473
  if (suggestion !== null)
327
474
  p.note(suggestion, 'Evidence policy');
328
475
  }