devflow-kit 3.8.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 (211) hide show
  1. package/CHANGELOG.md +231 -0
  2. package/README.md +6 -6
  3. package/dist/agents/code.md +10 -10
  4. package/dist/agents/fix.md +7 -7
  5. package/dist/agents/git.md +2 -2
  6. package/dist/agents/harden.md +7 -6
  7. package/dist/agents/refactor.md +6 -6
  8. package/dist/agents/skim.md +1 -1
  9. package/dist/agents/triage.md +7 -0
  10. package/dist/cli/commands/ambient.js +10 -2
  11. package/dist/cli/commands/compliance-prompts.js +9 -4
  12. package/dist/cli/commands/compliance.js +171 -24
  13. package/dist/cli/commands/curation.js +79 -23
  14. package/dist/cli/commands/gate-level-prompts.js +39 -23
  15. package/dist/cli/commands/gates.js +6 -7
  16. package/dist/cli/commands/init.js +150 -145
  17. package/dist/cli/commands/install-report.js +34 -0
  18. package/dist/cli/commands/legacy-sweep.js +64 -0
  19. package/dist/cli/commands/memory.js +59 -12
  20. package/dist/cli/commands/proxy.js +6 -1
  21. package/dist/cli/commands/rules.js +29 -12
  22. package/dist/cli/commands/skills.js +42 -9
  23. package/dist/cli/commands/tracker.js +9 -3
  24. package/dist/cli/commands/uninstall.js +354 -60
  25. package/dist/commands/bug-analysis.md +71 -122
  26. package/dist/commands/code-review.md +11 -11
  27. package/dist/commands/debug.md +2 -2
  28. package/dist/commands/dynamic-build.md +25 -25
  29. package/dist/commands/dynamic-plan.md +2 -2
  30. package/dist/commands/dynamic-profile.md +2 -2
  31. package/dist/commands/dynamic-tickets.md +2 -2
  32. package/dist/commands/explore.md +2 -2
  33. package/dist/commands/implement.md +48 -34
  34. package/dist/commands/plan.md +2 -2
  35. package/dist/commands/release.md +3 -3
  36. package/dist/commands/research.md +2 -2
  37. package/dist/commands/resolve.md +50 -46
  38. package/dist/commands/self-review.md +10 -9
  39. package/dist/compliance-off/agents/git.md +120 -0
  40. package/dist/compliance-off/agents/triage.md +180 -0
  41. package/dist/compliance-off/commands/bug-analysis.md +398 -0
  42. package/dist/compliance-off/commands/code-review.md +520 -0
  43. package/dist/compliance-off/commands/dynamic-build.md +1251 -0
  44. package/dist/compliance-off/commands/dynamic-tickets.md +676 -0
  45. package/dist/compliance-off/commands/implement.md +736 -0
  46. package/dist/compliance-off/commands/plan.md +671 -0
  47. package/dist/compliance-off/commands/release.md +309 -0
  48. package/dist/compliance-off/commands/resolve.md +742 -0
  49. package/dist/compliance-off/skills/git/references/decision-markers.md +17 -0
  50. package/dist/compliance-off/skills/git/references/learn-conventions.md +80 -0
  51. package/dist/compliance-off/skills/git/references/pr/check-ci-status.md +33 -0
  52. package/dist/compliance-off/skills/git/references/pr/check-merge-readiness.md +49 -0
  53. package/dist/compliance-off/skills/git/references/pr/fetch-review-threads.md +52 -0
  54. package/dist/compliance-off/skills/git/references/pr/validate-branch.md +47 -0
  55. package/dist/compliance-off/skills/git/references/tracker/_contract.md +33 -0
  56. package/dist/compliance-off/skills/git/references/tracker/_mcp.md +153 -0
  57. package/dist/compliance-off/skills/git/references/tracker/github/create-release.md +35 -0
  58. package/dist/compliance-off/skills/git/references/tracker/github/ensure-pr-ready.md +100 -0
  59. package/dist/compliance-off/skills/git/references/tracker/github/ensure-traceable-issue.md +74 -0
  60. package/dist/compliance-off/skills/git/references/tracker/github/fetch-issue.md +69 -0
  61. package/dist/compliance-off/skills/git/references/tracker/github/fetch-issues-batch.md +65 -0
  62. package/dist/compliance-off/skills/git/references/tracker/github/gather-release-evidence.md +48 -0
  63. package/dist/compliance-off/skills/git/references/tracker/github/setup-task.md +77 -0
  64. package/dist/compliance-off/skills/git/references/tracker/jira/create-release.md +35 -0
  65. package/dist/compliance-off/skills/git/references/tracker/jira/ensure-pr-ready.md +100 -0
  66. package/dist/compliance-off/skills/git/references/tracker/jira/ensure-traceable-issue.md +61 -0
  67. package/dist/compliance-off/skills/git/references/tracker/jira/fetch-issue.md +51 -0
  68. package/dist/compliance-off/skills/git/references/tracker/jira/fetch-issues-batch.md +63 -0
  69. package/dist/compliance-off/skills/git/references/tracker/jira/gather-release-evidence.md +47 -0
  70. package/dist/compliance-off/skills/git/references/tracker/jira/setup-task.md +82 -0
  71. package/dist/compliance-off/skills/git/references/tracker/linear/create-release.md +35 -0
  72. package/dist/compliance-off/skills/git/references/tracker/linear/ensure-pr-ready.md +100 -0
  73. package/dist/compliance-off/skills/git/references/tracker/linear/ensure-traceable-issue.md +61 -0
  74. package/dist/compliance-off/skills/git/references/tracker/linear/fetch-issue.md +51 -0
  75. package/dist/compliance-off/skills/git/references/tracker/linear/fetch-issues-batch.md +63 -0
  76. package/dist/compliance-off/skills/git/references/tracker/linear/gather-release-evidence.md +47 -0
  77. package/dist/compliance-off/skills/git/references/tracker/linear/setup-task.md +83 -0
  78. package/dist/compliance-off/skills/git/references/trust-rule.md +7 -0
  79. package/dist/core/agent-models.js +19 -4
  80. package/dist/core/area-pause-notice.js +44 -0
  81. package/dist/core/assets.js +126 -17
  82. package/dist/core/compliance-off-files.js +27 -0
  83. package/dist/core/curation-move-skip.js +4 -5
  84. package/dist/core/curation-queue-cleanup.js +6 -5
  85. package/dist/core/curation-store.js +1 -0
  86. package/dist/core/curation-variants.js +460 -112
  87. package/dist/core/evidence-policy.js +10 -8
  88. package/dist/core/flags.js +2 -2
  89. package/dist/core/installed-variants.js +63 -0
  90. package/dist/core/manifest.js +48 -5
  91. package/dist/core/mds-variants.js +700 -3
  92. package/dist/core/orphan-sweep.js +11 -2
  93. package/dist/core/plugins.js +69 -12
  94. package/dist/core/tracked-areas.js +138 -14
  95. package/dist/curation-compliance-off/agents/triage.md +176 -0
  96. package/dist/curation-compliance-off/commands/bug-analysis.md +349 -0
  97. package/dist/curation-compliance-off/commands/code-review.md +481 -0
  98. package/dist/curation-compliance-off/commands/dynamic-build.md +1211 -0
  99. package/dist/curation-compliance-off/commands/dynamic-tickets.md +634 -0
  100. package/dist/curation-compliance-off/commands/implement.md +689 -0
  101. package/dist/curation-compliance-off/commands/plan.md +631 -0
  102. package/dist/curation-compliance-off/commands/release.md +279 -0
  103. package/dist/curation-compliance-off/commands/resolve.md +692 -0
  104. package/dist/curation-off/agents/code.md +10 -10
  105. package/dist/curation-off/agents/fix.md +7 -7
  106. package/dist/curation-off/agents/harden.md +7 -6
  107. package/dist/curation-off/agents/refactor.md +6 -6
  108. package/dist/curation-off/agents/triage.md +7 -0
  109. package/dist/curation-off/commands/bug-analysis.md +69 -119
  110. package/dist/curation-off/commands/code-review.md +11 -11
  111. package/dist/curation-off/commands/debug.md +2 -2
  112. package/dist/curation-off/commands/dynamic-build.md +25 -25
  113. package/dist/curation-off/commands/explore.md +2 -2
  114. package/dist/curation-off/commands/implement.md +48 -34
  115. package/dist/curation-off/commands/plan.md +2 -2
  116. package/dist/curation-off/commands/release.md +1 -1
  117. package/dist/curation-off/commands/resolve.md +50 -46
  118. package/dist/curation-off/commands/self-review.md +10 -9
  119. package/dist/hud/components/curation-counts.js +27 -0
  120. package/dist/skills/git/references/pr/post-resolution-summary.md +12 -11
  121. package/dist/skills/git/references/pr/post-review-summary.md +12 -11
  122. package/dist/skills/git/references/pr/resolve-review-threads.md +1 -1
  123. package/dist/skills/git/references/pr/update-pr-evidence.md +2 -2
  124. package/dist/skills/git/references/publication-gate.md +6 -7
  125. package/dist/skills/git/references/tracker/github/manage-debt.md +6 -2
  126. package/dist/skills/git/references/tracker/jira/manage-debt.md +6 -2
  127. package/dist/skills/git/references/tracker/linear/manage-debt.md +6 -2
  128. package/dist/targets/claude-code/behavior-install.js +23 -9
  129. package/dist/targets/claude-code/charter-render.js +374 -0
  130. package/dist/targets/claude-code/compliance-install.js +32 -12
  131. package/dist/targets/claude-code/compliance-off-files.js +245 -0
  132. package/dist/targets/claude-code/curation-install.js +570 -100
  133. package/dist/targets/claude-code/installer.js +467 -62
  134. package/dist/targets/claude-code/language-stamp.js +6 -0
  135. package/dist/targets/claude-code/linked-folder.js +182 -0
  136. package/dist/targets/claude-code/reference-converge.js +118 -0
  137. package/dist/targets/claude-code/tracker-install.js +21 -8
  138. package/dist/targets/claude-code/tracker-servers.js +20 -6
  139. package/dist/targets/claude-code/workflow-folder-install.js +231 -0
  140. package/dist/variant-axes.json +109 -0
  141. package/package.json +1 -1
  142. package/src/assets/agents/_partials/_code.mds +6 -6
  143. package/src/assets/agents/code.mds +4 -4
  144. package/src/assets/agents/evaluate.md +7 -13
  145. package/src/assets/agents/fix.mds +1 -1
  146. package/src/assets/agents/git.mds +34 -2
  147. package/src/assets/agents/harden.mds +7 -6
  148. package/src/assets/agents/skim.mds +1 -1
  149. package/src/assets/agents/test.md +21 -12
  150. package/src/assets/agents/triage.mds +9 -0
  151. package/src/assets/agents/validate.md +27 -34
  152. package/src/assets/commands/_partials/_compliance.mds +5 -3
  153. package/src/assets/commands/_partials/_decisions.mds +10 -8
  154. package/src/assets/commands/_partials/_engine.mds +9 -8
  155. package/src/assets/commands/_partials/_publication.mds +10 -1
  156. package/src/assets/commands/_partials/_settings.mds +2 -2
  157. package/src/assets/commands/bug-analysis.mds +80 -126
  158. package/src/assets/commands/code-review.mds +52 -9
  159. package/src/assets/commands/dynamic-build.mds +109 -15
  160. package/src/assets/commands/dynamic-tickets.mds +13 -0
  161. package/src/assets/commands/implement.mds +145 -33
  162. package/src/assets/commands/plan.mds +19 -0
  163. package/src/assets/commands/release.mds +50 -1
  164. package/src/assets/commands/resolve.mds +229 -44
  165. package/src/assets/commands/self-review.mds +8 -7
  166. package/src/assets/mds/git/_ops.mds +29 -1
  167. package/src/assets/mds/git/_ops_pr.mds +20 -10
  168. package/src/assets/mds/git/_ops_steps.mds +27 -0
  169. package/src/assets/mds/git/_pr.mds +15 -15
  170. package/src/assets/mds/git/_references.mds +12 -7
  171. package/src/assets/mds/tracker/_common.mds +4 -0
  172. package/src/assets/mds/tracker/_github.mds +43 -4
  173. package/src/assets/mds/tracker/_jira.mds +44 -4
  174. package/src/assets/mds/tracker/_linear.mds +44 -4
  175. package/src/assets/rules/quality.md +2 -0
  176. package/src/assets/scripts/ci-wait.cjs +493 -118
  177. package/src/assets/scripts/hooks/assets/implement-installed +1 -0
  178. package/src/assets/scripts/hooks/assets/orchestrator-charter.md +13 -11
  179. package/src/assets/scripts/hooks/background-memory-update +79 -1
  180. package/src/assets/scripts/hooks/json-helper.cjs +44 -2
  181. package/src/assets/scripts/hooks/lib/curation-store.cjs +82 -20
  182. package/src/assets/scripts/hooks/lib/render-decisions.cjs +9 -1
  183. package/src/assets/scripts/hooks/lib/tracked-areas.cjs +226 -33
  184. package/src/assets/scripts/hooks/memory-permission +10 -2
  185. package/src/assets/scripts/hooks/memory-worker +11 -0
  186. package/src/assets/scripts/hooks/pre-compact-memory +11 -0
  187. package/src/assets/scripts/hooks/preamble +18 -8
  188. package/src/assets/scripts/hooks/queue-append +218 -1
  189. package/src/assets/scripts/hooks/session-start-context +89 -37
  190. package/src/assets/scripts/hooks/session-start-memory +39 -0
  191. package/src/assets/scripts/hooks/session-start-orchestrator +4 -2
  192. package/src/assets/scripts/pr-evidence.cjs +46 -4
  193. package/src/assets/scripts/resolve-settings.cjs +244 -30
  194. package/src/assets/scripts/verify-evidence.cjs +708 -124
  195. package/src/assets/skills/agent-references/SKILL.md +7 -1
  196. package/src/assets/skills/agent-references/references/commands/implement-levels.md +6 -4
  197. package/src/assets/skills/agent-references/references/commands/implement-revalidate.md +3 -3
  198. package/src/assets/skills/agent-references/references/commands/plan-levels.md +1 -1
  199. package/src/assets/skills/agent-references/references/commands/resolve-levels.md +3 -2
  200. package/src/assets/skills/agent-references/references/synthesize/bug-analysis.md +6 -2
  201. package/src/assets/skills/agent-references/references/synthesize/review.md +4 -4
  202. package/src/assets/skills/agent-references/references/triage/severity-tiers.md +43 -0
  203. package/src/assets/skills/agent-references/references/validate/passed-alone.md +14 -0
  204. package/src/assets/skills/docs-framework/SKILL.md +5 -2
  205. package/src/assets/skills/git/SKILL.md +3 -3
  206. package/src/assets/skills/git/references/github-api.md +2 -139
  207. package/src/assets/skills/git/references/github-posting.md +158 -0
  208. package/src/assets/skills/review-methodology/SKILL.md +1 -1
  209. package/src/assets/skills/review-methodology/references/patterns.md +1 -1
  210. package/src/assets/skills/review-methodology/references/report-template.md +1 -2
  211. package/src/assets/skills/testing/SKILL.md +2 -4
@@ -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
  }
@@ -9,8 +9,10 @@ import { loadShippedAgentDefaults } from '../../core/agent-models.js';
9
9
  import { readMachineFeature, writeMachineFeature } from '../../core/feature-switch.js';
10
10
  import { loadSettingsModule, narrowedSwitchLabel, personalConfigTrackedWarning } from '../../core/evidence-policy.js';
11
11
  import { getClaudeDirectory, getDevFlowDirectory } from '../../targets/claude-code/claude-paths.js';
12
- import { applyCurationToggle, describeCurationConverge, installedScriptsSkew, readRunningVersion, } from '../../targets/claude-code/curation-install.js';
12
+ import { applyVariantToggle, describeCurationConverge, describePostingLeft, installedScriptsSkew, readRunningVersion, } from '../../targets/claude-code/curation-install.js';
13
13
  import { getLedgerRoot } from '../../core/ledger-root.js';
14
+ import { AREA_PAUSE_NOTICE } from '../../core/area-pause-notice.js';
15
+ import { probeTrackedArea } from '../../core/tracked-areas.js';
14
16
  import { drainCurationQueue, drainDisabledCurationQueue, formatCurationNotDrained } from '../../core/curation-queue-cleanup.js';
15
17
  import { firstSymbolicLink } from '../../core/linked-path.js';
16
18
  import { describeMoveText, describeOutcome } from './curation-move-step.js';
@@ -74,10 +76,34 @@ function requireStore() {
74
76
  process.exitCode = 1;
75
77
  return null;
76
78
  }
79
+ /**
80
+ * Which fixed notice a store refusal of kind `tracked` takes: the reason the store names
81
+ * on the refusal. A refusal that names none is `unknown`, the notice that claims least.
82
+ */
83
+ function pauseReasonOf(error) {
84
+ return error.reason ?? 'unknown';
85
+ }
86
+ /**
87
+ * The fixed line of a paused curation area at `ledgerRoot`, or null when git tracks
88
+ * nothing in the curation folders there. The area is paused when git tracks files in the
89
+ * curation folder, the old ledger folder or a backup folder, or when a `.git` entry stands
90
+ * and git did not answer; a root with no `.git` entry makes no git call. Every command
91
+ * asks first, before it reads a ledger file, takes a lock or prompts.
92
+ */
93
+ async function pausedCurationLine(ledgerRoot) {
94
+ const { answer } = await probeTrackedArea(ledgerRoot, 'curation');
95
+ return answer === 'untracked' ? null : AREA_PAUSE_NOTICE.curation[answer];
96
+ }
97
+ /** Say that a command changed nothing because the curation area is paused: the fixed `line`, exit code 1. */
98
+ function refusePaused(line) {
99
+ p.log.error(line);
100
+ process.exitCode = 1;
101
+ }
77
102
  /** What a writer says when the store refused; `undone` completes "Nothing was …". */
78
103
  function storeRefusal(error, undone) {
79
104
  switch (error.kind) {
80
105
  case 'no-curation-dir': return NO_CURATION_DATA;
106
+ case 'tracked': return AREA_PAUSE_NOTICE.curation[pauseReasonOf(error)];
81
107
  case 'busy': return `The curation store is busy: another run holds its lock. Nothing was ${undone}; try again in a moment.`;
82
108
  default: return error.message;
83
109
  }
@@ -143,15 +169,21 @@ async function moveReadNotice(ledgerRoot) {
143
169
  }
144
170
  /**
145
171
  * The gate every writing subcommand passes first (--restore, --clear, --reset and the
146
- * project scope of --configure). Installed scripts of another version read and create
147
- * another ledger folder, so they refuse with the skew line. Then, where the old folder
148
- * is present, the shared move runs with no wait: a busy lock prints a retry message,
149
- * a queue claim that holds the move prints its own message, a refused or failed move
150
- * prints its reason, and a skipped move (git tracks files in a data folder, or git did
151
- * not answer) prints the fixed notice; in each case nothing is written.
172
+ * project scope of --configure). A paused curation area (git tracks files in a data
173
+ * folder, or git did not answer) refuses with the fixed notice before anything else, so
174
+ * a clear or a reset never reaches its confirm prompt. Installed scripts of another
175
+ * version read and create another ledger folder, so they refuse with the skew line.
176
+ * Then, where the old folder is present, the shared move runs with no wait: a busy lock
177
+ * prints a retry message, a queue claim that holds the move prints its own message, and
178
+ * a refused or failed move prints its reason; in each case nothing is written.
152
179
  * `undone` completes "Nothing was …". Answers true when the caller may write.
153
180
  */
154
181
  async function prepareToWrite(ledgerRoot, undone) {
182
+ const paused = await pausedCurationLine(ledgerRoot);
183
+ if (paused !== null) {
184
+ refusePaused(paused);
185
+ return false;
186
+ }
155
187
  const skew = await installedScriptsSkew(getDevFlowDirectory());
156
188
  if (skew !== null) {
157
189
  p.log.error(skew);
@@ -267,7 +299,8 @@ function backupLines(ledgerRoot) {
267
299
  * whose ledger still sits in the old folder gets the HUD's pointer (or refused line, or
268
300
  * the notice of a skipped move) in place of the counts; a project with both folders and
269
301
  * a skipped move gets the notice with the counts; a project with backup folders gets one
270
- * line for each.
302
+ * line for each. A paused area (git tracks files in a data folder, or did not answer)
303
+ * gets the fixed notice after the machine switch, nothing is read, and the exit code is 0.
271
304
  */
272
305
  async function handleStatus() {
273
306
  // D-FEATURES-NARROW-ONLY: the machine switch is the manifest's and reads the
@@ -282,6 +315,11 @@ async function handleStatus() {
282
315
  if (trackedWarning !== null)
283
316
  p.log.warn(trackedWarning);
284
317
  const ledgerRoot = await getLedgerRoot();
318
+ const paused = await pausedCurationLine(ledgerRoot ?? process.cwd());
319
+ if (paused !== null) {
320
+ p.log.info(`${stateLine}\n${paused}`);
321
+ return;
322
+ }
285
323
  if (!ledgerRoot) {
286
324
  p.log.info(`${stateLine}\nEntries: not in a git project`);
287
325
  return;
@@ -312,13 +350,19 @@ async function handleStatus() {
312
350
  }
313
351
  /**
314
352
  * `--list`: the store's listing — the same text json-helper's `list` op prints —
315
- * on stdout. Read-only. Outside a git project it reads the current directory. Where
316
- * the ledger still sits in the old folder it prints the pointer, the refused line or the
317
- * notice of a skipped move in place of the listing; with both folders and a skipped move
318
- * it prints the notice before the listing.
353
+ * on stdout. Read-only. Outside a git project it reads the current directory. A paused
354
+ * area prints the fixed notice and exits 1, and reads nothing. Where the ledger still
355
+ * sits in the old folder it prints the pointer, the refused line or the notice of a
356
+ * skipped move in place of the listing; with both folders and a skipped move it prints
357
+ * the notice before the listing.
319
358
  */
320
359
  async function handleList() {
321
360
  const root = (await getLedgerRoot()) ?? process.cwd();
361
+ const paused = await pausedCurationLine(root);
362
+ if (paused !== null) {
363
+ refusePaused(paused);
364
+ return;
365
+ }
322
366
  const notice = await moveReadNotice(root);
323
367
  if (notice !== null)
324
368
  p.log.info(notice.line);
@@ -342,12 +386,18 @@ async function handleList() {
342
386
  /**
343
387
  * `--show <id>`: one entry, by its anchor or its observation id, as the JSON
344
388
  * json-helper's `show` op prints, made terminal-safe. Read-only. Outside a git
345
- * project it reads the current directory. Where the ledger still sits in the old
346
- * folder it prints the pointer, the refused line or the notice of a skipped move as the
347
- * refusal; with both folders and a skipped move it prints the notice on stderr and goes on.
389
+ * project it reads the current directory. A paused area prints the fixed notice as the
390
+ * refusal and reads nothing. Where the ledger still sits in the old folder it prints the
391
+ * pointer, the refused line or the notice of a skipped move as the refusal; with both
392
+ * folders and a skipped move it prints the notice on stderr and goes on.
348
393
  */
349
394
  async function handleShow(key) {
350
395
  const root = (await getLedgerRoot()) ?? process.cwd();
396
+ const paused = await pausedCurationLine(root);
397
+ if (paused !== null) {
398
+ refusePaused(paused);
399
+ return;
400
+ }
351
401
  const notice = await moveReadNotice(root);
352
402
  if (notice?.kind === 'instead') {
353
403
  p.log.error(notice.line);
@@ -593,13 +643,15 @@ async function handleClear() {
593
643
  /**
594
644
  * Make the installed prompts match the switch just written
595
645
  * (D-LEARNING-VARIANT-INSTALL): the curation-on or curation-off commands and
596
- * agents, and the apply-decisions skill. The same convergence `devflow init` runs,
597
- * followed by reapplying the saved agent model mapping so `devflow agents`
598
- * overrides survive.
646
+ * agents, and the apply-decisions skill. The one converge for both axes that
647
+ * `devflow init` and `devflow compliance` run, followed by reapplying the saved
648
+ * agent model mapping so `devflow agents` overrides survive.
599
649
  *
600
650
  * Warn-not-fail: the switch itself is already recorded, and either mismatch is
601
651
  * safe (an on variant is still gated at run time, an off variant loads no
602
- * decisions), so a problem here never changes the exit code.
652
+ * decisions), so a problem here never changes the exit code. A compliance-off
653
+ * machine that still has a prompt that posts is a warning here too: the fail-closed
654
+ * exit belongs to `devflow compliance`, which changes that axis.
603
655
  *
604
656
  * Answers whether the converge ran, which means the installed files are the ones
605
657
  * this CLI writes. The toggle moves the ledger folder only then: under version skew
@@ -607,10 +659,11 @@ async function handleClear() {
607
659
  */
608
660
  async function convergeInstalledVariants(enabled) {
609
661
  try {
610
- const outcome = await applyCurationToggle({
662
+ const outcome = await applyVariantToggle({
611
663
  claudeDir: getClaudeDirectory(),
612
664
  devflowDir: getDevFlowDirectory(),
613
- curation: enabled,
665
+ axis: 'curation',
666
+ enabled,
614
667
  runningVersion: await readRunningVersion(),
615
668
  warn: (msg) => p.log.warn(msg),
616
669
  });
@@ -621,6 +674,8 @@ async function convergeInstalledVariants(enabled) {
621
674
  const changed = describeCurationConverge(outcome.result, enabled);
622
675
  if (changed !== null)
623
676
  p.log.info(color.dim(`Installed prompts: ${changed}`));
677
+ for (const line of describePostingLeft(outcome.result.posting))
678
+ p.log.warn(line);
624
679
  return 'converged';
625
680
  }
626
681
  catch (err) {
@@ -657,8 +712,9 @@ async function tryMoveForToggle(ledgerRoot) {
657
712
  * variants and skill (D-LEARNING-VARIANT-INSTALL), the current project's ledger
658
713
  * folder move, and on disable a drained queue in every ledger folder that exists and
659
714
  * in the backups a move left. The drain deletes no queue file, and prints the fixed
660
- * notice of a skipped move once, when git tracks any entry under a data folder or a
661
- * backup folder, or does not answer.
715
+ * pause notice of the curation area, when git tracks any entry under a data folder or a
716
+ * backup folder, or does not answer. A move that has just printed its own notice for the
717
+ * same reason leaves the drain's line short, so the cause is told once.
662
718
  * Never requires a git root: the switch is not a per-project setting.
663
719
  *
664
720
  * Under version skew the toggle records the switch and moves nothing; otherwise it
@@ -31,21 +31,18 @@ import { DEFAULT_GATE_LEVEL, GATE_LEVELS, MALFORMED_GATE_LEVEL_READS_AS, parseGa
31
31
  /** Message shown on the gate level select prompt. */
32
32
  export const GATE_LEVEL_SELECT_MESSAGE = 'Gate level for this machine';
33
33
  /**
34
- * The one-line effect of each level, as the select hint and the catalogue show it.
35
- * Each hint says what its level does. GATE_LEVEL_EFFECT in gates.ts says which commands
36
- * read the level. Change a hint when what a level does changes. A command that reads the
37
- * level and has no rule that changes a phase leaves the hints true, so it changes
38
- * GATE_LEVEL_EFFECT alone. One test pins the exact text of both tables, so a change to
39
- * either fails it until you check the other table and update the pin; further rows check
40
- * the clauses the two tables share (the lint pack level, the levels that run fewer review
41
- * focuses and the levels that skip Polish). The clause that lean skips Harden on a
42
- * docs-only diff has no facet row in `tests/gate-level-prompts.test.ts`: the docs guard
43
- * checks it at each doc site, against the row in the /implement level reference.
34
+ * One short line for each level, as the select hint and the catalogue show it.
35
+ * clack never wraps a note or a hint, so a hint stays within 60 characters and holds no line
36
+ * break (a guard in `tests/gate-level-prompts.test.ts` holds both). The full effects of each
37
+ * level live in GATE_LEVEL_EFFECT in gates.ts, in `devflow gates` and in the docs.
38
+ * Change a hint when the level it names changes how much it checks; then check
39
+ * GATE_LEVEL_EFFECT and update the pin. The comment on GATE_LEVEL_EFFECT holds the full
40
+ * update rule, the pin and the list of clauses that the two tables share.
44
41
  */
45
42
  export const GATE_LEVEL_HINTS = Object.freeze({
46
- full: 'the most checking: every review focus, the full planning shape and Polish',
47
- standard: 'fewer review focuses and planning agents, and no Polish in /implement and /resolve (the default)',
48
- lean: 'the least checking, and no Polish in /implement and /resolve; /implement also has Validate run the project lint pack, and skips Harden on a docs-only diff',
43
+ full: 'the most checking: every review focus, and Polish',
44
+ standard: 'fewer review focuses, no Polish; the default',
45
+ lean: 'the least checking: no Polish, Validate runs the lint pack',
49
46
  });
50
47
  /** Build clack select options for the gate level list, most to least checking. */
51
48
  export function gateLevelChoices() {
@@ -53,12 +50,24 @@ export function gateLevelChoices() {
53
50
  }
54
51
  /**
55
52
  * Format a padded level catalogue suitable for a clack note body.
56
- * Produces: ` standard fewer review focuses and planning agents, and no Polish in /implement and /resolve (the default)`
53
+ * Produces: ` standard fewer review focuses, no Polish; the default`
57
54
  */
58
55
  export function formatGateLevelCatalogue() {
59
56
  const width = Math.max(...GATE_LEVELS.map(level => level.length));
60
57
  return 'Levels:\n' + GATE_LEVELS.map(level => ` ${level.padEnd(width)} ${GATE_LEVEL_HINTS[level]}`).join('\n');
61
58
  }
59
+ // ── Where the stored level was found ───────────────────────────────────────────
60
+ /**
61
+ * Where init found the stored machine gate level:
62
+ *
63
+ * manifest in the manifest init reads (or nowhere, on a first install or after
64
+ * --reset): the seed holds it
65
+ * rejected in a manifest that readManifest() refuses, read raw: init keeps the
66
+ * level as stored while it replaces that manifest
67
+ * unreadable nowhere: the manifest cannot be read at all, or stores a level that cannot be
68
+ * written back as JSON, so what it stored is unknown
69
+ */
70
+ export const STORED_GATE_LEVEL_ORIGINS = Object.freeze(['manifest', 'rejected', 'unreadable']);
62
71
  /** `features.gateLevel` of a raw manifest, exactly as stored; `undefined` for an absent key. */
63
72
  function rawGateLevelOf(raw) {
64
73
  const features = raw.features;
@@ -226,17 +235,24 @@ export function describeGateLevel(stored) {
226
235
  export function gateLevelPromptSeed(stored, origin = 'manifest') {
227
236
  return appliedLevel(classifyGateLevel(stored, origin));
228
237
  }
229
- /** The "Current setting" lines of the prompt note, from the stored value and where it was found. */
238
+ /** The "Current setting" lines of the prompt note, from the stored value and where it was found. Each line fits the 72 columns the note allows. */
230
239
  function currentSettingLines(stored, origin) {
231
240
  switch (origin) {
232
241
  case 'manifest':
233
242
  return `Current setting: ${describeGateLevel(stored)}`;
234
243
  case 'rejected':
235
- return `Current setting: ${describeGateLevel(stored)}\n` +
236
- 'It comes from a manifest that devflow rejects. Init replaces that manifest and keeps this level unless you choose another.';
244
+ return [
245
+ `Current setting: ${describeGateLevel(stored)}`,
246
+ 'It comes from a manifest that devflow rejects.',
247
+ 'Init replaces that manifest and keeps this level',
248
+ 'unless you choose another.',
249
+ ].join('\n');
237
250
  case 'unreadable':
238
- return 'Current setting: unknown\n' +
239
- `The stored gate level cannot be read, so ${DEFAULT_GATE_LEVEL} applies until you choose a level.`;
251
+ return [
252
+ 'Current setting: unknown',
253
+ `The stored gate level cannot be read, so ${DEFAULT_GATE_LEVEL} applies`,
254
+ 'until you choose a level.',
255
+ ].join('\n');
240
256
  default: {
241
257
  const exhaustive = origin;
242
258
  return exhaustive;
@@ -318,10 +334,10 @@ export function buildClackGateLevelPrompts() {
318
334
  export async function runGateLevelStep(opts) {
319
335
  const { stored, storedOrigin = 'manifest', prompts } = opts;
320
336
  prompts.note(`${currentSettingLines(stored, storedOrigin)}\n\n` +
321
- 'Sets how much checking /code-review, /plan and /implement apply on this\n' +
322
- 'machine. A personal "gateLevel" in a repository\'s .devflow/config.json\n' +
323
- 'overrides it in that repository. Change it later with\n' +
324
- 'devflow gates --set <level>.\n\n' +
337
+ 'Sets how much checking /code-review, /plan, /implement and /resolve\n' +
338
+ 'apply on this machine. A personal "gateLevel" in a repository\'s\n' +
339
+ '.devflow/config.json overrides it in that repository. Change it later\n' +
340
+ 'with devflow gates --set <level>.\n\n' +
325
341
  formatGateLevelCatalogue(), 'Gate level');
326
342
  const outcome = await prompts.selectLevel({
327
343
  message: GATE_LEVEL_SELECT_MESSAGE,
@@ -39,17 +39,16 @@ export const GATE_LEVEL_MEANINGS = Object.freeze({
39
39
  /**
40
40
  * Which commands read the level today; each later command that reads it updates this text.
41
41
  *
42
- * The init prompt says what each level does in GATE_LEVEL_HINTS (gate-level-prompts.ts).
43
- * Change the hints too when what a level does changes. A command that reads the level and
44
- * has no rule that changes a phase leaves the hints true, so it changes this text alone.
42
+ * The init prompt gives each level one short line in GATE_LEVEL_HINTS (gate-level-prompts.ts);
43
+ * this text and the docs hold the full effects. Change the hints too when a level changes
44
+ * how much it checks. A command that reads the level and has no rule that changes a phase
45
+ * leaves the hints true, so it changes this text alone.
45
46
  * One test pins the exact text of both tables, so a change to either fails it until you
46
47
  * check the other table and update the pin; further rows check the clauses the two tables
47
48
  * share (the lint pack level, the levels that run fewer review focuses and the levels that
48
- * skip Polish). The clause that lean skips Harden on a docs-only diff has no facet row in
49
- * `tests/gate-level-prompts.test.ts`: the docs guard checks it at each doc site, against
50
- * the row in the /implement level reference.
49
+ * skip Polish).
51
50
  */
52
- export const GATE_LEVEL_EFFECT = '/code-review and /plan read the level: standard and lean run fewer review focuses and fewer planning agents. At lean, /implement has Validate run the project\'s lint pack. /implement and /resolve also read the level and name it in their report; at standard and lean they skip Polish, and full keeps it. At lean, /implement skips Harden on a docs-only diff.';
51
+ export const GATE_LEVEL_EFFECT = '/code-review and /plan read the level: standard and lean run fewer review focuses and fewer planning agents. At lean, /implement has Validate run the project\'s lint pack. /implement and /resolve also read the level and name it in their report; at standard and lean they skip Polish, and full keeps it. At lean, /implement skips Harden on a docs-only diff. At standard and lean, /implement runs one Evaluate pass and at most one Fix round. At standard and lean, /resolve applies severity tiers: it fixes or escalates every security finding, fixes CRITICAL, HIGH and severe MEDIUM findings in the branch\'s own code, fixes prose-only MEDIUM findings with text edits alone, and sends lower findings and pre-existing non-security findings to one backlog issue.';
53
52
  const MACHINE_MANIFEST_NAME = 'manifest.json';
54
53
  const MACHINE_INVALID_LINE = `invalid (${MACHINE_MANIFEST_NAME}; reads as ${MALFORMED_GATE_LEVEL_READS_AS})`;
55
54
  const VALID_LEVELS = GATE_LEVELS.join(', ');