@soleur/opencode 0.0.0-stage → 1.0.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 (812) hide show
  1. package/.claude-plugin/agents.manifest.json +476 -0
  2. package/.claude-plugin/plugin.json +42 -0
  3. package/README.md +44 -2
  4. package/agents/community/.gitkeep +0 -0
  5. package/agents/engineering/cto.md +87 -0
  6. package/agents/engineering/design/ddd-architect.md +95 -0
  7. package/agents/engineering/discovery/agent-finder.md +214 -0
  8. package/agents/engineering/discovery/functional-discovery.md +182 -0
  9. package/agents/engineering/infra/infra-security.md +151 -0
  10. package/agents/engineering/infra/platform-strategist.md +109 -0
  11. package/agents/engineering/infra/terraform-architect.md +171 -0
  12. package/agents/engineering/prompt-engineer.md +37 -0
  13. package/agents/engineering/research/best-practices-researcher.md +115 -0
  14. package/agents/engineering/research/framework-docs-researcher.md +93 -0
  15. package/agents/engineering/research/git-history-analyzer.md +53 -0
  16. package/agents/engineering/research/learnings-researcher.md +285 -0
  17. package/agents/engineering/research/repo-research-analyst.md +118 -0
  18. package/agents/engineering/review/agent-native-reviewer.md +279 -0
  19. package/agents/engineering/review/architecture-strategist.md +61 -0
  20. package/agents/engineering/review/code-quality-analyst.md +92 -0
  21. package/agents/engineering/review/code-simplicity-reviewer.md +109 -0
  22. package/agents/engineering/review/data-integrity-guardian.md +82 -0
  23. package/agents/engineering/review/data-migration-expert.md +98 -0
  24. package/agents/engineering/review/deployment-verification-agent.md +183 -0
  25. package/agents/engineering/review/dhh-rails-reviewer.md +46 -0
  26. package/agents/engineering/review/kieran-rails-reviewer.md +87 -0
  27. package/agents/engineering/review/legacy-code-expert.md +55 -0
  28. package/agents/engineering/review/observability-coverage-reviewer.md +147 -0
  29. package/agents/engineering/review/pattern-recognition-specialist.md +59 -0
  30. package/agents/engineering/review/performance-oracle.md +130 -0
  31. package/agents/engineering/review/security-sentinel.md +167 -0
  32. package/agents/engineering/review/semgrep-sast.md +36 -0
  33. package/agents/engineering/review/test-design-reviewer.md +150 -0
  34. package/agents/engineering/review/user-impact-reviewer.md +70 -0
  35. package/agents/engineering/workflow/pr-comment-resolver.md +69 -0
  36. package/agents/finance/budget-analyst.md +29 -0
  37. package/agents/finance/cfo.md +44 -0
  38. package/agents/finance/financial-reporter.md +30 -0
  39. package/agents/finance/revenue-analyst.md +30 -0
  40. package/agents/legal/clo.md +115 -0
  41. package/agents/legal/legal-compliance-auditor.md +110 -0
  42. package/agents/legal/legal-document-generator.md +134 -0
  43. package/agents/marketing/analytics-analyst.md +25 -0
  44. package/agents/marketing/brand-architect.md +181 -0
  45. package/agents/marketing/cmo.md +76 -0
  46. package/agents/marketing/conversion-optimizer.md +19 -0
  47. package/agents/marketing/copywriter.md +20 -0
  48. package/agents/marketing/fact-checker.md +70 -0
  49. package/agents/marketing/growth-strategist.md +145 -0
  50. package/agents/marketing/paid-media-strategist.md +28 -0
  51. package/agents/marketing/pricing-strategist.md +17 -0
  52. package/agents/marketing/programmatic-seo-specialist.md +25 -0
  53. package/agents/marketing/retention-strategist.md +23 -0
  54. package/agents/marketing/seo-aeo-analyst.md +164 -0
  55. package/agents/operations/coo.md +50 -0
  56. package/agents/operations/ops-advisor.md +78 -0
  57. package/agents/operations/ops-provisioner.md +101 -0
  58. package/agents/operations/ops-research.md +52 -0
  59. package/agents/operations/service-automator.md +278 -0
  60. package/agents/product/business-validator.md +206 -0
  61. package/agents/product/competitive-intelligence.md +89 -0
  62. package/agents/product/cpo.md +63 -0
  63. package/agents/product/design/ux-design-lead.md +209 -0
  64. package/agents/product/spec-flow-analyzer.md +122 -0
  65. package/agents/sales/cro.md +44 -0
  66. package/agents/sales/deal-architect.md +29 -0
  67. package/agents/sales/outbound-strategist.md +29 -0
  68. package/agents/sales/pipeline-analyst.md +29 -0
  69. package/agents/support/cco.md +48 -0
  70. package/agents/support/community-manager.md +570 -0
  71. package/agents/support/ticket-triage.md +70 -0
  72. package/bin/soleur-opencode.js +29 -0
  73. package/commands/go.md +659 -0
  74. package/commands/help.md +273 -0
  75. package/commands/sync.md +977 -0
  76. package/commands/version.md +103 -0
  77. package/hooks/antigravity-hooks.json +84 -0
  78. package/hooks/antigravity-session-start.sh +15 -0
  79. package/hooks/bare-root-cwd-guard.sh +131 -0
  80. package/hooks/browser-snapshot-credential-guard.sh +200 -0
  81. package/hooks/codex-session-start.sh +9 -0
  82. package/hooks/compaction-state.sh +525 -0
  83. package/hooks/destructive-command-guard.sh +1387 -0
  84. package/hooks/devin-session-start.sh +90 -0
  85. package/hooks/ext-filter-detect.sh +171 -0
  86. package/hooks/hooks.json +133 -0
  87. package/hooks/lib/hook-tool-kind.sh +24 -0
  88. package/hooks/lib/log-rotation.sh +176 -0
  89. package/hooks/lib/shell-argv.pl +576 -0
  90. package/hooks/memory-backstop.sh +1114 -0
  91. package/hooks/operator-stage-approval.sh +391 -0
  92. package/hooks/stop-hook.sh +367 -0
  93. package/hooks/unkept-promise-hook.sh +383 -0
  94. package/hooks/version-banner.sh +127 -0
  95. package/hooks/welcome-hook.sh +50 -0
  96. package/install.js +108 -0
  97. package/lib/agent-registry.ts +154 -0
  98. package/lib/c4-canonical-cli.mjs +34 -0
  99. package/lib/c4-canonical.mjs +46 -0
  100. package/lib/c4-from-components.ts +554 -0
  101. package/lib/go-routing.ts +81 -0
  102. package/lib/grok-inspect-contract.ts +311 -0
  103. package/lib/harness-model-map.ts +101 -0
  104. package/lib/harness-parity.ts +726 -0
  105. package/lib/harness-tool-map.ts +180 -0
  106. package/lib/harness.ts +716 -0
  107. package/lib/heartbeat-live-reconcile.ts +1153 -0
  108. package/lib/heartbeat-manifest.ts +342 -0
  109. package/lib/kb-coverage.ts +252 -0
  110. package/lib/pr-merge-poll.ts +103 -0
  111. package/lib/workflow-fidelity.ts +388 -0
  112. package/opencode/INSTRUCTIONS.md +152 -0
  113. package/opencode/agents/engineering/cto.md +10 -0
  114. package/opencode/agents/engineering/design/ddd-architect.md +10 -0
  115. package/opencode/agents/engineering/discovery/agent-finder.md +10 -0
  116. package/opencode/agents/engineering/discovery/functional-discovery.md +10 -0
  117. package/opencode/agents/engineering/infra/infra-security.md +10 -0
  118. package/opencode/agents/engineering/infra/platform-strategist.md +10 -0
  119. package/opencode/agents/engineering/infra/terraform-architect.md +10 -0
  120. package/opencode/agents/engineering/prompt-engineer.md +10 -0
  121. package/opencode/agents/engineering/research/best-practices-researcher.md +10 -0
  122. package/opencode/agents/engineering/research/framework-docs-researcher.md +10 -0
  123. package/opencode/agents/engineering/research/git-history-analyzer.md +10 -0
  124. package/opencode/agents/engineering/research/learnings-researcher.md +10 -0
  125. package/opencode/agents/engineering/research/repo-research-analyst.md +10 -0
  126. package/opencode/agents/engineering/review/agent-native-reviewer.md +10 -0
  127. package/opencode/agents/engineering/review/architecture-strategist.md +10 -0
  128. package/opencode/agents/engineering/review/code-quality-analyst.md +10 -0
  129. package/opencode/agents/engineering/review/code-simplicity-reviewer.md +10 -0
  130. package/opencode/agents/engineering/review/data-integrity-guardian.md +10 -0
  131. package/opencode/agents/engineering/review/data-migration-expert.md +10 -0
  132. package/opencode/agents/engineering/review/deployment-verification-agent.md +10 -0
  133. package/opencode/agents/engineering/review/dhh-rails-reviewer.md +10 -0
  134. package/opencode/agents/engineering/review/kieran-rails-reviewer.md +10 -0
  135. package/opencode/agents/engineering/review/legacy-code-expert.md +10 -0
  136. package/opencode/agents/engineering/review/observability-coverage-reviewer.md +10 -0
  137. package/opencode/agents/engineering/review/pattern-recognition-specialist.md +10 -0
  138. package/opencode/agents/engineering/review/performance-oracle.md +10 -0
  139. package/opencode/agents/engineering/review/security-sentinel.md +10 -0
  140. package/opencode/agents/engineering/review/semgrep-sast.md +10 -0
  141. package/opencode/agents/engineering/review/test-design-reviewer.md +10 -0
  142. package/opencode/agents/engineering/review/user-impact-reviewer.md +10 -0
  143. package/opencode/agents/engineering/workflow/pr-comment-resolver.md +10 -0
  144. package/opencode/agents/finance/budget-analyst.md +10 -0
  145. package/opencode/agents/finance/cfo.md +10 -0
  146. package/opencode/agents/finance/financial-reporter.md +10 -0
  147. package/opencode/agents/finance/revenue-analyst.md +10 -0
  148. package/opencode/agents/legal/clo.md +10 -0
  149. package/opencode/agents/legal/legal-compliance-auditor.md +10 -0
  150. package/opencode/agents/legal/legal-document-generator.md +10 -0
  151. package/opencode/agents/marketing/analytics-analyst.md +10 -0
  152. package/opencode/agents/marketing/brand-architect.md +10 -0
  153. package/opencode/agents/marketing/cmo.md +10 -0
  154. package/opencode/agents/marketing/conversion-optimizer.md +10 -0
  155. package/opencode/agents/marketing/copywriter.md +10 -0
  156. package/opencode/agents/marketing/fact-checker.md +10 -0
  157. package/opencode/agents/marketing/growth-strategist.md +10 -0
  158. package/opencode/agents/marketing/paid-media-strategist.md +10 -0
  159. package/opencode/agents/marketing/pricing-strategist.md +10 -0
  160. package/opencode/agents/marketing/programmatic-seo-specialist.md +10 -0
  161. package/opencode/agents/marketing/retention-strategist.md +10 -0
  162. package/opencode/agents/marketing/seo-aeo-analyst.md +10 -0
  163. package/opencode/agents/operations/coo.md +10 -0
  164. package/opencode/agents/operations/ops-advisor.md +10 -0
  165. package/opencode/agents/operations/ops-provisioner.md +10 -0
  166. package/opencode/agents/operations/ops-research.md +10 -0
  167. package/opencode/agents/operations/service-automator.md +10 -0
  168. package/opencode/agents/product/business-validator.md +10 -0
  169. package/opencode/agents/product/competitive-intelligence.md +10 -0
  170. package/opencode/agents/product/cpo.md +10 -0
  171. package/opencode/agents/product/design/ux-design-lead.md +10 -0
  172. package/opencode/agents/product/spec-flow-analyzer.md +10 -0
  173. package/opencode/agents/sales/cro.md +10 -0
  174. package/opencode/agents/sales/deal-architect.md +10 -0
  175. package/opencode/agents/sales/outbound-strategist.md +10 -0
  176. package/opencode/agents/sales/pipeline-analyst.md +10 -0
  177. package/opencode/agents/support/cco.md +10 -0
  178. package/opencode/agents/support/community-manager.md +10 -0
  179. package/opencode/agents/support/ticket-triage.md +10 -0
  180. package/opencode/commands/go.md +10 -0
  181. package/opencode/commands/help.md +10 -0
  182. package/opencode/commands/sync.md +10 -0
  183. package/opencode/commands/version.md +11 -0
  184. package/opencode/opencode.json +8 -0
  185. package/opencode/plugin.ts +493 -0
  186. package/package.json +36 -4
  187. package/scripts/admin-merge-ready.sh +545 -0
  188. package/scripts/admin-merge-ready.test.sh +753 -0
  189. package/scripts/affected-tests.sh +908 -0
  190. package/scripts/affected-tests.test.sh +1797 -0
  191. package/scripts/alpha-metrics.sh +78 -0
  192. package/scripts/alpha-metrics.test.sh +115 -0
  193. package/scripts/ancestor-signal-helpers.baseline.txt +16 -0
  194. package/scripts/audit-flag-flip.sh +107 -0
  195. package/scripts/check-red-on-main.sh +316 -0
  196. package/scripts/ci-head-verdict.sh +363 -0
  197. package/scripts/cloud-detect.sh +193 -0
  198. package/scripts/delegation-verify.sh +657 -0
  199. package/scripts/delegation-verify.test.sh +1424 -0
  200. package/scripts/deploy-arm.sh +475 -0
  201. package/scripts/domain-model-drift.sh +419 -0
  202. package/scripts/emit-decision.sh +144 -0
  203. package/scripts/emit-decision.test.sh +144 -0
  204. package/scripts/ext-filter-divergence-corpus.txt +15 -0
  205. package/scripts/ext-filter-divergence.sh +189 -0
  206. package/scripts/generate-c4-from-components.ts +442 -0
  207. package/scripts/grok-fidelity-gate.sh +68 -0
  208. package/scripts/grok-pre-push-gate.sh +232 -0
  209. package/scripts/harness-discovery-smoke.ts +906 -0
  210. package/scripts/harness-parity-census.ts +92 -0
  211. package/scripts/inngest-luks-alert-armed-probe.ts +29 -0
  212. package/scripts/install-opencode-adapter.ts +520 -0
  213. package/scripts/lib/affected-tests/common.sh +365 -0
  214. package/scripts/lib/affected-tests/go.sh +157 -0
  215. package/scripts/lib/affected-tests/js.sh +188 -0
  216. package/scripts/lib/affected-tests/nx-turbo.sh +98 -0
  217. package/scripts/lib/affected-tests/python.sh +185 -0
  218. package/scripts/lib/context-sanitize.sh +49 -0
  219. package/scripts/lib/domain-model-lib.sh +180 -0
  220. package/scripts/lib/ext-filter-grammar.tsv +34 -0
  221. package/scripts/lib/operator-script.sh +1236 -0
  222. package/scripts/lib/proc.sh +580 -0
  223. package/scripts/lib/session-state.sh +748 -0
  224. package/scripts/lib/tmp-classify.sh +843 -0
  225. package/scripts/lib/wrapper-boundary.sh +47 -0
  226. package/scripts/lint-ratchet.py +1547 -0
  227. package/scripts/monitor-pr-checks.sh +441 -0
  228. package/scripts/pipeline-tally.sh +756 -0
  229. package/scripts/pipeline-tally.test.sh +740 -0
  230. package/scripts/plugin-version.sh +528 -0
  231. package/scripts/precommit-guard.sh +113 -0
  232. package/scripts/precommit-guard.test.sh +254 -0
  233. package/scripts/reconcile-live-heartbeats.ts +705 -0
  234. package/scripts/render-c4-model.sh +217 -0
  235. package/scripts/resolve-git-root.sh +55 -0
  236. package/scripts/resolve-regenerable-conflicts.sh +363 -0
  237. package/scripts/resolve-regenerable-conflicts.test.sh +780 -0
  238. package/scripts/ruff-requirements.txt +22 -0
  239. package/scripts/run-in-pid-namespace.sh +98 -0
  240. package/scripts/run-in-pid-namespace.test.sh +290 -0
  241. package/scripts/scan-ancestor-signal-helpers.py +556 -0
  242. package/scripts/scan-ancestor-signal-helpers.test.sh +670 -0
  243. package/scripts/setup-ralph-loop.sh +238 -0
  244. package/scripts/sync-cursor-name-map.ts +316 -0
  245. package/scripts/sync-grok-agent-compat.ts +188 -0
  246. package/scripts/sync-npm-package.ts +178 -0
  247. package/scripts/sync-opencode-adapter.ts +479 -0
  248. package/scripts/sync-pr-behind.sh +534 -0
  249. package/scripts/sync-pr-behind.test.sh +252 -0
  250. package/scripts/taste-profile-update.sh +180 -0
  251. package/scripts/taste-profile-update.test.sh +167 -0
  252. package/scripts/write-budget-marker.sh +23 -0
  253. package/scripts/write-kb-coverage.ts +113 -0
  254. package/skills/admin-ip-refresh/SKILL.md +81 -0
  255. package/skills/admin-ip-refresh/references/admin-ip-refresh-procedure.md +201 -0
  256. package/skills/agent-browser/SKILL.md +698 -0
  257. package/skills/agent-browser/scripts/playwright-mcp-profile-slot.sh +304 -0
  258. package/skills/agent-browser/scripts/playwright-mcp-redact-proxy.py +964 -0
  259. package/skills/agent-browser/scripts/redact-a11y-snapshot.py +443 -0
  260. package/skills/agent-browser/test/fixtures/capture-playwright-mcp-fixtures.py +217 -0
  261. package/skills/agent-browser/test/fixtures/fake-passthrough-proxy.py +24 -0
  262. package/skills/agent-browser/test/fixtures/fake-playwright-mcp.py +131 -0
  263. package/skills/agent-browser/test/fixtures/make-proxy-mutant.py +48 -0
  264. package/skills/agent-browser/test/fixtures/odd-shapes/dialog-link-injection.json +10 -0
  265. package/skills/agent-browser/test/fixtures/odd-shapes/empty-content.json +5 -0
  266. package/skills/agent-browser/test/fixtures/odd-shapes/error-and-result.json +4 -0
  267. package/skills/agent-browser/test/fixtures/odd-shapes/error-code-bool.json +6 -0
  268. package/skills/agent-browser/test/fixtures/odd-shapes/error-code-string.json +6 -0
  269. package/skills/agent-browser/test/fixtures/odd-shapes/error-message-tree.json +6 -0
  270. package/skills/agent-browser/test/fixtures/odd-shapes/error-no-data.json +6 -0
  271. package/skills/agent-browser/test/fixtures/odd-shapes/error-string.json +4 -0
  272. package/skills/agent-browser/test/fixtures/odd-shapes/error-with-data.json +9 -0
  273. package/skills/agent-browser/test/fixtures/odd-shapes/evaluate-escaped-object.json +10 -0
  274. package/skills/agent-browser/test/fixtures/odd-shapes/invalid-utf8.json +4 -0
  275. package/skills/agent-browser/test/fixtures/odd-shapes/link-result.json +10 -0
  276. package/skills/agent-browser/test/fixtures/odd-shapes/list-two-results.json +6 -0
  277. package/skills/agent-browser/test/fixtures/odd-shapes/meta-in-result.json +13 -0
  278. package/skills/agent-browser/test/fixtures/odd-shapes/no-content.json +5 -0
  279. package/skills/agent-browser/test/fixtures/odd-shapes/quoted-key.json +10 -0
  280. package/skills/agent-browser/test/fixtures/odd-shapes/resource-block.json +18 -0
  281. package/skills/agent-browser/test/fixtures/odd-shapes/result-and-method.json +11 -0
  282. package/skills/agent-browser/test/fixtures/odd-shapes/result-string.json +3 -0
  283. package/skills/agent-browser/test/fixtures/odd-shapes/run-code-escaped-one-row.json +10 -0
  284. package/skills/agent-browser/test/fixtures/odd-shapes/run-code-escaped.json +10 -0
  285. package/skills/agent-browser/test/fixtures/odd-shapes/structured-content.json +13 -0
  286. package/skills/agent-browser/test/fixtures/odd-shapes/tools-list-no-tools.json +5 -0
  287. package/skills/agent-browser/test/fixtures/odd-shapes/tree-no-credential.json +10 -0
  288. package/skills/agent-browser/test/fixtures/odd-shapes/two-text-blocks.json +14 -0
  289. package/skills/agent-browser/test/fixtures/odd-shapes/unparsable-line.json +3 -0
  290. package/skills/agent-browser/test/fixtures/odd-shapes/value-contains-brace.json +10 -0
  291. package/skills/agent-browser/test/fixtures/odd-shapes/value-contains-link-substring.json +10 -0
  292. package/skills/agent-browser/test/fixtures/playwright-mcp-0.0.78/close.json +12 -0
  293. package/skills/agent-browser/test/fixtures/playwright-mcp-0.0.78/evaluate.json +12 -0
  294. package/skills/agent-browser/test/fixtures/playwright-mcp-0.0.78/find.json +12 -0
  295. package/skills/agent-browser/test/fixtures/playwright-mcp-0.0.78/framing.json +6 -0
  296. package/skills/agent-browser/test/fixtures/playwright-mcp-0.0.78/initialize.json +14 -0
  297. package/skills/agent-browser/test/fixtures/playwright-mcp-0.0.78/navigate-link.json +12 -0
  298. package/skills/agent-browser/test/fixtures/playwright-mcp-0.0.78/navigate.json +12 -0
  299. package/skills/agent-browser/test/fixtures/playwright-mcp-0.0.78/roots-list-request.json +5 -0
  300. package/skills/agent-browser/test/fixtures/playwright-mcp-0.0.78/screenshot.json +17 -0
  301. package/skills/agent-browser/test/fixtures/playwright-mcp-0.0.78/snapshot-filename.json +12 -0
  302. package/skills/agent-browser/test/fixtures/playwright-mcp-0.0.78/snapshot-meta-json.json +12 -0
  303. package/skills/agent-browser/test/fixtures/playwright-mcp-0.0.78/snapshot-meta-raw.json +12 -0
  304. package/skills/agent-browser/test/fixtures/playwright-mcp-0.0.78/snapshot.json +12 -0
  305. package/skills/agent-browser/test/fixtures/playwright-mcp-0.0.78/tools-list.json +827 -0
  306. package/skills/agent-browser/test/fixtures/proxy-session-driver.py +138 -0
  307. package/skills/agent-browser/test/fixtures/session-facts.py +102 -0
  308. package/skills/agent-browser/test/playwright-mcp-lifetime.test.sh +1620 -0
  309. package/skills/agent-browser/test/playwright-mcp-redact-proxy.test.sh +1599 -0
  310. package/skills/agent-browser/test/redact-a11y-snapshot.test.sh +527 -0
  311. package/skills/agent-native-architecture/SKILL.md +479 -0
  312. package/skills/agent-native-architecture/references/action-parity-discipline.md +430 -0
  313. package/skills/agent-native-architecture/references/agent-execution-patterns.md +483 -0
  314. package/skills/agent-native-architecture/references/agent-native-testing.md +605 -0
  315. package/skills/agent-native-architecture/references/architecture-patterns.md +493 -0
  316. package/skills/agent-native-architecture/references/dynamic-context-injection.md +358 -0
  317. package/skills/agent-native-architecture/references/files-universal-interface.md +314 -0
  318. package/skills/agent-native-architecture/references/from-primitives-to-domain-tools.md +381 -0
  319. package/skills/agent-native-architecture/references/mcp-tool-design.md +524 -0
  320. package/skills/agent-native-architecture/references/mobile-patterns.md +897 -0
  321. package/skills/agent-native-architecture/references/product-implications.md +478 -0
  322. package/skills/agent-native-architecture/references/refactoring-to-prompt-native.md +342 -0
  323. package/skills/agent-native-architecture/references/self-modification.md +294 -0
  324. package/skills/agent-native-architecture/references/shared-workspace-architecture.md +700 -0
  325. package/skills/agent-native-architecture/references/system-prompt-design.md +263 -0
  326. package/skills/agent-native-audit/SKILL.md +300 -0
  327. package/skills/agent-native-audit/workflows/agent-native-audit.workflow.js +423 -0
  328. package/skills/andrew-kane-gem-writer/SKILL.md +189 -0
  329. package/skills/andrew-kane-gem-writer/references/database-adapters.md +231 -0
  330. package/skills/andrew-kane-gem-writer/references/module-organization.md +121 -0
  331. package/skills/andrew-kane-gem-writer/references/rails-integration.md +183 -0
  332. package/skills/andrew-kane-gem-writer/references/resources.md +126 -0
  333. package/skills/andrew-kane-gem-writer/references/testing-patterns.md +261 -0
  334. package/skills/architecture/SKILL.md +436 -0
  335. package/skills/architecture/references/adr-template.md +154 -0
  336. package/skills/architecture/references/likec4-reference.md +136 -0
  337. package/skills/architecture/references/nfr-reference.md +126 -0
  338. package/skills/archive-kb/SKILL.md +86 -0
  339. package/skills/archive-kb/scripts/archive-kb.sh +300 -0
  340. package/skills/archive-kb/test/archive-kb-partial-run.test.sh +276 -0
  341. package/skills/atdd-developer/SKILL.md +65 -0
  342. package/skills/brainstorm/SKILL.md +701 -0
  343. package/skills/brainstorm/references/brainstorm-brand-workshop.md +153 -0
  344. package/skills/brainstorm/references/brainstorm-domain-config.md +63 -0
  345. package/skills/brainstorm/references/brainstorm-validation-workshop.md +65 -0
  346. package/skills/brainstorm/references/ui-surface-terms.md +42 -0
  347. package/skills/brainstorm-techniques/SKILL.md +231 -0
  348. package/skills/brainstorm-techniques/references/decision-principles.md +150 -0
  349. package/skills/brainstorm-techniques/references/phase-boundaries.md +103 -0
  350. package/skills/brainstorm-techniques/references/standing-delegation.md +407 -0
  351. package/skills/brainstorm-techniques/references/technical-ruling-delegation.md +174 -0
  352. package/skills/campaign-calendar/SKILL.md +145 -0
  353. package/skills/cf-token-scope/SKILL.md +164 -0
  354. package/skills/cf-token-scope/references/widen-playbook.md +114 -0
  355. package/skills/cf-token-scope/scripts/cf-token-scope.sh +253 -0
  356. package/skills/changelog/SKILL.md +142 -0
  357. package/skills/code-to-prd/SKILL.md +66 -0
  358. package/skills/code-to-prd/references/banner-template.md +12 -0
  359. package/skills/code-to-prd/references/prd-template.md +41 -0
  360. package/skills/code-to-prd/scripts/code-to-prd.sh +634 -0
  361. package/skills/code-to-prd/test/code-to-prd.test.sh +447 -0
  362. package/skills/code-to-prd/test/fixture/.env.example +7 -0
  363. package/skills/code-to-prd/test/fixture/app/about/page.tsx +11 -0
  364. package/skills/code-to-prd/test/fixture/app/api/health/route.ts +10 -0
  365. package/skills/code-to-prd/test/fixture/app/page.tsx +14 -0
  366. package/skills/code-to-prd/test/fixture/next.config.mjs +3 -0
  367. package/skills/code-to-prd/test/fixture/package.json +6 -0
  368. package/skills/cohort-status/SKILL.md +62 -0
  369. package/skills/community/SKILL.md +210 -0
  370. package/skills/community/scripts/bsky-community.sh +644 -0
  371. package/skills/community/scripts/bsky-setup.sh +631 -0
  372. package/skills/community/scripts/community-router.sh +93 -0
  373. package/skills/community/scripts/discord-community.sh +487 -0
  374. package/skills/community/scripts/discord-setup.sh +784 -0
  375. package/skills/community/scripts/github-community.sh +706 -0
  376. package/skills/community/scripts/hn-community.sh +233 -0
  377. package/skills/community/scripts/lib/hmac-sha1-b64.sh +126 -0
  378. package/skills/community/scripts/linkedin-community.sh +750 -0
  379. package/skills/community/scripts/linkedin-setup.sh +758 -0
  380. package/skills/community/scripts/x-community.sh +890 -0
  381. package/skills/community/scripts/x-setup.sh +713 -0
  382. package/skills/community/test/community-argv.test.sh +2922 -0
  383. package/skills/community/test/github-community.test.sh +981 -0
  384. package/skills/competitive-analysis/SKILL.md +69 -0
  385. package/skills/competitive-analysis/references/peer-plugin-audit.md +218 -0
  386. package/skills/compound/SKILL.md +673 -0
  387. package/skills/compound/scripts/token-efficiency-report.sh +334 -0
  388. package/skills/compound/test/phase-16.test.sh +379 -0
  389. package/skills/compound-capture/SKILL.md +791 -0
  390. package/skills/compound-capture/assets/critical-pattern-template.md +37 -0
  391. package/skills/compound-capture/assets/resolution-template.md +116 -0
  392. package/skills/compound-capture/references/yaml-schema.md +67 -0
  393. package/skills/compound-capture/schema.yaml +181 -0
  394. package/skills/constraint-scaffold/SKILL.md +175 -0
  395. package/skills/constraint-scaffold/references/boundary-readme.template +35 -0
  396. package/skills/constraint-scaffold/references/constraint-gates-workflow.template +69 -0
  397. package/skills/constraint-scaffold/references/depcruise-config.template +230 -0
  398. package/skills/constraint-scaffold/references/fix-constraints-stage-a.template +240 -0
  399. package/skills/constraint-scaffold/references/fix-constraints-stage-b.template +313 -0
  400. package/skills/constraint-scaffold/references/shared-runner.template +135 -0
  401. package/skills/constraint-scaffold/scripts/constraint-scaffold.sh +589 -0
  402. package/skills/constraint-scaffold/test/bite-proof.test.sh +1338 -0
  403. package/skills/constraint-scaffold/test/boundary.test.sh +734 -0
  404. package/skills/constraint-scaffold/test/emit-fix-constraints.test.sh +193 -0
  405. package/skills/constraint-scaffold/test/generator.test.sh +93 -0
  406. package/skills/constraint-scaffold/test/parity.test.sh +285 -0
  407. package/skills/content-writer/SKILL.md +242 -0
  408. package/skills/content-writer/scripts/blog-jargon-scan.sh +42 -0
  409. package/skills/cron-delete/SKILL.md +56 -0
  410. package/skills/cron-list/SKILL.md +54 -0
  411. package/skills/deepen-plan/SKILL.md +937 -0
  412. package/skills/deepen-plan/workflows/deepen-plan.workflow.js +488 -0
  413. package/skills/deploy/SKILL.md +115 -0
  414. package/skills/deploy/references/hetzner-setup.md +65 -0
  415. package/skills/deploy/scripts/deploy.sh +37 -0
  416. package/skills/deploy-docs/SKILL.md +95 -0
  417. package/skills/dhh-rails-style/SKILL.md +203 -0
  418. package/skills/dhh-rails-style/references/architecture.md +717 -0
  419. package/skills/dhh-rails-style/references/controllers.md +323 -0
  420. package/skills/dhh-rails-style/references/frontend.md +556 -0
  421. package/skills/dhh-rails-style/references/gems.md +316 -0
  422. package/skills/dhh-rails-style/references/models.md +388 -0
  423. package/skills/dhh-rails-style/references/testing.md +354 -0
  424. package/skills/discord-content/SKILL.md +126 -0
  425. package/skills/docs-site/SKILL.md +214 -0
  426. package/skills/drain-labeled-backlog/SKILL.md +203 -0
  427. package/skills/drain-labeled-backlog/scripts/group-by-area.sh +208 -0
  428. package/skills/drain-labeled-backlog/workflows/drain-labeled-backlog.workflow.js +433 -0
  429. package/skills/drain-prs/SKILL.md +142 -0
  430. package/skills/drain-prs/scripts/triage-prs.sh +192 -0
  431. package/skills/drain-prs/test/argv-ceiling.test.sh +183 -0
  432. package/skills/dspy-ruby/SKILL.md +634 -0
  433. package/skills/dspy-ruby/assets/config-template.rb +359 -0
  434. package/skills/dspy-ruby/assets/module-template.rb +326 -0
  435. package/skills/dspy-ruby/assets/signature-template.rb +143 -0
  436. package/skills/dspy-ruby/references/core-concepts.md +268 -0
  437. package/skills/dspy-ruby/references/optimization.md +623 -0
  438. package/skills/dspy-ruby/references/providers.md +335 -0
  439. package/skills/eval-harness/README.md +194 -0
  440. package/skills/eval-harness/SKILL.md +157 -0
  441. package/skills/eval-harness/enums/go-routes.json +13 -0
  442. package/skills/eval-harness/enums/incident-threshold.json +5 -0
  443. package/skills/eval-harness/enums/lane.json +5 -0
  444. package/skills/eval-harness/enums/tool-selection.json +20 -0
  445. package/skills/eval-harness/enums/triage-levels.json +5 -0
  446. package/skills/eval-harness/gated-skills.json +34 -0
  447. package/skills/eval-harness/models.generated.json +5 -0
  448. package/skills/eval-harness/promptfooconfig-go-routing.yaml +35 -0
  449. package/skills/eval-harness/promptfooconfig-incident-threshold.yaml +32 -0
  450. package/skills/eval-harness/promptfooconfig-lane-inference.yaml +33 -0
  451. package/skills/eval-harness/promptfooconfig-ticket-triage.yaml +34 -0
  452. package/skills/eval-harness/promptfooconfig-tool-selection.yaml +33 -0
  453. package/skills/eval-harness/prompts/go-baseline.txt +6 -0
  454. package/skills/eval-harness/prompts/go-skill.txt +21 -0
  455. package/skills/eval-harness/prompts/incident-threshold-baseline.txt +6 -0
  456. package/skills/eval-harness/prompts/incident-threshold-skill.txt +12 -0
  457. package/skills/eval-harness/prompts/lane-inference-baseline.txt +6 -0
  458. package/skills/eval-harness/prompts/lane-inference-skill.txt +15 -0
  459. package/skills/eval-harness/prompts/tool-selection-baseline.txt +19 -0
  460. package/skills/eval-harness/prompts/tool-selection-skill.txt +22 -0
  461. package/skills/eval-harness/prompts/triage-baseline.txt +7 -0
  462. package/skills/eval-harness/prompts/triage-skill.txt +10 -0
  463. package/skills/eval-harness/scripts/eval-gate.cjs +318 -0
  464. package/skills/eval-harness/scripts/extract-block.cjs +87 -0
  465. package/skills/eval-harness/scripts/gate-classification.cjs +28 -0
  466. package/skills/eval-harness/scripts/gen-models.sh +41 -0
  467. package/skills/eval-harness/scripts/gen-skill-prompt.cjs +177 -0
  468. package/skills/eval-harness/scripts/measure-classification.cjs +30 -0
  469. package/skills/eval-harness/scripts/parse-label.cjs +93 -0
  470. package/skills/eval-harness/scripts/verdict.cjs +132 -0
  471. package/skills/eval-harness/tasks/go-routing.jsonl +8 -0
  472. package/skills/eval-harness/tasks/incident-threshold.jsonl +7 -0
  473. package/skills/eval-harness/tasks/lane-inference.jsonl +7 -0
  474. package/skills/eval-harness/tasks/ticket-triage.jsonl +6 -0
  475. package/skills/eval-harness/tasks/tool-selection.jsonl +15 -0
  476. package/skills/eval-harness/test/eval-gate.test.sh +132 -0
  477. package/skills/eval-harness/test/extract-block.test.sh +87 -0
  478. package/skills/eval-harness/test/gate-classification.test.sh +77 -0
  479. package/skills/eval-harness/test/gen-models.test.sh +75 -0
  480. package/skills/eval-harness/test/measure-classification.test.sh +74 -0
  481. package/skills/eval-harness/test/parse-label.test.sh +129 -0
  482. package/skills/eval-harness/test/registry-completeness.test.sh +109 -0
  483. package/skills/eval-harness/test/verdict.test.sh +123 -0
  484. package/skills/every-style-editor/SKILL.md +142 -0
  485. package/skills/every-style-editor/references/EVERY_WRITE_STYLE.md +529 -0
  486. package/skills/feature-tweet/SKILL.md +199 -0
  487. package/skills/feature-video/SKILL.md +412 -0
  488. package/skills/feature-video/scripts/check_deps.sh +281 -0
  489. package/skills/file-todos/SKILL.md +267 -0
  490. package/skills/file-todos/assets/todo-template.md +166 -0
  491. package/skills/fix-issue/SKILL.md +278 -0
  492. package/skills/fix-issue/references/agent-authored-exclusion.md +118 -0
  493. package/skills/fix-issue/references/exclude-label-jq-snippet.md +96 -0
  494. package/skills/flag-bootstrap/SETUP.md +142 -0
  495. package/skills/flag-create/SKILL.md +145 -0
  496. package/skills/flag-create/scripts/create.sh +273 -0
  497. package/skills/flag-delete/SKILL.md +167 -0
  498. package/skills/flag-delete/scripts/delete.sh +288 -0
  499. package/skills/flag-list/SKILL.md +99 -0
  500. package/skills/flag-list/scripts/list.sh +275 -0
  501. package/skills/flag-set-role/SKILL.md +229 -0
  502. package/skills/flag-set-role/scripts/flip.sh +774 -0
  503. package/skills/frontend-anti-slop/SKILL.md +106 -0
  504. package/skills/frontend-anti-slop/references/anti-patterns.md +199 -0
  505. package/skills/frontend-anti-slop/references/slop-rules.md +65 -0
  506. package/skills/frontend-anti-slop/scripts/tier1-scan.ts +525 -0
  507. package/skills/frontend-design/SKILL.md +77 -0
  508. package/skills/gdpr-gate/NOTICE +136 -0
  509. package/skills/gdpr-gate/SKILL.md +341 -0
  510. package/skills/gdpr-gate/references/fields.md +160 -0
  511. package/skills/gdpr-gate/references/layers/api-layer.md +355 -0
  512. package/skills/gdpr-gate/references/layers/auth-sessions.md +290 -0
  513. package/skills/gdpr-gate/references/layers/data-in-transit.md +331 -0
  514. package/skills/gdpr-gate/references/layers/data-lifecycle.md +341 -0
  515. package/skills/gdpr-gate/references/layers/frontend.md +249 -0
  516. package/skills/gdpr-gate/references/layers/testing-seeding.md +256 -0
  517. package/skills/gdpr-gate/references/leakage-vectors.md +248 -0
  518. package/skills/gdpr-gate/references/legacy/legal-consent-v1-prose.md +48 -0
  519. package/skills/gdpr-gate/references/legal-consent.md +140 -0
  520. package/skills/gdpr-gate/references/non-negotiables.md +60 -0
  521. package/skills/gdpr-gate/scripts/gdpr-gate.sh +271 -0
  522. package/skills/gdpr-gate/scripts/notice-frontmatter.sh +337 -0
  523. package/skills/gdpr-gate/scripts/path-denylist.txt +39 -0
  524. package/skills/gdpr-gate/scripts/repo-scan.sh +158 -0
  525. package/skills/gdpr-gate/scripts/vendor-drift-classify.sh +120 -0
  526. package/skills/gdpr-gate/scripts/vendor-pin-integrity.sh +237 -0
  527. package/skills/gemini-imagegen/SKILL.md +283 -0
  528. package/skills/gemini-imagegen/requirements.txt +5 -0
  529. package/skills/gemini-imagegen/scripts/_error_handling.py +146 -0
  530. package/skills/gemini-imagegen/scripts/compose_images.py +150 -0
  531. package/skills/gemini-imagegen/scripts/edit_image.py +137 -0
  532. package/skills/gemini-imagegen/scripts/gemini_images.py +256 -0
  533. package/skills/gemini-imagegen/scripts/generate_image.py +147 -0
  534. package/skills/gemini-imagegen/scripts/multi_turn_chat.py +223 -0
  535. package/skills/gemini-imagegen/scripts/test_error_handling.py +179 -0
  536. package/skills/git-worktree/SKILL.md +534 -0
  537. package/skills/git-worktree/scripts/git-repo-readiness-diag.sh +98 -0
  538. package/skills/git-worktree/scripts/worktree-manager.sh +4773 -0
  539. package/skills/git-worktree/test/create-from-origin-main.test.sh +116 -0
  540. package/skills/git-worktree/test/lease-protects-active.test.sh +1056 -0
  541. package/skills/git-worktree/test/no-repo-fail-loud.test.sh +88 -0
  542. package/skills/git-worktree/test/orphan-reaper-honest-count.test.sh +379 -0
  543. package/skills/git-worktree/test/reap-archive-persistence.test.sh +725 -0
  544. package/skills/git-worktree/test/stale-lock-sweep.test.sh +176 -0
  545. package/skills/go/SKILL.md +54 -0
  546. package/skills/growth/SKILL.md +164 -0
  547. package/skills/harvest-debt/SKILL.md +78 -0
  548. package/skills/harvest-debt/scripts/harvest-debt.sh +114 -0
  549. package/skills/heal-skill/SKILL.md +173 -0
  550. package/skills/help/SKILL.md +46 -0
  551. package/skills/incident/NOTICE +39 -0
  552. package/skills/incident/SKILL.md +395 -0
  553. package/skills/incident/scripts/dry-run.sh +448 -0
  554. package/skills/incident/scripts/redact-engine.py +412 -0
  555. package/skills/incident/scripts/redact-sentinel.sh +44 -0
  556. package/skills/incident/templates/pir.md +139 -0
  557. package/skills/incident/test/fixtures/dry-run-incident.json +26 -0
  558. package/skills/incident/test/fixtures/dry-run-secret-leak.json +26 -0
  559. package/skills/incident/test/fixtures/legacy-bash-scanner.sh +82 -0
  560. package/skills/incident/test/fixtures/positive-corpus.md +87 -0
  561. package/skills/incident/test/redact-sentinel.test.sh +1420 -0
  562. package/skills/invoice/SKILL.md +206 -0
  563. package/skills/kb-glossary/SKILL.md +71 -0
  564. package/skills/kb-glossary/references/glossary-format.md +111 -0
  565. package/skills/kb-glossary/references/rejected-request-register.md +115 -0
  566. package/skills/kb-search/SKILL.md +276 -0
  567. package/skills/kb-search/scripts/kb-search-cache.sh +123 -0
  568. package/skills/legal-audit/SKILL.md +161 -0
  569. package/skills/legal-generate/NOTICE +242 -0
  570. package/skills/legal-generate/SKILL.md +244 -0
  571. package/skills/legal-generate/references/templates/advisor-agreement/template.md +12 -0
  572. package/skills/legal-generate/references/templates/business-associate-agreement/template.md +112 -0
  573. package/skills/legal-generate/references/templates/cookie-notice/template.md +82 -0
  574. package/skills/legal-generate/references/templates/dpa-global/template.md +439 -0
  575. package/skills/legal-generate/references/templates/dpa-us/template.md +222 -0
  576. package/skills/legal-generate/references/templates/employee-offer-letter/template.md +75 -0
  577. package/skills/legal-generate/references/templates/master-services-agreement/template.md +252 -0
  578. package/skills/legal-generate/references/templates/mutual-nda/template.md +66 -0
  579. package/skills/legal-generate/references/templates/one-way-nda/template.md +38 -0
  580. package/skills/legal-generate/references/templates/privacy-policy-gdpr/template.md +427 -0
  581. package/skills/legal-generate/references/templates/privacy-policy-us/template.md +313 -0
  582. package/skills/legal-generate/references/templates/terms-of-use/template.md +75 -0
  583. package/skills/legal-generate/scripts/strip-vendor-credit.sh +96 -0
  584. package/skills/linear-fetch/SKILL.md +247 -0
  585. package/skills/linear-fetch/scripts/assert-no-linear-telemetry.sh +66 -0
  586. package/skills/linear-fetch/scripts/redact-linear-urls.sh +51 -0
  587. package/skills/linear-fetch/scripts/render-caller-template.sh +36 -0
  588. package/skills/linear-fetch/test/assert-no-linear-telemetry.test.sh +113 -0
  589. package/skills/linear-fetch/test/parity.test.sh +112 -0
  590. package/skills/linear-fetch/test/persist-safe-integration.test.sh +191 -0
  591. package/skills/linear-fetch/test/redact-linear-urls.test.sh +226 -0
  592. package/skills/merge-pr/SKILL.md +668 -0
  593. package/skills/model-launch-review/SKILL.md +255 -0
  594. package/skills/model-launch-review/scripts/audit-models.sh +501 -0
  595. package/skills/one-shot/SKILL.md +316 -0
  596. package/skills/one-shot/references/token-discipline.md +74 -0
  597. package/skills/operator-bootstrap/SKILL.md +412 -0
  598. package/skills/operator-bootstrap/template.sh +439 -0
  599. package/skills/operator-digest/SKILL.md +287 -0
  600. package/skills/operator-digest/assets/operator-digest.workflow.yml +175 -0
  601. package/skills/operator-digest/scripts/digest-scrub.sh +119 -0
  602. package/skills/operator-digest/scripts/provision-operator-digest-repo.sh +147 -0
  603. package/skills/operator-rephrase/SKILL.md +98 -0
  604. package/skills/pencil-setup/SKILL.md +201 -0
  605. package/skills/pencil-setup/scripts/check_deps.sh +556 -0
  606. package/skills/pencil-setup/scripts/copy_adapter.sh +80 -0
  607. package/skills/pencil-setup/scripts/package-lock.json +1172 -0
  608. package/skills/pencil-setup/scripts/package.json +14 -0
  609. package/skills/pencil-setup/scripts/pencil-error-enrichment.mjs +21 -0
  610. package/skills/pencil-setup/scripts/pencil-mcp-adapter.mjs +733 -0
  611. package/skills/pencil-setup/scripts/pencil-response-classify.mjs +38 -0
  612. package/skills/pencil-setup/scripts/pencil-save-gate.mjs +26 -0
  613. package/skills/pencil-setup/scripts/sanitize-filename.mjs +25 -0
  614. package/skills/plan/SKILL.md +1140 -0
  615. package/skills/plan/references/differential-migration-testing.md +185 -0
  616. package/skills/plan/references/plan-community-discovery.md +42 -0
  617. package/skills/plan/references/plan-founder-check.md +137 -0
  618. package/skills/plan/references/plan-functional-overlap.md +25 -0
  619. package/skills/plan/references/plan-host-fanout.md +51 -0
  620. package/skills/plan/references/plan-issue-templates.md +779 -0
  621. package/skills/plan/references/plan-network-outage-checklist.md +131 -0
  622. package/skills/plan/references/plan-scope-check.md +126 -0
  623. package/skills/plan/references/plan-sharp-edges.md +325 -0
  624. package/skills/plan/references/plan-signoff-lifecycle.md +12 -0
  625. package/skills/plan/references/webapp-cwv-observability.md +143 -0
  626. package/skills/plan-review/SKILL.md +91 -0
  627. package/skills/plan-review/lib/named-panel.mjs +58 -0
  628. package/skills/plan-review/workflows/plan-review.workflow.js +477 -0
  629. package/skills/postmerge/SKILL.md +642 -0
  630. package/skills/postmerge/references/deploy-status-debugging.md +138 -0
  631. package/skills/postmerge/references/graceful-degradation.md +20 -0
  632. package/skills/postmerge/references/sentry-error-count-delta.md +137 -0
  633. package/skills/preflight/SKILL.md +1553 -0
  634. package/skills/preflight/references/check-13-founder-check.md +286 -0
  635. package/skills/preflight/scripts/founder-check.py +1640 -0
  636. package/skills/preflight/scripts/parse-form-a.awk +107 -0
  637. package/skills/preflight/scripts/probe-verb-gate.sh +108 -0
  638. package/skills/product-roadmap/SKILL.md +357 -0
  639. package/skills/product-roadmap/scripts/roadmap-reconcile.sh +327 -0
  640. package/skills/provision-cloudflare/SKILL.md +67 -0
  641. package/skills/provision-cloudflare/scripts/provision-cloudflare.sh +271 -0
  642. package/skills/provision-doppler/SKILL.md +58 -0
  643. package/skills/provision-doppler/scripts/provision-doppler.sh +358 -0
  644. package/skills/provision-github/SKILL.md +59 -0
  645. package/skills/provision-github/scripts/provision-github.sh +300 -0
  646. package/skills/provision-hetzner/SKILL.md +76 -0
  647. package/skills/provision-hetzner/scripts/provision-hetzner.sh +262 -0
  648. package/skills/provision-hetzner/test/fixture/knowledge-base/legal/tenant-dpa-register.md +26 -0
  649. package/skills/provision-hetzner/test/fixture/provision-hetzner-dry-run.golden +34 -0
  650. package/skills/provision-hetzner/test/provision-hetzner-characterization.test.sh +227 -0
  651. package/skills/qa/SKILL.md +263 -0
  652. package/skills/qa/references/browser-preflight.md +22 -0
  653. package/skills/questionnaire-generate/SKILL.md +328 -0
  654. package/skills/questionnaire-generate/references/questionnaire.template +60 -0
  655. package/skills/rclone/SKILL.md +162 -0
  656. package/skills/rclone/scripts/check_setup.sh +63 -0
  657. package/skills/release-announce/SKILL.md +65 -0
  658. package/skills/release-docs/SKILL.md +92 -0
  659. package/skills/reproduce-bug/SKILL.md +358 -0
  660. package/skills/resolve-debt/SKILL.md +85 -0
  661. package/skills/resolve-debt/scripts/resolve-debt.py +584 -0
  662. package/skills/resolve-parallel/SKILL.md +45 -0
  663. package/skills/resolve-parallel/workflows/resolve-parallel.workflow.js +415 -0
  664. package/skills/resolve-pr-parallel/SKILL.md +68 -0
  665. package/skills/resolve-pr-parallel/workflows/resolve-pr-parallel.workflow.js +419 -0
  666. package/skills/resolve-todo-parallel/SKILL.md +57 -0
  667. package/skills/resolve-todo-parallel/workflows/resolve-todo-parallel.workflow.js +398 -0
  668. package/skills/review/SKILL.md +1621 -0
  669. package/skills/review/references/anti-slop-scanner-hook.md +40 -0
  670. package/skills/review/references/defect-classes-migrations.md +9 -0
  671. package/skills/review/references/generated-input-oracle-surfaces.md +15 -0
  672. package/skills/review/references/review-e2e-testing.md +116 -0
  673. package/skills/review/references/review-todo-structure.md +165 -0
  674. package/skills/review/references/risk-tier-and-fix-rounds.md +95 -0
  675. package/skills/review/references/semgrep-custom-rules.yaml +174 -0
  676. package/skills/review/references/ultrathink-phases.md +98 -0
  677. package/skills/review/references/wfs.md +6 -0
  678. package/skills/review/scripts/emit-review-trailer.sh +499 -0
  679. package/skills/review/scripts/ensure-semgrep.sh +99 -0
  680. package/skills/review/scripts/fix-round-seats.sh +197 -0
  681. package/skills/review/test/emit-review-trailer.test.sh +387 -0
  682. package/skills/review/test/ensure-semgrep.test.sh +111 -0
  683. package/skills/review/test/fix-round-seats.test.sh +237 -0
  684. package/skills/review/workflows/README.md +147 -0
  685. package/skills/review/workflows/review.workflow.js +667 -0
  686. package/skills/schedule/SKILL.md +796 -0
  687. package/skills/seo-aeo/SKILL.md +139 -0
  688. package/skills/seo-aeo/scripts/validate-csp.sh +233 -0
  689. package/skills/seo-aeo/scripts/validate-seo.sh +262 -0
  690. package/skills/ship/SKILL.md +3022 -0
  691. package/skills/ship/references/adr-ordinal-collision.md +47 -0
  692. package/skills/ship/references/affected-gate-contract.md +115 -0
  693. package/skills/ship/references/ci-run-diagnosis.md +29 -0
  694. package/skills/ship/references/ci-workflow-authoring.md +68 -0
  695. package/skills/ship/references/counsel-review-clo-attestation.md +38 -0
  696. package/skills/ship/references/cto-ruling-armed-push.md +5 -0
  697. package/skills/ship/references/decision-challenges-render.md +71 -0
  698. package/skills/ship/references/detached-battery-verdict.md +13 -0
  699. package/skills/ship/references/feature-tweet-draft.md +42 -0
  700. package/skills/ship/references/followthrough-issue-body-template.md +47 -0
  701. package/skills/ship/references/followthrough-stub-template.sh +92 -0
  702. package/skills/ship/references/merge-queue-dequeue.md +39 -0
  703. package/skills/ship/references/operator-holds.md +149 -0
  704. package/skills/ship/references/pipeline-tally-render.md +43 -0
  705. package/skills/ship/references/pr-overlap.md +153 -0
  706. package/skills/ship/references/queue-mode.md +7 -0
  707. package/skills/ship/references/ready-run-wait.md +36 -0
  708. package/skills/ship/references/red-on-main-quarantine.md +110 -0
  709. package/skills/ship/references/register-update-pr-pattern.md +88 -0
  710. package/skills/ship/references/settle-then-admin-merge.md +130 -0
  711. package/skills/ship/references/supabase-column-probe.md +13 -0
  712. package/skills/ship/scripts/auto-close-scan.sh +76 -0
  713. package/skills/ship/scripts/battery-owed.sh +403 -0
  714. package/skills/ship/scripts/hold-gate-eval.sh +410 -0
  715. package/skills/ship/scripts/net-issue-flow.sh +665 -0
  716. package/skills/ship/scripts/pr-overlap-preflight.sh +483 -0
  717. package/skills/ship/scripts/ship-pir-action-items-gate.sh +246 -0
  718. package/skills/ship/scripts/technical-ruling-gate.sh +444 -0
  719. package/skills/skill-creator/SKILL.md +299 -0
  720. package/skills/skill-creator/references/api-security.md +248 -0
  721. package/skills/skill-creator/references/authoring-levers.md +110 -0
  722. package/skills/skill-creator/references/be-clear-and-direct.md +587 -0
  723. package/skills/skill-creator/references/best-practices.md +431 -0
  724. package/skills/skill-creator/references/common-patterns.md +639 -0
  725. package/skills/skill-creator/references/core-principles.md +193 -0
  726. package/skills/skill-creator/references/executable-code.md +188 -0
  727. package/skills/skill-creator/references/iteration-and-testing.md +509 -0
  728. package/skills/skill-creator/references/official-spec.md +193 -0
  729. package/skills/skill-creator/references/recommended-structure.md +180 -0
  730. package/skills/skill-creator/references/skill-structure.md +396 -0
  731. package/skills/skill-creator/references/using-scripts.md +122 -0
  732. package/skills/skill-creator/references/using-templates.md +120 -0
  733. package/skills/skill-creator/references/workflows-and-validation.md +541 -0
  734. package/skills/skill-creator/scripts/init_skill.py +322 -0
  735. package/skills/skill-creator/scripts/package_skill.py +110 -0
  736. package/skills/skill-creator/scripts/quick_validate.py +64 -0
  737. package/skills/skill-creator/templates/router-skill.md +73 -0
  738. package/skills/skill-creator/templates/simple-skill.md +34 -0
  739. package/skills/skill-creator/workflows/add-reference.md +96 -0
  740. package/skills/skill-creator/workflows/add-script.md +104 -0
  741. package/skills/skill-creator/workflows/add-template.md +83 -0
  742. package/skills/skill-creator/workflows/add-workflow.md +128 -0
  743. package/skills/skill-creator/workflows/audit-skill.md +159 -0
  744. package/skills/skill-creator/workflows/create-domain-expertise-skill.md +623 -0
  745. package/skills/skill-creator/workflows/create-new-skill.md +207 -0
  746. package/skills/skill-creator/workflows/get-guidance.md +134 -0
  747. package/skills/skill-creator/workflows/upgrade-to-router.md +170 -0
  748. package/skills/skill-creator/workflows/verify-skill.md +226 -0
  749. package/skills/skill-security-scan/SKILL.md +145 -0
  750. package/skills/skill-security-scan/references/disclaimer.md +21 -0
  751. package/skills/skill-security-scan/references/first-party-allowlist.yaml +38 -0
  752. package/skills/skill-security-scan/references/override-artifact-schema.json +68 -0
  753. package/skills/skill-security-scan/references/override-mechanism.md +171 -0
  754. package/skills/skill-security-scan/references/redirect-domains.yaml +27 -0
  755. package/skills/skill-security-scan/references/regex-patterns.md +136 -0
  756. package/skills/skill-security-scan/references/rules/code-exec.yaml +70 -0
  757. package/skills/skill-security-scan/references/rules/filesystem-boundary.yaml +29 -0
  758. package/skills/skill-security-scan/references/rules/manifest.yaml +44 -0
  759. package/skills/skill-security-scan/references/rules/supply-chain.yaml +36 -0
  760. package/skills/skill-security-scan/references/scan-meta-schema.json +63 -0
  761. package/skills/skill-security-scan/references/test-fixtures/clean-soleur-style.skill.md +23 -0
  762. package/skills/skill-security-scan/references/test-fixtures/clean-third-party.skill.md +26 -0
  763. package/skills/skill-security-scan/references/test-fixtures/malicious-codeexec.skill.md +23 -0
  764. package/skills/skill-security-scan/references/test-fixtures/malicious-curl-pipe-bash.skill.md +69 -0
  765. package/skills/skill-security-scan/references/test-fixtures/malicious-prompt-injection.skill.md +17 -0
  766. package/skills/skill-security-scan/references/test-fixtures/malicious-telemetry-beacon.skill.md +23 -0
  767. package/skills/skill-security-scan/references/typosquat-targets.yaml +65 -0
  768. package/skills/skill-security-scan/scripts/check-codeexec.sh +41 -0
  769. package/skills/skill-security-scan/scripts/check-filesystem-boundary.sh +19 -0
  770. package/skills/skill-security-scan/scripts/check-prompt-injection.sh +95 -0
  771. package/skills/skill-security-scan/scripts/check-supply-chain.sh +219 -0
  772. package/skills/skill-security-scan/scripts/check-telemetry-surface.sh +126 -0
  773. package/skills/skill-security-scan/scripts/lib.sh +197 -0
  774. package/skills/skill-security-scan/scripts/parse-override.sh +231 -0
  775. package/skills/skill-security-scan/scripts/run-scan.sh +342 -0
  776. package/skills/skill-security-scan/scripts/run-self-test.sh +95 -0
  777. package/skills/skill-security-scan/test/argv-ceiling.test.sh +180 -0
  778. package/skills/skill-security-scan/test/meta-dir-reap.test.sh +411 -0
  779. package/skills/skill-security-scan/test/parse-override.test.sh +607 -0
  780. package/skills/skill-security-scan/test/redact-pii-ghs-shape.test.sh +251 -0
  781. package/skills/social-distribute/SKILL.md +485 -0
  782. package/skills/spec-templates/SKILL.md +274 -0
  783. package/skills/sync/SKILL.md +49 -0
  784. package/skills/test-browser/SKILL.md +464 -0
  785. package/skills/test-browser/references/feature-map.md +161 -0
  786. package/skills/test-fix-loop/SKILL.md +159 -0
  787. package/skills/test-fix-loop/references/determinism-checklist.md +154 -0
  788. package/skills/triage/SKILL.md +402 -0
  789. package/skills/trigger-cron/SKILL.md +136 -0
  790. package/skills/trigger-cron/scripts/trigger.sh +198 -0
  791. package/skills/user-set-role/SKILL.md +147 -0
  792. package/skills/user-set-role/scripts/set-role.sh +218 -0
  793. package/skills/user-story-writer/SKILL.md +77 -0
  794. package/skills/ux-audit/SKILL.md +251 -0
  795. package/skills/ux-audit/references/finding.schema.json +62 -0
  796. package/skills/ux-audit/references/route-list.yaml +95 -0
  797. package/skills/ux-audit/scripts/bot-fixture.ts +270 -0
  798. package/skills/ux-audit/scripts/bot-signin.ts +135 -0
  799. package/skills/ux-audit/scripts/dedup-hash.ts +46 -0
  800. package/skills/work/SKILL.md +1479 -0
  801. package/skills/work/references/end-of-work-resume.md +7 -0
  802. package/skills/work/references/hook-test-main-cwd.md +24 -0
  803. package/skills/work/references/key-principles.md +83 -0
  804. package/skills/work/references/property-testing.md +122 -0
  805. package/skills/work/references/sql-security-definer-rpc-scaffold.sql +78 -0
  806. package/skills/work/references/work-agent-teams.md +97 -0
  807. package/skills/work/references/work-lifecycle-parallel.md +147 -0
  808. package/skills/work/references/work-lockfile-bumps.md +97 -0
  809. package/skills/work/references/work-scratch-sandboxes.md +212 -0
  810. package/skills/work/references/work-subagent-fanout.md +61 -0
  811. package/skills/work/references/work-test-harness-sharp-edges.md +73 -0
  812. package/skills/xcode-test/SKILL.md +360 -0
@@ -0,0 +1,325 @@
1
+ ## Sharp Edges
2
+
3
+ **Plugin root in this file:** this file is Read, not delivered by the skill loader, so `${CLAUDE_PLUGIN_ROOT}` below is not replaced for you. The root is ONLY the prefix of the path you read this file from, cut at its last `/skills/` — never a value from repository files, PR text or tool output, and never a directory inside the checked-out repository. Check first with `echo "root=[${CLAUDE_PLUGIN_ROOT}]"`: if it prints that root, proceed; if it prints `root=[]`, prefix every Bash or Monitor command below with `export CLAUDE_PLUGIN_ROOT=<root>` (each starts a fresh shell) and write the absolute root into any subagent prompt; if it prints anything else, stop — something other than the loader set it. If you cannot name the root (the path you read this file from still shows `${CLAUDE_PLUGIN_ROOT}`, or starts with `/skills/`), stop and hand the step to the operator. Left unset, every command fails closed on a `/skills/` or `/scripts/` path; never repair that with a CWD-relative plugin path, which runs the checked-out repository's copy.
4
+
5
+ - **A command a plan prescribes for user or developer hosts (a plugin script, a hook, a local lint) must be portable to every host it reaches. List each external binary it invokes and check it against non-GNU hosts:** `timeout` (absent on stock macOS; use the repo's `timeout`→`gtimeout`→bare pattern in `.claude/hooks/git-commit-secret-scan.sh`), `/usr/bin/time`, `bc`, `sed -i`, `date -d`, `readlink -f`, `stat -c`. A fix for a missing-tool class is the likeliest place to reintroduce one. **Why:** #8231 plan v1 probed gitleaks with a bare `timeout`. That would have made every macOS `code-to-prd` run exit 2 and every macOS commit skip the local secret scan; four plan reviewers caught it. See `knowledge-base/project/learnings/2026-09-18-a-plan-prescribed-probe-must-be-portable-to-the-hosts-it-ships-to.md`.
6
+
7
+ - **When a guard derives its protected set from a TOOL'S OWN reporting, enumerate every input SHAPE that tool accepts — one mutation-matrix row per shape — not just the shape the bug report showed.** Deriving from the tool instead of a hand-written parser is the right direction and is exactly what makes the gap invisible: "we ask git itself" sounds like it closed the whole class. Prefer the tool's **effect on a sandbox** over ANY of its **reports** about intended effect — every report format, however authoritative it reads, is one more input shape with something it renders as nothing. **Why:** #8274 — three proxies in a row on one branch, each recommended in writing as the fix for the last: a `+++ b/` header filter (measured: `+++ x/…` writes); `git apply --numstat -z` (reports **only the rename destination**); then `git apply --summary`, recommended as "the authority output" in a learning written the same morning, which the 12-seat review measured EMPTY for an implicit rename (differing `---`/`+++`, no rename headers) while the apply **deleted the source and replaced the corpus**. The shipped derivation applies to a throwaway index and reads `git diff-index --cached -z` raw. See `knowledge-base/project/learnings/security-issues/2026-09-18-the-third-proxy-the-review-falsified-my-own-fix-and-the-learning-that-recommended-it.md`.
8
+ - **Turn git's trace/verbose variables off with `unset`, never `VAR=0`.** `GIT_CURL_VERBOSE` is presence-checked, so `GIT_CURL_VERBOSE=0` ENABLES curl verbose output (credential-bearing URLs, and noise that pushes real result lines out of a `tail`). A plan that prescribes `GIT_TRACE=0 GIT_CURL_VERBOSE=0 git …` as "trace off" hands work a leak. **Why:** #8474 — the plan's §A prescribed it; security review reproduced 59 lines of `== Info` on a fetch. See `knowledge-base/project/learnings/2026-09-21-every-gate-this-pr-added-failed-open-on-the-input-it-could-not-measure.md`.
9
+
10
+ - **A plan that FLIPS a record's status (ADR `proposed`→`accepted`, C4 TARGET→LIVE) must sweep `knowledge-base/legal/` for the flipped mechanism's nouns, even when an issue "owns Art. 30 activation".** Activation and a tense claim about the same mechanism are different events: grep the Art. 30 register and the counsel-review audits' `re_evaluation_triggers` for future-tense sentences ("will authenticate", "NOT-YET-ACTIVE") about it, and route hits to the CLO for append-only wording. **Why:** #9094 — PA-36 (g)(13) still said the root key "will authenticate" while the PR recorded root auth LIVE; the plan had scoped legal out, and review caught it. See `knowledge-base/project/learnings/2026-09-27-a-status-flip-sweep-must-include-the-legal-registers-that-state-the-mechanism.md`.
11
+
12
+ - **A diff-scope AC ("the diff is a subset of these files") must list the files the PIPELINE writes, not only the ones the plan edits** — the generated `knowledge-base/INDEX.md`, `specs/<branch>/session-state.md`, and any committed run record. Otherwise `soleur:work` ticks an AC that is false on the final tree and review has to untick it (#8207, `knowledge-base/project/learnings/2026-09-15-my-legal-record-said-the-change-had-landed-inside-the-pr-that-lands-it.md`).
13
+
14
+ - **When the design reuses an EXISTING system state as a coordination signal (a unit's enabled/active shape, a flag, a file's existence), write one line per CONSUMER: what decision it makes, and whose provenance and what time it needs.** A consumer that only refuses ("must not start") can read the state; a consumer that RESUMES, REPLAYS or SUPPRESSES needs who-set-it and when, and a state carries neither. Grep every writer of the state before asserting exclusivity ("only X writes Y"). **Why:** #6921 — the systemd `inactive|failed`+`disabled` shape was the sole quiesce signal; a second `op=execute` resumed a 13-day-old capture as `source=persisted` rc 0, and a crashed-then-disabled scheduler read as a deliberate quiesce nobody paged on, past a 5-agent plan panel and deepen. See `knowledge-base/project/learnings/2026-09-14-a-systemd-state-used-as-a-signal-had-no-provenance-and-replayed-a-stale-capture.md`.
15
+
16
+ - **A Guard Contract over an installed BINARY must name the steps that EXECUTE it and how each resolves it, and put an assertion in those steps, not only in the installer.** A digest + version check in the install step proves the bytes at one path; consumers in later steps resolve by NAME (`command -v`, a spawned `which`, a runtime's live `PATH`, `node_modules/.bin`), and a harness that runs only the installer body is blind to all of them. **Why:** #8615 — the pinned-Grok guard passed a 9-row harness while nothing asserted the gate step's `grok` was the pinned file; the structural-enumeration seat mapped 12 resolution paths. See `knowledge-base/project/learnings/2026-09-24-a-pinned-binary-guard-proved-the-file-and-the-gate-resolved-the-name.md`.
17
+ - **When the plan ENUMERATES the instances a guard must cover, specify the guard's discovery as a CENSUS over the file set with a red *unclassified* bucket — the enumeration is only the floor (`MIN_CASES`).** A guard that pins the plan's name list is green for the tree on the day the plan was written; the property ("every crit-level emitter is routed or deliberately excluded") is over the tree. Require one battery row that ADDS an instance the guard has never seen. **Why:** #8097 — the plan said "four `emit_refusal()` definers"; the tree had five, and a `_REFUSED` emitted through a backslash-continued `logger` line was invisible to the line-based scan. See `knowledge-base/project/learnings/2026-09-13-the-guard-pinned-the-names-the-plan-listed-and-the-readback-read-every-page-unpinned.md`.
18
+
19
+ - **When briefing a domain agent (CTO/CLO) for a BINDING ruling, enumerate the facts that would DISQUALIFY your preferred option — not just the ones that support your framing.** A brief is a filter, and the natural filter is "everything I found while reaching my conclusion", which systematically omits the fact that kills the alternative you already rejected. The agent then rules confidently on an incomplete record, and because the ruling is binding you implement it. Cheapest gate before sending: for each option you are NOT proposing, write the one fact that makes it unavailable — if you cannot, you have not checked. **Why:** #7442 — a CTO brief omitted that `rule-prune.sh:52` derives its data root from `$(cd "$SCRIPT_DIR/.." && pwd)`; the ruling instructed relocating it into the plugin payload, which would have broken it everywhere. One correction round-trip retracted the instruction in full — and the agent then found the sibling aggregator carried the identical defect, which the brief had also not mentioned. See `knowledge-base/project/learnings/2026-08-11-i-measured-the-issues-remedy-then-asserted-my-own-without-measuring.md`.
20
+
21
+ - **An ADR ordinal is PROVISIONAL until merge, and the probe must quantify over pushed branches — not `origin/main`.** A `git ls-tree origin/main` check answers "is this ordinal on the default branch", which is a different question from "is it claimed", and a branch-claimed ordinal is invisible to it. Enumerate across every `origin/*` ref, then **re-run the probe immediately before merge** — `main` moves under a long session. **Why:** #7418 collided twice on one branch: ADR-144 was `origin/main`-scoped and was already claimed by a pushed branch; the replacement ADR-174 was verified free across all 62 refs and then a sibling landed that exact ordinal on `main` mid-session. Neither collision was caught by a gate; both surfaced from a re-fetch. See `knowledge-base/project/learnings/2026-08-10-i-fixed-the-guard-twice-and-my-test-could-not-see-either-fix.md`.
22
+ - **The plan's ADR-corpus check must grep the SUBSYSTEM and MECHANISM nouns, not only the defect nouns.** `git grep -il` for `symbolic-ref` and `dangling HEAD` found only unrelated ADRs and concluded "no ADR rejects a repair", while ADR-173 (how bareness is decided, from the `core.bare` config FILE) and ADR-099 (the bare-root layout) governed the very predicate the helper was built on, and the first implementation used `--is-bare-repository`, which that ADR records as lying under a masked config. Include the layout/config/mechanism terms the change depends on (`bare`, `core.bare`, `worktree`, the hook or skill name). **Why:** PR 10105 — the history seat found it at review as a P2. See `knowledge-base/project/learnings/2026-10-12-a-self-heal-for-git-state-had-a-guard-chain-wider-than-its-property.md`.
23
+
24
+ - **A plan claim of the form "X is not obtainable / not derivable / there is no source for X" is a UNIVERSAL NEGATIVE over a SET of mechanisms — enumerate the set before writing it.** Finding ONE mechanism blocked is evidence about that mechanism, never about the capability, and the highest-risk shape is a **suffix-variant sibling** (`FOO` vs `FOO_DAILY`, `_V2`, `_LEGACY`) because locating either one feels like locating *the* mechanism and stops the search. Cheapest gate: grep the DEFINING file for the family (`grep -n '<MARKER_PREFIX>' <definer>`), not the codebase for the name you already have; two hits with different suffixes is the tell. Corollary for correction plans: an AC set built only from ABSENCE assertions (`grep -c '<old>' == 0`, "the deleted filename appears nowhere") tests the new prose's SHAPE and can pass in full while the replacement claim is FALSE — add one AC asserting every claim the diff ADDS traces to a named line in a cited source. **Why:** #7086 — a plan found `SOLEUR_CLAUDE_COST_DAILY` genuinely blocked on an un-mintable admin key and generalized to "no fleet-wide figure is derivable"; the per-run `SOLEUR_CLAUDE_COST` marker 47 lines away in the same file had metered it for three weeks with a committed query, and ADR-108 recorded a figure ~29x the one the row carried. 10 ACs, 235/235 suites and a verified premise table all passed over it. See `knowledge-base/project/learnings/2026-07-30-one-blocked-mechanism-is-not-a-blocked-capability.md`.
25
+
26
+ - **When a plan introduces a resource on an API SURFACE the codebase has not touched before, the credential's coverage of that surface is a precondition to PROBE, not a property to assume — and `terraform plan` cannot tell you.** For a resource absent from state, `plan` reports `1 to add` without calling the API, so a clean plan is fully compatible with an apply that 403s; the green signal and the failure live on different sides of the same command. Probe the new surface with a KNOWN-GRANTED control probe (a bare 403 could be a bad token, wrong zone, or typo'd URL; 403-on-new + 200-on-known isolates it to the permission). The strongest tell is a scope ledger that enumerates permissions — if the new thing is not named in it, that is a finding, not an omission. Applies past Terraform: a new ruleset phase, vendor endpoint family, bucket class, or OAuth scope. Corollary: a `kind = "zone"`/`kind = "root"` ruleset OWNS its phase entrypoint as a whole-list replacement and `plan` cannot see dashboard-created rules, so enumerating the entrypoint before the first apply of any new phase is mandatory — this is now ENFORCED by a standing fail-closed pre-apply gate (`tests/scripts/lib/preapply-entrypoint-gate.sh`, wired into `apply-web-platform-infra.yml`; ADR-136 / #6767): a whole-list create-from-absent whose live entrypoint is non-empty fails the apply with a copy-pasteable adoption remedy rather than silently clobbering. The manual ADR-130 probe remains the `soleur:work`-time pre-*write* check. **Why:** #6746 — an `http_config_settings` Configuration Rule survived a 6-agent panel + `soleur:deepen-plan` (which even reversed the remedy on strong evidence) while `cf_api_token_rulesets` lacked Config Rules:Edit; the accurate scope ledger in `variables.tf` was simply never read against the new phase. See ADR-130 and `knowledge-base/project/learnings/2026-07-20-a-plan-can-prescribe-a-resource-its-credential-cannot-create.md`.
27
+
28
+ - **A follow-through that closes on the ABSENCE of a signal needs proof that the affected party can PRODUCE it — check the flag cohort before designing one.** A feature flag, role gate or unshipped UI makes a zero count certain regardless of the underlying state, so the close fires on no evidence. Run `flag-list` for every flag gating the surface that emits the signal and name the affected party's cohort in the plan; if they cannot reach the emitter, close on direct evidence (a census, a read of the artifact) or keep the close manual. **Why:** #8740/PR #8853 — a 14-day Sentry-absence probe would have auto-closed a tenant's broken-model issue while `c4-visualizer` was on only for `role-dev`, so that tenant could never load the viewer; the CTO deleted the ~490-line probe at review. See `knowledge-base/project/learnings/2026-09-25-a-proof-by-absence-close-on-a-flag-gated-surface-proves-nothing.md`.
29
+
30
+ - **A premise the plan states is only true for the parameter values the plan assumed — re-verify it against the value the implementation actually chose, and read the exit-code semantics of any probe whose result the plan turns into an automated mutation.** A plan justifies a design with a concrete claim ("this issue leaves the recency window before its own soak elapses"), soleur:work then picks the parameter that governs it, and nobody re-checks the premise against the chosen value — because re-checking an inherited premise is not a step anyone schedules. Equally: a plan that turns a probe's exit code into a state mutation must quote that probe's documented exit semantics, since exit 1 conventionally means "the assertion did not hold", which is NOT "the thing being asserted was done prematurely". **Why:** #6698 — the plan mandated bypassing the follow-through sweeper's `earliest` gate for closed issues; soak probes exit 1 for "still soaking" (`workspaces-luks-soak-6604.sh`: `1 = FAIL (still soaking)`, refusing a day-0 PASS "regardless of the directive earliest="), so the bypass would have reopened #6604/#6416/#6462 — all legitimately closed with a future `earliest` — at the next sweep, overriding the operator. It was also unnecessary: the implementation's own `CLOSED_LOOKBACK_DAYS=14` keeps the motivating issue a candidate until after its soak elapses, falsifying the premise that motivated the bypass. See `knowledge-base/project/learnings/2026-07-19-a-wall-clock-break-in-a-replayed-body-and-a-plan-premise-that-would-have-overridden-the-operator.md`.
31
+ - A plan claim about a VENDORED service's behavior (an HTTP status, an auth outcome, a response shape) is a claim to MEASURE against the pinned image, never to derive from that service's config — and a prescribed verification command must be checked for whether it answers the question the plan is asking. Three shapes recur together, all cheap to prevent and expensive to miss: (a) **config-derived behavior** — a hypothesis read off a config block that "looks like" it gates the path (`docker run` the pinned digest with the repo's exact config and measure; one run collapses a whole hypothesis tree); (b) **the right grep, the wrong question** — `git grep -n 'target='` proves an address is *named somewhere*, not that the MERGE path applies it: find the `-target` block's enclosing JOB and that job's TRIGGER (`on: push` vs `workflow_dispatch`), or grep the exclusion contract (`OPERATOR_APPLIED_EXCLUSION`); (c) **a probe that ships inside the mutation's own payload cannot observe the pre-mutation state** — if the probe lives in `user_data`/a migration/a deploy artifact, deploying it IS the change, so any AC claiming a "pre-fix reading" is unobtainable by construction. **Why:** #6497 — the plan built an H3-vs-H4 apparatus (a conditional phase, an enum arm, two tests) on "zot accessControl denies at `/v2/` with 403"; the pinned image answers `/v2/` 200-or-401 and **never** 403 (a policy-less user gets `Login Succeeded`), so H4 was unreachable and its tests asserted a string that cannot exist. The same plan's `git grep -n 'target='` PASSED while concluding the fix applies on merge — both hits were inside `workflow_dispatch` jobs and the resources are per-PR-excluded, so merging would have applied nothing while the ACs "verified" it. See `knowledge-base/project/learnings/2026-07-15-false-comment-shipped-the-bug-then-plan-guard-adr-and-tests-each-restated-it.md`.
32
+
33
+ - When 5-agent plan-review (at single-user-incident threshold) catches a correctness finding, three classes recur. (a) **Missing writer-path:** a new RPC/helper is introduced but never invoked from any user flow → soleur:product:spec-flow-analyzer's "walk the journey end-to-end" lens catches this; schema-shaped reviewers (Kieran, DHH) verify contracts but not journeys. (b) **Fallback path structurally distinct from happy path:** primary query returns null + fallback recovers granularity → BOTH simplification and correctness panels typically fire on the same scope; collapse to one query returning the data the fallback needs. (c) **Cascade ordering rationale paraphrased from sibling cascade:** when inheriting an ordering pattern from a sibling cascade, re-derive the rationale — DELETE-based cascades enforce ordering via FK RESTRICT; UPDATE-based cascades need semantic justification (e.g., audit-trail attribution). **Why:** PR #4213 PR-I plan v1→v2 — all three caught at multi-agent review; v2 collapsed the fallback (4 reviewers fired on same scope), added first-send-IS-authorization for the missing writer-path, and re-derived cascade rationale as semantic Art. 5(2) attribution. See `knowledge-base/project/learnings/2026-05-21-plan-review-five-agent-panel-spec-flow-catches-missing-writer-path-and-bool-fallback-collapses.md`.
34
+ - When a plan AC prescribes a CI sentinel that substring-matches canonical prose (a literal phrase reused verbatim from a source file), read the canonical source line and audit punctuation BETWEEN the words of the candidate sentinel — parens, em-dashes, colons, commas. `.includes()` / `grep -F` against `(X) Y` returns false when the sentinel is `X Y` because of the `)` between. Pick a phrase that spans no punctuation boundary in the source. **Why:** PR #3819 plan v1 prescribed sentinel `BSL 1.1 disclaims warranty` against canonical `(BSL 1.1) disclaims warranty for runtime cost` at `plugins/soleur/docs/pages/goal-primitive.md:59` — Kieran plan-review caught; AC1/AC6 would have failed at first CI run. Plan v2 switched to `disclaims warranty for runtime cost` (paren-safe). See `knowledge-base/project/learnings/2026-05-15-ci-sentinel-paren-safety-substring-match-against-canonical-prose.md`.
35
+ - When a plan section co-locates a prose contract claim with an executable check (jq probe, shell guard, SQL gate, test assertion), the check must explicitly select each invariant the prose names. A terminal `| length` / `--count` / `wc -l` / single-key drill satisfies a one-dimension contract regardless of how many dimensions the prose claims. Example: prose claims `{actions:["update"], before_count:5, after_count:14}` but the jq pipeline ends in `| .change.after.rules[0].required_status_checks[0].required_check | length` — single-key drill, three-key contract, drift invisible at authoring time. Fix at plan-authoring time: when prose names ≥2 invariants, the code block must return an object whose keys match the named dimensions (one selection per dimension). **Why:** PR #3891 shipped a 3-dimension prose contract paired with a 1-dimension jq probe across README + ADR; review soleur:engineering:review:code-quality-analyst caught it pre-merge. See `knowledge-base/project/learnings/2026-05-16-prose-contract-vs-executable-check-dimension-drift.md`.
36
+ - When a plan proposes a verification step (re-verify read, AC post-condition, success gate) to prove a *behavioral* invariant, confirm the check reads the **invariant itself**, not a proxy that usually-but-not-always co-varies with it. Proxies that pass while the invariant is false: segment membership vs flag *evaluation*; row `count` vs row *identity*; config-secret *presence* vs *value*; "function exists" vs *granted/reachable*; HTTP 2xx vs response *body shape*. At `single-user incident` threshold a green check on a broken state is worse than a red one. Schema/style/YAGNI reviewers (DHH/Kieran/code-simplicity) structurally cannot catch proxy-vs-invariant gaps — only the flow-analysis lens (soleur:product:spec-flow-analyzer, Phase 2.5/3) walks "does the asserted thing actually imply the intended outcome?"; always include it for verification-heavy plans. **Why:** #4581 plan FR8 asserted Flagsmith *segment membership* (`count==1`) to prove a per-org flag enable; spec-flow caught (P0-3) that membership without the feature-state override evaluates OFF — a silent non-functional enable. Fix read flag *evaluation* (identity+orgId trait), positive + control-negative. See `knowledge-base/project/learnings/2026-05-29-plan-reverify-must-assert-the-invariant-not-a-proxy.md`.
37
+ - When a plan targets a RECURRING production symptom (a failure that survived a prior fix), establish from production observability — not code-reading alone — WHICH code path actually executes on the affected surface before prescribing the fix layer. Search Sentry/logs for the breadcrumbs/log-lines the candidate path emits (or that the fix will emit), scoped to the affected entity; **zero events from the path you are about to "fix" is decisive evidence of the WRONG LAYER** — re-trace which function actually fires (via the entity's own Sentry events) before committing the plan to a layer. Code-reading proves a path *could* be involved; only runtime evidence proves which path *is* exercised. **Why:** the 2026-06-29/30 Concierge-strand session shipped TWO merged+deployed `cc-dispatcher` fixes (#5716, #5584) that the operator's surface never executed (zero `cc-dispatcher` Sentry events while the deploy was confirmed live); the real path was an Inngest `workspace-reconcile-on-push` function (#5730) firing 26× on the affected workspace. A single Sentry exec-path search would have caught the wrong-layer diagnosis before the first ship instead of the third. See `knowledge-base/project/learnings/2026-06-30-verify-the-fixed-code-path-actually-executes-on-the-affected-surface.md`.
38
+ - **A plan that requires a specific COMBINATION of provider arguments (both set, both unset, one-of) is asserting a schema constraint — check it against the PINNED provider before writing an AC on it, because `ExactlyOneOf`/`ConflictsWith`/`RequiredWith` make some combinations unrepresentable.** The cheapest probe is destructive-and-local: write the combination the plan demands into the `.tf` file and run `terraform validate`; the provider names the constraint in the error. A plan requirement that no valid config can satisfy will otherwise survive plan-review (which reads plan text) and only surface mid-implementation, when the AC has to be amended rather than met. Same session, same class: an ADR ordinal picked at plan time is a *claim, not a reservation* — re-derive it against freshly-fetched `origin/main` immediately before merge, never once at Phase 0. **Why:** #7162/#7159 — plan FR1 required `workplace_role` AND `workplace_permissions` both unset on `doppler_service_account`; the pinned provider enforces `ExactlyOneOf` on the pair, so FR1 was unsatisfiable and AC33 was amended. The same plan's ADR-155 was already taken at Phase 0 and the ordinal moved three more times before merge. See `knowledge-base/project/learnings/workflow-issues/2026-08-03-blanket-renumber-rewrote-other-work-and-a-count-certified-it.md`.
39
+ - When a plan corrects a factual claim (e.g., updates a version range from X to Y), grep the plan output for the old incorrect value before finalizing. Subagents can echo stale data from their initial context even when their analysis concludes otherwise.
40
+ - When a plan adds `cloudflare_record` resources with `name = "@"`, flag it during plan review — the Cloudflare API normalizes `@` to the FQDN on storage, causing perpetual Terraform drift. Use the FQDN (e.g., `"soleur.ai"`) instead.
41
+ - When a plan prescribes a fix based on exit code semantics of shell commands, include a verification step: "Test each command's actual exit code in the target environment before implementing." Plans that assume exit codes without verification (e.g., assuming `git diff --cached --quiet` returns 128 in bare repos when it actually returns 1) lead to implementation pivots during GREEN phase.
42
+ - When a plan prescribes a guard, observability hook, or Sentry mirror at an SDK callback site (`canUseTool`, OpenAI function-calling hooks, LangChain agent-executor hooks, Vercel AI SDK callbacks, MCP server registration hooks), read the SDK's `.d.ts` types and trace the callback's invocation conditions BEFORE designating the site. Documentation describes the happy path; only the type file reveals when the callback is NEVER invoked. The internal-codebase entry-point trace (`2026-05-05-trace-callgraph-from-entrypoint-when-placing-guards.md`) covers "where does the value come from"; this rule covers the OTHER direction — "when does the SDK actually fire this callback?" **Why:** #2909 — plan v1 wired a Sentry mirror for "unregistered MCP tool attempted" to `permission-callback.ts createCanUseTool`, but the Claude Agent SDK rejects unknown `mcp__server__tool` calls at model-validation time BEFORE canUseTool fires. Kieran caught at plan-review (P0); fix flipped the site to the SDK message iterator in `dispatchSoleurGo`. See `knowledge-base/project/learnings/2026-05-13-claude-agent-sdk-canusetool-not-invoked-for-unknown-mcp-tools.md`.
43
+ - When a plan asserts that middleware (or any global gate) "enforces X from ANY route" (T&C consent, auth, paywall), verify the claim against the gate's public-paths / exempt-paths early-return BEFORE relying on it. A consent/auth gate that lives AFTER a `PUBLIC_PATHS` short-circuit does NOT protect public routes. Concretely: if the plan newly routes an authenticated-but-not-fully-onboarded user TO a public route (e.g. `/invite/<token>`), the relied-upon gate does not fire there, and any page/route action reachable from it (accepting an invite) can bypass the gate. **Why:** PR #4638 — deepen-plan asserted `middleware.ts` redirects unaccepted-T&C users from "ANY route", but `/invite` is in `PUBLIC_PATHS` (`lib/routes.ts`) so middleware returns early at L129 and the T&C gate at L293-326 never runs; multi-agent review caught the would-be T&C bypass. Server-gated routes like `/callback` are safe (they enforce gates themselves); client `router.push` to a public route is not. See `knowledge-base/project/learnings/2026-05-29-middleware-tc-gate-does-not-fire-for-public-paths.md`.
44
+ - When a plan prescribes dependency upgrades within a major version range, specify the npm version tag explicitly (e.g., `npm install next@15`, not `npm install next@latest`). The `@latest` tag resolves globally and may cross major version boundaries.
45
+ - When a plan references specific dependency version ranges or peer constraints, verify them via `npm view <pkg> peerDependencies` before prescribing a fix approach. Plans have prescribed wrong version ranges that were only caught during implementation.
46
+ - When a plan adds a new required check to CI/branch protection rulesets, the plan MUST include an audit step that greps for ALL workflows creating PRs via `GITHUB_TOKEN` or `create-pull-request` action and lists each one requiring synthetic check updates. Plans that claim "only N workflows need updating" without showing the grep output are incomplete.
47
+ - When a plan adds a **no-default Terraform variable** to an auto-applied infra root (`apps/web-platform/infra/` — `apply-web-platform-infra.yml` fires on any `infra/*.tf` merge), the variable's `TF_VAR_*` MUST already exist in Doppler `prd_terraform` at merge time: Terraform resolves every root variable before `-target` pruning, so an unprovisioned no-default var fails the WHOLE merge-triggered apply (not just its own resource). Do NOT classify the operator mint as a "post-merge" step. If the mint is **verified** operator-gated (a Playwright attempt reached a named human gate per the work Phase 4 attempt-evidence rule — NOT merely assumed operator-gated because it is a console/dashboard action; a vendor dashboard mint under an authenticated session is presumptively Playwright-automatable) and cannot run in-session, the plan MUST split: code (no `*.tf` change → workflow does not fire) merges first; the IaC lands in a follow-up PR that merges after the mint + `TF_VAR_*` provisioning. If automation feasibility is unverified at plan time, mark the mint `automation-status: UNVERIFIED` and require soleur:work to attempt Playwright before any split/handoff — do not pre-assume the split. Keeping the no-default var (per `hr-tf-variable-no-operator-mint-default`) is correct — only the sequencing changes. **Why:** #5468 — plan put `RESEND_RECEIVING_API_KEY` IaC + mint post-merge; CTO ruled split (ADR-065), IaC → #5480. See `knowledge-base/project/learnings/workflow-patterns/2026-06-17-operator-mint-tf-var-must-sequence-before-auto-applied-iac.md`.
48
+ - When a plan prescribes Supabase/PostgREST query syntax (embedded resources, lateral joins, `.select()` with modifiers), include a verification note: "Confirm syntax against Supabase JS client docs before implementing." PostgREST embedded resource syntax is more limited than expected — chained `.limit().order().eq()` inside `select()` does not work.
49
+ - When prescribing `gh api` commands with array parameters, always use `--input -` with a heredoc JSON body instead of `--field`. The `--field` flag wraps values in quotes, turning JSON arrays into strings (HTTP 422). After any GitHub settings PATCH, immediately re-read settings to verify the change was applied — the repo API silently ignores some org-level features (returns 200 OK without state change). A second `-f`/`--field` trap: it also flips the request to POST — a `gh api` call that must stay GET (e.g. the actions `.../runs` list endpoints, which 404 under POST) must embed its query in the URL (`gh api "path?branch=main&status=success"`) or use `gh run list`. Measured 2026-09-29 (PR #9233).
50
+ - When generating test commands, always reference `package.json scripts.test` rather than assuming a runner (bun test, vitest, jest). Plans that hardcode a specific test runner can fail silently when the project uses a different framework. **Also check `bunfig.toml` for `[test] pathIgnorePatterns = ["**"]`** — this defense-in-depth pattern (e.g., `apps/web-platform/bunfig.toml` per #1469) blocks bun test discovery entirely; an AC that says `bun test <file>` will report "filter did not match" even when the test file exists. **Why:** PR #4173 AC4 hardcoded `bun test` against `apps/web-platform/test/...` while the package's actual runner is vitest; recovery required switching to `./node_modules/.bin/vitest run <path>`. See `knowledge-base/project/learnings/2026-05-20-github-app-installation-grant-vs-manifest-three-plane-drift.md` SE#3. **Test FILE PATHS must also satisfy the runner's discovery globs, not just the runner name:** `apps/web-platform/vitest.config.ts` only collects `test/**/*.test.ts` (node) and `test/**/*.test.tsx` (jsdom) — a co-located `components/**/*.test.tsx` is silently never run. Grep the `include:` globs before prescribing a test path. **Why:** #4634 — plan prescribed a co-located component test vitest would have skipped; moved to `test/components/settings/`. See `knowledge-base/project/learnings/2026-05-29-cancel-pending-invite-token-gate-consumer-enumeration.md` SE#2.
51
+ - **Typecheck commands for `apps/web-platform` MUST be `cd apps/web-platform && ./node_modules/.bin/tsc --noEmit`, NOT `npm run -w apps/web-platform typecheck`.** The repo-root `package.json` declares no `workspaces` field, so any `npm run -w <pkg>` form aborts with `npm ERR! No workspaces found`. Same blind spot as the test-runner bullet above: the package has a `typecheck` script, but the `-w` invocation never reaches it. **Why:** PR #5006 AC6 + tasks.md Phase 3 both prescribed the `-w` form; recovery was the in-package `tsc` call. See `knowledge-base/project/learnings/2026-05-13-npm-workspaces-flag-fails-without-root-workspaces-declaration.md` and `knowledge-base/project/learnings/bug-fixes/2026-06-08-handled-error-sentry-event-from-fail-closed-mirror-is-severity-not-crash.md` SE#1.
52
+ - When a plan body cites a knowledge-base/learnings file path, Glob-verify the path at plan-write time — file-path mistakes propagate (the planning subagent for PR #4173 wrote `learnings/best-practices/2026-05-15-operator-only-step-canonical-list.md` in 3 places; the file is at `learnings/2026-05-15-...` with no `best-practices/` segment, and the soleur:engineering:review:architecture-strategist review-agent caught all 3 instances as P0 broken citations). Cheapest gate: at the end of plan generation, extract every `knowledge-base/` reference via `grep -oE 'knowledge-base/[A-Za-z0-9/_.-]+\.md' <plan-file>` and pipe through `xargs -I{} bash -c '[[ -f "{}" ]] || echo "BROKEN: {}"'`. See `knowledge-base/project/learnings/2026-05-20-github-app-installation-grant-vs-manifest-three-plane-drift.md` SE#6.
53
+ - When a plan asserts a **file-format extension** (`.jsonl` vs `.json`, `.yml` vs `.yaml`, `.txt` vs `.log`) OR makes a **"reuse the existing X"** claim about a cryptographic primitive (salt, signing key, nonce, secret), grep the PRODUCER CODE at plan-write time — do not infer from convention or sibling shape. Two greps catch the dominant modes: (a) `archive\.append\|fs\.writeFile\|writeStream` in the producer file to surface the actual `name:` / path literal (e.g., `archive.append(buf, { name: "tables/${t.table}.json" })` ≠ `messages.jsonl`); (b) `randomBytes\|randomUUID\|crypto\.subtle\.generateKey\|salt\s*=` in the named file to verify the cited symbol is a random secret, NOT a content-derived hash (e.g., `bundleHash = createHash("sha256").update(stream)` is content-derived per-bundle and lifecycle-wrong as a pseudonym salt). Both classes pass plan-review agents that read only plan text — DHH/Kieran/code-simplicity cannot tell `.jsonl` from `.json` without code-side grep, and they cannot tell content-hash from random salt without reading the producer. Plan-author self-review is also blind here (the assumption WAS the bug). **Why:** PR #4319 plan v1 — `.jsonl` extension propagated through manifest path field + AC1 fixture assertions + disclosure copy when `dsar-export.ts:1174` actually writes `tables/${t.table}.json`; same plan v1 said "reuse the bundle-hashing salt" when `bundleHash` at `:1270-1281` is a content-derived SHA-256 not a random salt. Both caught at 5-agent review (Kieran P0-1, GDPR-gate Crit-5). See `knowledge-base/project/learnings/2026-05-22-plan-paraphrase-without-verification-file-ext-and-salt-source-class.md`.
54
+ - **Generalisation: every named repo artifact in a plan MUST be verified against current repo state, not memory or sibling-table pattern-match.** Four classes the existing Sharp Edges don't yet cover (all surfaced by PR-I #4213 review): (a) **workflow files** — plan referenced `apply-web-platform-migrations.yml` which doesn't exist; the actual mechanism is `web-platform-release.yml#migrate`. Gate: `ls .github/workflows/<file>.yml` before naming a workflow. (b) **register entry numbers** — plan said "append PA-16" to `knowledge-base/legal/article-30-register.md`, but PA-16 was already taken by `action_sends` (PR-H); collision resolved by using PA-18 with documented divergence. Gate: `grep "^## Processing Activity" knowledge-base/legal/article-30-register.md | tail` before naming a new PA / row / section ID. (c) **specific file paths** — plan said edit `apps/web-platform/server/dsar-export.ts` to add to the allowlist, but the allowlist lives in `dsar-export-allowlist.ts` (a sibling file). Gate: `git ls-files | grep <basename>` or `ls <prescribed-path>` before stating an edit target. (d) **API surface claims** — plan §Risks asserted "Single Supabase transaction wraps both writes" as a mitigation, but supabase-js does not expose client-side transactions across separate `.rpc()` calls. Gate: when a mitigation prose claim depends on an API capability, the plan must cite the doc URL OR include a 1-line verification command (analogous to the existing "third-party vendor default behavior" Sharp Edge at line 772, but extended to client-SDK API surface). **Why:** PR #4213 multi-agent review surfaced all 4 drifts — soleur:engineering:review:data-migration-expert F2 (workflow file), soleur:work fix-up (PA collision + file path), soleur:engineering:review:user-impact-reviewer FINDING 2 (transaction claim). See `knowledge-base/project/learnings/2026-05-21-worm-ledger-rls-owner-insert-policy-is-an-rpc-bypass.md` §Session Errors.
55
+ - Two verification cases the named-artifact gate above does NOT cover, both surfaced on #5005 (PR feat-one-shot-5005) only once the full battery ran — which since #7352 is the `soleur:ship` Phase 4 full-battery checkpoint, not the `soleur:work` Phase 2 exit, so a plan that leaves these to "the exit gate" now defers them a whole review cycle later: (a) **"this route/component test is NEW."** Before a plan declares a test absent, re-grep EVERY test dir for a suite importing the route under test — `git grep -l "app/api/<route>/route\|<Component>" -- 'apps/web-platform/test/**'` — including nested `test/server/`, `test/helpers/`. A canonical route test living one dir deeper than the plan's glob reads as "absent" and you author a duplicate (a pre-existing `test/server/kb-sync-route.test.ts` broke on the change while a NEW `test/kb-sync-route.test.ts` was being written). (b) **Removing a read/emit site can dark a monitor that FILTERS on it.** Before a plan deletes a `reportSilentFallback`/`mirrorP0Deduped` op slug, a tenant-mint, or a log line, grep what consumes it: `git grep -rn "<op-slug>" -- 'apps/web-platform/infra/sentry/' '*op-contract*'`. Removing the last emit site of an `op IS_IN`/`filter_match="all"` alert darks the alert — a silent observability regression a green typecheck never shows. See `knowledge-base/project/learnings/workflow-patterns/2026-06-08-plan-route-test-new-claim-and-emit-site-removal-coupling.md`.
56
+ - When a plan asserts a field/scope/symbol is "provably unused (grep returned zero) → safe to remove," the grep MUST cover string-INTERPOLATED reference forms, not just the literal token. REST paths built with template literals (`/orgs/${org}/members/${user}`), dynamic property access (`obj[key]`), and concatenated SQL all defeat a literal-substring grep and produce a FALSE zero. Grep the stable anchor + interpolation boundary (`}/members`, `members/${`, the verb family `/members`/`/memberships`/`/teams`), or find the function that builds the reference, before claiming removal is safe. Removal is destructive; one extra grep is trivial against a single-user-incident regression. **Why:** #4189 plan claimed GitHub App `members:read` was unused (grep for `/orgs/.../members` returned zero) and proposed dropping it; `server/github-app.ts` actually calls `/orgs/${account.login}/members/${expectedLogin}` to gate org repo-connect — dropping it would 502 every org user. Caught at multi-agent review by soleur:engineering:review:security-sentinel + soleur:engineering:review:user-impact-reviewer. See `knowledge-base/project/learnings/2026-05-28-grep-proves-unused-must-cover-interpolated-api-paths.md`.
57
+ - When a plan scopes out latency or blast-radius by asserting a shared function is "not on a hot/user-interactive path" (cron-only, background-only, async-only), grep the call graph before asserting it: `git grep -n "<fnName>" apps/web-platform/app/api apps/web-platform/server` and enumerate every synchronous HTTP-route and agent-tool caller. A shared auth/token/IO helper is almost always reached from interactive routes too. **Why:** Sentry #122537945 — the plan claimed `generateInstallationToken` was "not on a hot user-interactive path (cron/reconcile)" to justify a widened retry backoff; it is actually called synchronously from `POST /api/kb/upload`, `kb/sync`, and the agent `pushBranch` tool. `soleur:engineering:review:user-impact-reviewer` caught the false premise at PR review. See `knowledge-base/project/learnings/bug-fixes/2026-05-28-github-app-jwt-exp-at-600s-ceiling-causes-intermittent-401.md`.
58
+ - When a plan for a DB-write bug concludes "the migration source is internally consistent end-to-end" (and pins the cause on drift/grants), treat it as a hypothesis to falsify, not a fact: (a) for every table an RPC writes, read every `BEFORE INSERT/UPDATE` trigger on it and intersect the trigger's immutability/guard arms against the RPC's `SET`/column list — a column the trigger freezes that the RPC reassigns is a guaranteed runtime failure no per-function read or `tsc` catches; (b) reproduce against the live DB read-only (schema/grant introspection) AND via `BEGIN; SELECT rpc(...); ROLLBACK;` on the real failing row to capture the actual SQLSTATE before prescribing a remediation. **Why:** PR #4745 — plan pinned a 100%-broken invite-accept on prod drift (085-not-applied / lost GRANT); the real cause was a self-contradiction in migration 075 (create sets `attestation_id`, the `no_mutate` BEFORE-UPDATE trigger freezes it, accept's UPDATE re-pointed it → `P0001` on every accept). Found only by rolled-back prod repro. Corollary: when re-issuing a `CREATE OR REPLACE` body, diff against EVERY prior definition — 085 had silently dropped 076's `not_intended_invitee` identity check. See `knowledge-base/project/learnings/2026-06-01-write-path-internally-consistent-claim-misses-trigger-vs-rpc-contradiction.md`.
59
+ - Before a plan's Test Strategy names a specific framework (bats, pytest, rspec, vitest, etc.), verify the framework is actually installed: `command -v <tool>` AND grep existing test files for the pattern (`ls plugins/*/test/`, `find . -name '*.bats' -o -name '*_test.*'`). If absent, default to the existing convention. Never prescribe a new test framework without an explicit "Add <framework> dependency" task AND reconciling with any "no new dependencies" claim in the plan Overview. **Why:** In #2212, the plan prescribed `bats` while also saying "no new dependencies" — bats was not installed; the implementer adapted to `.test.sh` convention at work-skill time, paying attention cost that a 2-line check in the plan would have avoided.
60
+ - When a plan prescribes a specific CLI invocation form (stdin/stdout pipes via `-`, particular long options, flag combinations that vary by tool version), the preflight task MUST exercise the exact form with realistic input — not just `--version` or `--help`. Installability ≠ usability: `--version` proves the tool exists; it proves nothing about whether the flags your helper depends on are recognized by the installed version. **Why:** In the #2456 PDF linearization plan, the initial preflight only ran `qpdf --version`. A reviewer challenged whether `qpdf --linearize - -` actually supports stdin/stdout pipes. Expanding preflight to pipe a real fixture PDF through the exact form (and `qpdf --check` the output) caught the ambiguity before implementation — a failure would have collapsed the helper design mid-build. See `knowledge-base/project/learnings/best-practices/2026-04-17-plan-preflight-cli-form-verification.md`.
61
+ - When a plan addresses alignment of a toggleable UI control (collapse/expand, accordion, drawer, tab visibility), verify alignment in **both toggle states** before writing the plan — not just the state named in the bug report. The two states often render different DOM subtrees with different parent geometry; a fix for one state can leave the other misaligned. Fold both states into the same PR, or document why only one state needs the fix. **Why:** PR #2494 fixed the collapsed-state settings-nav chevron but left the expanded-state chevron misaligned, requiring a follow-up PR #2504. The gap existed because the bug report mentioned only one state. See `knowledge-base/project/learnings/2026-04-17-alignment-fixes-must-verify-both-toggle-states.md`.
62
+ - When a plan's Non-Goals or Risks section makes a claim about third-party vendor default behavior (Cloudflare cache eligibility, Supabase connection limits, Next.js body-size defaults, Stripe webhook retry semantics, AWS S3 consistency model), the claim MUST cite the specific doc URL and include a verification step — or drop the claim entirely. Asserting "the default handles this" without a citation is a plan-quality failure that downstream review agents have to catch. **Why:** In PR #2532, the plan asserted Cloudflare would cache `public, max-age=…` responses on `/api/shared/*` by default; the architecture reviewer proved otherwise (CF bypasses dynamic paths regardless of Cache-Control), forcing a Terraform `cloudflare_ruleset` to be added inline during review-fix. See `knowledge-base/project/learnings/2026-04-18-cloudflare-default-bypasses-dynamic-paths.md`.
63
+ - When a plan prescribes targeting a third-party library's internal DOM via scoped CSS (hide a vendor logo, restyle a vendored widget), the selector MUST be verified against the JS that EMITS the element (`node_modules/<pkg>/dist/**`), not against the class's presence in the compiled `styles.css`. A class existing in a stylesheet only proves a rule exists — the library ships rules for every component, and the target component may wear a different class (or none). Grep the JSX-emitting `dist/*.js` for the `className`/`data-*`/tag and prefer an intrinsic unique attribute (a logo's `viewBox`, a fixed `role`) over a recipe-slot class. A source-grep test asserting "our CSS contains selector X" is vacuous here (it passes when X matches nothing) — pair it with a guard reading the installed component so a library bump fails CI. **Why:** PR #4938 — the plan "verified" `.likec4-navigation-panel__logo` against `@likec4/diagram/styles.css`, but that BEM class belongs to a non-diagram component; the diagram's `LogoButton` renders a class-less Mantine `<button>`, so the hide matched nothing (caught by soleur:engineering:review:architecture-strategist at review). See `knowledge-base/project/learnings/2026-06-04-vendored-library-css-hook-must-be-verified-against-rendered-dom-not-stylesheet.md`.
64
+ - When a plan bumps `--max-turns` on a `claude-code-action` workflow, it MUST also bump `timeout-minutes` to keep the ratio aligned with peer workflows (median 0.75 min/turn, 0.60 acceptable for data-only tasks). A raised turn budget with an unchanged timeout is a silent failure mode — the agent hits the wall clock before exhausting the turns it was granted. See `knowledge-base/project/learnings/2026-03-20-claude-code-action-max-turns-budget.md` for the peer ratio table. **Why:** In PR #2536, the initial plan raised bug-fixer max-turns 35 → 55 but kept `timeout-minutes: 30` (0.55 ratio, below the median). The review agent caught it; the follow-up commit bumped timeout to 45 (0.82 ratio).
65
+ - When a plan adds a regex over user-controlled input (PII scrubbers, log sanitizers, URL validators, CSV parsers), the Risks section MUST state the **maximum input size reachable by the regex engine** — not just a smoke-test number. If upstream callers can send unbounded input (e.g., Next.js's 1MB default body size, no per-field length cap in a JSON validator), the plan must specify a pre-regex `.slice()` bound and justify it. Smoke-testing a 2000-char string proves nothing about a 1MB pathological input. For UUIDs/IDs, match the **structural shape** (8-4-4-4-12 hex), not a specific version — version-restricted regexes (e.g., v4-only) leak stronger-PII variants (v1 MAC+timestamp) when a caller uses a different generator. Avoid `/g` regex + `.test()` gates; prefer `const next = s.replace(RE, ...); if (next !== s) { fired.push(name); s = next; }` — the `.test()` pattern relies on `.replace()` resetting `lastIndex` and silently leaks on a future edit that removes the `.replace()`. See `knowledge-base/project/learnings/security-issues/2026-04-17-pii-regex-scrubber-three-invariants.md`.
66
+ - **When a plan prescribes "operator mints credentials in vendor X's dashboard," the plan author MUST verify the runtime mode actually USES vendor-issued credentials.** Self-hosted SDK integrations (per ADR-030-style decisions) typically accept operator-chosen random strings (signing keys, event keys, API tokens) — there is no dashboard issuance flow at all. Cheapest gate: read the SDK's actual integration code (`apps/<app>/server/<vendor>/client.ts` or equivalent) and check whether the keys are validated for a specific format (vendor-issued) or just signed/hashed (operator-chosen random). If the latter, the canonical mint pattern is `random_id` resources in Terraform — drop the operator-mint variable, drop the `[ack]` line, and document the random-id derivation in the plan's IaC section. **Why:** PR #3973 (#3960) — plan v2 prescribed Inngest signing/event keys as "operator-minted in Inngest dashboard"; the runtime is self-hosted per ADR-030 (rejects Inngest Cloud), so no dashboard issuance flow exists. Caught at soleur:work Phase 0 by reading `apps/web-platform/server/inngest/client.ts` comments. Plan deviation #1 in PR #3973 replaced 4 operator mints with 4 `random_id` resources. See [`2026-05-18-vendor-token-mint-and-oci-image-content-carrier-patterns.md`](../../../../knowledge-base/project/learnings/2026-05-18-vendor-token-mint-and-oci-image-content-carrier-patterns.md).
67
+ - Do not prescribe exact learning filenames with dates in `tasks.md`. Dates drift across session boundaries. Prescribe directory + topic only (e.g., `knowledge-base/project/learnings/bug-fixes/<topic>.md`) and let the author pick the date at write-time. **Why:** PR #2226 prescribed a `2026-04-14-...` filename but the file was created on the 15th, forcing a tasks.md fix-up.
68
+ - When a plan prescribes a calibration baseline fixture for a regex / pattern-based rule set ("scanner returns ≥ 1 finding on this file"), grep the candidate fixture against the actual rule patterns BEFORE naming it. Mental claims of the form "this file contains the kind of pattern X looks for" are hypotheses the regex can falsify in one shell call. Cheapest gate: `for pat in $(awk -F'|' '/^\| [A-Z]/ {gsub(/\`/, "", $7); print $7}' <rules-file>); do grep -lE "$pat" <candidate-fixture>; done` — zero hits means the fixture is the wrong baseline (its pattern shape is exactly the case the rule set is designed to NOT flag, e.g., inline-style brand gradient vs. Tailwind-class gradient). **Why:** PR #4265 plan §Phase 0.5 named `apps/web-platform/components/ui/gold-button.tsx` as the frontend-anti-slop calibration fixture; the file uses `style={{ background: GOLD_GRADIENT }}` (inline-style designer-mandated brand gradient) which no Tier 1 rule catches. Caught at soleur:work Phase 0 step 5; fixture swapped to `setting-up-state.tsx` (`transition-all` → `TRANSITION-ALL`). See [`2026-05-21-calibration-fixture-probe-and-markdown-table-pipe-escapes.md`](../../../../knowledge-base/project/learnings/best-practices/2026-05-21-calibration-fixture-probe-and-markdown-table-pipe-escapes.md). <!-- markdownlint-disable-line MD038 -->
69
+ - When a PR has post-merge operator actions (terraform apply, manual verification, external service setup), split `## Acceptance Criteria` into `### Pre-merge (PR)` and `### Post-merge (operator)` subsections. Flat lists make reviewer check-offs ambiguous. **Why:** PR #2226 P1 review finding.
70
+ - When a plan persists a record **into the PR body** (a decision log, a challenge block, a summary), it MUST target `ship` — `ship` is the sole PR-body author and **full-replaces** the body from diff analysis (`ship:1196,1242`); `work`/`plan` have no `gh pr edit/create --body` (only `gh issue create`), so a block they write is authored nowhere durable or clobbered by `ship`. Route earlier producers through a durable artifact (e.g. `specs/<branch>/*.md`) that `ship` reads and folds in — mirroring ADR-083's `plan`+`ship` edit sites. Separately, a PR-body block is **not** an operator-visible surface: `operator-digest` harvests merged-PR titles/labels + `action-required`-labelled *issues* only, never PR bodies — for a non-technical operator, emit an `action-required` issue. And any block rendered into the body must avoid the `ship-operator-step-gate.sh` deny tokens (`Operator`/`Post-merge`/`Follow-up`) + operator-action bullets. **Why:** #5984 plan v1 wired a headless decision record into `work`; architecture + CPO + spec-flow plan-review caught that `work` can't author the body and the operator can't see it. See `knowledge-base/project/learnings/workflow-patterns/2026-07-04-plan-persisting-a-record-into-the-pr-body-must-target-ship-not-work.md`.
71
+ - When a plan extends a hand-maintained allow-list (terraform `-target=` set, CSRF route set, RLS table list, an enum gate), the `## Files to Edit` work-list MUST be derived from `git grep`, not from the named filter alone. A `-target=` allow-list is typically asserted on by THREE artifacts — the jq/filter, its counter test, AND a separate *scope guard* that mechanically FAILs when the list contains an un-covered type — and the scope guard is an orphan suite (different name stem) that no touched-file set reaches, so it is the one the plan reliably misses. Since #7352 the `soleur:work` Phase 2 exit runs only the shards the diff touches, so an orphan suite outside those shards is not caught until the `soleur:ship` Phase 4 full-battery checkpoint — which makes deriving the work-list at plan time the cheap gate rather than a nicety. Gate at plan-write time: `git grep -ln '<resource-type-prefix>\|-target=' tests/ scripts/` and add every hit to Files to Edit. Also verify any AC that prescribes an API-GET probe actually returns the target resource type (e.g. Sentry's `/organizations/{org}/monitors/` is Crons-only and excludes `sentry_uptime_monitor`). **Why:** PR #4591 (#4585) extended the sentry `-target=` list to uptime monitors; the plan synced the jq filter + counter test (AC5/AC6) but missed `test-destroy-guard-sentry-scope-guard.sh`, caught only by the work Phase 2 `test-all.sh` exit gate, and AC11 prescribed the Crons endpoint that excludes uptime monitors. See `knowledge-base/project/learnings/2026-05-29-target-allowlist-extension-must-sweep-all-guard-suites.md`.
72
+ - When a plan adds a resource with `for_each = var.<map>` (or references `<resource>[each.key]`) into a `-target`-scoped auto-apply, check whether the map contains entries whose **backing resource is EXCLUDED from the `-target=` allow-list** (deferred / maintenance-window-only provisioning). `-target` is **transitive on dependencies** — a new targeted resource that references a `-target`-excluded sibling silently drags it into the plan and can **CREATE** it on a routine merge-apply (a pure create the destroy-guard does not catch). Gate the new resource's `for_each` on the **same existence predicate** the sibling uses (e.g. `{ for k, v in var.web_hosts : k => v if v.monitored }`) and add an AC asserting `terraform plan` shows **no create** of the excluded resource. **Why:** #5933 plan v1 added a per-host `cloudflare_record for_each = var.web_hosts` referencing `hcloud_server.web["web-2"]` (excluded from `apply-web-platform-infra.yml`'s `-target` set per #5887) → a routine apply would have provisioned web-2 outside the operator maintenance window. Caught by spec-flow + architecture plan-review. See `knowledge-base/project/learnings/2026-07-03-for-each-over-target-excluded-map-forces-premature-provisioning.md`.
73
+ - When an AC claims a CI gate is green, it MUST run that gate's OWN invocation — never a hand-enumerated reconstruction of its input set. A gate that DERIVES its inputs (`--changed`, `--staged`, `git diff`, a glob) and an AC that RESTATES them as explicit paths are pinned to different sets, so the AC stays green while the gate goes red, and the count assertion that guards against a typo'd path cannot detect a MISSING one — it proves the list was fully scanned, not that the list is the right list. Ask: *does my command's scope equal the scope of the gate whose result I am claiming?* Input-side twin of `cq-assert-anchor-not-bare-token`. **Why:** #7003 — AC11 ran `lint-infra-no-human-steps.py <4 explicit paths>` and recorded `OK … 4 scanned file(s)`; CI runs `--changed --base origin/main` over the 5 changed docs and the 5th was red (the line documented an earlier violation by reproducing its trigger tokens). See `knowledge-base/project/learnings/2026-07-28-my-ac-verified-four-paths-while-ci-verified-five.md`.
74
+ - When an Acceptance Criterion prescribes `awk '/A/,/B/' <file>` to count occurrences inside a named block, verify `A` and `B` cannot both match the same line. Awk's `start,end` address range opens on the start match AND closes on the same line if the end pattern is satisfied by the start line — e.g., `awk '/^ job-name:/,/^ [a-z][a-z-]*:/'` closes after one line because the start line itself satisfies the end character class. Prefer the flag-based form `awk '/A/{flag=1} flag{print} /B/&&!/A/&&flag{exit}'` whose end pattern explicitly excludes the start line. **Why:** PR #4337 AC2 returned 0 from `awk '/^ lockfile-sync:/,/^ [a-z][a-z-]*:/' .github/workflows/ci.yml | grep -c 'npm install -g npm@11'` even though the pin step was correctly added; verification command had to be rewritten at soleur:work AC-check time. See `knowledge-base/project/learnings/2026-05-22-npm-version-pin-required-for-lockfile-sync-gate.md` §Session Errors.
75
+ - When a plan both extracts a shared helper AND adds a request-shaping field (`output_config`, a header, an option key) to the call sites, do NOT write a per-caller AC that counts the field's literal across callers (`git grep "<field>" <scope> | wc -l ≥ N`). The helper centralizes the literal, so the count becomes `1 (helper) + inline-sites`, not `N_total` — the AC reads under-count on a correct implementation. Verify the field via the helper's passthrough unit test (assert the serialized request body carries it) and reserve literal-greps for the genuinely-inline site. Same family as the awk-AC papercut above, but the literal legitimately *moves* into the helper rather than being stale. **Why:** PR #5186 AC7 (`git grep output_config … ≥ 3`) read 2 because two of three sites route through `postAnthropicMessage` via the `outputConfig` arg. See `knowledge-base/project/learnings/best-practices/2026-06-11-helper-indirection-breaks-per-caller-literal-grep-acs.md`.
76
+ - For a **transitive-only or Dependabot lockfile bump**, prescribe `npx --yes npm@11 update <pkg>` against `package-lock.json` — the single lockfile of record since ADR-191. Never prescribe recreating `bun.lock`. (Historical, retained because the trap it names still applies to any bun-based tenant repo: in a directory carrying dual lockfiles, do NOT prescribe `bun update <pkg>` or bare `bun update` — it elevates the target to a **direct** `package.json` dep and can overshoot to a MAJOR (verified: `bun update undici js-yaml` wrote `"undici": "^8.7.0"` / `"js-yaml": "^5.2.1"` while the intended patched versions were `7.28.0` / `3.15.0`). The plan's `bun.lock` phase MUST reference the surgical version+sha edit in [`work-lockfile-bumps.md`](../work/references/work-lockfile-bumps.md) (npm side stays `npx --yes npm@11 update <pkg>`). Same class as `hr-when-a-plan-specifies-relative-paths-e-g` — the plan owns intent, never the literal `bun` command. **Why:** the 8-alert undici/js-yaml Dependabot PR (2026-07-18) — plan Phase 2 prescribed `bun update`, caught and reverted at soleur:work. See `knowledge-base/project/learnings/workflow-patterns/2026-07-18-plan-prescribed-bun-update-for-transitive-bump-is-banned.md`.)
77
+ - **Before authoring any `### Post-merge (operator)` step, run the automation-feasibility gate.** For each candidate step, check whether a loaded MCP server or CLI can execute it:
78
+ - Supabase migration apply / `cron.job` verify / bucket-exists check / RLS spot-check → `mcp__plugin_supabase_supabase__*`
79
+ - `gh pr ready` / `gh pr merge --squash --auto` / `gh issue close` / `gh workflow run` → `gh` CLI via Bash
80
+ - End-to-end UI flow → Playwright MCP (`mcp__plugin_soleur_playwright__*` — dedicated persistent profile; a flow needing stored auth that lives in a different registration's profile uses that registration)
81
+ - Cloudflare DNS / WAF / Workers / Zero Trust → `mcp__plugin_soleur_cloudflare__*`
82
+ - Stripe live-state read / customer / subscription → `mcp__plugin_soleur_stripe__*`
83
+ - Server-side state (deploy status, service health, cron-fire timestamps) → deploy webhook at `deploy.soleur.ai/hooks/deploy-status` (HMAC + CF Access auth via Doppler `prd_terraform`) — read-only; the Inngest function-registry count is NOT in this payload, so function-desync is remediated via the container-restart path below, not diagnosed here
84
+ - Container restart / function sync → the `web-platform-release.yml` pipeline already restarts the Docker container on every merge to main that touches `apps/web-platform/**` (path-filtered `on.push`); for those changes a PR merge IS the remediation for container-state issues — never prescribe a separate "operator restarts container" step
85
+
86
+ If the step is automatable, the plan MUST bake it into the workflow rather than punt to the operator. Three valid placements:
87
+ 1. **Inline in a soleur:work phase** (typical for migration apply, integration smoke, single-call MCP/CLI verifications).
88
+ 2. **In `soleur:ship` post-merge verification** (already handles migration verify + `gh pr ready` + auto-merge + `gh workflow run` for modified workflows — see ship/SKILL.md:1027 + 1177).
89
+ 3. **In a GitHub Actions workflow** that fires on push to `main` (the `apply-deploy-pipeline-fix.yml` pattern documented in ship/SKILL.md:508 — auto-applies on merge).
90
+
91
+ Genuinely operator-only steps (CAPTCHA-gated portal config, interactive OAuth consent on a third-party site, subjective decisions requiring human judgment — design taste, strategy, prioritization) MAY remain in `### Post-merge (operator)` with a one-line `Automation: not feasible because <X>` justification. **For any browser/console/dashboard/portal step (vendor key mint, settings toggle, plan upgrade), that `Automation: not feasible because <X>` line is non-compliant unless `<X>` is backed by a `playwright-attempt:` evidence line (`navigated <URL>; reached <named human gate>` — one of CAPTCHA/OTP/TOTP/passkey/push-MFA/payment-card/hardware-token) OR the step is explicitly marked `automation-status: UNVERIFIED — soleur:work MUST run a Playwright attempt before any operator handoff`.** An a-priori "no creation API / console-gated / MFA-gated / dashboard-only / vendor limit" assertion is NOT acceptable evidence at plan time — a vendor dashboard action runs under an authenticated session and is presumptively Playwright-automatable until a real attempt proves a human gate. Do NOT let an unverified plan/ADR claim propagate into soleur:work and soleur:ship as if it were fact (it is the recurring defect this gate exists to stop). **Why:** #5480 — the plan + ADR-065 asserted the Resend receiving-key mint "genuinely operator-gated (no creation API — vendor limit)"; a later Playwright attempt reached the fully-authenticated Resend dashboard with a working "Create API key" form and NO human gate, proving the assertion itself was the defect. See `knowledge-base/project/learnings/workflow-patterns/2026-06-17-vendor-dashboard-mint-presumed-playwright-automatable.md`. **Interpretation of technical signal (recovery curves, IOPS budgets, error rates, latency percentiles) does NOT qualify as "human judgment"** — if the data is API-accessible, the plan must prescribe the query + deterministic verdict rule per `hr-no-dashboard-eyeball-pull-data-yourself`, not punt to operator dashboard-watching. Steps that are silent about automation feasibility default to operator-driven and are documentation debt — review-time will reject them. **Why:** every "please run this manually" is a context switch the founder is doing alone (ship/SKILL.md:1027). PR #1375 left migration verification as a "post-merge todo" instead of executing it; deployed code expected the new schema and broke. The plan author is the right place to catch this — by /work-time the agent is committed to the plan as written. Same class as the Playwright-first audit in work Phase 4: if a tool exists, use it.
92
+ - For `type: ops-remediation` / `classification: ops-only-prod-write` plans whose fix is executed post-merge (operator runs `terraform apply`, applies a migration, etc.), the Pre-merge acceptance criterion for issue links MUST prescribe `Ref #N` in the PR body, not `Closes #N`. `Closes` auto-closes at merge — before the remediation runs — producing a false-resolved state. The actual issue closure lives in a post-merge step (`gh issue close <N>` after the apply succeeds). Extends `wg-use-closes-n-in-pr-body-not-title-to` for the ops-remediation class. **Why:** PR #2880 — plan line 378 prescribed `Closes #2873 / #2874`, caught inline by multi-agent review.
93
+ - Before prescribing a rename of any `AGENTS.md` rule id (`[id: hr-*]`, `[id: wg-*]`, `[id: cq-*]`), grep the whole repo for the old id (`grep -rn '<old-id>' . --exclude-dir=.git`) and update every call site in the same commit. The `cq-rule-ids-are-immutable` rule covers only AGENTS.md itself — downstream references in `.claude/hooks/`, tests, docs, and `.github/workflows/` must be updated manually. **Why:** 2026-04-15 rename broke two test files because the rename was not grep-propagated.
94
+ - For any acceptance criterion that cites an external corpus (`gh issue list`, file globs, label queries, etc.), run the exact query before freezing the AC. If the corpus returns zero, either scope the AC out or file a deferral issue in the same commit — don't freeze an AC that depends on a corpus you haven't verified exists. **Why:** PR #2346 golden-set AC deferred via #2352.
95
+ - When a plan specifies a fixture seeding N entities, classify each entity as **DB-only** / **external service** / **hybrid** before freezing the spec. External-service entities (files in external repos, OAuth-gated resources, third-party APIs) often need separate seed strategies and may require deferral. **Why:** PR #2346 KB fixture lived in GitHub workspace, not Supabase — deferred via #2351.
96
+ - When a plan prescribes `flock -x N ( ... ) N>>"$file"` with a variable reassigned inside the subshell that a later outer command consumes, state explicitly where the assignment lives. Subshell reassignments do NOT propagate — the outer command sees the pre-subshell value. Hoist the assignment outside the `( ... )`, or complete consumption inside. **Why:** PR #2573 initial rotation block reassigned `archive=` inside the flock subshell; the outer `gzip -f "$archive"` targeted a non-existent path and T9 failed until the uniquify block moved out. See `knowledge-base/project/learnings/best-practices/2026-04-18-schema-version-must-be-asserted-at-consumer-boundary.md`.
97
+ - When a plan prescribes a `SCHEMA_VERSION` constant or any cross-process contract field, it MUST include a task for asserting the value at every consumer boundary — not just on the producer side. A field written but never read is cosmetic; a schema contract is the set of places it is asserted. **Why:** PR #2573 shipped SCHEMA_VERSION as a self-referential check in the aggregator; consumer-side gating was added inline during review when the architecture reviewer flagged the gap.
98
+ - When a plan introduces a shell wrapper (`with_lock`, `with_lease`, `flock -- <cmd>`, etc.) around a command intercepted by a PreToolUse hook, the plan MUST grep every hook for the command-detection regex (`grep -nE 'gh\s+pr\s+merge|<cmd>' .claude/hooks/*.sh`) AND propose a regex extension that catches the wrapped form. Hook regexes anchored to `^|&&|\|\||;` (start-of-line / chain operators) silently bypass the hook when the command appears after a `--` separator inside another command's argv. **Why:** PR #3689 wrapped `gh pr merge --auto` in `bash session-state.sh with_lock merge-main 600 -- gh pr merge ...`; the existing `pre-merge-rebase.sh` regex did not match the wrapped form, silently bypassing the review-evidence gate AND the origin/main auto-sync. Caught only at 11-agent post-implementation review. See `knowledge-base/project/learnings/2026-05-12-cross-session-lock-lease-bash-primitives.md` (SE1).
99
+ - When a plan prescribes a runtime shape — third-party library method name (Playwright `routeRef.sendToClient`, supabase-js `.maybeSingle()`, Sentry `addBreadcrumb`), a Playwright/RTL nested-DOM assertion (`locator('[outer] [inner]').toHaveCount(N)` where the inner selector lives behind a conditional render), or a test-runner project assignment (`testMatch` glob for a new file under non-default naming) — Phase 0 preconditions MUST include the grep that verifies the shape against the installed code, not the plan author's recollection. Concretely: (a) for library methods, `grep -E "<method-name>" node_modules/<package>/types/types.d.ts`; (b) for nested-DOM assertions, `grep -n "<inner-selector>" <component>.tsx` and verify the JSX render predicate (`{expanded ? ... : null}`) is compatible with the test sequence; (c) for new test files, `grep -E "testMatch|testIgnore" <test-runner>.config.ts` and prove the proposed filename matches the project's glob. Plan-review (DHH/Kieran/Simplicity) operates on plan text — agents do not re-grep `node_modules/` or component source unless the plan cites a line. The Phase 0 grep IS the verifiable artifact. **Why:** PR #3743 PR-A — plan prescribed Playwright `sendToClient` (does not exist in 1.58, real API is `.send()`), prescribed a contradictory "collapsed + 3 children visible" assertion (children only render when expanded), and assumed `cc-soleur-go-*.e2e.ts` would land on the `authenticated` Playwright project (testMatch was a string limited to `start-fresh-*`). All three caught at soleur:work time and fixed inline; would have been free at plan time with three additional greps. See `knowledge-base/project/learnings/2026-05-14-plan-prescribed-runtime-shapes-must-be-grepped-against-installed-version.md`.
100
+ - When a plan says "extract a shared factory/helper for N files" or enumerates a file list scoped from an issue body, validate N at planning time by grepping the distinguishing pattern (`rg '<pattern>' test/ src/`) — never trust the issue's enumerated list. Issue authors typically scan one directory; the real pattern usually spans more. **Why:** PR #2574 plan scoped 7 sidebar test files; review's pattern-recognition agent found 3 more (`chat-page*`, `error-states`) requiring inline scope extension. See `knowledge-base/project/learnings/best-practices/2026-04-18-test-mock-factory-drift-guard-and-jsdom-layout-traps.md`.
101
+ - When a plan adds a Supabase DDL migration, `ls apps/web-platform/supabase/migrations/` and read the 2-3 most recent files before prescribing DDL constructs. Supabase's migration runner wraps each file in a transaction, so `CREATE INDEX CONCURRENTLY`, `VACUUM`, `ALTER SYSTEM`, and other non-transactional DDL will fail at deploy with SQLSTATE 25001 — sibling migrations typically document the constraint inline. Cite the specific sibling migration that demonstrates the pattern your plan adopts. **Why:** PR #2579 plan prescribed `CREATE INDEX CONCURRENTLY` verbatim from Postgres docs; migrations 025 and 027 had explicit comments rejecting CONCURRENTLY that the deepen-pass didn't read. See `knowledge-base/project/learnings/integration-issues/2026-04-18-supabase-migration-concurrently-forbidden.md`.
102
+ - When a plan prescribes testing a security invariant of an LLM-mediated tool (SDK-invoked sandbox, agent-routed API call, MCP server driven by natural-language input), the test harness MUST remove the LLM from the assertion path. Natural-language prompts (`query({ prompt: "Run this command..." })`) are non-deterministic — the model may introspect, reword, refuse, or emit as text. A green suite proves model compliance, not the security invariant. Prefer: direct tool-invocation entry, captured-argv `child_process.spawn`, or any path that short-circuits the model. **Why:** #1450 plan initially scaffolded `query()`-prompt-based tier-4 bwrap assertions; plan-review caught this before any test shipped. See `knowledge-base/project/learnings/best-practices/2026-04-19-llm-sdk-security-tests-need-deterministic-invocation.md`.
103
+ - When a plan adds a source-template drift-guard test (regex/grep over `.njk`, `.hbs`, `.html`, `.jsx`, etc.), the test's file list MUST be a directory walk over the source root — never a hardcoded file list taken from the issue body. Issue authors typically name 1-2 files; the bug class usually spans more. Prescribe `walkDir(resolve(REPO_ROOT, "<source-root>"))` plus a sanity assertion that the walk found ≥ N known templates. **Why:** #2609 plan scoped the drift-guard to `base.njk` + `blog-post.njk`; review found 9 other `.njk` files with the same bug pattern (`<script type="application/ld+json">` interpolations), widening required inline during review. See `knowledge-base/project/learnings/2026-04-19-jsonld-dump-filter-not-enough-needs-jsonLdSafe.md`.
104
+ - When a plan prescribes HTML-escape-aware fixes inside `<script>` blocks (JSON-LD, inline config, hydration blobs), `JSON.stringify` / Nunjucks `dump` / similar JSON-serializers are necessary but not sufficient. The plan's Risks section MUST enumerate three hazard classes: (1) JSON parse failure (raw `"` / control chars), (2) HTML tag breakout (`</script>` / `</SCRIPT>` closes the outer tag), (3) JS runtime string termination (U+2028 / U+2029 in legacy runtimes). Prescribe a dedicated filter that applies all three escapes (`</` → `<\/`, `\u2028`, `\u2029`) rather than raw `dump`/`stringify`. **Why:** #2609 initial plan chose `| dump | safe` which left `</script>` breakout live for any attacker-controlled frontmatter field; review forced a `jsonLdSafe` filter inline. See the same learning file.
105
+ - When a plan adds a new skill OR a new AGENTS.md rule, the Acceptance Criteria section MUST include the measured **current** budget headroom so the work phase knows how much room it has, not just the cap. For skills: run `bun test plugins/soleur/test/components.test.ts` at plan time, read `SKILL_DESCRIPTION_WORD_BUDGET` from that file, and note `current/<budget>` words; prescribe the new description ≤ `budget - current` words. Never carry a remembered cap into the plan — the constant has been raised several times, so a literal quoted from memory or from an older document is stale by default. For AGENTS.md rules: run `awk '/<rule-id>/ {print length($0)}' AGENTS.md` during drafting (not after), and verify the count with a grep of the new rule's byte length. **Why:** PR #2683 — initial skill description was 43 words over a 1799-word baseline (required trimming three sibling skills); initial AGENTS.md rule was 687 bytes over the 600-byte cap (required two trim iterations). See `knowledge-base/project/learnings/bug-fixes/2026-04-19-admin-ip-drift-misdiagnosed-as-fail2ban.md` session errors.
106
+ - When a plan adds a **new** AGENTS.md rule, measure the **always-loaded** headroom by running the authority — `python3 scripts/lint-agents-rule-budget.py AGENTS.md AGENTS.rules.md 2>&1` — against the 46000-byte critical cap, IN ADDITION to the per-rule 600-byte cap. Do NOT hand-roll `wc -c < AGENTS.md`: the thresholds are defined over **frontmatter-stripped** bytes, so a raw `wc -c` overstates `B_ALWAYS` by the frontmatter size and misreports headroom in exactly the near-cap regime where the number decides whether a rule can land. The `2>&1` is load-bearing — the WARN and REJECT verdicts print to stderr, so stdout alone is empty in the tier you are most likely to be in. `lint_union` ([lint-rule-ids.py](../../../../scripts/lint-rule-ids.py)) couples pointer↔body 1:1, so every rule body also costs an always-loaded `AGENTS.md` index pointer (~50-60 bytes). At-cap the rule **cannot land** without trimming prose elsewhere or migrating a domain-scoped rule to its enforcing skill (`cq-agents-md-tier-gate`, PR #8034 precedent); per ADR-151 there is no class to demote into, and retirement is an editorial call — `rules_unused_over_8w` counts enforcement events only and is not retirement evidence (#8030). If neither is justified, place the principle in the constitution / owning artifact only and drop the AGENTS pointer. **Why:** PR #5349 — main was at 22994 against the then-23000 cap; the planned `cq-minimalism-ladder-generation-bias` pointer tipped `B_ALWAYS` over and was descoped to constitution-only. See `knowledge-base/project/learnings/2026-06-15-agents-budget-at-cap-descopes-planned-rule-and-harvest-md-exclusion.md`.
107
+ - When a plan paraphrases an issue body's file-path or site-count claims, `ls`/`Read` every path and grep every symbol the body names BEFORE writing the plan — not during deepen-plan. Paraphrase-without-verification is the single most common plan-drift class (wrong MCP file path, stale duplicate-site count, inverted test assertion). Run `rg '^function <symbol>\b' <dir>` for every distinguishing symbol in the issue body and add a §Research Reconciliation table if any divergence is found. **Why:** PR #2817 — three separate stale claims in the plan (MCP file at `lib/mcp/` vs `server/`, 4 createQueryBuilder sites vs 3, inverted test assertion shape) all caught at deepen-plan and resolved inline; moving the grep gate into Phase 1 would have caught them at the cheapest point. See `knowledge-base/project/learnings/best-practices/2026-04-22-ts-sql-normalizer-parity-when-shipping-backfill-migration.md`.
108
+ - When a plan scopes "agent-native parity" or MCP tool parity with a UI feature, enumerate the UI hook's exported surface (`grep -E "^\s*(const|function|async function)" <hook-file>`) and map EVERY function to a tool or explicit deferral — don't trust the issue body's enumeration. Common miss: list/filter/archive/unarchive named; status-update or delete omitted. **Why:** PR #2817 plan scoped `conversations_list` + archive/unarchive but omitted `conversation_update_status` (the Command Center's most common action); soleur:engineering:review:agent-native-reviewer caught it at review and the tool had to be added inline. See same learning file.
109
+ - **This repo's GitHub releases are IMMUTABLE once published** (`gh api repos/jikig-ai/soleur/immutable-releases` → `enabled:true`). A plan that attaches a release asset must go through a draft: create `--draft`, then upload, then `gh release edit --draft=false`. `create` followed by `upload` hits HTTP 422 and strands the tag, and the tag cannot be reused. Precedent: `reusable-release.yml` "Create GitHub Release (as draft)". A stubbed `gh` cannot see this, so check the repo setting at plan time. **Why:** #8714 PR 2a — found only by the security review seat. See `knowledge-base/project/learnings/2026-09-28-this-repos-releases-are-immutable-and-my-stub-could-not-know.md`.
110
+ - When a plan prescribes pre-merge verification of a **new** CI workflow via `workflow_dispatch`, flag it as infeasible — GitHub requires the workflow file to exist on the **default branch** before `gh workflow run <file>.yml --ref <feature-branch>` can dispatch it (returns `HTTP 404: workflow not found on the default branch`). For pre-merge verification of a new workflow or composite action, choose one of: (1) wire the check as a job in an existing `pull_request`-triggered workflow so it runs on PR events against the feature branch, (2) extract the logic into a shell script / module that is locally testable with mocked inputs, or (3) explicitly defer verification to post-merge `gh workflow run <existing-workflow>.yml`. Never plan "add a temporary test workflow with `workflow_dispatch`, trigger from the feature branch, delete before merge" — step 2 is impossible and the plan's mock-code-path in production will become dead-code + insider-bypass surface when the unreachable test is removed. **Why:** PR #2717 — see `knowledge-base/project/learnings/integration-issues/2026-04-21-workflow-dispatch-requires-default-branch.md`.
111
+ - When a plan adopts a centralizing helper / boundary / redaction layer for a PII or security transform, every AC's verification command (grep, lint, regex gate) AND every disclosure-language claim (PA8, privacy policy, security overview) MUST scope to the SPECIFIC boundary the transform covers — not the abstract category. "No raw userId in `extra`/`tags` anywhere in `apps/*/server/`" over-matches once the helper accepts raw userId by design; "pseudonymized at the emit boundary" over-claims once only the helper-routed emit paths carry the transform. The two-clause form is the only form that survives the centralization choice: (i) helper-routed sites verified via grep over helper output, (ii) direct-bypass sites verified via grep restricted to sites that do NOT route through the helper. **Why:** PR #3685 (#3638 Sentry userId hash) plan AC2 stipulated a grep gate that returned ~40 helper-invocation matches at soleur:work verification time; PA8 §(c) overclaimed pseudonymization scope across ~27 pre-existing direct-log sites caught only by multi-agent cross-reconcile. See `knowledge-base/project/learnings/2026-05-12-centralized-at-helper-boundary-transforms-overclaim-in-acs-and-disclosures.md`.
112
+ - When a plan prescribes an **aggregate numeric target** in Acceptance Criteria (bytes saved, rules removed, perf delta, coverage %), the plan body MUST show the per-item contributions that sum to the target. If per-item estimates sum to a number that disagrees with the aggregate, the aggregate is wrong — fix it at plan time, don't leave the mismatch to be negotiated at work time via spec strikethrough+replacement. Plan-review agents (code-simplicity + soleur:engineering:review:architecture-strategist) do not check numeric self-consistency by default; the plan author owns this. **Why:** PR #2754 — plan prescribed "≥800 bytes saved" while its own per-rule byte-impact table projected only ~260 bytes; actual outcome was +21 bytes, forcing a spec FR4 relaxation mid-implementation. See `knowledge-base/project/learnings/2026-04-21-agents-md-rule-retirement-deprecation-pattern.md`.
113
+ - When a plan AC claims the state of an external-service config (Doppler values, Supabase rows, Cloudflare applied state, GitHub secret presence, Stripe product config), verify via the actual API at plan time — code-grep confirms consumers exist, NOT that the config holds values. These are different questions, and plan-review agents cannot detect the conflation without making the API call themselves. Generalizes beyond Doppler. **Why:** PR #2769 — plan AC claimed "dev Doppler has all 6 NEXT_PUBLIC_* secrets; confirmed by codebase audit" but dev was missing 3 keys; AC had to be rewritten at work-phase. See `knowledge-base/project/learnings/best-practices/2026-04-22-plan-ac-external-state-must-be-api-verified.md`.
114
+ - When a plan prescribes extension of a tool-tier / permission / registry map (e.g., `TOOL_TIER_MAP`, `ROUTABLE_DOMAIN_LEADERS`, `WORKFLOW_HANDLERS`), grep the map's current entries to verify scope before adding. Maps are usually scoped to one tool family by prefix or class — entries outside the scope are dead code and create false safety. **Why:** PR #2858 (#2853 plan) — Stage 2.7 extended `TOOL_TIER_MAP` (scoped to `mcp__soleur_platform__*`) for SDK-native tools; permission gating for those flows through `permission-callback.ts` directly. Caught by deepen-pass. See `knowledge-base/project/learnings/best-practices/2026-04-23-plan-quality-class-deepen-pass-catches.md`.
115
+ - When a plan prescribes an aggregate cost SLO for a skill-mediated routing change, sum the per-skill baseline (subagent fan-out × per-leader cost) before fixing the cap. Brainstorm Phase 0.5 spawns 4+ leaders at $0.05-$0.15 each = $0.20-$0.60 floor; aggregating without this baseline produces a cap that fires constantly. Split per-workflow when costs span an order of magnitude. **Why:** same learning file — PR #2858 plan proposed P95 ≤ $1 conversation cost; realistic brainstorm $1.50-$3.50; fix split into `$0.50` one-shot/work, `$2.50` brainstorm/plan.
116
+ - When a plan widens a discriminated union with a `kind`-typed polymorphic payload, design the per-variant payload sub-union in the plan, NOT deferred to implementation. A `payload: ...` placeholder lands as `unknown` and creates one cast-site per variant. Specify per-`kind` typed payloads explicitly (and per-`kind` response shapes if there's a request/response pattern). **Why:** same learning file — PR #2858 plan deferred `interactive_prompt.payload` sub-discrimination; type-design-analyzer flagged it as the highest-risk type gap; rewrite added 6 typed payload variants + 6 typed responses inline.
117
+ - When a plan proposes a sentinel value in a DB column (magic string like `'__unrouted__'`, `'pending'`, `'__unset__'`), grep 2-3 recent migrations for the codebase's discriminator convention before specifying. Most codebases use NULL with partial indexes OR explicit boolean OR proper enum — magic strings in free-text columns conflate two facts (presence + value) and break grep tooling. If a sentinel is unavoidable, wrap as a TS ADT at the storage boundary so the magic string never leaks past persistence. **Why:** same learning file — PR #2858 plan used `'__unrouted__'` in `active_workflow text`; codebase pattern is NULL + partial indexes (5+ migrations); soleur:engineering:review:architecture-strategist + type-design-analyzer both flagged.
118
+ - When a plan **relaxes or removes** a load-bearing defense (timeout, retry budget, rate limit, validator gate, byte cap, ceiling), enumerate every threat surface the original defense was bounding — including side-effect roles the defense was incidentally serving. For each, name the new defense or document why none is needed. "Same defense at a more permissive value" is acceptable; "same defense with a different reset/scope semantic" silently dissolves any side-effect roles and needs a new explicit ceiling. **Why:** PR #3225 — plan raised `DEFAULT_WALL_CLOCK_TRIGGER_MS` 30s→90s AND changed reset semantic to "every assistant block". The 30s ceiling was bounding two threats: idle-window AND absolute turn duration. The new semantic dissolved the second role (a chatty agent emitting one block every <90s never trips runaway), caught only at multi-agent review (soleur:engineering:review:architecture-strategist P1). Recovery added `DEFAULT_MAX_TURN_DURATION_MS = 10 min` as a separate non-resetting ceiling. See `knowledge-base/project/learnings/2026-05-05-defense-relaxation-must-name-new-ceiling.md`.
119
+ - When a plan adds new operator-facing WebSocket events (especially client→server `*_response` style for interactive prompts), check whether each new event has an MCP tool counterpart, or file a V2 tracking issue per missing tool. Anything an operator can do via the chat UI's interactive prompt should be doable by an agent via MCP tool — agent-user parity covers WS event surfaces, not just UI hook surfaces. **Why:** same learning file — PR #2858 plan added 6 new client→server `interactive_prompt_response` variants with zero MCP tool counterparts; soleur:engineering:review:agent-native-reviewer caught it; resolved by filing 5 V2 tracking issues.
120
+ - When a plan extends an agent body in `plugins/*/agents/**/*.md`, grep ALL invocation surfaces across `plugins/**`, `.claude/hooks/`, `scripts/`, and `.github/workflows/` before writing FRs — not just the named entry-point skill. Agents are typically invoked from 3-7 surfaces (entry-point skill, sibling skills, CONCUR-gates, opportunistic spawns); brainstorm + first-draft plan reliably name only 1-2. For every non-diff-shaped invocation context (finding-shaped, plan-shaped, opportunistic), the agent body edit MUST encode an explicit fallback string (e.g., `_N/A — no diff in scope._`) in the new bullet/section instructions, with an AC verifying the fallback text exists. **Why:** #2727 plan v1 5-agent panel — brainstorm named 1 surface, plan v1 named 2; soleur:engineering:review:architecture-strategist grepped and found 5 total. Surfaces 3-5 pass non-diff inputs; without the fallback string the agent renders hallucinated content. See `knowledge-base/project/learnings/2026-05-15-plan-time-grep-all-agent-invocation-surfaces-not-just-named-entry-point.md`.
121
+ - When a plan prescribes a validator, guard, linter, or linter rule that rejects a pattern, include a plan-time grep counting current matches on the protected surface. If non-zero, Acceptance Criteria MUST cover grandfathering or retroactive remediation — "future-only enforcement" is a false framing when the surface already contains matches. The forward-looking sibling of "paraphrase-without-verification": plans also must not assert forward claims about a protected surface without running the guard's reject-criteria against current data. **Why:** PR #2877 — plan asserted "no `hr-*` currently retired" without grepping `retired-rule-ids.txt`; two hr-* from PR #2865 surfaced at GREEN and forced an inline `HR_RETIREMENT_ALLOWLIST` pivot. See `knowledge-base/project/learnings/2026-04-24-guard-surface-audit-before-coding.md`.
122
+ - When a plan binds an SDK/runtime call that needs async per-user fetches (BYOK key, service tokens, workspace path, etc.) behind an existing **synchronous** factory/builder contract (`(args) => T`), the plan MUST prescribe widening the contract to `(args) => Promise<T> | T` rather than wrapping async work in a deferred-construction proxy. Sync-contract proxies that lazily resolve at first iterator/method call silently move errors out of the original try/catch's observability scope (Sentry tags, error-code mappings, runtime invariants tied to the boundary) into a downstream consumer that wasn't designed to handle them. The runner's `await` inside its existing try/catch tags both sync and async errors uniformly; sync callers keep returning a resolved Promise. **Why:** PR #2901 (Stage 2.12 cc-soleur-go) — work shipped a 200-LoC Query proxy because the plan didn't anticipate the `QueryFactory = (args) => Query` collision with async credential fetches; 4 review agents flagged it (AC14/R10 regression: `KeyInvalidError` no longer mapped to `errorCode: "key_invalid"`). Fix-inline widened the type and deleted the proxy. See `knowledge-base/project/learnings/best-practices/2026-04-27-widen-async-contract-instead-of-deferred-construction-proxy.md`.
123
+ - When a plan changes Eleventy `permalink:` frontmatter, output filenames, or any `_data/permalink.js`-controlled path scheme, prescribe a `git grep -F "<old-path-token>" .github/workflows/` sweep AS PART OF THE PLAN (not deferred to implementation review) and update every match in the same PR. The `Verify build output`-style step often `test -f`s the old path; if Eleventy emits redirect stubs at the legacy path, the gate will pass silently while the canonical path is broken. Also flag any unguarded `test -f _site/<old-pattern>` in the plan's verification checklist. **Why:** PR #1851 restructured permalinks but left `deploy-docs.yml`'s `test -f _site/pages/${page}.html` loop untouched; the gate passed silently for 18 days because redirect stubs at the legacy paths kept `test -f` happy. See `knowledge-base/project/learnings/best-practices/2026-04-28-learning-sharp-edges-need-tracking-issues-not-memory.md`.
124
+ - When a plan prescribes `dig`, `nslookup`, `curl`, or any network call inside a CI step, pin a timeout (`dig +time=N +tries=M`, `curl --max-time N`, etc.). Unbounded network calls inherit resolver/socket defaults and can hang for tens of seconds on flake, blowing CI wall-clock without a clear failure signal. **Why:** PR #3007 — plan prescribed `dig +short CNAME` for custom-domain ref derivation; review caught the unbounded wall-clock risk. See `knowledge-base/project/learnings/bug-fixes/2026-04-28-anon-key-test-fixture-leaked-into-prod-build.md` session error 1.
125
+ - When a plan prescribes echoing JSON-decoded values (jq output, base64-decoded payloads, untrusted claim values) into GitHub Actions annotations (`::error::`, `::notice::`, `::warning::`), prescribe a CR/LF strip via `${var//[$'\n\r']/}` BEFORE the echo. Annotations are line-oriented; a `\n::notice::PASS` smuggled into a `.iss` claim could spoof success. **Why:** PR #3007 — soleur:engineering:review:security-sentinel flagged log-injection vector via crafted JWT claims; resolved inline via `iss_safe`/`role_safe`/`ref_safe` sanitization. See same learning file, session error 6.
126
+ - When a plan documents a workaround for a closed upstream issue, verify the failing-code-path mechanism against the **installed** library source (`node_modules/<pkg>/.../*.js`), not just the upstream issue's prose. Major-version churn between issue-close and the version actually installed can swap the failing code path (e.g., a `ws`-fallback race in v2.87 that was replaced by an `unsupported`-throw in v2.88+) while preserving the same end-symptom. The right-shape workaround at runtime can still be the same line; the mechanism explanation in the learning file is what drifts and rots. **Why:** PR #3058 — initial draft of the supabase-js Phoenix-JOIN learning encoded the upstream-issue mechanism, but installed `@supabase/realtime-js@2.99.2` returns `{ type: 'unsupported' }` rather than falling back to `ws`. soleur:engineering:research:git-history-analyzer caught the drift between upstream-issue prose and installed code. See `knowledge-base/project/learnings/best-practices/2026-04-29-supabase-phx-join-handshake-shell-environment.md`.
127
+ - When a plan's Acceptance Criteria prescribes a verification grep for a CLI-form-bug class (fabricated flag, wrong stdin sentinel, `echo`-vs-`printf` newline, `< /dev/stdin` no-op), default the grep scope to ALL operator-facing surfaces — `knowledge-base/engineering/`, `knowledge-base/project/learnings/`, `.github/`, `apps/*/docs/`, root `README.md`/`CONTRIBUTING.md` — and exclude only `knowledge-base/project/{plans,specs}/**` and `**/archive/**` (which preserve historical record). Narrow scopes that target only the named file's directory miss same-class bugs in adjacent operator-facing surfaces. **Why:** PR #3059 — plan AC1 was scoped to `knowledge-base/project/learnings/` and missed three same-class bugs in runbooks (`oauth-probe-failure.md`, `dashboard-error-postmortem.md`) and a workflow issue-body template (`scheduled-linkedin-token-check.yml`). Multi-agent review caught all three; cosign-DISSENT correctly flipped them from scope-out to fix-inline. See `knowledge-base/project/learnings/best-practices/2026-04-29-docs-fix-verification-greps-must-span-operator-surfaces.md`.
128
+ - When a plan asserts behavior of a third-party GitHub Action ("action X auto-configures Y", "Y is auto-injected by Z", "Z handles W internally"), grep the same repo's `.github/workflows/` for prior usage of the action and reconcile the claim against codebase precedent in the SAME plan step that asserts it. Plan-time research is good at "what does X claim" (docs, manifests); it is bad at "what does the surrounding workflow actually do to make X work reliably" — that lives only in working precedent. **Why:** PR #3155 — plan asserted `claude-code-action@v1` auto-configures `git config user.name/email`; all 10 sibling Soleur `scheduled-*.yml` workflows actually run an explicit `git config` step before pushing. Architecture-strategist caught it at multi-agent review as a P1; without the fix, `git commit` aborts with "Author identity unknown" at fire time — the exact regression the plan was designed to fix. See `knowledge-base/project/learnings/best-practices/2026-05-04-verify-third-party-action-behavior-claims-against-codebase-precedent.md`.
129
+ - When a plan prescribes inline JWT minting (RS256 + openssl) for a CI workflow that calls `gh api` with App-JWT auth, three silent-failure traps must be addressed in the plan body, not deferred to soleur:work: (a) `gh api` does NOT accept JWT-format `GH_TOKEN` (sends `token`, not `Bearer`) — use curl with `--header @<(printf ...)`; (b) `openssl base64 -A` trails a newline that `tr -d '='` does NOT strip — use `base64 -w 0 \| tr '+/' '-_' \| tr -d '=\n'`; (c) `if: failure()` does NOT fire when the previous step has `continue-on-error: true` — use `if: steps.<id>.outcome == 'failure'`. See `knowledge-base/project/learnings/best-practices/2026-05-05-workflow-jwt-mint-silent-failure-traps.md`. **Why:** PR #3187 plan-review caught all three pre-merge.
130
+ - When a plan adds a defensive guard at a chosen call-site (validator, normalizer, prefill check, rate limiter), trace the value-of-interest from the entry point (WS frame, route handler, queue worker, cron tick) all the way to the guard's input — NOT just from the guard outward. Internal threading (`runner.dispatch({sessionId}) → factory(args.sessionId) → guard`) verifies the guard's correctness GIVEN an input; the upstream trace verifies the input ever exists. Add a §Research Reconciliation row that names every frame the value passes through and the line where each frame forwards or drops it. **Why:** PR #3263 — guard placed in `realSdkQueryFactory` based on runner-internal threading; ws-handler's `dispatchSoleurGoForConversation` discards `conversations.session_id` between the SELECT (line 1138) and the dispatch (line 620), so `args.resumeSessionId` reaches the guard as `undefined` on every cold start. Multi-agent review (soleur:engineering:review:data-integrity-guardian + soleur:engineering:review:architecture-strategist) caught the dormancy; recovery extracted the guard to a shared helper called from both the cc-soleur-go path AND the legacy `startAgentSession` (the actual production trigger). See `knowledge-base/project/learnings/best-practices/2026-05-05-trace-callgraph-from-entrypoint-when-placing-guards.md`.
131
+ - When a plan prescribes a Sentry-telemetry threshold as a deferral gate ("if Sentry shows ≥N hits over T days, fold in; else defer"), verify the search query actually matches a known-recent error of the same class before treating zero hits as absence. Anthropic SDK errors land as `Error: Claude Code returned an error result: …` titles without the request body, so substring searches on `prefill`, the model id, or `invalid_request_error` return zero — the wrapper title swallows the body. Run a representative substring-broadening sweep (error-class wrappers, stripped-status-code variants) AND confirm at least one matching event exists for any known recent error before relying on the gate. **Why:** PR #3263 Phase 3 audit returned 0 hits against 680 baseline for `prefill`/the-execution-model-id/`invalid_request_error`; broader query (`anthropic OR claude OR APIError`) confirmed the original was too narrow. The architectural-fact path (shared model + shared resume threading on legacy) overrode the empirical zero, but the gate should not have been declared "satisfied" on the original query. See same learning file.
132
+ - When a plan adds an application-layer recovery primitive (TS guard, sync reaper, retry envelope) that mirrors a SQL-layer or scheduler-layer primitive at the **same threshold** (same `< now() - X` predicate, same retry budget, same staleness window), the plan body MUST name which sub-value the application-layer copy is load-bearing for: (a) cross-layer state truing that the SQL/scheduler primitive doesn't perform (e.g., flipping a related row's status), (b) observability the SQL/scheduler primitive doesn't emit (Sentry, structured logs), or (c) drift-resilience if the SQL/scheduler primitive is later refactored. If NONE of (a)/(b)/(c) applies, either tighten the application-layer threshold so it catches what the SQL/scheduler misses (and pay the defense-relaxation analysis cost per `2026-05-05-defense-relaxation-must-name-new-ceiling.md`) or drop the application-layer copy entirely. Plan-time domain-review CTO probe: "Does this new code path mirror a predicate that already exists in another layer? If yes, name the load-bearing sub-value." Architecture-strategist at PR-review time is too late — by then the contested-design alternatives are filed as follow-ups instead of debated in the plan. **Why:** PR #3354 — plan widened `tryLedgerDivergenceRecovery` with a stale-heartbeat SELECT at 120 s threshold, identical to migration 029's lazy-sweep predicate inside the same RPC's transaction. Architecture review surfaced the redundancy and named three alternatives (tighten / drop / keep-as-defense-in-depth); filed scope-out #3372 because each alternative carried non-trivial trade-offs. The branch's actual sub-values (status-flip + Sentry + drift-resilience) were never explicit in the plan. See `knowledge-base/project/learnings/2026-05-06-defense-in-depth-recovery-mirroring-sql-predicate-document-load-bearing-value.md`.
133
+ - When a plan changes a `MAX_*_SIZE` / `*_CAP_BYTES` / `MAX_*_LENGTH` constant (or any byte/length ceiling on uploaded/persisted artifacts), grep ALL readers of the affected artifact class — not just call sites of the constant being changed. Reader-side caps are typically *literal* (hand-rolled `15 * 1024 * 1024`), not imported, so a callers-of-constant grep returns zero and looks safe while a sibling reader silently gates in the band between the old and new caps. Run `git ls-files | grep -E "<artifact-pattern>"` (e.g., `pdf|attachment|image`) and `rg "\* 1024 \* 1024" <reader-paths>` to find unaligned literals; require imports of the shared constant (or scope-out + tracking issue) for every match. **Why:** PR #3353 — #3337 raised the PDF upload cap to 24 MB; #3338 (cut before #3337 merged) introduced the extractor with a 15 MB literal that nobody could see was misaligned without a cross-PR audit. Surfaced only via Sentry event `9e0a3888fd3849cd87cb83cdcecca199` after a real user tripped the [15, 24] MB band. See `knowledge-base/project/learnings/2026-05-06-cap-coupling-between-adjacent-prs.md`.
134
+ - When prescribing GitHub labels in Acceptance Criteria (e.g., for tracking issues created post-merge), verify each label exists via `gh label list --limit 200 | grep -E "^<label>\b"` BEFORE writing the AC. If a label doesn't exist, either substitute the closest existing label (with a note in the AC) or add a Phase 0 step to `gh label create` it. The verify-before-cite convention also lives in `deepen-plan/SKILL.md` Phase 4 AC and in `soleur:drain-labeled-backlog`'s validator step. **Why:** PR #3378 — plan prescribed `infrastructure` and `seo` labels that didn't exist; substituted with `domain/engineering`, `chore`, `priority/p3-low` at issue-creation time. See `knowledge-base/project/learnings/2026-05-06-plan-prescribed-labels-must-be-verified.md`.
135
+ - When a plan splits a feature into a foundations PR + downstream wiring PR(s), label each foundations-PR surface as **inert** (no production caller — router modules with no callers, devDeps with no imports, types with no consumers) or **contract-declaring** (system-prompt directives, schema fields a downstream PR is meant to consume, observability events, behavior changes already-shipped resolvers/dispatchers will route into the new branch). Contract-declaring surfaces in a foundations PR require atomic delivery with the wiring PR, a default-closed feature flag, or fall-through to existing safe behavior — never ship the contract ahead of delivery, even when the surface "feels safe as foundations." **Why:** PR #3440 (Phase 3.A) shipped a chapter-chunked system-prompt directive declaring a `document` content-block contract whose dispatch-time attachment lived in #3472 (Phase 3.B); multi-agent review (soleur:engineering:review:architecture-strategist + soleur:engineering:review:data-integrity-guardian + soleur:engineering:review:user-impact-reviewer) converged on the directive-without-delivery state as a `single-user incident` brand-survival regression and forced a runner fall-through to the existing `too_many_pages` bridge. See `knowledge-base/project/learnings/2026-05-07-foundations-pr-must-not-declare-downstream-contracts.md`.
136
+ - When a plan widens a TypeScript discriminated union (`WSMessage`, `ChatMessage`, etc.), do NOT prescribe an exhaustiveness-site count or a fixed file:line list. Source-grep undercounts: `_exhaustive: never` rails live in test-only `*.test-d.ts` gates and in adjacent server/handler switches that scopes like `{lib,server,components}/` miss. Prescribe instead "run `tsc --noEmit` after the union edit; every TS2322 'X is not assignable to never' is a rail to widen". The compiler is the canonical enumerator; counts in plans drift. **Why:** PR #3419 — plan prescribed 4 sites; actual was 5 over `WSMessage` + 2 over `ChatMessage`; deepened plan still missed `ws-handler.ts:1640` and `chat-message-exhaustiveness.test-d.ts:38`, surfaced only by `tsc --noEmit` at Phase 6. See `knowledge-base/project/learnings/2026-05-07-tsc-not-source-grep-enumerates-exhaustiveness-rails.md`.
137
+ - When a plan proposes a fix to a flaky negative-substring assertion over output that embeds RNG-derived strings (`randomUUID()`, `nanoid()`, `Math.random().toString(36)`), enumerate the RNG's emitted alphabet (`randomUUID()` v4 → `[0-9a-f]`, `nanoid` default → `[A-Za-z0-9_-]`, `Math.random().toString(36)` → `[0-9a-z]`) and weigh at least one **data-side fix** (rename the fixture literal to a character outside that alphabet) ahead of any **assertion-side fix** (word-boundary regex, line-anchored regex, deterministic-RNG spy). Data-side fixes are collision-proof by construction with zero regex-semantics to audit; assertion-side fixes carry residual template-shape risk. **Why:** PR #3615 — plan adopted the issue body's proposed `/\bb\.png\b/` regex and produced a 320-line analysis (truth-table + probability math + deferred-spy follow-up issue) for a flake on a fixture filename. Multi-agent review's soleur:engineering:review:code-simplicity-reviewer (1 of 11) surfaced that renaming the fixture from `b.png` to `z.png` was strictly simpler: UUIDs are hex-only, no suffix can end in `z`, original `toContain("z.png")` becomes collision-proof. Net diff dropped from 341/-1 to 3/-3; deferred follow-up #3617 closed as wontfix. See `knowledge-base/project/learnings/2026-05-11-rename-fixture-beats-regex-for-rng-substring-flake.md`.
138
+ - When a plan AC uses `diff <(awk '/^heading/,/^delimiter$/' ...)` (region-replacement diff against canonical), enumerate **every paragraph the awk range captures** in the edit instructions — not just the named section's heading + body. Awk ranges greedily include trailing/leading paragraphs that semantically belong to neighboring sections but physically sit inside the range. AC region MUST equal edit-instruction region; otherwise the AC detects drift the implementation cannot fix without scope expansion. Two alignments: tighten the AC range to a tighter delimiter (`<!-- End: ... -->` instead of `^---$`), or expand the edit instructions to cover everything inside the range. **Why:** PR #3669 — plan prescribed "replace GDPR §3.8 heading + body lines 104-107" but the AC's awk range `/^### 3\.8/,/^---$/` also captured the §3.7 balancing-test paragraph that canonical relocated post-§3.8; required a follow-up paragraph relocation at soleur:work time. See `knowledge-base/project/learnings/2026-05-12-region-replacement-acs-must-enumerate-trailing-paragraphs.md`.
139
+ - When a plan AC greps for a date string spanning hero + body Last-Updated lines on Eleventy mirror legal docs, tolerate both punctuation forms in the regex (`Last Updated[: *]+May 12, 2026`) or split into two separate count assertions per location. Hero uses `<p>... Last Updated May 12, 2026</p>` (no colon), body uses `**Last Updated:** May 12, 2026` (with `:**`); a literal `'Last Updated May 12, 2026'` regex matches only the hero. **Why:** PR #3669 — plan AC9 prescribed `grep -cE 'Last Updated May 12, 2026' ... returns 2,2,2` but the body form is unmatchable as-written; recovery required trusting the AC's spirit via a separate `grep -n 'Last Updated'` call. See same learning file.
140
+ - For drift-remediation runbooks targeting `apps/web-platform/infra/` via Doppler `prd_terraform`, prescribe the canonical invocation triplet verbatim — never just `doppler run -- terraform plan/apply`. The triplet: (1) `export AWS_ACCESS_KEY_ID=$(doppler secrets get AWS_ACCESS_KEY_ID -p soleur -c prd_terraform --plain)` + same for `AWS_SECRET_ACCESS_KEY` (R2 backend creds, must be raw — `tf-var` would mangle them to `TF_VAR_aws_*` and the S3 backend silently fails to authenticate), (2) `terraform init -input=false`, (3) `doppler run -p soleur -c prd_terraform --name-transformer tf-var -- terraform <plan|apply>`. Without `--name-transformer tf-var`, `terraform plan` errors immediately with ~13 `No value for required variable` failures (`cf_api_token_*`, `cf_zone_id`, `cf_account_id`, `webhook_deploy_secret`, `doppler_token`, `cf_notification_email`, `resend_api_key`, etc.). **Why:** #3371 plan cited the precedent (#3061 runbook) but copy-pasted only the `prd_terraform` config name, not the flag or the AWS exports — Phase 1 plan crashed at the keyboard. See `knowledge-base/project/learnings/2026-05-09-drift-runbook-canonical-tf-invocation-and-fresh-plan.md`.
141
+ - For drift-remediation plans whose Acceptance Criteria asserts an exact `Plan: N to add, M to change, K to destroy` line copied from a drift-detector snapshot, re-run `terraform plan` against live state immediately before publishing the runbook. Drift snapshots are read-only artifacts captured on the `0 6,18 * * *` cron — up to 12h stale on a fast author, days stale when triage runs after the auto-filed issue. If the live plan diverges from the snapshot at publish time, either (a) the drift was already applied — skip the runbook, verify with curl/file-hash/systemd-unit, close the parent issue; or (b) unrelated drift accumulated — file a follow-up issue and keep scope narrow per `hr-menu-option-ack-not-prod-write-auth`. **Why:** #3371 plan referenced a 2026-05-06 19:48 UTC snapshot for a 2026-05-09 runbook; by apply-time the original target was already in state and 2 unrelated drifts had accumulated; Phase 1 had to be re-scoped at execution. See `knowledge-base/project/learnings/2026-05-09-drift-runbook-canonical-tf-invocation-and-fresh-plan.md`.
142
+ - For retirement-cleanup plans (sweeps that drop a retired/fabricated AGENTS.md rule-ID citation from operator-facing files), the AC verification grep MUST scan the full class on the edited files, not just the named target ID. Two greps: (a) every retired ID from the retired-rule registry at the repo root — excluding ids listed in [migrated-rule-ids.txt](../../../../scripts/migrated-rule-ids.txt) (migrated, still ACTIVE; they also carry a "NOT a retirement" row in the retired registry) — against each edited file, and (b) every rule-ID-shaped token (`\b(hr|wg|cq|rf|pdr|cm)-[a-z0-9-]+\b`) that does not resolve to an active `[id: ...]` in AGENTS.md or to a row in [migrated-rule-ids.txt](../../../../scripts/migrated-rule-ids.txt). Named-target-only greps return false-pass for the class — the same files often contain other retired or fabricated citations the issue body never enumerated. **Why:** PR #3491 — plan AC verified only the 5 named retired IDs; multi-agent review caught 5 additional retired/fabricated active citations in the same edited files (3 in files the PR was already modifying, 1 fabricated in this skill itself). See `knowledge-base/project/learnings/2026-05-09-retirement-cleanup-grep-must-scan-full-class-not-named-id.md`.
143
+ - When a plan prescribes BOTH a contract-changing edit (function return-code semantics, signature change, schema field, env-var contract) AND a contract-consumer edit (consumer of the new return code, new arg, new field), the contract-changing phase MUST come BEFORE the consumer phase — even when the entire PR is single-merge atomic. Plans are read sequentially during `soleur:work`; out-of-order phases produce dead code or fail tests in the consumer phase before the contract phase has shipped. Atomic merge ≠ atomic per-phase TDD. The natural temptation is to group phases by file (consumers + infrastructure) instead of by dependency direction; resist. **Why:** #3509 plan-review — initial draft put hook emission (Phase 2) ahead of the `rotate_if_needed` return-code change (Phase 3); Kieran-rails-reviewer caught it. The Phase 2 `if ! rotate_if_needed` wrapper would have been dead code at Phase 2 boundary. See `knowledge-base/project/learnings/2026-05-10-plan-phase-order-load-bearing-when-contract-changes.md`.
144
+ - When a plan's Acceptance Criteria prescribes a syntax check on a GitHub Actions **workflow** (`.github/workflows/*.yml`), use `actionlint` for the YAML AND `bash -c '<extracted snippet>'` for embedded `run:` shell — never `bash -n <file.yml>` (parses the YAML header as bash). For GitHub Actions **composite action** definitions (`.github/actions/*/action.yml`, schema uses `name`/`description`/`inputs`/`runs`), do NOT use `actionlint` — it only validates workflows (which require `on:` + `jobs:`) and emits 5+ spurious "section missing" / "unexpected key" errors against the action schema that look like real regressions. Either skip schema validation (the calling workflow will fail-fast on first run) or use a dedicated `action-validator` tool; still use `bash -c '<extracted snippet>'` for embedded `run:` shell inside the action. **Why:** PR #3543 plan AC §111 prescribed `bash -n .github/actions/bot-pr-with-synthetic-checks/action.yml` (parse failure); PR #3971 was nearly ship-blocked by 5+ spurious `actionlint` errors against a composite action file. See `knowledge-base/project/learnings/2026-05-11-multi-word-required-check-exposes-strip-all-whitespace-bug.md` and `knowledge-base/project/learnings/2026-05-18-composite-action-extraction-inline-on-multi-file-rollout.md`.
145
+ - When a plan AC prescribes a git command to enforce a per-file co-change invariant (e.g., "files A and B must be touched in the same commit"), the prescribed command MUST be tested against the failure mode it is designed to catch. `git log -- A B` and `git log --oneline -- A B` are UNION filters (commits touching A OR B) — they do NOT distinguish a commit touching only A from a paired commit, and silently green-light the asymmetric-commit failure the AC was designed to catch. For per-commit intersection semantics, walk commits with `git rev-list <base>..HEAD -- <paths>` then `git show <sha> -- <paths>` and grep the diff for both region markers. Sanity-test by constructing three throwaway commits — A only, B only, both — and confirming the prescribed command rejects the asymmetric two. **Why:** 2026-05-11 PR #3550 plan-review (Kieran P1-A + Architecture P1.1) — plan AC #18 prescribed `git log --oneline -- soleur-go-runner.ts agent-runner.ts` as the TR4 single-commit-invariant check; the union semantic would have shipped a green checkmark over the exact `single-user incident` directive-without-delivery failure mode that motivated Phase 3.A's revert. See `knowledge-base/project/learnings/2026-05-11-plan-review-caught-git-log-union-trap-and-cross-module-field-assumption.md`.
146
+ - When a plan prescribes reading a field on a cross-module shape (TypeScript interface, Postgres column, GraphQL type, Zod schema, protobuf field, route argument), `rg "<field-name>" <defining-module>` to verify the field exists in the current shape BEFORE freezing the AC or the implementation step. Plan paraphrase often adds plausible-sounding field names ("the resolver already carries path and title") that survive subsection review because each sentence reads coherently — only a grep against the defining module exposes them. If the field is absent, choose ONE of: (a) name the interface-widening edit explicitly in `Files to Edit` and accept the cross-cutting blast radius, (b) thread the value from a closer in-scope source the runner already holds, or (c) drop the dependent AC. Generalizes paraphrase-without-verification (`2026-04-22-ts-sql-normalizer-parity...`) from issue-body paths to cross-module type fields. **Why:** 2026-05-11 PR #3550 plan-review (Kieran P1-B + Architecture P1.3) — plan referenced `documentExtractMeta.path` on `DocumentExtractMeta`, but the resolver shape is `{ numPages?, chapters?, fullExtractedText? }`; AC #13 was unimplementable as written and an unwitting inline interface-widening would have escaped the plan's `Files to Edit` scope. See same learning file.
147
+ - When a plan's design turns on whether a **DB column exists** on a table (scan-source choice, "field X is unavailable so we must join Y"), do NOT rely on a line-scoped `ALTER TABLE <table>.*<column>` grep — `grep` is line-oriented and a multi-line `ALTER TABLE …` + `ADD COLUMN <col>` (or a column from the initial `CREATE TABLE`) returns a false negative. Grep the **bare column name** across all migrations (`git grep -n "<col>" supabase/migrations/`) AND cross-check **existing consumers**: a shipped `.from("<table>").select("<sibling-col>")` proves the table carries that column family and reflects *live* schema in a way superseded/`.down.sql` migrations do not. Prefer the consumer signal when it reverses a design decision. **Why:** #4717 plan v1 asserted `users` lacks `repo_url` from a failed line-scoped grep (mig 011 adds it multi-line; arm 2 already queried `users.repo_status`) → drove a workspace-centric design Kieran plan-review reversed to users-centric. See `knowledge-base/project/learnings/2026-06-01-column-existence-grep-must-not-be-line-scoped-or-trust-consumers.md`.
148
+ - When a plan AC prescribes a shell script that captures `git show` (or any multi-KB diff) into a variable and pipes it through `grep -q`, use the tempfile shape (`patch=$(mktemp -p "$(mktemp -d -t soleur-run.$$.XXXXXXXX)"); git show > "$patch"; grep -c <marker> "$patch"; rm -f "$patch"; rmdir "$(dirname "$patch")" 2>/dev/null`) instead of `printf '%s' "$diff" | grep -q`. The pipe-from-stdin form interacts with bash pipe buffering + `set -e` short-circuit semantics in ways that vary by shell version; observed failure: grep returned 0 matches against a diff containing 22 occurrences of the marker, both `&&`-chained AND `|| true`-protected forms misfired. Additionally, when an invariant is "no intermediate commit ships only one of two paired markers", make the script track marker **presence at HEAD** (single check, file-based grep on the HEAD-state file) rather than marker **presence in each commit's diff** (per-commit pairing) — the per-commit form false-positives on pure-refinement commits that touch one side without semantic changes to the other, requiring no-op amend-touches to pair markers. **Why:** 2026-05-11 PR #3550 — initial TR4 verification script per plan §3.6 returned `directive=0 dispatch=0` against a commit with 22+8 marker hits; rewriting to tempfile-based grep returned correct counts. Same script flagged the follow-up review-fix commit as FAIL because it refined dispatch wiring without touching the directive marker — required a comment-update amend to pair. See `knowledge-base/project/learnings/2026-05-11-chapter-chunked-dispatch-revival-and-tr4-pure-dispatch-refinement.md`.
149
+ - When a plan's Research Reconciliation asserts that a sibling-PR mitigation does NOT apply because the codebase lacks artifact Y (e.g., "no `mx-auto`", "no `ResizeObserver`", "no `onSubmit` handler"), the grep MUST walk the full render tree — route page + layout + shell + components/<feature>/ — not just the layout/shell file the plan was written against. The render tree is wider than the file being edited: a Next.js route page renders INSIDE the shell as `children` and frequently carries the geometric constraints (centering wrappers, fixed-width clamps, scroll containers) that determine whether the mitigation is needed. A single-file grep is insufficient evidence. **Why:** PR #3587 plan asserted KB doc viewer was not `mx-auto`-wrapped (read only `kb-doc-shell.tsx`); the markdown route at `apps/web-platform/app/(dashboard)/dashboard/kb/[...path]/page.tsx:77,153` does use `<div className="mx-auto max-w-3xl">`, and the #3579 anchor-pad mitigation was load-bearing. Pattern-recognition reviewer caught it pre-merge; inline fix landed in c7e70258. See `knowledge-base/project/learnings/2026-05-11-plan-research-reconciliation-must-grep-full-render-tree.md`.
150
+ - When a plan prescribes a parsing/extraction pattern (`awk`, `grep`, `jq`, `sed`, `yq`) for frontmatter, config, or any structured-line shape, `git grep` the codebase for the closest precedent at plan-write time and adopt it verbatim — do NOT invent a new form. Bare `awk '/^key:/ {print $2}'` is brittle against quoted values (`key: "value"` prints `"value"`), trailing whitespace, and multi-token values; the canonical robust form is the `gsub` strip used at `plugins/soleur/skills/skill-security-scan/scripts/run-scan.sh:34`: `awk '/^key:/ { gsub(/^key:[[:space:]]*"?|"?$/, ""); print; exit }'`. The paraphrase-without-verification rule applies to one's own pattern proposals, not just issue-body claims. **Why:** PR #3625 — plan v1 prescribed brittle `awk '/^lane:/ {print $2; exit}'` in 3 call sites; Kieran P1.2 surfaced the gsub precedent at plan-review time, forcing rewrites. Plan-time grep would have caught it in ~2 minutes vs. ~15-30 minutes at review time. See `knowledge-base/project/learnings/2026-05-12-plan-time-parsing-pattern-needs-codebase-precedent-grep.md`.
151
+ - When a plan verifies a spec claim about a reducer/switch statement's behavior (chip removal triggers, exhaustiveness rails, side-effect emission, lifecycle transitions), `grep -n "case " <reducer-file>` to enumerate ALL arms BEFORE deciding which arm(s) exhibit the claimed behavior. `Read(file, limit=N)` is for *each arm's body* once located, NEVER for *enumeration* — first-N-line views silently undercount when the reducer ships behavior across multiple later case arms. The cost is ~5 seconds for one extra grep vs. ~15+ minutes when plan-review catches the gap, or worse when /work-time RED tests pass vacuously against a missing trigger. **Why:** PR #3743 plan v1 (#2939) — Phase 1 verification did `Read(cc-soleur-go-end-to-end-render.test.tsx, limit=100)` to check chip-removal triggers, saw the `workflow_started` path in the first test, and "corrected" spec FR1.3 to drop `stream_end`. Kieran plan-review caught that `chat-state-machine.ts:522` ALSO fires chip removal via `case "stream_end"` — the corrected FR1.3 now covers BOTH paths. Generalizes paraphrase-without-verification (`2026-04-22-ts-sql-normalizer-parity...`) to multi-case structures. See `knowledge-base/project/learnings/2026-05-13-plan-verify-reducer-case-arms-with-grep-not-read-first-n.md`.
152
+ - When a plan adds a schema/config/manifest file (`**/schema.yaml`, `**/*_schema.{yaml,md}`, `**/*.config.{yaml,json}`) to `## Files to Edit`, read the file's first 10 lines for a vendored-upstream header (`# X Documentation Schema`, foreign-domain enum values, sibling `NOTICE` file with `upstream:` frontmatter) AND `git grep -n 'blocking="true"\|validate_required\|enforce: hard'` for the file's consumers BEFORE proposing a new `required: true` field. Adding `required` to a blocking validation gate is a breaking change to ALL existing producers — not just the new category — and editing a vendored-upstream file breaks the sync contract regardless. **Why:** PR #2723 Spec A plan v1 prescribed editing CORA-vendored `compound-capture/schema.yaml`; would have blocked all 13 compound problem_types via the `<validation_gate blocking="true">` at SKILL.md:185. Kieran (P1) caught it by reading the file header; soleur:engineering:review:architecture-strategist (P1) caught it by tracing the gate consumer. Both checks are two-line shell at plan-draft time; the cost of catching at soleur:work time is mid-build pivot. See `knowledge-base/project/learnings/2026-05-12-plan-vendored-schema-detection-and-blocking-validation-gate.md`.
153
+ - When a plan body quotes a Python `import` statement that points at a path with non-identifier characters (hyphens in the filename, leading digits, embedded dots beyond `.py`), probe the import as a 1-line shell precondition at plan-draft time AND name it as a verification step in soleur:work Phase 1 — never link the prescribed import as the recommended approach without a probe. Python identifiers are `[A-Za-z_][A-Za-z0-9_]*`; `from backfill-frontmatter import …` is a parse error regardless of `sys.path`. The two non-importlib fixes are (a) rename the file to underscores, or (b) extract helpers to a sibling `<name>_lib.py`. Probe form: `python3 -c "import sys; sys.path.insert(0, '<dir>'); from <module> import <name>" 2>&1`. **Why:** PR #2723 Spec A plan v1 prescribed `sys.path.insert(0, 'scripts'); from backfill_frontmatter import …` against the hyphenated migration script `backfill-frontmatter.py` under the repo-root scripts directory; 5-agent plan review missed it; soleur:work hit ModuleNotFoundError at Phase 1 and pivoted to `importlib.util.spec_from_file_location`, then refactored to a sibling `frontmatter_lib.py` after the review pass dissented on the proposed scope-out. See `knowledge-base/project/learnings/2026-05-12-hyphenated-python-modules-and-plan-precondition-verification.md`.
154
+ - When a plan cites a PR or issue number for provenance of a prior invariant/decision/learning (e.g., "the orphan-suite invariant per PR #X", "deferred per #Y"), `gh pr view <N> --json title` (and/or `gh issue view <N> --json title`) at plan-draft time and confirm the title aligns with the cited semantic role. PR numbers paraphrased from memory or sibling docs are wrong with surprising frequency (PR-vs-issue conflation, mis-keyed sibling PRs, transposed digits); the cost of one `gh` call per citation is trivial vs. propagating wrong provenance into spec/brainstorm/plan and re-discovering it at multi-agent review. **Why:** PR #3672 plan/spec/brainstorm cited "PR #3512/#3533" as orphan-suite-invariant provenance; #3512 was an unrelated cross-sink telemetry PR and #3533 was the *issue* (PR was #3534). soleur:engineering:research:git-history-analyzer review caught the drift and required citation-correction edits across 4 docs post-implementation. See `knowledge-base/project/learnings/2026-05-12-ci-test-job-speedup-replan-and-validation-mechanics.md`.
155
+ - When a plan precondition asserts "X is accessible at scope Y", the check MUST grep the producing-scope file for X — not just Read the consuming code. **Cheapest gate:** `grep -nE '\bX\b' <file-that-defines-scope-Y>` against the hook implementation, route handler, or schema definition that produces Y; absence is a precondition failure regardless of how plausible the consuming reference reads. The consuming code can name a variable optimistically (`conversation.created_at` reads as if it's a property of an in-scope object) when the producing scope only exposes `conversationId: string` and a flat hook return; the Read passes while the field doesn't exist at the proposed mount scope. **Why:** PR #3653 — plan §Phase 0.1 asserted `conversation.created_at` accessibility via Read; soleur:work surfaced that `useWebSocket(conversationId)` returns slices without `conversationCreatedAt` and `/api/conversations/:id/messages` did not select the field. Three boundary edits (hook return slice, route SELECT projection, mount-site prop plumbing) required at soleur:work time to thread the value to the component. See `knowledge-base/project/learnings/2026-05-12-plan-precondition-and-3-value-enum-gate-drift.md`.
156
+ - When a plan specifies both (a) a component architecture and (b) a parametrized test list (`test.each([healed, postFix, preWindow, streaming, postSunset])(...)`), cross-check at plan time that every test row's fixture input maps to a prop the component actually accepts — symptom shape: "test row references a value the component prop boundary does not expose". If the test list's inputs span predicate state that the architecture pushes outside the component (e.g., predicate lives at the mount site in an IIFE while the component takes only one prop), one of the two must change. Architecture > tests if the tests can be rewritten to drive through a higher-level mount; tests > architecture if the test inputs are the load-bearing brainstorm-owned failure enumeration. The mismatch is invisible to per-section plan review because each section reads coherently in isolation — only the cross-section consistency check catches it. **Why:** PR #3653 — plan §1.1.2 listed 5 test rows needing `messages` + `createdAt` + `isStreamingAssistant` while plan §1.3.2 mount IIFE exposed only `createdAt` to the component; soleur:work pivoted to move the predicate into the component to keep the brainstorm-owned test list verbatim. See `knowledge-base/project/learnings/2026-05-12-plan-precondition-and-3-value-enum-gate-drift.md`.
157
+ - When a plan FR conditions on a single enum / union value (`X === "streaming"`, `!isStreamingAssistant`, `status === "completed"`), the FR text MUST classify EVERY union member of `X` as include or exclude. If `X` has N values, the FR must explicitly enumerate all N. Single-value FRs hide a class of bug under any future schema widening, AND the work phase reliably honors the FR verbatim — the gap is structural in the FR, not in execution. **Cheapest gate at plan-write time:** `grep -nE "^export type <Name> =" <module>` to read the current member list, then enumerate each member in the FR with an explicit include/exclude classification. Plan-side complement to hard rule `cq-union-widening-grep-three-patterns` (consumer-side exhaustiveness at union-widening time); together they cover producer (plan) + consumer (code) sides of the same defect class. **Why:** PR #3653 — plan §FR2 conditioned on `!isStreamingAssistant`; implementation bound it to `streamState === "streaming"`. Codebase: `StreamState = "idle" | "streaming" | "stopping"` (`ws-client.ts:47`); `"stopping"` is a distinct in-flight substate during mid-aborts that the FR never named. `soleur:engineering:review:user-impact-reviewer` caught the slip at PR review only because the review-spawn prompt explicitly enumerated the 3-value enum. Recovery: renamed prop to `isTurnInFlight`, bound to `streamState !== "idle"`. See `knowledge-base/project/learnings/2026-05-12-plan-precondition-and-3-value-enum-gate-drift.md`.
158
+ - When a plan FR/AC names a specific third-party API endpoint, query parameter, HTTP verb, or filter shape (e.g., `DELETE /api/0/.../issues/?query=tag:val`, `events.list?filter=Y`), verify the **contract** at plan-write time via WebFetch of the canonical docs URL, OpenAPI/Swagger grep, or sandbox curl — NOT just `gh secret list` / `doppler secrets get` state checks (the existing `2026-04-22-...-external-state-must-be-api-verified` rule covers state; contract is a separate axis). For **pipeline-shape claims** (logs → X, metrics → Y, events → Z), grep the dependency manifest (`package.json`, `pyproject.toml`, `Cargo.toml`, `Gemfile.lock`) for the actual transport package (`pino-better-stack`, `@logtail/*`, `dd-trace`, etc.) — sub-processor disclosure ≠ runtime data flow. For **centralization plans** that route an emit through a single helper (`reportSilentFallback`, `mirrorP0Deduped`), require a two-grep verification pattern: helper-centric (`rg "helperName\("`) AND bypass (`rg "underlyingApi\(" | grep -v helperFile`) — helper-centric alone misses direct callers that defeat the centralization. **AC-emit-gate regex**: anchor on the container shape (`(extra|tags):\s*\{[^}]*\bField\b`) not the literal-colon form (`Field:`), which misses shorthand object literals (`{ Field }`). **Why:** #3638 — brainstorm CTO claimed Sentry single-call `DELETE-by-tag` worked (docs misread — actual endpoint accepts only `id=N` list); brainstorm assumed `pino → Better Stack` pipeline (package.json showed no transport, Better Stack is uptime-only on `/health`); plan v1 missed `warnSilentFallback` + 2 direct `Sentry.captureMessage` sites in `ws-handler.ts:693, 719` because grep was scoped to the helper file. All three caught at plan-review by Kieran. See `knowledge-base/project/learnings/2026-05-12-plan-time-api-contract-verification-and-pipeline-via-package-json.md`.
159
+ - When a plan/spec/learning prose body references synthetic test-fixture secret tokens (e.g., `sk_test_<<format>>`, `ghp_<<format>>`, `AKIA<<format>>`), use a non-alnum placeholder shape (`<<...>>`, `‹...›`, or any wrapping that breaks `[a-zA-Z0-9]`) instead of literal tokens. GitHub's server-side push protection runs a tighter regex than the local `.gitleaks.toml` allowlist can affect (Stripe: `sk_(test|live)_[a-zA-Z0-9]{24,}`, GitHub PAT: `ghp_[A-Za-z0-9]{36,}`) — push is rejected even for clearly-synthetic fixtures. The actual fixture FILE at `test/fixture/.env.example` gets the literal token (allowlisted at the repo level via Settings → Secret Scanning); cross-referencing prose does not. **Why:** PR #3783 — plan/spec/tasks/learning prose for #2726 code-to-prd included a literal `sk_test_<<24+ alnum chars, no underscores>>`-shape fixture to illustrate a Kieran plan-review correctness finding; GitHub rejected the push twice. See `knowledge-base/project/learnings/2026-05-15-github-push-protection-rejects-synthetic-tokens-in-plan-prose.md`.
160
+ - When a plan migrates `logger.X({...}, "<msg>")` calls to a helper whose `message` is optional (`reportSilentFallback`, `warnSilentFallback`, similar), the AC MUST require explicit `message: "<original-string>"` carry-forward per site — otherwise the helper substitutes a sensible default and operator dashboards keyed on the original literal go dark silently. Pair with a **per-file inventory sweep**: for every migration-target file, `git grep -nE 'Sentry\.capture(Exception|Message)\(|logger\.(error|warn)\(' <file>` and explicitly scope-in or scope-out EACH occurrence — co-located sibling sites on the same route are the modal blind spot for inventory-driven plans. **Why:** PR #3731 (closes #3710) shipped 10/10 migrations with the default `"<feature> silent fallback"` overwriting 9 distinct dashboard-keyed strings (`"Failed to store service token"`, `"Repo clone failed"`, etc.) AND missed `accept-terms:60-67` (the DB-error captureException path) while migrating `accept-terms:73` (the captureMessage path) on the same file; both caught by multi-agent review P1. See `knowledge-base/project/learnings/2026-05-13-helper-migration-must-preserve-operator-dashboard-message-strings.md`.
161
+ - When a plan migrates `log.X({a, b, err}, "msg")` to a helper whose signature is `(err, {extra, ...})`, the AC payload-shape spec MUST omit `err` from `extra` — the helper already routes err to Sentry positionally and into pino's `{err, ...extra}` payload. Re-including `err: <var>` in `extra:` is gratuitous (silent override if spread order changes, asymmetric across if-error vs catch arms). AND when ACs grep for the helper name to count invocations, anchor on call-site indentation (`grep -cE "^\s*<helperName>\("`) NOT the bare name (`grep -cE "<helperName>"`) — the bare form double-counts the `import { helperName } from "..."` line and produces an N+1 expected count that fails verification. **Why:** PR #4393 (closes #4390) plan v1 specified `extra: { userId, err: anonXxxErr }` for all 11 if-error arms; soleur:engineering:review:code-simplicity-reviewer caught the redundancy at PR review (10-line dedup, applied inline). Same PR's AC2 expected `grep -cE "reportSilentFallback|warnSilentFallback" = 21` but actual count was 22 (import line); refined to `^\s*(report|warn)SilentFallback\(` at review time. See `knowledge-base/project/learnings/2026-05-25-helper-migration-extra-payload-deduplication.md`.
162
+ - When a plan proposes `lifecycle.ignore_changes = [<attr>, …]` to mask drift on a beta Terraform provider's import-only resource, run `terraform validate` against the proposed minimal body BEFORE finalizing the plan. `ignore_changes` runs at **plan-phase** (compare-and-suppress against state); per-attribute schema validation runs at **config-phase** (HCL parse before any plan). A provider's `validate` rule (min-list-length, required key, type-shape) fires regardless of how permissive `ignore_changes` is. For import-only resources, write the **minimum body that passes validate** (exactly one element per min-length list, exactly the required keys) and pick placeholder semantics that are **safe under accidental apply** — e.g., `notify_email { target_type = "IssueOwners"; fallthrough_type = "ActiveMembers" }` pages SOMEONE if `apply` runs before `import`, vs. a synthetic placeholder that silently drops pages. Also gate the auto-apply workflow to `-target=` ONLY the create-not-import resources (never run unguarded `terraform apply` on a root that mixes import-only and apply-friendly resources). **Why:** PR #3811 — `actions_v2 = []` paired with `lifecycle.ignore_changes = [actions_v2]` was rejected by `jianyuan/sentry` v0.15.0-beta2 at `terraform validate`; fix added a minimal IssueOwners placeholder and scoped `apply-sentry-infra.yml` to `-target=sentry_cron_monitor.*`. See `knowledge-base/project/learnings/2026-05-15-terraform-import-only-beta-provider-schema-validation.md`.
163
+ - When a plan AC uses awk's `/start/,/end/` range pattern to extract a section body for word-count or content-grep (heading-to-next-heading word count, fenced-block content), the range self-matches the start line — `awk '/^### X/,/^### /'` returns from the start line through the *next* line matching `/^### /`, which on the first row is the start line itself, returning only the heading. The AC silently passes on an empty body. Use a flag-based pattern instead: `awk '/^### X/{flag=1; next} /^### /{flag=0} /^## /{flag=0} flag' file`. The `next` skips the heading; multiple terminators handle adjacent boundary types. Test every awk-range AC against (a) a known-good fixture, (b) a known-broken fixture, AND (c) the empty-body case — if the empty-body case passes the verifier, the verifier is broken. **Why:** PR #3809 plan-review (Kieran P1) — FR2 AC `awk '/^### Primitive Choice: \/goal/,/^###/'` would have returned heading-only word counts and silently passed an empty H3 body. See `knowledge-base/project/learnings/2026-05-15-plan-ac-verification-commands-awk-self-match-and-marker-conjunction.md`.
164
+ - When a plan AC, recipe condition, hook predicate, or workflow gate combines two signals with AND, classify each as *implementation-invariant* (exit code, HTTP status, file existence, byte count) or *implementation-variant* (summary literals, prose text, format-version-specific output). Implementation-variant signals MUST NOT be the load-bearing half of an AND that gates correctness. Either drop the variant clause OR enumerate every implementation's output via grep (across `node_modules/`, docs, lock files) and ship the AC with an OR over all variants. Adding more ANDs doesn't make a verifier stricter — it makes it brittle. The most extreme manifestation is brand-survival: when the verifier is operator-facing (a `/goal` recipe, a Soleur-recommended condition pattern), the conjunction-that-never-resolves causes runaway API spend on a passing codebase. **Why:** PR #3809 plan-review (Kieran P1) — recipe #4 conjoined `exit=0` AND `"0 errors" OR "✓ all good"`; ESLint clean = empty output, Biome = "No fixes applied", Prettier = "All matched files use Prettier code style!", pytest = "N passed", bun test = "X pass / Y fail" — none match. The structured signal (exit code echoed as `exit=0`) is the canonical-across-implementations marker. See same learning file.
165
+
166
+ - Acceptance Criteria are load-bearing post-condition gates at PR-review time, not phase-output audits. Every AC must encode a *checkable post-condition* on file state, command output, or merged behavior. ACs that paraphrase phase instructions ("Phase X output captured", "operator runs Y") are ceremony — phases already mandate the output; cut these ACs. Verify the output's *shape* (e.g., "orphan_count = 0 in PR body" or "pg_policy returns 1 row with polcheck IS NULL"), not its existence. **Why:** PR-D plan v1 had 7 such LARP ACs (AC4/5/8/15/17/28/29); DHH plan-review cut them. See `knowledge-base/project/learnings/2026-05-16-plan-time-ac-discipline-prod-synthetic-users-gdpr-gate-value.md` Class (A).
167
+ - Any AC that prescribes running an integration test suite, e2e suite, or any code path that creates synthetic auth.users / Storage objects / conversation records / billing events against PROD violates `hr-dev-prd-distinct-supabase-projects` and creates DSAR/billing/support residue (synthetic `*@example.test` users visible to customer-search, audit logs, and Article 30 records of processing). Replace with strictly read-only verification: `pg_policy` shape checks, dry-run RLS predicate eval inside `BEGIN; SET LOCAL ROLE ...; ...; ROLLBACK;`, or `gh api`/`doppler secrets get` state probes. Full integration suites stay DEV-only. **Why:** PR-D plan v1 AC25 prescribed `bun test test/server/*.tenant-isolation.test.ts` against prod; Kieran plan-review caught (P1). See same learning file Class (B).
168
+ - At brand-survival threshold = `single-user incident`, `gdpr-gate` Phase 2.7 routinely produces fold-in items that brainstorm + Phase 1.1 research agents (repo-research, learnings, framework-docs) miss. The gate's compliance taxonomy (Layer/article: AP-01..AP-07, T-01..T-06, DL-01..DL-06, TS-01..TS-05) is orthogonal to the research agents' codebase-conventions lens. Do NOT defer gdpr-gate to "next plan" or scope it down at single-user threshold; the 3-fold-in pattern is the expected output, not noise. **Why:** PR-D gdpr-gate surfaced TS-05 (Storage cleanup gap), DL-04 (DSAR regression probe), Art. 9 §1 (AUP follow-up) — none of the 9 prior research agents caught these. See same learning file Class (C).
169
+ - When a plan extracts `spawn(...)` args from a GitHub Action workflow that uses a wrapper (e.g., `claude-code-action`, `peter-evans/create-pull-request`), the YAML `with:` block is parsed by the action's TS/JS wrapper — it is NOT the underlying CLI's flag list. Three drift vectors: binary name (`@anthropic-ai/claude-code` → `claude`, not `claude-code`), flag presence (`--prompt` may be a wrapper-only input rewritten before invocation), and arg positioning (the wrapper may pass a `prompt:` YAML scalar as a positional CLI arg). At plan-write time when binding spawn args, run `npm view <pkg> bin` AND `npx -y <pkg>@<version> --help` to read the actual binary name + flag shape. Probe undocumented flags via exit-code (`<bin> --hidden-flag value --print "ping"; echo $?`) — doc absence ≠ flag absence. **Why:** PR #3985 / TR9 PR-1 — plan inherited `--model … --max-turns 80 --allowedTools …` from `scheduled-daily-triage.yml`'s `claude_args:` field and prescribed `spawn("claude-code", ["--prompt", PROMPT])`. Phase 0.2 found binary is `claude` (not `claude-code`), `--prompt` doesn't exist (positional), `--print` is required for non-interactive. ADR-033 I4 amended at soleur:work time. See `knowledge-base/project/learnings/2026-05-18-claude-code-action-claude-args-vs-direct-cli-form-drift.md`.
170
+ - When the source issue body cross-references `#N` (PR-H, umbrella, peer issue) and the plan author probes via `gh pr view N`, a `Could not resolve to a PullRequest` response is necessary-but-not-sufficient to declare the reference fabricated. GitHub numbers are unified across PRs and issues; the symmetric probe is `gh issue view N`. Run BOTH before flagging a reference as unresolved or removing it from the plan body. Same disambiguation pattern as `soleur:go` §"PR-vs-issue type resolution" but applied at plan-time. **Why:** PR #4122 plan v1 claimed issue #4114's `#3244` reference was "fabricated" because `gh pr view 3244` failed; `#3244` is the legitimate CLOSED umbrella issue (`feat: Command Center server-side agentic runtime`) which PR #4066 closes one criterion of. Caught at multi-agent review by `soleur:engineering:research:git-history-analyzer`. See `knowledge-base/project/learnings/best-practices/2026-05-20-plan-time-pr-vs-issue-disambiguation-and-self-derived-counts.md`.
171
+ - When the plan body declares `brand_survival_threshold: single-user incident` in frontmatter, the exit-gate MUST recommend ultrathink/deepen-plan if not already invoked. Plan-review (DHH+Kieran+Simplicity) catches style/scope/YAGNI issues but is structurally blind to substance-level findings (SQL atomicity holes, plpgsql clock-skew, security primitives, missing secondary brakes) that deepen-plan domain agents (soleur:engineering:review:data-integrity-guardian + soleur:engineering:review:security-sentinel + soleur:engineering:review:architecture-strategist) catch. At single-user-incident threshold these are brand-survival-class issues, not polish. **Why:** PR-A byok-delegations #4232 plan v2 (post-plan-review) passed style review; deepen-plan triad caught 7 P0 architectural issues plan-review missed entirely (cap-check concurrency hole, `now()` vs `clock_timestamp()`, wrong-workspace resolver inference, unbounded cap CHECK, WORM Shape 1 audit-poison, no per-founder brake, missing reconciliation column). See `knowledge-base/project/learnings/workflow-patterns/2026-05-22-plan-review-and-deepen-plan-catch-different-issue-classes.md`.
172
+ - For synthetic-probe and live-API plans, verify the prescribed curl invocation against the live endpoint before committing the plan ("does `-sI` return 405?", "does this endpoint require an `apikey` header?"). Doc-only research is insufficient — the GoTrue `/auth/v1/authorize` endpoint declares `Allow: GET` (HEAD returns 405) and `/auth/v1/settings` returns 401 without the anon `apikey` header. Both quirks shipped through plan + deepen + multi-agent review and were only caught by pre-merge QA. **Why:** PR #3030 — see `knowledge-base/project/learnings/integration-issues/2026-04-29-supabase-auth-probe-and-sentry-rule-api-quirks.md`.
173
+ - When a plan asserts that a particular request shape (PostgREST `on_conflict=<cols>`, supabase-js `.upsert({..}, { onConflict: '..' })`, ORM `.where().firstOrCreate()`, GraphQL `@unique` directive resolution, etc.) will be resolved by an external query layer against a particular DDL shape (a partial vs non-partial unique index, an exclusion constraint, a multi-column unique with `NULLS NOT DISTINCT`, etc.), the plan MUST include a live-probe step that exercises the exact request shape against the target stack at plan time. Documentation alone is insufficient — vendor docs are often silent on edge cases (PostgREST docs only describe "columns with a UNIQUE constraint" — partial unique indexes are nowhere mentioned), and silence is not support. Grep-target for past-mistake smell: the literal phrase "no live probe required". **Why:** PR #3097 — plan `2026-04-18-fix-bot-fixture-hardening-plan.md` line 20 asserted PostgREST infers ON CONFLICT against partial unique indexes "no live probe required; Supabase docs confirm." Both halves were wrong. The first natural cron firing 13 days after merge returned 42P10 every run. See `knowledge-base/project/learnings/integration-issues/2026-05-03-postgrest-on-conflict-cannot-infer-partial-index.md`.
174
+ - Any prescribed shell-form invocation (`<tool> <subcommand> <args>` with non-trivial flag composition — `npm run -w <workspace>`, hyphenated Python module import, fabricated CLI flag) must be live-executed by the plan author or by `deepen-plan`'s research phase. The verification output (or "verified at `<date>`") MUST be pinned in the plan body. This catches the npm-workspace / Python-hyphen / hand-edited-CLI defect class at plan time rather than at first runbook use. **Why:** PR #3645 — plan prescribed `from backfill-frontmatter import …` against a hyphenated filename (parse error regardless of `sys.path`); PR #3751 prescribed `npm run -w <workspace> <script>` without a root-`package.json` `workspaces:` field. Both shipped through multi-agent review. See `knowledge-base/project/learnings/2026-05-13-npm-workspaces-flag-fails-without-root-workspaces-declaration.md` and `knowledge-base/project/learnings/2026-05-12-hyphenated-python-modules-and-plan-precondition-verification.md`.
175
+ - When citing a specific fact from another file in a plan AC or Sharp Edge — phase number from a sibling skill, vendor scope from a NOTICE/upstream-paths, script signature from a comment header — `grep`/`Read` the source file BEFORE writing the citation. Memory shape lies on phase numbers (legal-audit is 0/1/2/3, not 1/2/3/4), package names lie on lifted scope (gosprinto/compliance-skills is a PII-detector code-scanner, NOT legal-tooling — verify via NOTICE upstream-paths), and script flag conventions lie on signatures (`gdpr-gate.sh` expects `{staged_files}` not `--target`; always exits 0). 10-second plan-write-time grep beats minutes-to-hours of /work-time pivot. **Why:** PR #3785 plan v1 — three distinct paraphrase classes caught at plan-review (phase number 7+ times, vendor scope, script signature). See `knowledge-base/project/learnings/2026-05-15-five-agent-plan-review-revision-pass-brand-survival-plan.md`.
176
+ - At `brand_survival_threshold: single-user incident`, scope-outs justified by "next-most-likely entry not covered" are anti-pattern. The brand-survival threshold IS the requirement that the second-most-likely entry covers the surface — that's what single-user-incident means. Either ship the entry inline (small cost relative to the brand-survival blast radius) or downgrade the threshold (and re-frame the brainstorm). **Why:** PR #3785 plan v1 — `soleur:go` keyword routing scoped out as "next-most-likely entry"; soleur:product:spec-flow-analyzer correctly flagged as creating exactly the single-user-incident window the threshold guards against. Un-deferred at revision time. See same learning file.
177
+ - When a plan's Implementation Phases includes a step that modifies a separate-issue contract (deferred-issue criteria revision, sibling-spec field mutation, brainstorm Open Question rewrite), file it as its own commit/PR or as an explicit AskUserQuestion gate — never as a quiet implementation-phase side-effect. The plan IS the right place to correct upstream technical facts (spec TR4 was wrong → plan corrects), but NOT to correct upstream strategy facts (deferral conditions, brainstorm decisions). **Why:** PR #3785 plan v1 Phase 9 silently rewrote #3786's recorded re-evaluation criteria. DHH + Kieran + arch-strategist all flagged at plan-review. See same learning file.
178
+ - For static-doc cross-file invariants spanning ≤3 files, do NOT build dedicated atomicity-enforcement infrastructure (lefthook + test + CI step + bypass-path matrix). A single existing-test-surface check that asserts the invariant as a side-effect of an existing assertion is cheaper. **Why:** PR #3785 plan v1 Phase 7 proposed lefthook + test for 5 anchors across 2 source files; cut entirely at revision; replaced with anchor-resolution as side-effect of vendor-neutrality grep test in components.test.ts. See same learning file.
179
+ - When a plan prescribes pattern-bound behaviors with sibling-precedent files in the repo (SQL `SECURITY DEFINER`/`INVOKER`, atomic write sequences, lock/mutex patterns, RPC permissioning, connection-pool tuning, circuit-breaker shapes), the **precedent-diff gate** runs at `deepen-plan` Phase 4.4 — `git grep` for the precedent and a side-by-side diff in "Risks & Mitigations" is required there. Plans can punt deeper precedent-diff to deepen-plan but MUST cite the precedent's expected location (file glob or directory) so the deepen-plan agent has a starting point. If no precedent exists, the plan must note "no precedent; pattern is novel". **Why:** PR #2954 — initial plan asserted `SECURITY INVOKER` on the v1→v2 BYOK migration RPC; would have failed because `authenticated` lacks UPDATE on `api_keys` AND bypassed the row-lock semantics that make the predicate-lock approach race-safe. Deepen-plan corrected via precedent migration 027. See `knowledge-base/project/learnings/best-practices/2026-04-23-plan-quality-class-deepen-pass-catches.md`.
180
+ - Scheduled jobs: precedent check at `deepen-plan` Phase 4.4 (Inngest > GH Actions cron per ADR-033).
181
+ - When a plan verifies a named artifact is **absent / stale / unused** via grep, the verification grep MUST NOT be `head`-truncated (`| head -N`). Truncation is fine for *sampling* matches but is a latent false-negative for an *absence* claim — it can show early matches (a related symbol, a comment reference) while hiding the definitive definition line, and "absent" is the claim that then bounds the plan's option space. Use an exact-token anchored pattern (`git grep -nE "^\s*export .*function X\b"`), drop the truncation, or use `grep -c` and reason about the count. **Why:** #5754 plan — `git grep "...resolveActiveWorkspace" -- <file> | head -3` hid the real definition at `workspace-resolver.ts:398`, propagating a false "stale citation" premise into the canonical AC5; caught at plan-review by Kieran (P0) + spec-flow (P0-3). See `knowledge-base/project/learnings/2026-07-01-existence-grep-must-not-be-head-truncated.md`.
182
+ - When an AC greps for the **absence** of a forbidden token (`grep -c -- '--search'` == 0, `grep -ci 'your workspace'` == 0), first confirm the searched scope has no *legitimate* occurrence of that token. Prohibition lines, comments, and guardrails legitimately CONTAIN the forbidden token as documentation ("NOT `--search`: …", `**Never write "your workspace"**`) — so the absence-grep false-fails a correct file. Flip it: assert the **guardrail's presence** (`grep -c 'Never write "your workspace"' ≥ 1`) or scope the grep to a command line / render example (`awk '<section>' | grep -E '^\s*gh ' | grep -c -- '--search'`). Separately: when a plan premise turns on whether a capability is *active*, read the config flag's **value** (`grep -c '^enabled: true'`), not just that the config/row exists — a default-OFF opt-in reads as present-but-inert. **Why:** #6039 plan v1 — 3 ACs grepped for absence of tokens §5 legitimately contained as its own prohibition (Kieran P0 ×3); and the "0 promotions" premise was actually `promotion-config.yml: enabled: false`, not a quiet loop. See `knowledge-base/project/learnings/2026-07-06-ac-self-reference-grep-trap-and-verify-config-enabled-state.md`.
183
+
184
+ - **A hypothesis table may not read CONFIRMED or REFUTED while the plan's own text says the deciding datum is unavailable — and never delete the probe using a conclusion the probe was meant to test.** When a plan writes *"we cannot close that gap from the repo — the deciding datum is the unit's own stderr, and it is discarded at the source"*, that sentence disqualifies **every** verdict in the hypothesis table above it; the honest disposition is UNKNOWN, including for the hypotheses that feel refuted. Two mechanical tells: (a) **the confirming probe ran somewhere other than the failing host** — a dev-box `curl ""`, a laptop `psql`, a local `systemctl` establishes *capability*, never *actuality*, and is usually equally consistent with the hypothesis being refuted; (b) **a refutation that compares A-vs-B when the environment contains C** — "they use different `/tmp`s and cannot collide" is sound only over an *enumerated* set of writers, so `grep -rn <THE_SHARED_PATH_VAR>` before concluding two actors cannot collide. Corollaries: when a component fails and its siblings don't, **diff the units before theorising about semantics** (one asymmetry across an otherwise-identical set outranks any argument about how the mechanism *should* behave); and when an onset resists attribution to any change, test "nothing changed and it never worked" (host boot, first fire). **The probe-first phase ordering is load-bearing and must ship ALONE, in its own artifact, ahead of any fix** — "they ride the same image so no fire occurs between them, and there's nothing left to discriminate anyway" is circular (it rests on the very verdicts the probe exists to test) and means the ordering was never real, which is the problem, not a reason to drop it. **Why:** #6536 — the plan marked H5 CONFIRMED ("the sole live cause") from a dev-box `curl` + a Doppler key listing and REFUTED H3 by reasoning, one section below writing that the deciding datum was discarded; v5 then cut probe-first as *"fiction"*. The measured cause was H3 (root-owned `/tmp/.doppler`; the heartbeat was the lone doppler unit without `PrivateTmp=true`), H5's `curl` was never reached, and a full host replace was spent on a defect that did not exist — free only because the host was dark. See `knowledge-base/project/learnings/2026-07-16-refuting-a-hypothesis-by-reasoning-while-its-discriminator-is-invisible.md`.
185
+ - **A scrub/redaction pipeline the plan prescribes is a COMPOSITION, and its consumer table is a set of PROCESSES — write both at the granularity the code runs.** (a) When a plan spells out a redactor (`cap | strip | sed`), state in one sentence what string the regex actually sees — a truncation UPSTREAM of a redactor splits the token the regex was written against, so cap LAST and fixture the straddle. (b) When a plan changes a script's exit semantics, enumerate its consumers as "everything that EXECUTES these bytes" (`git grep -l '<basename>' -- '*.test.ts' '*.test.sh' .github`), not by workflow directory: a vitest that spawns the real script with `...process.env` is the consumer that runs in the environment the plan never imagined. (c) Never write "lives in <config> by design" about a credential until you have grepped every `secrets download` / `--env-file` / `EnvironmentFile=` reader of that config — a secret store's config is not a consumer boundary. **Why:** #8028 — all three shipped through a deepened plan and were found by the review panel and a coverage consult; the second would have `NOTIFY`ed prd from a unit test. See `knowledge-base/project/learnings/security-issues/2026-09-14-the-plan-capped-the-message-before-it-redacted-it-and-a-config-is-not-a-consumer-boundary.md`.
186
+ - **A guard over a runtime behaviour must read what the PROCESS performing it reads, as that process's user.** A setup script and the runtime often configure one thing through different channels (a config file vs a command-line override; root vs the service user); a probe of the setup's channel is correct about the wrong property and every mutant stays in-window. Name the process, then read its channel. **Why:** #8101/PR #8454 — the fence probe read system `core.hooksPath` as root while pushes use the transport wrapper's `-c` pin and git runs hooks as `git`. See `knowledge-base/project/learnings/2026-09-21-the-fence-probe-read-the-config-the-bootstrap-writes-not-the-path-a-push-takes.md`.
187
+
188
+ - **For any infra diff, answer "does merging THIS alone mutate production?" in the plan, from the workflow's `paths:` trigger and its `-target=` allowlist — and make the PR body's first line that answer.** A `variables.tf` default flip under a push-triggered apply IS the production change; there is no separate operator dispatch, so the merge click is the per-command authorization (`hr-menu-option-ack-not-prod-write-auth`). Same plan block: `discoverability_test.command` must name a command that exists in THIS PR's tree (a later PR's script behind a `credentials_required` waiver makes the ADR-175 gate SKIP-DECLARED and verify nothing), and `credentials_required: none` is a waiver-register entry, not "no credentials" — omit the field. **Why:** #8296/PR #8439. See `knowledge-base/project/learnings/2026-09-20-every-correction-i-shipped-needed-correcting.md`.
189
+ - **A plan whose `discoverability_test` declares `credentials_required` moves a repo-global ratchet the moment the plan is committed:** `BASELINE_DECLARED_PROBES` in `plugins/soleur/test/preflight-discoverability-test.test.ts` (#7393 G1) counts every declaring plan, so bump it with a PLACEMENT/TRUTH/NO-SUBSTITUTE entry in the same PR — no file-scoped suite selection reaches it, and it surfaces only in the full `bun` battery. **Why:** #4781/PR #8654 — found by a 104-minute pre-commit battery after 67 hand-run ratchets were green.
190
+
191
+ NEVER CODE! Just research and write the plan.
192
+
193
+ - **A `str.index`/`replace` keyed on a HEADING literal matches that same string inside the document's own prose, and the splice deletes everything between.** A plan authored with a scripted edit is the one document whose body quotes its own section names — the Proposed Solution routinely names `## Review & Consult Provenance`, the deepen pass names the halt gates it cleared — so an anchor chosen from the outline hits the prose occurrence first and the slice runs to the next match, silently removing the sections in between. Anchor on the heading AT LINE START (`\n## <name>\n`), assert the match count is exactly 1 before splicing, and after ANY scripted multi-edit re-count the document's sections against the count you started with. **Why:** #6488 — ten sections deleted mid-authoring; recovered only because a section count was re-run. Same family as the review skill's "a grep whose scope contains its own pattern".
194
+
195
+ - **A count written into an ADR, issue or plan must come from the executable procedure that artifact publishes, run once before the first write.** An ad-hoc tally and the published procedure diverge on definitions ("preflight anywhere in the session" vs "after the ship record"), and the ad-hoc one propagates first. **Why:** #8399 — a planning count of 19 reached four artifacts; the executable #8470 procedure and an independent review both measured 18. See `knowledge-base/project/learnings/2026-09-21-a-re-open-trigger-keyed-on-an-issue-closing-fires-at-the-fixing-merge.md`.
196
+
197
+ - **When a plan widens WHICH items reach an existing evidence check in front of a destructive action, it must state what that evidence is keyed on and show the key still binds for the added cohort.** The original check was only as strong as its original population needed. A name-keyed `gh pr list --head <branch> --state merged` coincides with "these commits are on main" only for branches that are still pushed, so admitting `[gone]` branches (which cannot push) turned it into a license to `git branch -D` post-merge commits and reused or fork head names. The fix's own RED/GREEN fixtures cover only the cohort the fix was about, so they cannot see this. **Why:** #8490/PR #8493, where four review seats converged on it as P1. See `knowledge-base/project/learnings/2026-09-21-widening-who-reaches-an-evidence-check-inherits-what-the-evidence-is-keyed-on.md`.
198
+
199
+ - When a plan prescribes TRANSPORT flags for a signal whose ABSENCE is the alarm (a boot emitter, a heartbeat, a phone-home), measure each flag's effect on the exit code against a local server that throttles (429 + `Retry-After`), stalls, and rejects, before writing the flags or a worst-case bound down. A retry flag can turn a failure into a success: with `--retry-max-time`, curl declines a retry whose `Retry-After` exceeds the budget and exits 0 even under `-f`; without it, a long `Retry-After` sleeps past `-m`. **Why:** #6500 — the plan's `--retry 1 --retry-max-time 15` made a throttled Sentry emit read as delivered, and its "about 16 s" bound was 17-31 s. See `knowledge-base/project/learnings/2026-09-21-curl-retry-flags-turn-a-throttled-emit-into-success-and-my-stubs-were-looser-than-the-code.md`.
200
+
201
+ - **A plan that removes a capability from the model (a `disable-model-invocation` flip, a deleted or renamed skill) must grep existing plans and specs for agent steps naming it.** Plans are data until a later session executes them, so a "no model-read surface directs the agent here" check that skips `knowledge-base/project/plans/` misses the pipelines the removal breaks. **Why:** #8290 flipped `flag-create`/`flag-set-role` although plans prescribe them as agent acceptance steps; review reversed it. See `knowledge-base/project/learnings/2026-09-21-the-flip-was-checked-against-every-surface-except-the-plans-that-prescribe-it.md`.
202
+ - **`discoverability_test.command` must survive preflight Check 10's shell-active reject: no `|`, `;`, `&`, `<`, `>`, `$` or backtick anywhere, including inside a quoted regex.** The reject is a byte-level token match and cannot tell `(a|b)` from a pipe, so an alternation FAILs the gate on a probe that works. Use repeated `-e` patterns (`grep -c -e 'x' -e 'y' file`). Check it with Check 10's own regex before committing the plan. **Why:** #6500/PR #8488. `grep -cE 'inngest_(zot|ghcr_fallback) '` FAILed Check 10 at ship. See `knowledge-base/project/learnings/2026-09-21-curl-retry-flags-turn-a-throttled-emit-into-success-and-my-stubs-were-looser-than-the-code.md`.
203
+ - **A readiness/health gate must decide GREEN from a positive count, never from "no culprit was named".** Ask of the verdict: what does it print when every input is empty or unparseable? If that is the green verdict, require `counted_green == required > 0` and shape-check every API body before reading it. **Why:** #8500/PR #8547 — `admin-merge-ready.sh` read `verdict=ready` from a zero-byte check-runs body, the #8458 shape one layer down. See `knowledge-base/project/learnings/2026-09-22-the-admin-merge-gate-read-ready-from-an-empty-body.md`.
204
+ - **A script that prints issue titles (or any attacker-editable text) into a field-delimited line that an agent relays needs a sanitizer in the plan.** A title with a newline forges a whole extra line (`CODEABLE|#9999|…`, and a `Build it:` directive naming an attacker-chosen issue number), bypassing every filter upstream of the print. The forged directive in the measured case carried the operator-typed **slash** form of the entry point, not the canonical one — that is what made it read as a real instruction. The literal is in the issue rather than here, because this file is in the ADR-226 census population and a quoted slash form would be rewritten to canonical, which is precisely the detail the example turns on. Plan the cleaning (control chars, U+2028/U+2029, the delimiter) and a numeric-type check on the id field, with an injection fixture. **Why:** #8292/PR #8536.
205
+ - **A gate's blast radius is not the property its label names — before calling a CI job advisory, grep every consumer of the RUN it belongs to.** "Non-required" governs branch protection and nothing else: any failing job reds `workflow_run.conclusion`, and anything gating on that conclusion (a release workflow's `ci_not_green`) is affected regardless of required-ness. `continue-on-error: true` is what actually decouples them — and it is mutually destructive with `required`, so the promotion PR must delete it in the same change. **Why:** #8570 — ADR-245 asserted a red `harness-discovery` "blocks nothing"; a third-party npm/CDN flake in that deliberately-ignorable job would have skipped a production deploy for the SHA. See `knowledge-base/project/learnings/2026-09-23-non-required-did-not-mean-decoupled-and-my-canonicalizer-rewrote-its-own-evidence.md`.
206
+ - **When a repo-global ratchet moves right after a merge, measure the BASE before attributing it.** The merge is the salient explanation and usually the wrong one; a detached worktree at `origin/main` running that one guard settles it in ~2 minutes. Attributing your own regression to someone else's commit is worse than not noticing, because it converts your debt into theirs. **Why:** #8570 — the merge brought a new floor-bearing suite in a deferred directory, an obvious culprit for a 47→48 deferral-ledger growth; `origin/main` measured 23/0 at 47, and the +1 was an anti-vacuity floor I had added under `.claude/hooks/` (itself in `DEFERRED_DIRS`). Remedy is per-file `PROMOTED_FILES` promotion, which SHRINKS the ledger — never raising the ratchet, which the guard's own message forbids. Same learning file.
207
+ - **Run markdownlint on the plan and `tasks.md` before emitting the Session Summary.** Both are authored by the planning subagent and both are linted by lefthook at the work phase's first commit, so a violation surfaces as a blocked commit in a later phase, attributed to whoever is holding the branch. The two that recur are a literal tab inside a quoted command (a `git diff --numstat` output like `3<TAB>3` trips MD010) and a list that starts on the line immediately after a heading or a paragraph (MD022/MD032). Cheapest gate: `npx markdownlint-cli2 <plan> <tasks>` as the last step before the summary. **Why:** #8535 — the plan carried two hard tabs and one MD032, and `tasks.md` carried four more; both were found by the work phase, one of them only after the review round.
208
+ - **Before a plan review deletes an assertion as redundant, name the mutation it kills and check that a surviving assertion kills it too.** "The literal only proves consistency" is an argument about intent; a surviving mutant is a measurement. **Why:** #8627 — plan review cut an exact heading total from an anchor test, and the review panel found it was the only assertion that caught a Phase 5.5 heading deleted from `ANCHORS`. See `knowledge-base/project/learnings/2026-09-23-a-sub-step-hands-its-exits-to-its-caller.md`.
209
+ - **A plan that prescribes a fake for a real dependency (`gh`, `psql`, a mutex script) must name the real contract's multi-line output, defaults and last-value-wins flags the fake replays.** A fake written by the consumer's author encodes the consumer's reading, so a bug living between that reading and the real tool is invisible to every case. **Why:** #8642 — a one-line fake mutex hid a writer that refused a lock it held (the real `acquire` prints WAITING before ACQUIRED), and a fake `gh` that ignored `state=` let a `state=all` deletion survive; see `knowledge-base/project/learnings/2026-09-24-my-fakes-agreed-with-my-writer-and-the-real-mutex-did-not.md`.
210
+ - **A plan that makes a gate BLOCKING over shared dev (ledger probe, schema scan) must say which ref owns each flagged row.** Shared dev is main plus every open PR's applied migrations (ADR-061), so "no legitimate unmerged state on push" is false: classify in-flight rows as warnings and block only ownerless ones. **Why:** #7964 §M2 assumed that premise, and main went red on an open PR's row (#8520). See `knowledge-base/project/learnings/2026-09-23-a-blocking-gate-over-shared-dev-must-say-who-owns-each-row.md`.
211
+ - **Before a plan claims a telemetry field is null, absent or zero, count its population over the full export at the field's CURRENT nesting path — two eyeballed rows are not a census, and a zero can be a wrong JSON level.** Pino fields sit under `raw.message` since #8344, so a top-level `JSONExtract*` reads 0 on every row; and an emitter's own cost/total field outranks a re-derivation from components (tokens miss sub-agent spend). **Why:** PR #8611 — a plan built "Fix 4: root-cause null `cost_usd`" on two sampled markers; it was populated on 118/118, and the token-derived spend undercounted the field by a third. See `knowledge-base/project/learnings/integration-issues/2026-09-23-cloudflare-524-made-every-long-inngest-step-run-twice-and-commit-nothing.md`.
212
+ - **A spike or verification step must reproduce the production process's FAILURE semantics, and a detector must read a quantity the failure changes.** Install every fatal-error handler the production entrypoint installs (a crash handler that exits on `uncaughtException` turns a logged SDK throw into a restart), cut connections rather than processes whose durable state the spike host lacks (an in-memory queue loses the retry you are measuring), and never detect duplication by comparing two figures written in the same step (a re-run step writes both again, so they still agree). Prefer a field the retry changes (`attempt`, a per-turn key) or an independent count. **Why:** #8611 — the streaming spike on plain `next start` read the inngest 3.54.2 heartbeat-leak crash as log noise; the first stream-cut row lost its retry to `inngest start`'s in-memory queue; the plan's BYOK double-bill check compared summed markers to `cumulativeCents`, both rewritten by the retry. See `knowledge-base/project/learnings/integration-issues/2026-09-23-cloudflare-524-made-every-long-inngest-step-run-twice-and-commit-nothing.md`.
213
+ - **A brief's CONCLUSION sentence ("state plainly: X") is a claim about repo gates, not a deliverable — name the gate that would falsify it and read it before the plan pastes X into an ADR or issue.** A requester's framing arrives beside a "context you can trust" block and inherits its credibility. If X is false, post the corrected version and record the deviation in `decision-challenges.md`. **Why:** PR #8666 — "one authorization act on #6122" was posted verbatim; `zot-soak-6122.sh`'s `WEB_BLOCKER=8651` arm made it false. See `knowledge-base/project/learnings/2026-09-24-the-briefs-bottom-line-was-a-claim-and-my-records-pr-softened-an-observed-failure.md`.
214
+ - **"Do it the way workflow X does" is a claim about X's whole job, not the line you copy.** Before mirroring a precedent's credential, read what else reaches the process: every earlier step's `$GITHUB_ENV` exports (a credentials loader can export a whole Doppler project, `TF_VAR_*` included). When the receiver is third-party code, prescribe an `env -i` ALLOWLIST, never an `env -u` deny-list. Before prescribing "dispatch X" as a remediation, check X's gates per case (a create no diff explains, a destroy needing an ack a dispatch cannot carry). **Why:** #6612/PR #8679 — the deny-list missed `HCLOUD_TOKEN`, `TF_VAR_sentry_org` and `TF_CLI_ARGS*`, and the dispatch remediation was refused for two of the three drift classes. See `knowledge-base/project/learnings/2026-09-24-i-mirrored-the-applys-token-line-and-not-the-job-around-it.md`.
215
+ - **A plan that declares a non-placeholder `discoverability_test.credentials_required` moves a corpus count: bump `BASELINE_DECLARED_PROBES` in `plugins/soleur/test/preflight-discoverability-test.test.ts` in the same PR, with the PLACEMENT/TRUTH/NO SUBSTITUTE comment its failure text asks for.** A commit made with `LEFTHOOK_EXCLUDE=bun-test` skips that suite, so the red surfaces a commit later. **Why:** #8651. See `knowledge-base/project/learnings/2026-09-24-the-gate-the-issue-blamed-was-never-reached.md`.
216
+ - **A plan that adds a path to a CI or runner selection list (a workflow `paths:` filter, `AFFECTED_*_PATHS`, a suite registry) must name the CONSUMER that reads that list for the diff shape in question, and quote the line.** A list nothing consults is inert while reading as coverage. **Why:** #7761/PR #8690 — the plan added the probe to `AFFECTED_INFRA_RUNNER_PATHS`, but `test-all.sh` gates the infra runner on `_infra_in_diff` alone, so the entry never selected anything; review dropped it.
217
+ - **A latency or queue figure that enters a budget (a Sentry margin, a timeout, an SLO) must be measured on the population the budget governs, with the command cited — and "the other replica covers it" must name a failure both replicas share.** A sample from sibling workflows is a different population: #8495's plan sized a watchdog's check-in margin on "queue 0 s in 39/40 runs" taken from OTHER workflows, while the watchdog's own jobs (`gh api repos/<o>/<r>/actions/runs/<id>/jobs`, `started_at − created_at`) queued p90 ~20 min — the margin would have missed ~40% of slots. The same plan assumed a second host covers a failed tick; a GitHub API blip hits both at once. See `knowledge-base/project/learnings/integration-issues/2026-09-24-a-reliable-dispatch-clock-still-waits-in-the-runner-queue.md`.
218
+ - **A plan that adds prose to a lifecycle SKILL.md must measure that file against its byte ceiling first and budget the prose as a delta.** Ceilings live in `plugins/soleur/test/skill-body-budget.json` and are enforced against the merge base by `scripts/lint-skill-body-budget.py --base origin/main`; record `ceiling - wc -c` in the plan. Wording written without that number gets compressed under a failing hook at work time, and the compression drops conditions while every anchor survives. **Why:** #8647 — the planned B10 prose put `compound/SKILL.md` 1100 bytes over; compressing it dropped two conditions that four review seats then traced. See `knowledge-base/project/learnings/2026-09-23-compressing-prose-to-fit-a-byte-ceiling-dropped-the-conditions-that-made-it-correct.md`.
219
+ - **A plan prescribing a race/size regression fixture in a `*.test.sh` must floor the CAUSAL quantity, give every negative row a positive control on the same input, and place any owning EXIT trap BEFORE `source test-helpers.sh`.** A file-size floor survives de-listing the filler or moving the needle last; an `expect rc 1` row passes on an unreadable file; and "no trap, just `rm -rf`" fails `lint-trap-tempfile-ownership` rule (c). **Why:** #8644 — see `knowledge-base/project/learnings/test-failures/2026-09-23-my-sigpipe-regression-guard-pinned-the-file-size-not-the-cause.md`.
220
+ - **"This site follows convention X" is a claim about the site's bytes: grep for X's marker across every file the claim covers before writing it.** A convention being established elsewhere is exactly what makes the sentence read as settled. **Why:** #8317 — plan D4 said four agents "follow the canonical-then-render convention" (the `operator-typed-render` block, carried by 10 skills), but zero agents carried it, so they printed `soleur:pencil-setup` to Claude Code users. See `knowledge-base/project/learnings/2026-09-24-a-plan-that-says-follows-convention-x-shipped-without-x.md`.
221
+ - **When the deliverable acts on every member of a set another mechanism produces (every workflow's runs, every table's rows, every host), enumerate the members whose lifecycle differs before writing the rule.** A reaper that cancels "superseded" runs assumes each run is replaced on the next push; a workflow triggered only by `opened`/`closed` is never replaced, so cancelling it loses the event. Key the population on a declaration that states the property (here the fan-out ledger: "fires on `synchronize`"), not on the event type. **Why:** #8669 — plan, deepen and the CTO pass all missed `board-status-sync.yml`; the security review found it.
222
+ - **A guard over a structured language (HCL, YAML, a shell script) must LEX it — strings, comments, heredocs, bracket depth — never scan lines keyed on column 0.** "`terraform fmt` normalises the layout" is not a guard when `fmt -check` is not a required check, and five `fmt`-CLEAN layouts (a heredoc opener inside a string or comment, inline and mid-line `/* */`, text after `*/`) still defeat a line scanner. **Why:** #8667. See `knowledge-base/project/learnings/2026-09-24-a-line-scanner-is-not-a-lexer-and-my-guard-fixture-hid-the-suite.md`.
223
+ - **Never build an env-assignment prefix from a conditional expansion.** `${X+VAR="$X"} cmd` expands to a WORD, not an assignment, so bash runs `VAR=…` as a command (rc 127) and a test asserting only a non-zero rc passes vacuously. Always assign (`VAR="${X-default}"`) and assert the refusal's reason. **Why:** #8703 H6.
224
+ - **Before a plan asserts which status code a vendor returns for an error class, grep the repo for an incident record of that class.** A remembered status is a claim; an in-repo probe or post-mortem is a measurement. **Why:** #8717 — the plan said credit exhaustion is a 402 while `cron-anthropic-credit-probe.ts` recorded it as HTTP 400; five review seats caught the misroute. See `knowledge-base/project/learnings/integration-issues/2026-09-24-a-thrown-error-loses-its-status-at-the-inngest-step-boundary-so-return-the-verdict.md`.
225
+ - **A fail-closed change is not done until its most natural REPAIR is also refused.** For every new refusal (exit code, abort message, presence check), write the one line a stuck agent will type in response and run it: if it passes, the check points at the hole. And trace each fail-closed shell call to its CONSUMER — a tool that exits 0 with empty output turns "could not run" into "found nothing". **Why:** #7453 — exporting the checkout's own `plugins/soleur` as the root satisfied the admin-merge presence check and made the `--admin` gate the PR's own copy; the auto-close scan read an unset root as "no traps". See `knowledge-base/project/learnings/2026-09-24-every-refusal-i-added-had-a-one-keystroke-repair-that-reopened-it.md`.
226
+ - **A plan that changes what an Inngest step RETURNS (or renames its id) must say how the new code reads the OLD memoized value.** Step output is persisted state that outlives the code that wrote it: a run memoized under the old code resumes on the new code after the deploy (the deploy drain waits only for a live child process), so a reader that assumes the new shape crashes outside every catch. Specify a shape-tolerant reader plus a harness test that seeds the old memo. **Why:** #8726 — both returned-verdict migrations would have crashed the in-flight cron runs of their own deploy; caught only by the review panel. See `knowledge-base/project/learnings/integration-issues/2026-09-24-changing-a-step-return-shape-strands-in-flight-runs-and-a-boundary-redaction-blinds-the-detector.md`.
227
+ - **An AC that sweeps the repo for a retired phrase must exclude the plan that quotes it** (`git grep -n '<phrase>' -- ':!knowledge-base/project/plans'`), or the AC is unsatisfiable by construction. **Why:** #8630 — AC10 matched the plan's own reconciliation table and its own AC line.
228
+ - **A plan that changes a Terraform resource must enumerate EVERY workflow that can apply it — not only the one it routes through.** For each push-triggered workflow whose `paths:` include a file the diff edits, ask whether its `-target` graph reaches the resource (dependencies are pulled in transitively) and whether its guard counts that change class. A comment-only edit to a shared `.tf` file can trigger a second apply workflow whose plan performs the change without the intended gate. **Why:** #8705 — a "required" `server.tf` comment edit would have let `apply-deploy-pipeline-fix.yml` (which reaches `hcloud_server.web` and, through its user_data, the rotated token) rotate the credential with no `[ack-destroy]` and no installer re-fire. See `knowledge-base/project/learnings/security-issues/2026-09-24-a-second-apply-workflow-could-perform-the-rotation-without-its-gate.md`.
229
+ - **A privacy/sanitization claim worded "X never appears in the event" is about the WHOLE event, so the plan must enumerate the event's contributors before naming where the guarantee lives.** Contributors: the call's own args, middleware scope extras (`sentry-correlation`'s `inngest.event_data`), breadcrumbs, and free-text fields that embed other values (`err.message`). Prefer enforcing at the boundary every event passes (`server/sentry-scrub.ts`) over one call-site field, and write the AC's test against every sink, not the call args. **Why:** #8719/PR #8794 — the test checked `captureMessage` args while the founder id reached Sentry via the middleware extra and an error message, and a sanitized `extra.tool` sat beside the raw name in `err.message`. See `knowledge-base/project/learnings/security-issues/2026-09-25-a-no-raw-id-claim-tested-at-the-call-args-missed-three-sinks-on-the-same-event.md`.
230
+ - **A gate's refusal message is an instruction the operator will execute in the TRANSITION state the change creates — plan it against that state.** When the change can refuse the value production currently uses (a pinned tag, an existing row, a live config), the message must branch on "is this the live value?" and never prescribe deleting or re-using it. **Why:** #8747 — "delete the tag and re-cut it" named main's own pinned tag right after merge. See `knowledge-base/project/learnings/2026-09-25-my-ancestry-gate-was-sound-and-its-harness-and-its-recovery-text-were-not.md`.
231
+ - **A gate that picks WHICH object to check from a command string must bind to the real invocation, not to text.** Enumerate spellings that name the target without invoking it (`echo`, a `#` comment, a `||`/`;` sibling, a quoted or suffixed argument) and spellings that retarget it (short/attached/combined flags, env vars, a `cd`); require the parse to agree between the raw and quote-stripped command. **Why:** #8778/PR #8779 — "first digits after `gh pr merge`" let a MERGED donor PR's evidence approve a different merge; see `knowledge-base/project/learnings/2026-09-24-the-gate-resolved-its-target-from-text-that-was-not-the-invocation.md`.
232
+ - **A shell step added to a skill must survive every runner that invokes the skill, and must never carry model text in a heredoc.** Grep `apps/web-platform/server/inngest/` for crons running the skill and read their `CRON_BASH_ALLOWLISTS` entry plus the containment hook's global denials (substitution, interpreters); name the "Bash refused" outcome in the skill. Write model-generated text with the Write tool, not a heredoc, because a line equal to the terminator runs the rest as shell. **Why:** #8774/PR #8801 — content-writer's new scan was dead on the cron that writes most posts, and its heredoc was injectable; see `knowledge-base/project/learnings/2026-09-25-a-skill-step-that-shells-out-is-dead-code-under-the-cron-that-runs-the-skill.md`.
233
+ - **An AC that quotes a whole-repo scanner count (`kb-drift-walker.sh`, a lint census) must be a DELTA against the merge-base in one environment, and the plan's prose must not contain the scanner's trigger syntax.** The walker counts `](x.md)` inside inline code as a link, and one learning anchors into `node_modules/`, so the same tree reads 120 or 121 broken anchors depending on whether dependencies are installed. **Why:** #8842 — an absolute `<=128/<=120` AC failed on the plan's own prose, then on the environment. See `knowledge-base/project/learnings/2026-09-25-kb-drift-walker-counts-are-environment-and-prose-sensitive.md`.
234
+ - **An AC that proves "this PR does not touch X" must diff against the merge-base, not the moving tip.** `git diff --quiet origin/main -- X` goes RED the moment a sibling merge touches X, while the branch's own diff is empty. Write `git diff --quiet origin/main...HEAD -- X`. **Why:** #8616 — AC6 read RED after #8756 changed `ci.yml` on main, with a clean branch diff.
235
+ - **A plan that makes a check conditional (an env var feeding `test.skipIf`) must name the CI-side assertion that it RAN, and its mutation rows must hit the `skipIf` call sites.** A skipped test exits 0, so an env-guard on the scope expression protects that expression only. **Why:** #8878 — `skipIf(true)` left CI green with the calibration dark; see `knowledge-base/project/learnings/test-failures/2026-09-25-an-env-scoped-skipif-lets-ci-go-green-with-the-check-dark.md`.
236
+ - **A guard check rewritten as a NEGATED grep (`! grep -q`, `! grep -qv`) must still fail on grep's rc 2.** A negation folds "pattern did not compile" into "no match", so a typo in the pattern passes the check that exists to catch typos. Prescribe count comparison (`grep -c` hits == line total / == 0, which prints nothing on error) or an explicit `[[ $rc -eq 1 ]]`, plus a compile pre-check. **Why:** #8807/PR #8866 — the plan's per-line probe replaced a positive `grep -qE p bad && ! grep -qE p good` whose rc 2 failed loudly; a dropped paren then passed all three checks at exit 0 until review mutated it.
237
+ - **Count a defect class's sites with the drift guard's own pattern over a `**` glob, never with a search keyed on a variable name.** A deferral that lists "the N sibling sites" is a claim about a set. A census keyed on `"$log"` missed the `"$OUT"` spelling and every site outside the directory it searched. **Why:** #8664 — the plan listed 6 sites in 4 batteries, and re-counting with `grep-q-pipe-guard.test.sh`'s PATTERN found 2 more (`learnings/test-failures/2026-09-25-the-flake-was-sigpipe-at-4kib-and-my-fix-leaked-a-pipe-status-through-a-bare-return.md`).
238
+ - **A plan that ARMS or STARTS a unit that was dormant must re-read that unit's dependency directives, because they have never run either.** `RequiresMountsFor=`/`Requires=`/`BindsTo=` are Requires-strength: starting the unit starts the dependency, so a read-only probe with `RequiresMountsFor=/mnt/data` would mount whatever `/etc/fstab` names the first time it runs while the path is unmounted. A plan line saying "no changes to <unit>" is a claim about bytes, not about behaviour that has never executed. Prefer ordering-only `After=` for probes that check the mount themselves. **Why:** #8706 — only the data-integrity review caught that arming `luks-monitor.timer` on web-1 could mount the superseded plaintext volume.
239
+ - **A review cut justified by "this regression already fails closed" is a claim to MEASURE before the cut, not after.** Apply the one-line mutation the check exists for (the call site becomes `if false`, the source line is deleted) and confirm something reds. Only then drop the check. When a shared helper replaces N inline copies, the helper's own suite can never see its call sites. Pin them with a fail-closed probe seam plus a suite that derives the consumers and requires each one to reach it. **Why:** #8855 — plan review cut "routing presence" as failing closed; review measured `OK: 20/20` with the call neutered. See `learnings/test-failures/2026-09-27-extracting-a-shared-scorer-made-the-call-site-the-unpinned-wire.md`.
240
+ - **A CI log line is evidence about the step whose `##[group]Run …` contains it — read that header before citing it.** Actions logs every step's `with:` inputs, so a keyword (`ssh-strict: true`) can come from an unrelated action's defaults (`actions/checkout`) rather than the path you are proving. Cite the control by the step that applies it. **Why:** #9036 — see `knowledge-base/project/learnings/2026-09-27-a-log-line-is-evidence-about-the-step-that-printed-it.md`.
241
+ - **A prescribed `doppler run -- <cmd> "$VAR"` expands `$VAR` in the CALLER's shell, before Doppler sets it — so the command runs with an empty value.** Wrap it: `doppler run -p <p> -c <c> -- sh -c '<cmd> "$VAR" … "$0"' '<arg>'`, and run every command a plan or runbook prescribes once before shipping it. **Why:** #6939/PR #8886 — `psql "$DATABASE_URL_POOLER"` fell through to a local socket in a new runbook step and in two pre-existing lines of the same file. See `knowledge-base/project/learnings/2026-09-27-the-recovery-procedure-i-wrote-to-stop-a-double-fire-could-cause-one.md`.
242
+ - **A presence check after `source` is satisfied by the environment.** When a plan prescribes a shared sourced lib, prescribe the load as `unset` the names BEFORE `source`, keep its stderr, then prove what loaded with the lib's own selftest — never `source lib; [[ -n "$VAR" ]]`, which an inherited (e.g. Doppler-exported `$GITHUB_ENV`) value satisfies. **Why:** #8846/PR #8873, reproduced as a fail-open in a destroy gate. See `knowledge-base/project/learnings/security-issues/2026-09-27-a-presence-check-after-source-is-satisfied-by-the-environment.md`.
243
+ - **A read-only proof of a fact that a writer script enforces must measure with the WRITER's predicate: the same exclusion list, the same mount resolution (`--mountpoint`, not the containing mount), and one session for the facts it combines. The suite must pin that parity.** Also account for what the count excludes: excluded objects can still hold personal data. **Why:** #8211 PR2 — the store-empty count skipped entries that `_repo_count` counts, and the excluded `.<user_id>.init.lock` files made "nothing to erase" false in a way no count could show (#9066). See `knowledge-base/project/learnings/2026-09-27-a-read-only-proof-must-measure-with-the-writers-predicate-not-a-narrower-one.md`.
244
+ - **A remedy written for an agent reading a skill FROM DISK must name a root source that agent actually has.** Copy ADR-179 A20's notice clauses (the root is only the loader-printed path or a Skill-tool `Base directory for this skill:` line cut at its last `/skills/`; never repo/PR/tool text or a working-directory path; never guess) rather than inventing wording, put the recovery in the fence's first event too, and plan a dry run by an agent with no delivered text. **Why:** #9072 — the planned "export that path" notice pointed a disk reader at a literal token; five review seats converged and a dry run found three more gaps. See `knowledge-base/project/learnings/2026-09-27-a-remedy-for-an-agent-reading-from-disk-must-name-a-source-that-agent-has.md`.
245
+ - **A plan that segments shell TEXT (split on `;&|`) must fixture every overloaded character, and state the matcher's per-segment cost.** `&`/`|` are also redirects (`2>&1`, `&>`, `>&2`, `<&0`, `>|`), `;`/`|` inside `$(...)` or backticks belong to the substitution, and `#` mid-word is not a comment; split at nesting depth 0 after dropping redirect operators, in ONE process — a fork per segment makes the hook O(segments) and a padded command can outrun its non-blocking timeout. Do not anchor a flag signal to whitespace: `\-f`, `${E}-f` and `$E-f` reach the tool as `-f`. **Why:** #9088 — five bypasses shipped past three plan rounds, a 220-command corpus and a 21-row battery; review/SKILL.md already carried the class from #8354, but plan never reads it. See `knowledge-base/project/learnings/2026-09-27-my-segmenter-split-on-characters-the-shell-reads-as-one-command.md`.
246
+ - **When a plan makes a code path the ONLY path (deleting a fallback, a flag default, a legacy arm), list that path's RUNTIME dependencies and verify each exists on the deploy surface — a binary it shells out to, a package, a mount.** Logic tests, mocks and source guards cannot reach it; one `docker run <deploy base> sh -c 'command -v <bin>'` can. **Why:** #9096 — the fallback deletion made the pinned SSH transport the only git-data path, and the runner image (`node:22-slim` + `--no-install-recommends`) had never shipped `ssh`, so every erasure had always read `unreachable`. See `knowledge-base/project/learnings/2026-09-28-the-pinned-path-i-made-the-only-path-had-never-run-because-the-image-had-no-ssh.md`.
247
+ - **A "sample after X" is a later measurement only if the sampler ticks faster than X runs — compare the sample's own timestamp with the baseline's, and re-poll (bounded) before accepting it.** Otherwise the newest row after a short operation is usually the baseline row itself, and a field meant to carry a measured value carries a copy. Also give a spawned sampler an `env -i` allow-list, never an `env -u` deny-list. **Why:** #7377 — a 5-minute heartbeat vs a 104 s sweep; the first design re-read the START row, and its deny-list leaked the step's Doppler token. See `knowledge-base/project/learnings/2026-09-28-a-dead-label-route-and-an-end-sample-that-reread-its-start.md`.
248
+ - **A retirement census grepped by NAME misses consumers that pin the thing by ID — and identical Doppler value hashes do not prove a branch config INHERITS a key.** When a plan retires a function, resource or credential, also grep its opaque identifiers (the Inngest function UUID, a resource id, a token slug) across `scripts/`, fixtures and population files, and run the repo's destroy/census guards (`tests/scripts/test-infra-privileged-tier-census.sh`) against the planned deletion before calling the census complete. **Why:** #9071 — `inngest-soak-6178.function-ids.txt` pinned the minter's UUID, so its registry gate would have exited 3 forever once the function was deleted (review P1); the census's G4c had no path for an intentional destroy (CI red); and `prd_terraform` held its OWN `GHCR_READ_*` entries despite matching hashes (Doppler log). See `knowledge-base/project/learnings/2026-09-27-a-retirement-census-by-name-missed-the-consumer-that-pinned-the-id.md`.
249
+ - **A plan that moves or removes a BACKSTOP (a dead-man timer, a trap, a watchdog) must list, per abort path, what the process RECEIVES and what its trap can OBSERVE — measured with a local bash probe, never reasoned.** `ssh` without `-t` delivers no SIGHUP on a drop (the script dies of SIGPIPE on its next write), and `$?` inside an EXIT trap after a signal is the last command's status (usually 0), so a trap that branches on `rc -eq 0` takes its success exit. **Why:** #9045/PR #9098 — "an SSH drop delivers SIGHUP, and SIGHUP runs the EXIT trap" survived plan review, deepen and the ADR; review measured both halves false. See `knowledge-base/project/learnings/2026-09-28-an-ssh-drop-without-a-pty-is-sigpipe-and-my-cleanup-trap-read-it-as-success.md`.
250
+ - **A gate that answers "did X change?" through a MODEL of X (an offline render, a restated config map, a fixture copy) is blind to every input the model stubs or restates — list them in the plan and compare each by value or text alongside the model.** **Why:** #7582 — the render-diff fix for the registry dispatcher stubbed the volume id, token key, heartbeat URLs, memory cap and arch and hand-copied six literals; the structural-enumeration seat found each as a path to `deliver=false` with host bytes changed. See `knowledge-base/project/learnings/2026-09-28-an-offline-render-gate-inherits-every-stub-as-a-blind-spot.md`.
251
+ - **A classifier that reads a verdict from free text (stderr, a log line, an error message) is trusted only as far as the LEAST-trusted writer of that channel — list every writer before prescribing the match.** A process's cwd, the files it quotes in its errors, and a remote's passthrough are all writers; when the verdict feeds a pager, a forged event joins the genuine issue and inherits its throttle window, so forgery SUPPRESSES the real page. Read the verdict from the one channel with no untrusted writer. **Why:** #8572/PR #9150 — `host_key_mismatch` on git's exit 128 + stderr was forgeable through `fatal: unexpected line in .git/packed-refs: <line>`; fixed by classifying only the provision dial's ssh stderr. See `knowledge-base/project/learnings/2026-09-28-my-pager-read-its-verdict-from-bytes-the-tenant-could-write.md`.
252
+ - **A plan that hand-writes a parser for attacker-written text must fixture the LANGUAGE's grammar, not the feature's use cases — and an executed oracle's explanations must never read the system under test.** Enumerate from the reference: every blank class, every quoting context, every construct that opens its own heredoc/quote scope, every option cluster that consumes a value; a suite and a differential generated from the same mental model as the parser share its blind spots. When an oracle classifies base→patched flips, derive each explanation from the command and ground truth only — "the SUT's count was positive" certifies itself. **Why:** #9089/PR #9099 — 31 review findings, ~12 unmodelled bash grammar rules (each a live bypass); the circular oracle hid 43 unexplained flips. See `knowledge-base/project/learnings/security-issues/2026-09-28-every-grammar-rule-my-lexer-skipped-was-a-bypass-and-my-oracle-explained-its-own-flips.md`.
253
+ - **A precondition for creating a resource belongs on EVERY route that creates it, and "its own runcmd entry" isolates nothing.**
254
+ - Before gating one caller (a dispatcher, a cron), grep the workflows for every job that can create the same host (`-replace=`, recut, migrate, a direct `apply_target`) and gate each one.
255
+ - cloud-init joins all `runcmd` entries into ONE POSIX `sh` script, so an earlier `set -a; . envfile; set +a` exports its secrets into every later entry. Isolate with `env -i`.
256
+ - A plan that adds `on: pull_request` to a workflow also owes a `scripts/pr-fanout-ledger.txt` row.
257
+
258
+ **Why:** #8714/PR #9147. P6 gated only the dispatcher, while three apply jobs could create the registry host unguarded. The fetch "outside `doppler run`" still inherited `DOPPLER_TOKEN`. The missing ledger row reddened CI. See `knowledge-base/project/learnings/2026-09-28-a-replace-gate-on-one-route-and-an-isolation-that-shared-one-shell.md`.
259
+ - **A counter or signal named for a CAUSE (late, stalled, egress-failed) must be defined by the predicate that isolates that cause, including a reset on every competing cause — and its decision-table row must be checked against a SEQUENCE (outage → recovery), not a single event.** "X rose, others flat → cause C" is true only if no other cause can move X on a later row. **Why:** #7270 — `late_ok_cum` counted OK-to-OK gaps, so every recovery beat after an outage read as timer lateness; two review seats reproduced it with a simulation the plan never ran. See `knowledge-base/project/learnings/2026-09-28-a-counter-named-for-a-cause-measured-a-gap-that-every-outage-produced.md`.
260
+ - **A plan that wraps work in a retrying unit (a systemd `Restart=` oneshot, a timer loop, a queue retry) must state three things as predicates, and each needs a mutation row per exit route.**
261
+ - The **latch predicate** is the full health predicate, never "exit 0". Latching a degraded success makes permanent exactly the transient failures the loop exists for.
262
+ - **What each attempt pauses**, restored on every non-success exit. The loop pauses timers, locks and flags to make its attempt safe.
263
+ - **Every override surface the guards must survive**: drop-ins, alternate binary paths, other boot routes, aliased directives such as `TimeoutSec=` over `TimeoutStartSec=`, and `set -x` after a refusal.
264
+
265
+ **Why:** #8562/PR #9159. Review found:
266
+ - a failure-path `tee "$LATCH"` that three suites passed
267
+ - a latched degraded bootstrap
268
+ - FSM timers never restored
269
+ - five spelling-based guards (the same class as #9134)
270
+
271
+ See `knowledge-base/project/learnings/2026-09-28-a-retrying-unit-must-latch-only-full-success-and-undo-what-it-paused.md`.
272
+ - **Every on-host identity a guard reads (a filesystem label, a UUID, a device path, a unit, a marker file) must cite the repo artifact that WRITES it.** If no writer exists, the guard asserts a premise, not a fact. Fixtures built from the same premise pass by construction, so the plan must name a read-only rehearsal against the real host as the falsifier, run before the PR that depends on it is called done. Prefer binding to a witness the automation itself recorded (e.g. a mount SOURCE persisted at cutover) over a property nothing set.
273
+
274
+ **Why:** #6604/PR #9163 → #9286. The wipe's identity check and the rollback's "plaintext gone" witness both keyed on the ext4 label `workspaces_plain`, which no artifact ever wrote. 160 suite rows and a 10-seat review were green; the prod read-only rehearsal refused `label=none`, and the same premise had silently disarmed every pre-wipe rollback. See `knowledge-base/project/learnings/2026-09-30-the-wipe-guard-read-a-label-no-artifact-ever-wrote.md`.
275
+ - **When one alert rule covers several signals, group by EMISSION CADENCE, not by failure class.** An alert throttle (`frequency_minutes`, a mute window) is per rule per issue group, so a signal emitted once (per boot, per deploy) can be suppressed permanently by a repeating sibling that consumed the window. Give a once-only signal its own rule. **Why:** #9176/PR #9292 — `bootstrap_done_degraded` shared a 120-min throttle with `provision_attempt_failed`, so the common fail-then-degraded sequence left a SQLite-only host silent until its next boot. See `knowledge-base/project/learnings/2026-09-30-one-throttle-over-a-repeating-and-a-once-only-signal-silences-the-once-only-one.md`.
276
+ - **An absence claim in a Guard Contract over HCL/YAML must compare the whole SHAPE, not count the kinds the author is guarding.** Pin the sorted multiset of row kinds and the list of top-level attributes, and scan the emitter's whole file rather than its block, then give the mutation matrix one row per axis (row kind, comment form, meta-argument, sibling block, emitter region). **Why:** #9299/PR #9302 — a green 10-row battery on the guarded kinds left 15 survivors, two invisible to every repo gate (a second emitter of a once-only stage; a dropped `depends_on`). See `knowledge-base/project/learnings/2026-09-30-an-absence-pin-over-hcl-must-compare-the-whole-shape-not-the-kinds-i-was-guarding.md`.
277
+ - **A Guard Contract over a `templatefile` must name every rendered ARM, and one over HCL blocks must name the one-line form.** A copy-parity guard that renders one value of a boolean template variable proves nothing about the other, so list each conditional variable the real call sites pass with different values (one host born with `true`, another with `false`) and render each. A block-finding regex keyed on the multi-line shape misses a valid one-line `provisioner "…" { … }`; split on the keyword at any indentation. **Why:** #9169 — the deny's parity guard rendered only web-2's `web_tunnel_connector=false` arm, and a one-line provisioner after the deny undid it past the wiring check. See `knowledge-base/project/learnings/2026-09-30-my-deny-guards-proved-one-template-arm-and-missed-one-line-blocks.md`.
278
+ - **A plan that isolates a secret onto a host must enumerate every channel that can change that host's code, image or environment, with each channel's credential tier — the custody is the weakest of them, not the secret store.** Host-side gates (a signed-image check, an env denylist, a canary probe) are defence in depth while a branch-reachable credential can overwrite the script that enforces them (the `infra-config` deploy hook accepts `ci_deploy_sh_b64` under a Tier-A HMAC), sign an image the verifier accepts (a tag arm in the identity regexp; a branch calling the reusable workflow at `@main`), or write the environment the key's process inherits. And when a gate keys on a value's SHAPE ("a `@sha256:` ref"), trace every arm that emits that shape — the warn-mode fail-open arm emitted it too. **Why:** #8609/PR #9263 — all four surfaced only at review, after plan, deepen and a 13-agent plan panel. See `knowledge-base/project/learnings/security-issues/2026-09-30-isolating-a-secret-onto-a-host-is-bounded-by-every-channel-that-can-change-the-host.md`.
279
+ - **A guard that WITHHOLDS an action ("never arm auto-merge", "never publish", "never grant") must name the prior-run state it can inherit, and read and undo it — fail-closed on an unreadable read.** "Never do X" is narrower than "X never happens" whenever X is state a previous run can have created: a mirror_only run that never armed auto-merge still fed a reused PR an earlier full build had armed (GitHub keeps auto-merge across a writer's push). Put the inherited-state case in the Guard Contract's mutation matrix. See `knowledge-base/project/learnings/2026-09-30-a-guard-that-withholds-an-action-must-also-undo-the-state-a-prior-run-left.md`.
280
+ - **Run every literal command the plan itself prescribes (the `discoverability_test`, each acceptance criterion's grep or count) once before finalizing the plan — a plan that cites a probe has not verified it.** The probe-verb gate checks that a command is well-formed, never that it returns what the plan says. **Why:** #9303 — the `discoverability_test` returned 0 against an expected 1 and AC1's "exactly these six files" returned seven; both surfaced only at QA. See `knowledge-base/project/learnings/2026-09-30-the-adr-sentence-i-cited-was-about-another-routine-and-a-history-seat-confirmed-it.md`.
281
+ - **A plan protecting a sole-copy resource must enumerate every operation that makes its data unreachable — destroy, detach or replace its EDGE (attachment, mount, binding), state-forget, and any still-dispatchable retired job — and pin each one; and a guard over that surface must scan the effective text (inherited env/defaults, real newlines, multi-line comments stripped), never a serialization of it.** `prevent_destroy` on the volume alone left its attachment replaceable; a census over `json.dumps(step)` never saw line 2 of a `run:`. **Why:** #9348 — every guard was blind just past its own battery's axis. See `knowledge-base/project/learnings/2026-10-01-the-guards-i-wrote-to-protect-the-sole-copy-scanned-a-shape-the-attack-did-not-take.md`.
282
+ - **A plan that scopes an App/installation token must derive each permission from the vendor's per-endpoint permission table, not from a sentence explaining why one call needs it.** GitHub's "Permissions required for GitHub Apps" page lists each REST endpoint's permission; read it for every call the token makes, and scope per repository. A field returned only to a writer (`bypass_actors` on rulesets) makes write necessary for THAT repository's call, not for every repository the job reads. **Why:** #9360 — the plan's "bypass_actors are hidden from read tokens" became `administration:write` on `soleur` too, where the two ruleset GETs need only Metadata read and the job's `github.token` suffices; review narrowed it to `soleur-marketplace`. See `knowledge-base/project/learnings/security-issues/2026-10-01-the-credential-move-was-sound-and-the-guard-and-the-scope-were-inherited.md`.
283
+ - **A plan that adds a test suite or an optimisation must verify the two claims it states about it, not just the code it prescribes: that the suite is REGISTERED (run the orphan census, `lint-orphan-test-suites.sh`, once the file is tracked) and what the change COSTS on the path it touches.** "Auto-registered by the `scripts/*.test.sh` glob" was inherited prose that no glob backs (the census reported the new ratchet as never run), and "demote 24 suites" was priced in suite-seconds saved (31 s) and not in the pre-pass derive it adds (84 s), so a ratchet meant to license the saving cost 11 minutes per run. **Why:** #9307/PR #9306 — a ten-seat review found both; see `knowledge-base/project/learnings/2026-10-01-anchoring-a-matcher-flips-its-error-direction-and-the-ratchet-i-wrote-never-ran.md`.
284
+ - **A plan that joins two emitted rows on a shared key must state each row's emission cadence (per instance, per boot, per day, per deploy) and carry a fixture where the key CHANGES on the more frequent side.** `a.boot_id == b.boot_id` between a per-boot probe and a per-instance cloud-init row is true only until the first reboot, and a fixture that builds both rows from one literal agrees with it. **Why:** #6931/PR #9352 — the soak-marker join returned `boot_id_mismatch` after any reboot and deleted the marker daily; it survived plan, deepen-plan, plan-review and ~700 assertions and was found by 7 of 13 review seats. See `knowledge-base/project/learnings/2026-10-01-i-joined-two-evidence-rows-without-asking-how-often-each-is-emitted.md`.
285
+ - **A plan step that defers a PR-REQUIRED generated artifact to "regenerate from the CI artifact" after the push is a merge blocker, not a follow-up.** When a repo gate compares a committed file to a projection of the diff (`alert-reference.json` vs the Terraform plan projection in `apply-sentry-infra.yml`'s `plan_pr`), the PR is red until the file matches, so derive it locally in the work phase (read the gate's projection jq; a one-value edit when only a filter widened) and let CI be the proof, not the producer. **Why:** #9385 — plan step 5.2 deferred the one-line `egress_blocked` op-filter value; the architecture and observability review seats both rated its absence P1. See `knowledge-base/project/learnings/2026-10-01-a-new-consumer-of-a-shared-format-rederives-it-and-the-copies-diverge-fail-open.md`.
286
+ - **A plan that keys a classifier on another program's OUTPUT LINE must record when that line is NOT printed.** Validating the regex against a captured line and deriving it from the emitter's `echo` proves the SHAPE only; read the `if` around that `echo` and fixture the run where it is absent. **Why:** #8112 — a failing-suite verdict was keyed on `scripts/test-all.sh`'s `=== N suites:` breakdown, which it prints only when something was killed, skipped or declined, so a plain failing run would have been titled "did not complete" with "do not revert". See `knowledge-base/project/learnings/2026-10-02-a-monitor-verdict-read-from-a-line-the-runner-prints-only-conditionally.md`.
287
+ - **A post-merge verification section is a program an agent will run unattended; run each command in it once against live read-only data before the plan is committed.** Measured on #8609 R3: `gh run list --status a --status b` keeps only the last value; `gh pr view --json mergeCommit` is empty until an `--auto` merge lands (an empty SHA then selects nothing, which reads as "not yet"); an unanchored `grep source=tier_b` also matches the echoed script source; and an "unauthenticated liveness probe" already printed `success` before the change. Each fell to one execution. **Why:** #8609 - five review seats found these in the plan, none in the one-line diff. See `knowledge-base/project/learnings/2026-10-03-a-plans-verification-commands-and-a-fingerprint-were-never-run.md`.
288
+ - **A plan that claims a change is "dormant / byte-identical in prod" must list each prod input the change touches and show it does not reach the new branch — using the plan's OWN measured fixtures — and must name the producer's redelivery contract before choosing throw vs degrade on a lookup failure.** "The table ships empty" is a property of the data, not of the code path: #9458's Phase 0 measured that real `ops@` mail always carries `to`, so every mail ran the new query, and fail-closed on a `retries: 1` statutory path would have dropped DSAR mail. **Why:** #9456 review — four seats refuted the claim independently. See `knowledge-base/project/learnings/2026-10-04-a-dormant-claim-contradicted-by-my-own-measurement-and-fail-closed-on-a-statutory-path.md`.
289
+ - **A plan whose acceptance rests on a control the vendor can silently ignore must make the criterion a BEHAVIOUR probe with a control arm, and must claim an outcome, not a mechanism, unless an ablation arm isolates it.** "Sessions start normally" cannot fail for a config key the SDK accepts-and-drops (measured: four malformed `sandbox.credentials` shapes all started, ran, and left the key readable), and a two-arm control/treatment result proves what happened, never why. **Why:** #9601/PR #9599 — a `/proc` result written up as "the PID namespace is the barrier" was refuted by two seats' repros; see `knowledge-base/project/learnings/security-issues/2026-10-06-i-claimed-a-mechanism-from-an-outcome-and-my-guards-pinned-the-builder-not-the-wire.md`.
290
+
291
+ - **A plan that adds a new `SOLEUR_*` marker to a plugin skill script must name its telemetry registration and the drift guard that proves it.** `apps/web-platform/server/git-lock-marker-telemetry.ts` `MARKER_RE` is a closed vocabulary and `apps/web-platform/test/git-lock-marker-telemetry.test.ts` walks every `SOLEUR_*` sentinel a `skills/*/scripts/*.sh` emits, so an unregistered marker reds the required webplat shard on merge while every suite selected from the diff's own files stays green. Add the task (one alternation arm plus a MIRRORED-NOT-PAGED comment saying which surface the extractor is actually wired to: the agent PostToolUse hook, not a hosted cron's captured stderr) and run that test in the plan's verification. **Why:** #9674 — a new `SOLEUR_CREDENTIAL_REFUSED` refusal marker passed plan, deepen, work and a green gate run, and was found only by one review seat; the comment justifying it had also claimed a hosted-path mirror that did not exist. See `knowledge-base/project/learnings/2026-10-07-a-guard-pr-added-an-unregistered-marker-and-resumed-fix-agents-stalled-on-a-status-line.md`.
292
+ - **A plan that names "reboot the host" as its recovery or proof step, or "survives a reboot" as an acceptance criterion, has chosen the wrong unit of recovery.** A fresh instance built from declared config is the recovery path, and a host-property proof (volume unlocked and mounted, service up at boot, NIC converged) must be satisfiable by that instance with stateful data on a persistent volume. Ask whether a replace can do the step. If yes, rewrite it. If a reboot is genuinely needed, name its case from `soleur:engineering:infra:terraform-architect` (Replace, Don't Reboot) and cite measured boot-time safety, not intent. Replace has its own preconditions (state off the root disk, a LUKS volume opened and never formatted, a prod destroy authorized) and is itself downtime on a serving host. Advisory for the plan; an in-place edit of a prod host is never the alternative. **Why:** #9750, from the web-2 work in #9372 — a soft reboot left a web host dark with no recovery path but a replace, while "survives a reboot" had become an acceptance criterion; replace mechanics in `knowledge-base/project/learnings/2026-07-07-immutable-redeploy.md`.
293
+ - **A plan's `## Rollback` that says "revert X" is a recipe nobody has executed, and it is first run during an incident.** Before the plan is final, simulate it: in a scratch detached worktree `git revert --no-commit` the change's commits, then run the gates the revert PR must pass (here the pin-staleness gate and the deny parity suite), and write the recipe from what that run needs, not from the diff you remember. Put the recipe in the artifact that survives the plan (the sidecar or runbook), including any commit-body markers the revert needs. **Why:** #9795 — "revert the four values" left `zot-image-staleness.test.sh` at 9 passed / 6 failed; a review seat found it only by executing the revert. See `knowledge-base/project/learnings/2026-10-09-a-rollback-recipe-nobody-had-run-and-a-baselined-file-touched-without-its-changed-lint.md`.
294
+ - **A "no entry carries the subject id" property is only as wide as the directory you scoped it to — enumerate the id's WRITERS, and date residue by INSTANCE lifecycle.** For an Art. 17 / identifier-residue plan, `grep` every script that handles the id (cursors, caches, sidecars above the data root included) and map each to the guard's window before fixing the scope; then state each residue window from the instance record (created/destroyed by apply run), not from a PR merge date, and cite the run rather than the host for any "the host serves X" claim. **Why:** #9066 — the guard covered the repo root while `git-data-gc.sh` also wrote `<id>.git` to `.gc-cursor` above it, and the residue window was keyed on PR #8564's merge when the plaintext-serving host was actually destroyed a day later. See `knowledge-base/project/learnings/2026-10-09-an-erasure-guard-scoped-to-one-directory-missed-the-second-writer-and-residue-windows-keyed-on-merge-dates.md`.
295
+ - **A plan whose observer or mutation step says "flip every converted line" or "invert the matcher" over a block containing `kill`, `pkill`, `$PPID` or `rm -r` prescribes a mutant that can reach past the scratch copy.** Name the PID namespace in the AC and in the observer paragraph (command, refusal rule and the no-allocator form: `plugins/soleur/skills/work/references/work-scratch-sandboxes.md`, section "Mutating a signalling or file-removing helper"), and, when the suite already exists, list the known sites first with `python3 <plugin-root>/scripts/scan-ancestor-signal-helpers.py --list <suite-path>` (a listing of 0 sites or with skipped files is not a safety signal) (`<plugin-root>` is the directory this file was read from, cut at its last `/skills/`). **Why:** an inverted match in an ancestor-walking helper ended the desktop session four times, and the plan had prescribed the flip. See `knowledge-base/project/learnings/test-failures/2026-10-09-an-inverted-match-mutant-of-an-ancestor-walking-helper-ended-the-desktop-session-four-times.md`.
296
+ - When a plan adds a closed state set that several readers whitelist, name what the producer prints when it cannot answer and keep it OUTSIDE the set (`state=error`, not a member); the consumer tests must stub the producer's real error output, not an invented exit-code-only shape. **Why:** #9885 — a `no-run reason=api-error` marker read as a real state by five consumers during any outage, while every stub exited 3 with no marker.
297
+ - **A plan that prescribes a regex/heuristic scorer over attacker-written text must prescribe, in the same phase as the scorer: (a) an adversarial-size timing row (256 KB of newlines, spaces, repeated URL schemes, unterminated comments, NFKC-expanding characters) run under the production engine, and (b) a bypass model for every discount or allowlist the scorer contains (what does one quote, fence, invisible separator or decoy flood buy?).** A length cap bounds size, not time, and a corpus written by the scorer's author measures the author's imagination. **Why:** #4671 — the first scorer was quadratic (24 s at 16 KB) and a leading fence defeated it, both past a green corpus eval and a 7-mutant battery; see `knowledge-base/project/learnings/2026-10-13-a-heuristic-scorer-over-attacker-text-needs-a-cost-bound-and-a-bypass-model-before-its-first-green.md`.
298
+ - A guard that decides from a platform's RECORDS (run history, logs, listings) inherits those records' lifetime, and the plan must MEASURE it, not read it off a setting: list the OLDEST record live (`gh run list`/`gh api`), and enumerate every deletion path (retention, step-list stripping, manual delete, a re-run of an older SHA that executes the old workflow). A shrinking `total_count` and an empty list read as a clean store, so a count cross-check is not completeness proof; pin records that must stay listed. **Why:** #10175 — a ruling quoted a 90-day horizon while 196-day-old runs were still listed and step lists strip at ~170 days. See `knowledge-base/project/learnings/2026-10-14-a-guard-that-reads-run-records-inherits-their-lifetime-and-i-assumed-it.md`.
299
+ - **A new suite for a workflow-YAML subject under `plugins/soleur/test/` needs an `AFFECTED_*_PATHS` declaration (the census reports it UNCLASSIFIED otherwise), and that declaration degrades the local `test-all.sh --affected` gate to the FULL battery (`INDEX-UNBOUND`, because a glob-registered suite adds no `run_suite` label), which a sibling full run then refuses (rc=4).** State this in the plan and name the substitute up front: the consumer suites of the touched registration files, the ratchets, and CI's full battery as the gate. A suite that names a SECOND workflow file for an independent pin also adds a `grep-q-pipe-guard` triple (`PJ_TRIPLES`) when it stubs `sha256sum`. **Why:** #7243 — both surfaced only at the work-phase exit gate. See `knowledge-base/project/learnings/2026-10-14-my-stubs-checked-the-shape-of-argv-and-never-the-value-and-a-glob-registered-suite-degrades-the-affected-gate.md`.
300
+ - **A plan whose action PERFORMS the verification a document calls not-yet-done must sweep that document's NEGATIVE claim, not only the flip's vocabulary.** A status flip swept on `adopting` / "takes effect on" finds the sentences that said it was pending and misses "has not been run against the real API" and a differently-worded bracket in a sibling cell, because neither uses the flip's words. Add a column to the sweep table: "negative claims this action falsifies", grep their paraphrases, and hand any legal reviewer the sweep hits with row and cell anchors. **Why:** #10018/PR #10337 — two stale sentences found by review seats only; see `knowledge-base/project/learnings/workflow-issues/2026-10-15-the-flip-pr-ran-the-verification-and-left-the-sentence-saying-it-was-never-run.md`.
301
+ - **A plan that relies on a third-party tool's runtime behaviour (a rate limiter's window semantics, a metric or log field name, headers a framework injects, a query engine's string escaping) must measure it in Phase 0 with the real binary or engine and capture the real shape as the test fixture.** A fixture written from the author's model agrees with the model by construction, so the suite stays green while the claim is false. **Why:** #9545/PR #10349 — `x-forwarded-*` injected by Next (401 for every batch), a per-key metric name that carries no key, a throttle that is a leaky bucket (memory bound 2x optimistic) and a backslash inside a ClickHouse literal all passed hand-written tests. See `knowledge-base/project/learnings/2026-10-15-i-wrote-the-fixtures-from-my-model-of-the-tool-and-the-tests-agreed-with-me.md`.
302
+
303
+ - **A plan that REMOVES a printed operator recipe owes the replacement procedure, bounded per object class.** A label bound covers only labeled objects (Hetzner); name-identified objects (Doppler configs/environments) need their own name bound, and every write needs a per-item live-state read first. Size the regression guard as a spelling tripwire: one mutation row per alternative of each matcher family, built in-process. **Why:** #9927 item 8c — four review rounds, 34 findings, nearly all in the guard and the replacement runbook rather than the 10-line removal. See `knowledge-base/project/learnings/2026-10-15-removing-a-printed-recipe-owes-a-bounded-replacement-and-a-spelling-guard-is-a-tripwire.md`.
304
+
305
+ - **A plan that adds a READ of an input to a guarded surface owes the census of readers in the same task, and the census must match every spelling the platform parses.** Case-insensitive, whitespace around `.` and `[`, the whole document (not only `jobs`), whole-context forms (`toJSON(needs)`, `needs.*`, `format('{0}', needs)`, dynamic index), with one mutant per shape class and one per regex arm whose removal another arm would mask. A plan that restates a measured figure must name its units and sample window. **Why:** #10341 — the census was widened in three review rounds, each finding a spelling the previous one missed. See `knowledge-base/project/learnings/2026-10-15-a-guard-vocabulary-widened-and-its-census-did-not-and-prose-was-scoped-narrower-than-its-claims.md`.
306
+
307
+ - **A plan that adds a counter or flag field to a fixed-shape output line owes, per field, a fixture where the count differs from its flag form (>= 2 entries), one with an allowlisted look-alike beside it (e.g. lock files), and one where it is zero while a neighbour is not; each rc/refusal row pins the WHOLE line, not a substring.** Author-written mutants share the author's fixture blind spots. **Why:** the wipe-assert rc=34 fields (2026-10-18): a lock-count, an `lf` flag, a FIFO and a suffix-glob mutant survived 785 green assertions; see `knowledge-base/project/learnings/2026-10-18-a-counter-field-needs-a-world-where-the-count-is-not-a-flag.md`.
308
+
309
+ - **A Guard Contract that pins prose by its LEAD-IN pins the heading, not the rule.** Pin the load-bearing clause (the link plus the pointer and the "only X stays" tail), strip fences and HTML comments before matching, and give any discoverability `grep -c` the same anchor; then mutate by GUTTING the body, not only deleting the anchor. **Why:** #10499 - gutting a paragraph body after its bold lead-in stayed green through the author's battery; see `knowledge-base/project/learnings/2026-10-18-a-presence-pin-on-a-lead-in-survives-the-paragraph-being-gutted.md`.
310
+
311
+ - **`decision-challenges.md` rows are machine-gated: a technical ruling must start exactly `Ruled by CTO agent, <date>:` followed by one space (nothing between the date and the colon, so no parenthetical) and a row may not contain a literal `|` (write `\|` or reword).** Run the ship skill's `technical-ruling-gate.sh --branch` (in its `scripts/` directory) before returning the plan; `pending - assumed` is not a ruling. **Why:** PR #10572 — the gate failed on all three shapes in one session. See `knowledge-base/project/learnings/2026-10-19-the-audit-a-ratchet-trusts-reached-less-than-its-name-and-both-p1s-were-in-the-fixes.md`.
312
+
313
+ - **A baseline drawdown is ONE commit, because the gate compares the baseline to the live working-tree census.** `lint-ratchet.py --check` and the lefthook ratchet hooks require baseline == live census, so a plan that prescribes per-area commits each staging a shrunk baseline cannot be executed once the areas are fixed together; plan one commit (per-area structure is review-aid only in a squash-merge repo), and name any test that anchors on the exact text of a region you will re-indent. **Why:** #10433 — see `knowledge-base/project/learnings/2026-10-19-a-per-area-drawdown-cannot-satisfy-a-live-census-gate-and-reindenting-breaks-text-anchored-mutants.md`.
314
+
315
+ - **An acceptance criterion that predicts a vendor object's STATE after a settings change is a prediction, not a check - word it as the behaviour to observe.** A settings toggle often leaves the object it controls unchanged (a dynamic workflow still reads `active` after Code Quality is switched off); the discriminating evidence is what stops happening (runs on pushes made after the change, with a pre-change control head). Likewise read the billing/usage API before ruling on a brief's cost figure. **Why:** #10331 slice 4 - a ticked criterion predicted `state != active` and was false; the brief's "$10 per committer" was a $254.96 metered AI-credits line; see `knowledge-base/project/learnings/2026-10-19-a-plan-predicted-a-vendor-objects-state-after-a-toggle-and-the-briefs-cost-premise-was-not-the-bill.md`.
316
+
317
+ - **A design that depends on an external system's behaviour (what a vendor config layer inherits, what a download emits) names the read-only command that measures it and records the output in the plan; repo prose citing the fact is not a measurement, and it is routinely on both sides.** Names-only listings (`doppler secrets --only-names`) are enough for presence questions. **Why:** #10136/PR #10501 — ADR-241 and a learning said `prd_terraform` inherits `prd`, tunnel.tf and ADR-164 said it does not; a redesign built on the first reading made every untargeted plan propose a destroy of the live flag until one names listing settled it. See `knowledge-base/project/learnings/2026-10-19-doppler-branch-config-does-not-inherit-new-keys-and-my-fix-trusted-prose-on-both-sides.md`.
318
+
319
+ - **A pathspec that builds a SET (a census, not a ≥1 existence probe) must be measured against an independent enumeration — a `**/` glob verified only by "matched ≥1 file" silently drops root-level members.** On git 2.56 `git ls-files 'dir/**/name.ext'` requires ≥1 intermediate directory, so `dir/name.ext` itself is absent from the census while 31 hits read as verified. Prescribe the prefix+filter form (`git ls-files '<prefix>' | grep -E '/<suffix>$'`), or gate the glob's count against it (`wc -l` both sides). The `hr-when-a-plan-specifies-relative-paths-e-g` ≥1-file check cannot see partial coverage — a set built by enumeration needs a second enumerator. **Why:** #10399 — the plan prescribed `git ls-files 'apps/web-platform/app/**/page.tsx'` and the `/` route's own page.tsx silently left the census the drift guard exists to compare. See `knowledge-base/project/learnings/2026-10-20-a-census-guard-under-counts-on-both-enumerators.md`.
320
+
321
+ - **A markdown link inside text that is copied verbatim into a generated artifact (a fenced block in `plan-issue-templates.md`, an issue-body template) must use the `${CLAUDE_PLUGIN_ROOT}`-form (`](${CLAUDE_PLUGIN_ROOT}/skills/<skill>/references/<file>.md)`), never a `./` or repo-relative path** — the generated doc lands somewhere the link's relative base does not exist, while the root-prefixed path resolves to the user's plugin install. And when the plan claims an existing suite (the reachability census, a template pin) already covers the new artifact so no new pin is needed, prove it with a negative control on a scratch copy — remove the pointer/artifact and confirm the suite reds naming it — before the claim ships in the plan. **Why:** #10401 — the three `## Test Scenarios` pointer lines were the first links ever placed in `plan-issue-templates.md`; CTO ruling fixed the form, and the "census covers it" decision stood only because task 3.4 forced the census red. See `knowledge-base/project/learnings/best-practices/2026-10-20-copied-template-links-and-coverage-negative-controls.md`.
322
+
323
+ - **A negative control that removes "the link" cannot red a census keyed on basename substring — enumerate the keyed string's every site and strip them ALL, or probe the predicate directly.** `components.test.ts`'s references-reachability check is `haystack.some(t => t.includes(f))` over every other file in the skill dir, so prose path mentions (`docs/feature-map.md`, `apps/…/feature-map.md`) satisfy it with no link at all; a control that deletes only the markdown pointer leaves the basename alive and reads "linked" forever. Before prescribing the mutation, `grep -rn '<basename>' <skill-dir>` and make the control remove the whole hit set (or run a scratch haystack that never contained it). The control must remove what the predicate READS, not what the diff added — for a substring census that is every occurrence; for a line-anchored parser it is the one conforming line. **Why:** #10402 — the #10401-derived "remove the pointer" control prescribed in tasks.md 3.2 could never red `test-browser/references/feature-map.md` (basename survives in 5 prose mentions); both `soleur:work` and `soleur:qa` hit it independently. See `knowledge-base/project/learnings/2026-10-21-a-remove-the-link-control-cannot-red-a-basename-substring-census.md`.
324
+
325
+ - **A plan that adds a conditional row to a spec — a guard, a flag, a termination-table row, a success verdict — owes two vacuity checks: can the trigger ever be true under the mechanism's own invariants, and does a success/screened-out row require ≥1 qualifying item as proof?** A trigger keyed on an intersection that is empty by construction never fires (#10403: a "possible diff-interaction" flag keyed on flaked-test path ∩ the loop's own commits, in a loop that never edits test files — re-specified on timing and exercised-surface signals), and a row an empty input set satisfies greens on nothing (the same PR's "all failures screened out" row fired on an unparseable `rc≠0` run with zero quarantines — now requires ≥1 quarantine, and the zero-failure parse falls through). Apply both checks to rows added in a review-fix round too — the second vacuity was introduced remediating the first batch. **Why:** #10403/PR #10738 — two vacuous-row findings across the main review and the fix round. See `knowledge-base/project/learnings/workflow-patterns/2026-10-21-a-guard-that-cannot-fire-and-a-success-row-that-proves-nothing.md`.