agentfluent 0.8.0__tar.gz → 0.10.0__tar.gz

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 (365) hide show
  1. agentfluent-0.10.0/.claude/claude-security-guidance.md +69 -0
  2. agentfluent-0.10.0/.claude/hooks/guard_append_only.py +173 -0
  3. agentfluent-0.10.0/.claude/security-patterns.json +40 -0
  4. {agentfluent-0.8.0 → agentfluent-0.10.0}/.claude/settings.json +19 -2
  5. agentfluent-0.10.0/.claude/skills/release-loop/SKILL.md +204 -0
  6. agentfluent-0.10.0/.claude/specs/agent-sdk-session-format-findings.md +312 -0
  7. agentfluent-0.10.0/.claude/specs/analysis/2026-05-30-v08-dogfood/analysis.md +186 -0
  8. agentfluent-0.10.0/.claude/specs/analysis/2026-06-05-v09-dogfood/analysis.md +164 -0
  9. agentfluent-0.10.0/.claude/specs/analysis/2026-06-05-v09-dogfood/analyze.json +127576 -0
  10. agentfluent-0.10.0/.claude/specs/analysis/2026-06-05-v09-dogfood/analyze.table.txt +2823 -0
  11. agentfluent-0.10.0/.claude/specs/analysis/407-calibration/calibration.md +196 -0
  12. agentfluent-0.10.0/.claude/specs/analysis/407-calibration/detections.tsv +196 -0
  13. agentfluent-0.10.0/.claude/specs/analysis/407-calibration/export.py +126 -0
  14. agentfluent-0.10.0/.claude/specs/analysis/407-calibration/sample.py +40 -0
  15. agentfluent-0.10.0/.claude/specs/analysis/407-calibration/tune.py +45 -0
  16. agentfluent-0.10.0/.claude/specs/backlog-v0.9.md +458 -0
  17. {agentfluent-0.8.0 → agentfluent-0.10.0}/.claude/specs/decisions.md +171 -0
  18. {agentfluent-0.8.0 → agentfluent-0.10.0}/.claude/specs/prd-advanced-tool-use-diagnostics.md +11 -6
  19. agentfluent-0.10.0/.claude/specs/prd-agent-sdk-discovery.md +135 -0
  20. agentfluent-0.10.0/.claude/specs/prd-loop-engineering.md +757 -0
  21. agentfluent-0.10.0/.claude/specs/prd-pricing-genai-migration.md +151 -0
  22. agentfluent-0.10.0/.claude/specs/prd-v0.10.md +227 -0
  23. agentfluent-0.10.0/.claude/specs/prd-v0.9.md +225 -0
  24. agentfluent-0.10.0/.claude/specs/value-review-437-verbosity.md +182 -0
  25. {agentfluent-0.8.0 → agentfluent-0.10.0}/.github/workflows/ci.yml +1 -1
  26. {agentfluent-0.8.0 → agentfluent-0.10.0}/.github/workflows/claude-review.yml +1 -1
  27. {agentfluent-0.8.0 → agentfluent-0.10.0}/.github/workflows/release-please.yml +1 -1
  28. {agentfluent-0.8.0 → agentfluent-0.10.0}/.github/workflows/security-review.yml +9 -2
  29. {agentfluent-0.8.0 → agentfluent-0.10.0}/.gitignore +8 -0
  30. agentfluent-0.10.0/.release-please-manifest.json +3 -0
  31. {agentfluent-0.8.0 → agentfluent-0.10.0}/CHANGELOG.md +59 -0
  32. {agentfluent-0.8.0 → agentfluent-0.10.0}/CLAUDE.md +43 -6
  33. {agentfluent-0.8.0 → agentfluent-0.10.0}/CONTRIBUTING.md +18 -0
  34. {agentfluent-0.8.0 → agentfluent-0.10.0}/PKG-INFO +32 -12
  35. {agentfluent-0.8.0 → agentfluent-0.10.0}/README.md +31 -11
  36. agentfluent-0.10.0/docs/COST_MODEL.md +35 -0
  37. agentfluent-0.10.0/docs/EFFICIENCY_DIAGNOSTICS_RESEARCH.md +130 -0
  38. {agentfluent-0.8.0 → agentfluent-0.10.0}/docs/GLOSSARY.md +349 -2
  39. {agentfluent-0.8.0 → agentfluent-0.10.0}/docs/ROADMAP.md +43 -2
  40. agentfluent-0.10.0/images/demo-analyze.svg +426 -0
  41. agentfluent-0.10.0/images/demo-config-check.svg +136 -0
  42. agentfluent-0.10.0/images/demo-diagnostics.svg +445 -0
  43. agentfluent-0.10.0/images/demo-diff.svg +466 -0
  44. agentfluent-0.10.0/images/demo-subagents.svg +167 -0
  45. {agentfluent-0.8.0 → agentfluent-0.10.0}/pyproject.toml +4 -1
  46. agentfluent-0.10.0/research/agent-sdk-probe/FINDINGS.md +607 -0
  47. agentfluent-0.10.0/research/agent-sdk-probe/README.md +107 -0
  48. agentfluent-0.10.0/research/agent-sdk-probe/agent.py +269 -0
  49. agentfluent-0.10.0/research/agent-sdk-probe/fixture.txt +7 -0
  50. agentfluent-0.10.0/research/agent-sdk-probe/probe.py +38 -0
  51. agentfluent-0.10.0/research/agent-sdk-probe/run_matrix.py +208 -0
  52. agentfluent-0.10.0/research/agent-sdk-probe/sampledata/code.py +7 -0
  53. agentfluent-0.10.0/research/agent-sdk-probe/sampledata/data.csv +6 -0
  54. agentfluent-0.10.0/research/agent-sdk-probe/sampledata/notes.txt +12 -0
  55. {agentfluent-0.8.0 → agentfluent-0.10.0}/src/agentfluent/agents/extractor.py +3 -0
  56. {agentfluent-0.8.0 → agentfluent-0.10.0}/src/agentfluent/agents/models.py +33 -0
  57. agentfluent-0.10.0/src/agentfluent/analytics/agent_metrics.py +288 -0
  58. {agentfluent-0.8.0 → agentfluent-0.10.0}/src/agentfluent/analytics/pipeline.py +112 -2
  59. {agentfluent-0.8.0 → agentfluent-0.10.0}/src/agentfluent/analytics/pricing.py +56 -16
  60. {agentfluent-0.8.0 → agentfluent-0.10.0}/src/agentfluent/analytics/tokens.py +23 -10
  61. {agentfluent-0.8.0 → agentfluent-0.10.0}/src/agentfluent/cli/commands/analyze.py +22 -1
  62. {agentfluent-0.8.0 → agentfluent-0.10.0}/src/agentfluent/cli/commands/explain.py +17 -2
  63. {agentfluent-0.8.0 → agentfluent-0.10.0}/src/agentfluent/cli/commands/report_renderers.py +29 -8
  64. {agentfluent-0.8.0 → agentfluent-0.10.0}/src/agentfluent/cli/formatters/diff_table.py +30 -7
  65. agentfluent-0.10.0/src/agentfluent/cli/formatters/helpers.py +218 -0
  66. {agentfluent-0.8.0 → agentfluent-0.10.0}/src/agentfluent/cli/formatters/table.py +65 -4
  67. agentfluent-0.10.0/src/agentfluent/config/hook_inspector.py +142 -0
  68. {agentfluent-0.8.0 → agentfluent-0.10.0}/src/agentfluent/config/models.py +51 -0
  69. agentfluent-0.10.0/src/agentfluent/config/retention.py +179 -0
  70. {agentfluent-0.8.0 → agentfluent-0.10.0}/src/agentfluent/core/parser.py +34 -1
  71. {agentfluent-0.8.0 → agentfluent-0.10.0}/src/agentfluent/core/paths.py +18 -0
  72. {agentfluent-0.8.0 → agentfluent-0.10.0}/src/agentfluent/core/session.py +60 -2
  73. {agentfluent-0.8.0 → agentfluent-0.10.0}/src/agentfluent/diagnostics/_complexity.py +68 -0
  74. agentfluent-0.10.0/src/agentfluent/diagnostics/agent_audit.py +163 -0
  75. {agentfluent-0.8.0 → agentfluent-0.10.0}/src/agentfluent/diagnostics/aggregation.py +3 -0
  76. {agentfluent-0.8.0 → agentfluent-0.10.0}/src/agentfluent/diagnostics/correlator.py +372 -13
  77. {agentfluent-0.8.0 → agentfluent-0.10.0}/src/agentfluent/diagnostics/model_routing.py +47 -46
  78. {agentfluent-0.8.0 → agentfluent-0.10.0}/src/agentfluent/diagnostics/models.py +7 -3
  79. {agentfluent-0.8.0 → agentfluent-0.10.0}/src/agentfluent/diagnostics/parent_workload.py +2 -1
  80. {agentfluent-0.8.0 → agentfluent-0.10.0}/src/agentfluent/diagnostics/pipeline.py +48 -3
  81. {agentfluent-0.8.0 → agentfluent-0.10.0}/src/agentfluent/diagnostics/signals.py +26 -11
  82. agentfluent-0.10.0/src/agentfluent/diagnostics/tool_orchestration.py +183 -0
  83. {agentfluent-0.8.0 → agentfluent-0.10.0}/src/agentfluent/diagnostics/trace_signals.py +215 -15
  84. {agentfluent-0.8.0 → agentfluent-0.10.0}/src/agentfluent/diff/compute.py +23 -0
  85. {agentfluent-0.8.0 → agentfluent-0.10.0}/src/agentfluent/diff/models.py +39 -0
  86. {agentfluent-0.8.0 → agentfluent-0.10.0}/src/agentfluent/glossary/loader.py +32 -0
  87. {agentfluent-0.8.0 → agentfluent-0.10.0}/src/agentfluent/glossary/models.py +2 -0
  88. {agentfluent-0.8.0 → agentfluent-0.10.0}/src/agentfluent/glossary/terms.yaml +313 -2
  89. {agentfluent-0.8.0 → agentfluent-0.10.0}/src/agentfluent/traces/models.py +41 -0
  90. {agentfluent-0.8.0 → agentfluent-0.10.0}/src/agentfluent/traces/parser.py +36 -9
  91. agentfluent-0.10.0/tests/fixtures/hooks/inline_bash_timing.yaml +15 -0
  92. agentfluent-0.10.0/tests/fixtures/hooks/no_post_hook.yaml +9 -0
  93. agentfluent-0.10.0/tests/fixtures/hooks/secrets_hook.py +18 -0
  94. agentfluent-0.10.0/tests/fixtures/hooks/timing_hook.py +15 -0
  95. agentfluent-0.10.0/tests/fixtures/nested_session/README.md +43 -0
  96. agentfluent-0.10.0/tests/fixtures/nested_session/nested-session-1/subagents/agent-leaf0001.jsonl +4 -0
  97. agentfluent-0.10.0/tests/fixtures/nested_session/nested-session-1/subagents/agent-leaf0001.meta.json +1 -0
  98. agentfluent-0.10.0/tests/fixtures/nested_session/nested-session-1/subagents/agent-worker001.jsonl +6 -0
  99. agentfluent-0.10.0/tests/fixtures/nested_session/nested-session-1/subagents/agent-worker001.meta.json +1 -0
  100. agentfluent-0.10.0/tests/fixtures/nested_session/nested-session-1.jsonl +4 -0
  101. {agentfluent-0.8.0 → agentfluent-0.10.0}/tests/fixtures/report/analyze_snapshot.json +8 -2
  102. {agentfluent-0.8.0 → agentfluent-0.10.0}/tests/fixtures/report/expected_report.md +6 -4
  103. agentfluent-0.10.0/tests/fixtures/sdk_session/README.md +62 -0
  104. agentfluent-0.10.0/tests/fixtures/sdk_session/sdk-main-1/subagents/agent-child0000001.jsonl +2 -0
  105. agentfluent-0.10.0/tests/fixtures/sdk_session/sdk-main-1/subagents/agent-child0000001.meta.json +5 -0
  106. agentfluent-0.10.0/tests/fixtures/sdk_session/sdk-main-1.jsonl +4 -0
  107. {agentfluent-0.8.0 → agentfluent-0.10.0}/tests/unit/cli/conftest.py +11 -0
  108. {agentfluent-0.8.0 → agentfluent-0.10.0}/tests/unit/cli/test_active_duration_display.py +66 -0
  109. agentfluent-0.10.0/tests/unit/cli/test_agent_duration_cell.py +128 -0
  110. {agentfluent-0.8.0 → agentfluent-0.10.0}/tests/unit/cli/test_axis_json_contract.py +30 -0
  111. agentfluent-0.10.0/tests/unit/cli/test_cleanup_warning_cli.py +96 -0
  112. {agentfluent-0.8.0 → agentfluent-0.10.0}/tests/unit/cli/test_diff_table_signed.py +17 -0
  113. agentfluent-0.10.0/tests/unit/cli/test_environment_warning_banner.py +77 -0
  114. {agentfluent-0.8.0 → agentfluent-0.10.0}/tests/unit/cli/test_explain.py +5 -2
  115. {agentfluent-0.8.0 → agentfluent-0.10.0}/tests/unit/cli/test_json_output.py +6 -0
  116. {agentfluent-0.8.0 → agentfluent-0.10.0}/tests/unit/cli/test_report_golden.py +10 -0
  117. {agentfluent-0.8.0 → agentfluent-0.10.0}/tests/unit/cli/test_report_renderers.py +27 -1
  118. {agentfluent-0.8.0 → agentfluent-0.10.0}/tests/unit/diff/test_compute.py +129 -1
  119. {agentfluent-0.8.0 → agentfluent-0.10.0}/tests/unit/test_agent_metrics.py +164 -1
  120. {agentfluent-0.8.0 → agentfluent-0.10.0}/tests/unit/test_agent_models.py +34 -0
  121. agentfluent-0.10.0/tests/unit/test_append_only_guard.py +267 -0
  122. agentfluent-0.10.0/tests/unit/test_complexity.py +51 -0
  123. {agentfluent-0.8.0 → agentfluent-0.10.0}/tests/unit/test_correlator.py +321 -8
  124. {agentfluent-0.8.0 → agentfluent-0.10.0}/tests/unit/test_diagnostics_pipeline.py +200 -1
  125. {agentfluent-0.8.0 → agentfluent-0.10.0}/tests/unit/test_extractor.py +5 -0
  126. {agentfluent-0.8.0 → agentfluent-0.10.0}/tests/unit/test_glossary_loader.py +35 -0
  127. agentfluent-0.10.0/tests/unit/test_hook_inspector.py +137 -0
  128. {agentfluent-0.8.0 → agentfluent-0.10.0}/tests/unit/test_parent_workload_cost.py +18 -2
  129. {agentfluent-0.8.0 → agentfluent-0.10.0}/tests/unit/test_parser.py +131 -0
  130. {agentfluent-0.8.0 → agentfluent-0.10.0}/tests/unit/test_paths.py +9 -0
  131. agentfluent-0.10.0/tests/unit/test_pipeline.py +281 -0
  132. {agentfluent-0.8.0 → agentfluent-0.10.0}/tests/unit/test_pricing.py +78 -8
  133. agentfluent-0.10.0/tests/unit/test_retention.py +183 -0
  134. agentfluent-0.10.0/tests/unit/test_sdk_session_fixture.py +123 -0
  135. {agentfluent-0.8.0 → agentfluent-0.10.0}/tests/unit/test_session_models.py +49 -0
  136. {agentfluent-0.8.0 → agentfluent-0.10.0}/tests/unit/test_tokens.py +38 -3
  137. agentfluent-0.10.0/tests/unit/test_tool_inventory_audit.py +204 -0
  138. agentfluent-0.10.0/tests/unit/test_tool_orchestration.py +193 -0
  139. {agentfluent-0.8.0 → agentfluent-0.10.0}/tests/unit/test_trace_signals.py +207 -1
  140. {agentfluent-0.8.0 → agentfluent-0.10.0}/tests/unit/test_traces_fixtures.py +9 -0
  141. agentfluent-0.10.0/tests/unit/test_traces_nested_fixture.py +110 -0
  142. {agentfluent-0.8.0 → agentfluent-0.10.0}/tests/unit/test_traces_parser.py +113 -1
  143. {agentfluent-0.8.0 → agentfluent-0.10.0}/uv.lock +221 -2
  144. agentfluent-0.8.0/.claude/agents/tester.md +0 -167
  145. agentfluent-0.8.0/.release-please-manifest.json +0 -3
  146. agentfluent-0.8.0/images/demo-analyze.svg +0 -374
  147. agentfluent-0.8.0/images/demo-config-check.svg +0 -132
  148. agentfluent-0.8.0/images/demo-diagnostics.svg +0 -421
  149. agentfluent-0.8.0/images/demo-diff.svg +0 -362
  150. agentfluent-0.8.0/images/demo-subagents.svg +0 -167
  151. agentfluent-0.8.0/src/agentfluent/analytics/agent_metrics.py +0 -152
  152. agentfluent-0.8.0/src/agentfluent/cli/formatters/helpers.py +0 -113
  153. agentfluent-0.8.0/src/agentfluent/diagnostics/agent_audit.py +0 -60
  154. agentfluent-0.8.0/tests/unit/test_pipeline.py +0 -85
  155. {agentfluent-0.8.0 → agentfluent-0.10.0}/.claude/agents/anthropic-research.md +0 -0
  156. {agentfluent-0.8.0 → agentfluent-0.10.0}/.claude/agents/candidate-verifier.md +0 -0
  157. {agentfluent-0.8.0 → agentfluent-0.10.0}/.claude/hooks/block_secret_reads.py +0 -0
  158. {agentfluent-0.8.0 → agentfluent-0.10.0}/.claude/hooks/detect_secrets_in_output.py +0 -0
  159. {agentfluent-0.8.0 → agentfluent-0.10.0}/.claude/skills/promote-candidates/SKILL.md +0 -0
  160. {agentfluent-0.8.0 → agentfluent-0.10.0}/.claude/specs/.gitkeep +0 -0
  161. {agentfluent-0.8.0 → agentfluent-0.10.0}/.claude/specs/analysis/2026-04-29-agent-portfolio-analysis.md +0 -0
  162. {agentfluent-0.8.0 → agentfluent-0.10.0}/.claude/specs/analysis/2026-05-17-v07-dogfood-analysis.md +0 -0
  163. {agentfluent-0.8.0 → agentfluent-0.10.0}/.claude/specs/analysis/333-error-pattern-precision/findings.md +0 -0
  164. {agentfluent-0.8.0 → agentfluent-0.10.0}/.claude/specs/analysis/333-error-pattern-precision/labels.tsv +0 -0
  165. {agentfluent-0.8.0 → agentfluent-0.10.0}/.claude/specs/analysis/333-error-pattern-precision/sample.tsv +0 -0
  166. {agentfluent-0.8.0 → agentfluent-0.10.0}/.claude/specs/analysis/333-error-pattern-precision/script.py +0 -0
  167. {agentfluent-0.8.0 → agentfluent-0.10.0}/.claude/specs/analysis/402-calibration/calibration.md +0 -0
  168. {agentfluent-0.8.0 → agentfluent-0.10.0}/.claude/specs/analysis/402-calibration/pairs.tsv +0 -0
  169. {agentfluent-0.8.0 → agentfluent-0.10.0}/.claude/specs/backlog-mvp.md +0 -0
  170. {agentfluent-0.8.0 → agentfluent-0.10.0}/.claude/specs/backlog-v0.3.md +0 -0
  171. {agentfluent-0.8.0 → agentfluent-0.10.0}/.claude/specs/backlog-v0.5.md +0 -0
  172. {agentfluent-0.8.0 → agentfluent-0.10.0}/.claude/specs/backlog-v0.6.md +0 -0
  173. {agentfluent-0.8.0 → agentfluent-0.10.0}/.claude/specs/backlog-v0.7.md +0 -0
  174. {agentfluent-0.8.0 → agentfluent-0.10.0}/.claude/specs/backlog-v0.8.md +0 -0
  175. {agentfluent-0.8.0 → agentfluent-0.10.0}/.claude/specs/plan-100-mcp-assessment.md +0 -0
  176. {agentfluent-0.8.0 → agentfluent-0.10.0}/.claude/specs/plan-116-mcp-extraction.md +0 -0
  177. {agentfluent-0.8.0 → agentfluent-0.10.0}/.claude/specs/prd-context-fork-loop-detection.md +0 -0
  178. {agentfluent-0.8.0 → agentfluent-0.10.0}/.claude/specs/prd-date-range-filtering.md +0 -0
  179. {agentfluent-0.8.0 → agentfluent-0.10.0}/.claude/specs/prd-glossary.md +0 -0
  180. {agentfluent-0.8.0 → agentfluent-0.10.0}/.claude/specs/prd-mvp.md +0 -0
  181. {agentfluent-0.8.0 → agentfluent-0.10.0}/.claude/specs/prd-quality-axis.md +0 -0
  182. {agentfluent-0.8.0 → agentfluent-0.10.0}/.claude/specs/prd-research-pipeline-phase-3.md +0 -0
  183. {agentfluent-0.8.0 → agentfluent-0.10.0}/.claude/specs/prd-tier3-github-enrichment.md +0 -0
  184. {agentfluent-0.8.0 → agentfluent-0.10.0}/.claude/specs/prd-tool-name-normalization.md +0 -0
  185. {agentfluent-0.8.0 → agentfluent-0.10.0}/.claude/specs/prd-v0.3.md +0 -0
  186. {agentfluent-0.8.0 → agentfluent-0.10.0}/.claude/specs/prd-v0.5.md +0 -0
  187. {agentfluent-0.8.0 → agentfluent-0.10.0}/.claude/specs/prd-v0.6.md +0 -0
  188. {agentfluent-0.8.0 → agentfluent-0.10.0}/.claude/specs/prd-v0.7.md +0 -0
  189. {agentfluent-0.8.0 → agentfluent-0.10.0}/.claude/specs/prd-v0.8.md +0 -0
  190. {agentfluent-0.8.0 → agentfluent-0.10.0}/.claude/specs/research/anthropic-feature-watch.md +0 -0
  191. {agentfluent-0.8.0 → agentfluent-0.10.0}/.claude/specs/research-update-2026-04-15.md +0 -0
  192. {agentfluent-0.8.0 → agentfluent-0.10.0}/.claude/specs/v0.4-scope-review.md +0 -0
  193. {agentfluent-0.8.0 → agentfluent-0.10.0}/.github/ISSUE_TEMPLATE/bug_report.yml +0 -0
  194. {agentfluent-0.8.0 → agentfluent-0.10.0}/.github/ISSUE_TEMPLATE/feature_request.yml +0 -0
  195. {agentfluent-0.8.0 → agentfluent-0.10.0}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
  196. {agentfluent-0.8.0 → agentfluent-0.10.0}/.github/dependabot.yml +0 -0
  197. {agentfluent-0.8.0 → agentfluent-0.10.0}/.github/workflows/dependabot-auto-merge.yml +0 -0
  198. {agentfluent-0.8.0 → agentfluent-0.10.0}/.github/workflows/milestone-docs-issue.yml +0 -0
  199. {agentfluent-0.8.0 → agentfluent-0.10.0}/.github/workflows/pr-template-check.yml +0 -0
  200. {agentfluent-0.8.0 → agentfluent-0.10.0}/.python-version +0 -0
  201. {agentfluent-0.8.0 → agentfluent-0.10.0}/LICENSE +0 -0
  202. {agentfluent-0.8.0 → agentfluent-0.10.0}/SECURITY.md +0 -0
  203. {agentfluent-0.8.0 → agentfluent-0.10.0}/docs/AGENT_ANALYTICS_RESEARCH.md +0 -0
  204. {agentfluent-0.8.0 → agentfluent-0.10.0}/docs/RAG_OVER_TOOLS_RESEARCH.md +0 -0
  205. {agentfluent-0.8.0 → agentfluent-0.10.0}/docs/SECURITY.md +0 -0
  206. {agentfluent-0.8.0 → agentfluent-0.10.0}/docs/codefluent_cli_review_042526.md +0 -0
  207. {agentfluent-0.8.0 → agentfluent-0.10.0}/images/archive/v0.3/demo-analyze.svg +0 -0
  208. {agentfluent-0.8.0 → agentfluent-0.10.0}/images/archive/v0.3/demo-config-check.svg +0 -0
  209. {agentfluent-0.8.0 → agentfluent-0.10.0}/images/archive/v0.3/demo-diagnostics.svg +0 -0
  210. {agentfluent-0.8.0 → agentfluent-0.10.0}/images/archive/v0.3/demo-subagents.svg +0 -0
  211. {agentfluent-0.8.0 → agentfluent-0.10.0}/release-please-config.json +0 -0
  212. {agentfluent-0.8.0 → agentfluent-0.10.0}/scripts/calibration/README.md +0 -0
  213. {agentfluent-0.8.0 → agentfluent-0.10.0}/scripts/calibration/build_notebook.py +0 -0
  214. {agentfluent-0.8.0 → agentfluent-0.10.0}/scripts/calibration/quality_labels.json +0 -0
  215. {agentfluent-0.8.0 → agentfluent-0.10.0}/scripts/calibration/threshold_validation.ipynb +0 -0
  216. {agentfluent-0.8.0 → agentfluent-0.10.0}/scripts/generate_glossary_md.py +0 -0
  217. {agentfluent-0.8.0 → agentfluent-0.10.0}/scripts/generate_readme_screenshots.py +0 -0
  218. {agentfluent-0.8.0 → agentfluent-0.10.0}/src/agentfluent/__init__.py +0 -0
  219. {agentfluent-0.8.0 → agentfluent-0.10.0}/src/agentfluent/agents/__init__.py +0 -0
  220. {agentfluent-0.8.0 → agentfluent-0.10.0}/src/agentfluent/analytics/__init__.py +0 -0
  221. {agentfluent-0.8.0 → agentfluent-0.10.0}/src/agentfluent/analytics/tools.py +0 -0
  222. {agentfluent-0.8.0 → agentfluent-0.10.0}/src/agentfluent/cli/__init__.py +0 -0
  223. {agentfluent-0.8.0 → agentfluent-0.10.0}/src/agentfluent/cli/_time_args.py +0 -0
  224. {agentfluent-0.8.0 → agentfluent-0.10.0}/src/agentfluent/cli/commands/__init__.py +0 -0
  225. {agentfluent-0.8.0 → agentfluent-0.10.0}/src/agentfluent/cli/commands/config_check.py +0 -0
  226. {agentfluent-0.8.0 → agentfluent-0.10.0}/src/agentfluent/cli/commands/diff_cmd.py +0 -0
  227. {agentfluent-0.8.0 → agentfluent-0.10.0}/src/agentfluent/cli/commands/list_cmd.py +0 -0
  228. {agentfluent-0.8.0 → agentfluent-0.10.0}/src/agentfluent/cli/commands/report.py +0 -0
  229. {agentfluent-0.8.0 → agentfluent-0.10.0}/src/agentfluent/cli/exit_codes.py +0 -0
  230. {agentfluent-0.8.0 → agentfluent-0.10.0}/src/agentfluent/cli/formatters/__init__.py +0 -0
  231. {agentfluent-0.8.0 → agentfluent-0.10.0}/src/agentfluent/cli/formatters/json_output.py +0 -0
  232. {agentfluent-0.8.0 → agentfluent-0.10.0}/src/agentfluent/cli/main.py +0 -0
  233. {agentfluent-0.8.0 → agentfluent-0.10.0}/src/agentfluent/config/__init__.py +0 -0
  234. {agentfluent-0.8.0 → agentfluent-0.10.0}/src/agentfluent/config/mcp_discovery.py +0 -0
  235. {agentfluent-0.8.0 → agentfluent-0.10.0}/src/agentfluent/config/scanner.py +0 -0
  236. {agentfluent-0.8.0 → agentfluent-0.10.0}/src/agentfluent/config/scoring.py +0 -0
  237. {agentfluent-0.8.0 → agentfluent-0.10.0}/src/agentfluent/core/__init__.py +0 -0
  238. {agentfluent-0.8.0 → agentfluent-0.10.0}/src/agentfluent/core/discovery.py +0 -0
  239. {agentfluent-0.8.0 → agentfluent-0.10.0}/src/agentfluent/core/filtering.py +0 -0
  240. {agentfluent-0.8.0 → agentfluent-0.10.0}/src/agentfluent/core/timeutil.py +0 -0
  241. {agentfluent-0.8.0 → agentfluent-0.10.0}/src/agentfluent/diagnostics/__init__.py +0 -0
  242. {agentfluent-0.8.0 → agentfluent-0.10.0}/src/agentfluent/diagnostics/_clustering.py +0 -0
  243. {agentfluent-0.8.0 → agentfluent-0.10.0}/src/agentfluent/diagnostics/_git_helpers.py +0 -0
  244. {agentfluent-0.8.0 → agentfluent-0.10.0}/src/agentfluent/diagnostics/builtin_actions.py +0 -0
  245. {agentfluent-0.8.0 → agentfluent-0.10.0}/src/agentfluent/diagnostics/delegation.py +0 -0
  246. {agentfluent-0.8.0 → agentfluent-0.10.0}/src/agentfluent/diagnostics/git_signals.py +0 -0
  247. {agentfluent-0.8.0 → agentfluent-0.10.0}/src/agentfluent/diagnostics/github_signals.py +0 -0
  248. {agentfluent-0.8.0 → agentfluent-0.10.0}/src/agentfluent/diagnostics/mcp_assessment.py +0 -0
  249. {agentfluent-0.8.0 → agentfluent-0.10.0}/src/agentfluent/diagnostics/quality_signals.py +0 -0
  250. {agentfluent-0.8.0 → agentfluent-0.10.0}/src/agentfluent/diff/__init__.py +0 -0
  251. {agentfluent-0.8.0 → agentfluent-0.10.0}/src/agentfluent/diff/loader.py +0 -0
  252. {agentfluent-0.8.0 → agentfluent-0.10.0}/src/agentfluent/github/__init__.py +0 -0
  253. {agentfluent-0.8.0 → agentfluent-0.10.0}/src/agentfluent/github/cache.py +0 -0
  254. {agentfluent-0.8.0 → agentfluent-0.10.0}/src/agentfluent/github/client.py +0 -0
  255. {agentfluent-0.8.0 → agentfluent-0.10.0}/src/agentfluent/github/consent.py +0 -0
  256. {agentfluent-0.8.0 → agentfluent-0.10.0}/src/agentfluent/github/detection.py +0 -0
  257. {agentfluent-0.8.0 → agentfluent-0.10.0}/src/agentfluent/github/models.py +0 -0
  258. {agentfluent-0.8.0 → agentfluent-0.10.0}/src/agentfluent/glossary/__init__.py +0 -0
  259. {agentfluent-0.8.0 → agentfluent-0.10.0}/src/agentfluent/glossary/render.py +0 -0
  260. {agentfluent-0.8.0 → agentfluent-0.10.0}/src/agentfluent/py.typed +0 -0
  261. {agentfluent-0.8.0 → agentfluent-0.10.0}/src/agentfluent/traces/__init__.py +0 -0
  262. {agentfluent-0.8.0 → agentfluent-0.10.0}/src/agentfluent/traces/discovery.py +0 -0
  263. {agentfluent-0.8.0 → agentfluent-0.10.0}/src/agentfluent/traces/linker.py +0 -0
  264. {agentfluent-0.8.0 → agentfluent-0.10.0}/src/agentfluent/traces/retry.py +0 -0
  265. {agentfluent-0.8.0 → agentfluent-0.10.0}/tests/__init__.py +0 -0
  266. {agentfluent-0.8.0 → agentfluent-0.10.0}/tests/_builders.py +0 -0
  267. {agentfluent-0.8.0 → agentfluent-0.10.0}/tests/conftest.py +0 -0
  268. {agentfluent-0.8.0 → agentfluent-0.10.0}/tests/fixtures/.gitkeep +0 -0
  269. {agentfluent-0.8.0 → agentfluent-0.10.0}/tests/fixtures/agents/empty_prompt.md +0 -0
  270. {agentfluent-0.8.0 → agentfluent-0.10.0}/tests/fixtures/agents/no_frontmatter.md +0 -0
  271. {agentfluent-0.8.0 → agentfluent-0.10.0}/tests/fixtures/agents/no_tools.md +0 -0
  272. {agentfluent-0.8.0 → agentfluent-0.10.0}/tests/fixtures/agents/vague_description.md +0 -0
  273. {agentfluent-0.8.0 → agentfluent-0.10.0}/tests/fixtures/agents/well_configured.md +0 -0
  274. {agentfluent-0.8.0 → agentfluent-0.10.0}/tests/fixtures/mcp/claude_user_only.json +0 -0
  275. {agentfluent-0.8.0 → agentfluent-0.10.0}/tests/fixtures/mcp/claude_user_with_disabled.json +0 -0
  276. {agentfluent-0.8.0 → agentfluent-0.10.0}/tests/fixtures/parent_workload_session.jsonl +0 -0
  277. {agentfluent-0.8.0 → agentfluent-0.10.0}/tests/fixtures/session_basic.jsonl +0 -0
  278. {agentfluent-0.8.0 → agentfluent-0.10.0}/tests/fixtures/session_block_per_line.jsonl +0 -0
  279. {agentfluent-0.8.0 → agentfluent-0.10.0}/tests/fixtures/session_malformed.jsonl +0 -0
  280. {agentfluent-0.8.0 → agentfluent-0.10.0}/tests/fixtures/session_skip_types.jsonl +0 -0
  281. {agentfluent-0.8.0 → agentfluent-0.10.0}/tests/fixtures/session_streaming_dupes.jsonl +0 -0
  282. {agentfluent-0.8.0 → agentfluent-0.10.0}/tests/fixtures/session_with_agent.jsonl +0 -0
  283. {agentfluent-0.8.0 → agentfluent-0.10.0}/tests/fixtures/session_with_tool_calls.jsonl +0 -0
  284. {agentfluent-0.8.0 → agentfluent-0.10.0}/tests/fixtures/subagents/agent-basic.jsonl +0 -0
  285. {agentfluent-0.8.0 → agentfluent-0.10.0}/tests/fixtures/subagents/agent-empty.jsonl +0 -0
  286. {agentfluent-0.8.0 → agentfluent-0.10.0}/tests/fixtures/subagents/agent-errors.jsonl +0 -0
  287. {agentfluent-0.8.0 → agentfluent-0.10.0}/tests/fixtures/subagents/agent-large.jsonl +0 -0
  288. {agentfluent-0.8.0 → agentfluent-0.10.0}/tests/fixtures/subagents/agent-malformed.jsonl +0 -0
  289. {agentfluent-0.8.0 → agentfluent-0.10.0}/tests/fixtures/subagents/agent-retry.jsonl +0 -0
  290. {agentfluent-0.8.0 → agentfluent-0.10.0}/tests/fixtures/subagents/agent-streaming-dupes.jsonl +0 -0
  291. {agentfluent-0.8.0 → agentfluent-0.10.0}/tests/fixtures/subagents/agent-stuck.jsonl +0 -0
  292. {agentfluent-0.8.0 → agentfluent-0.10.0}/tests/integration/__init__.py +0 -0
  293. {agentfluent-0.8.0 → agentfluent-0.10.0}/tests/integration/test_agent_extraction.py +0 -0
  294. {agentfluent-0.8.0 → agentfluent-0.10.0}/tests/integration/test_analytics.py +0 -0
  295. {agentfluent-0.8.0 → agentfluent-0.10.0}/tests/integration/test_analyze_session_scope.py +0 -0
  296. {agentfluent-0.8.0 → agentfluent-0.10.0}/tests/integration/test_config_assessment.py +0 -0
  297. {agentfluent-0.8.0 → agentfluent-0.10.0}/tests/integration/test_diagnostics.py +0 -0
  298. {agentfluent-0.8.0 → agentfluent-0.10.0}/tests/integration/test_git_signals_real.py +0 -0
  299. {agentfluent-0.8.0 → agentfluent-0.10.0}/tests/integration/test_real_sessions.py +0 -0
  300. {agentfluent-0.8.0 → agentfluent-0.10.0}/tests/integration/test_subagent_traces.py +0 -0
  301. {agentfluent-0.8.0 → agentfluent-0.10.0}/tests/unit/__init__.py +0 -0
  302. {agentfluent-0.8.0 → agentfluent-0.10.0}/tests/unit/cli/__init__.py +0 -0
  303. {agentfluent-0.8.0 → agentfluent-0.10.0}/tests/unit/cli/_recommendation_helpers.py +0 -0
  304. {agentfluent-0.8.0 → agentfluent-0.10.0}/tests/unit/cli/test_analyze_github_flag.py +0 -0
  305. {agentfluent-0.8.0 → agentfluent-0.10.0}/tests/unit/cli/test_analyze_session_scope.py +0 -0
  306. {agentfluent-0.8.0 → agentfluent-0.10.0}/tests/unit/cli/test_analyze_since_until.py +0 -0
  307. {agentfluent-0.8.0 → agentfluent-0.10.0}/tests/unit/cli/test_axis_labels.py +0 -0
  308. {agentfluent-0.8.0 → agentfluent-0.10.0}/tests/unit/cli/test_claude_config_dir.py +0 -0
  309. {agentfluent-0.8.0 → agentfluent-0.10.0}/tests/unit/cli/test_cost_labeling.py +0 -0
  310. {agentfluent-0.8.0 → agentfluent-0.10.0}/tests/unit/cli/test_deep_diagnostics_formatting.py +0 -0
  311. {agentfluent-0.8.0 → agentfluent-0.10.0}/tests/unit/cli/test_diagnostics_default.py +0 -0
  312. {agentfluent-0.8.0 → agentfluent-0.10.0}/tests/unit/cli/test_diagnostics_smoke.py +0 -0
  313. {agentfluent-0.8.0 → agentfluent-0.10.0}/tests/unit/cli/test_diff_by_model_table.py +0 -0
  314. {agentfluent-0.8.0 → agentfluent-0.10.0}/tests/unit/cli/test_diff_cmd.py +0 -0
  315. {agentfluent-0.8.0 → agentfluent-0.10.0}/tests/unit/cli/test_diff_summary.py +0 -0
  316. {agentfluent-0.8.0 → agentfluent-0.10.0}/tests/unit/cli/test_exit_codes.py +0 -0
  317. {agentfluent-0.8.0 → agentfluent-0.10.0}/tests/unit/cli/test_glossary_footer.py +0 -0
  318. {agentfluent-0.8.0 → agentfluent-0.10.0}/tests/unit/cli/test_help.py +0 -0
  319. {agentfluent-0.8.0 → agentfluent-0.10.0}/tests/unit/cli/test_json_alias.py +0 -0
  320. {agentfluent-0.8.0 → agentfluent-0.10.0}/tests/unit/cli/test_list_since_until.py +0 -0
  321. {agentfluent-0.8.0 → agentfluent-0.10.0}/tests/unit/cli/test_min_severity_filter.py +0 -0
  322. {agentfluent-0.8.0 → agentfluent-0.10.0}/tests/unit/cli/test_offload_candidates_formatting.py +0 -0
  323. {agentfluent-0.8.0 → agentfluent-0.10.0}/tests/unit/cli/test_output_modes.py +0 -0
  324. {agentfluent-0.8.0 → agentfluent-0.10.0}/tests/unit/cli/test_parse_warnings.py +0 -0
  325. {agentfluent-0.8.0 → agentfluent-0.10.0}/tests/unit/cli/test_recommendations_top_n.py +0 -0
  326. {agentfluent-0.8.0 → agentfluent-0.10.0}/tests/unit/cli/test_report_cmd.py +0 -0
  327. {agentfluent-0.8.0 → agentfluent-0.10.0}/tests/unit/cli/test_report_pipeline.py +0 -0
  328. {agentfluent-0.8.0 → agentfluent-0.10.0}/tests/unit/cli/test_tier3_degraded_banner.py +0 -0
  329. {agentfluent-0.8.0 → agentfluent-0.10.0}/tests/unit/cli/test_time_args.py +0 -0
  330. {agentfluent-0.8.0 → agentfluent-0.10.0}/tests/unit/cli/test_verbose_signal_message.py +0 -0
  331. {agentfluent-0.8.0 → agentfluent-0.10.0}/tests/unit/diff/__init__.py +0 -0
  332. {agentfluent-0.8.0 → agentfluent-0.10.0}/tests/unit/test_agent_audit.py +0 -0
  333. {agentfluent-0.8.0 → agentfluent-0.10.0}/tests/unit/test_clustering.py +0 -0
  334. {agentfluent-0.8.0 → agentfluent-0.10.0}/tests/unit/test_core_filtering.py +0 -0
  335. {agentfluent-0.8.0 → agentfluent-0.10.0}/tests/unit/test_dedup.py +0 -0
  336. {agentfluent-0.8.0 → agentfluent-0.10.0}/tests/unit/test_delegation.py +0 -0
  337. {agentfluent-0.8.0 → agentfluent-0.10.0}/tests/unit/test_delegation_yaml_draft.py +0 -0
  338. {agentfluent-0.8.0 → agentfluent-0.10.0}/tests/unit/test_diagnostics.py +0 -0
  339. {agentfluent-0.8.0 → agentfluent-0.10.0}/tests/unit/test_discovery.py +0 -0
  340. {agentfluent-0.8.0 → agentfluent-0.10.0}/tests/unit/test_git_signals.py +0 -0
  341. {agentfluent-0.8.0 → agentfluent-0.10.0}/tests/unit/test_github_cache.py +0 -0
  342. {agentfluent-0.8.0 → agentfluent-0.10.0}/tests/unit/test_github_client.py +0 -0
  343. {agentfluent-0.8.0 → agentfluent-0.10.0}/tests/unit/test_github_consent.py +0 -0
  344. {agentfluent-0.8.0 → agentfluent-0.10.0}/tests/unit/test_github_detection.py +0 -0
  345. {agentfluent-0.8.0 → agentfluent-0.10.0}/tests/unit/test_github_signals_ci_failure.py +0 -0
  346. {agentfluent-0.8.0 → agentfluent-0.10.0}/tests/unit/test_github_signals_density.py +0 -0
  347. {agentfluent-0.8.0 → agentfluent-0.10.0}/tests/unit/test_glossary_drift.py +0 -0
  348. {agentfluent-0.8.0 → agentfluent-0.10.0}/tests/unit/test_glossary_render.py +0 -0
  349. {agentfluent-0.8.0 → agentfluent-0.10.0}/tests/unit/test_mcp_assessment.py +0 -0
  350. {agentfluent-0.8.0 → agentfluent-0.10.0}/tests/unit/test_mcp_discovery.py +0 -0
  351. {agentfluent-0.8.0 → agentfluent-0.10.0}/tests/unit/test_model_routing.py +0 -0
  352. {agentfluent-0.8.0 → agentfluent-0.10.0}/tests/unit/test_parent_workload_cluster.py +0 -0
  353. {agentfluent-0.8.0 → agentfluent-0.10.0}/tests/unit/test_parent_workload_extract.py +0 -0
  354. {agentfluent-0.8.0 → agentfluent-0.10.0}/tests/unit/test_quality_signals.py +0 -0
  355. {agentfluent-0.8.0 → agentfluent-0.10.0}/tests/unit/test_recommendation_aggregation.py +0 -0
  356. {agentfluent-0.8.0 → agentfluent-0.10.0}/tests/unit/test_scanner.py +0 -0
  357. {agentfluent-0.8.0 → agentfluent-0.10.0}/tests/unit/test_scoring.py +0 -0
  358. {agentfluent-0.8.0 → agentfluent-0.10.0}/tests/unit/test_signals.py +0 -0
  359. {agentfluent-0.8.0 → agentfluent-0.10.0}/tests/unit/test_smoke.py +0 -0
  360. {agentfluent-0.8.0 → agentfluent-0.10.0}/tests/unit/test_timeutil.py +0 -0
  361. {agentfluent-0.8.0 → agentfluent-0.10.0}/tests/unit/test_tools.py +0 -0
  362. {agentfluent-0.8.0 → agentfluent-0.10.0}/tests/unit/test_traces_discovery.py +0 -0
  363. {agentfluent-0.8.0 → agentfluent-0.10.0}/tests/unit/test_traces_linker.py +0 -0
  364. {agentfluent-0.8.0 → agentfluent-0.10.0}/tests/unit/test_traces_models.py +0 -0
  365. {agentfluent-0.8.0 → agentfluent-0.10.0}/tests/unit/test_traces_retry.py +0 -0
@@ -0,0 +1,69 @@
1
+ # Security Guidance — AgentFluent
2
+
3
+ Threat model and review checklist for the `security-guidance` plugin's
4
+ LLM-backed review layers (end-of-turn diff review and commit/push review).
5
+
6
+ > **Status:** Currently dormant. The LLM layers are disabled
7
+ > (`ENABLE_CODE_SECURITY_REVIEW=0` in `.claude/settings.json`), so only the
8
+ > free deterministic pattern layer runs today. This file activates the moment
9
+ > those layers are turned on — keep it in sync with the CI workflow's
10
+ > `custom-security-scan-instructions` in `.github/workflows/security-review.yml`
11
+ > so the in-session review and the PR-time review share one threat model.
12
+
13
+ ## What AgentFluent is
14
+
15
+ A local-first Python CLI that reads user session data and agent definitions
16
+ from `~/.claude/projects/` and `~/.claude/agents/`. It stores no credentials and
17
+ transmits no user data, and runs no web server, HTML rendering, or webview. Its
18
+ **only** network egress and subprocess use is the `gh` / `git` CLI (the Tier 3
19
+ GitHub-signals feature in `src/agentfluent/github/` and `diagnostics/`), invoked
20
+ list-form with `shell=False`. No in-process HTTP client
21
+ (`requests`/`httpx`/`urllib`/`socket`) is imported anywhere outside
22
+ `src/agentfluent/github/`.
23
+
24
+ ## Attack surfaces to focus on
25
+
26
+ 1. **JSONL parser** (`src/agentfluent/core/parser.py`) — reads arbitrary
27
+ user-controlled content. Check for unsafe deserialization, resource-exhaustion
28
+ on malformed/oversized input (unbounded reads, pathological nesting), and any
29
+ code path that evaluates or executes parsed strings.
30
+ 2. **Agent definition parser** (`src/agentfluent/config/scanner.py`) — parses
31
+ YAML frontmatter. Must use `yaml.safe_load` (never `yaml.load`) to prevent
32
+ arbitrary code execution via YAML tags.
33
+ 3. **Path handling in discovery** (`src/agentfluent/core/discovery.py`) —
34
+ iterates user directories. Check for path traversal, symlink following that
35
+ escapes the projects root, and TOCTOU races between stat and open.
36
+ 4. **CLI argument parsing** (`src/agentfluent/cli/`) — user strings from
37
+ `--project`, `--agent`, `--session` are matched against discovered data.
38
+ Ensure none reach a shell, `subprocess`, or any eval-like API.
39
+
40
+ ## Project-specific sensitivities
41
+
42
+ - **Secret hygiene in output.** AgentFluent reads session transcripts that may
43
+ contain credential-looking values. Flag any new code path that echoes raw tool
44
+ output, renders un-redacted file contents, or logs values matching
45
+ `KEY|TOKEN|SECRET|PASSWORD`. (Two enforcement hooks already guard secret reads
46
+ and output — see `docs/SECURITY.md`.)
47
+ - **Untrusted strings into rendering.** Sanitize user-controlled strings before
48
+ passing them to Rich/terminal formatting; never interpolate them into shell
49
+ commands.
50
+
51
+ ## Low priority (typically non-issues here)
52
+
53
+ No web server, no HTML rendering, no webview, no authentication surface. The only
54
+ sanctioned network egress is the `gh` / `git` CLI (`src/agentfluent/github/`,
55
+ `diagnostics/`); findings that assume an in-process HTTP or web attack surface
56
+ elsewhere are almost certainly false positives — note them but rank low.
57
+
58
+ ## Review checklist
59
+
60
+ - [ ] No `eval`, `exec`, `os.system`, `subprocess(..., shell=True)`, or
61
+ `compile()` on parsed/user-derived strings.
62
+ - [ ] YAML loaded only via `yaml.safe_load`; no `pickle.load` /
63
+ `pickle.loads` on file or session content.
64
+ - [ ] User-supplied paths are resolved and confined to their expected root
65
+ (no `..` escape, no symlink escape).
66
+ - [ ] Parser bounds memory/CPU on malformed input — no unbounded buffering.
67
+ - [ ] No credential-shaped value is logged, printed, or rendered un-redacted.
68
+ - [ ] New external calls (file I/O, any future network) are wrapped in
69
+ try/except with user-friendly, non-leaking error messages.
@@ -0,0 +1,173 @@
1
+ #!/usr/bin/env python3
2
+ """PreToolUse hook: protect append-only spec logs from entry-dropping writes.
3
+
4
+ Receives the PreToolUse event JSON on stdin. For a `Write` targeting a
5
+ registered append-only file (currently `.claude/specs/decisions.md`), it
6
+ compares the set of decision-entry IDs (`## Dxxx` headings) already on disk
7
+ against the set in the proposed `content`. If the write would drop any
8
+ existing entry, the call is denied.
9
+
10
+ Background: an append-only decision log was once clobbered by an agent whose
11
+ `Write` tool does full-file replacement -- a pm intending to append one
12
+ `Dxxx` entry replaced the entire file, dropping D001-D042 in the working copy
13
+ (GitHub issue #500). This hook is the durable, agent-agnostic guard.
14
+
15
+ Scope and bypass surface (deliberately bounded):
16
+ - Guards the `Write` tool only. `Edit` is surgical (it cannot easily drop the
17
+ whole file) and simulating its result to detect drops is fragile, so it is
18
+ not guarded.
19
+ - It does NOT cover `Bash` redirection (`cat > file`, `tee`, `sed -i`), which
20
+ an agent with Bash could use to clobber the file. This hook therefore
21
+ protects *entry existence against full-file Writes*, not body content, and
22
+ is not an absolute "any tool" guarantee. A Bash-command scan is the natural
23
+ follow-up extension if that guarantee is needed.
24
+
25
+ Emits a JSON decision on stdout and exits 0 (the modern pattern); exit 2 +
26
+ stderr is the fail-closed fallback for an unparseable event, matching
27
+ `block_secret_reads.py`.
28
+
29
+ Cross-platform: stdlib only, no shell dependencies.
30
+ """
31
+
32
+ from __future__ import annotations
33
+
34
+ import json
35
+ import re
36
+ import sys
37
+ from pathlib import Path, PurePath
38
+ from typing import Any
39
+
40
+ # Registry of append-only files to protect, keyed by path SUFFIX (not bare
41
+ # basename, to avoid false positives on unrelated `decisions.md` files
42
+ # elsewhere in the tree). Each value is the anchored, multiline regex that
43
+ # captures the entry IDs whose existence must be preserved across a write.
44
+ # To protect another append-only spec, add its suffix + ID pattern here.
45
+ #
46
+ # The ID capture includes an optional `-<suffix>` segment so suffixed entries
47
+ # (e.g. `## D038-A:`) are tracked as distinct IDs. Without it, `D\d+` would
48
+ # truncate `D038-A` to `D038`, collapsing it with a sibling `## D038:` entry
49
+ # and silently allowing one of them to be dropped (the exact #500 hazard).
50
+ DECISION_ID_PATTERN = re.compile(r"^##\s+(D\d+(?:-[A-Za-z0-9]+)?)", re.MULTILINE)
51
+
52
+ APPEND_ONLY_FILES: dict[str, re.Pattern[str]] = {
53
+ ".claude/specs/decisions.md": DECISION_ID_PATTERN,
54
+ }
55
+
56
+ DENY_REASON = (
57
+ "Blocked by AgentFluent append-only guard hook "
58
+ "(.claude/hooks/guard_append_only.py). "
59
+ "This Write would drop one or more existing entries from an append-only "
60
+ "log, which is a data-loss risk (see issue #500). "
61
+ "The guard protects entry existence (`## Dxxx` headings), not body text -- "
62
+ "editing the body of an existing entry is fine as long as no entry is "
63
+ "removed. To add an entry, append to the file (e.g. read the current "
64
+ "content and write it back with the new entry appended, or use Edit), "
65
+ "preserving every existing `## Dxxx` heading."
66
+ )
67
+
68
+
69
+ def normalize(path_str: str) -> str:
70
+ """Return a forward-slash path string for suffix matching."""
71
+ return PurePath(path_str).as_posix()
72
+
73
+
74
+ def match_registered_file(path_str: str) -> re.Pattern[str] | None:
75
+ """Return the ID pattern for a registered append-only file, else None."""
76
+ if not path_str:
77
+ return None
78
+ normalized = normalize(path_str)
79
+ for suffix, pattern in APPEND_ONLY_FILES.items():
80
+ # Require a path-component boundary so an unrelated file whose tail
81
+ # merely ends in the suffix string (e.g. `.../vendor.claude/specs/
82
+ # decisions.md`) is not mistaken for the protected log.
83
+ if normalized == suffix or normalized.endswith("/" + suffix):
84
+ return pattern
85
+ return None
86
+
87
+
88
+ def extract_ids(text: str, pattern: re.Pattern[str]) -> set[str]:
89
+ """Extract the set of entry IDs from text using the registered pattern."""
90
+ return set(pattern.findall(text))
91
+
92
+
93
+ def evaluate(
94
+ existing_content: str, proposed_content: str, pattern: re.Pattern[str]
95
+ ) -> tuple[bool, str]:
96
+ """Decide whether a write should be blocked. Pure (no I/O).
97
+
98
+ Returns (blocked, reason). Blocks when any ID present in the existing
99
+ content is absent from the proposed content.
100
+ """
101
+ existing_ids = extract_ids(existing_content, pattern)
102
+ if not existing_ids:
103
+ # Nothing to protect yet (file exists but has no recognized entries).
104
+ return False, ""
105
+ proposed_ids = extract_ids(proposed_content, pattern)
106
+ missing = sorted(existing_ids - proposed_ids)
107
+ if missing:
108
+ return True, f"{DENY_REASON} (would drop: {', '.join(missing)})"
109
+ return False, ""
110
+
111
+
112
+ def check(event: dict[str, Any]) -> tuple[bool, str]:
113
+ """Inspect a PreToolUse event; return (blocked, reason)."""
114
+ if event.get("tool_name", "") != "Write":
115
+ return False, ""
116
+
117
+ tool_input = event.get("tool_input") or {}
118
+ path = tool_input.get("file_path") or ""
119
+ pattern = match_registered_file(path)
120
+ if pattern is None:
121
+ return False, ""
122
+
123
+ proposed_content = tool_input.get("content") or ""
124
+
125
+ try:
126
+ existing_content = Path(path).read_text(encoding="utf-8")
127
+ except FileNotFoundError:
128
+ # New file: there is nothing to drop.
129
+ return False, ""
130
+ except (OSError, UnicodeDecodeError) as e:
131
+ # A real read error on an existing protected file means we cannot
132
+ # verify the write is safe. Fail closed -- deny rather than risk a
133
+ # silent clobber. (Such errors on a local file are vanishingly rare.)
134
+ return True, (
135
+ f"{DENY_REASON} (could not read the existing file to verify no "
136
+ f"entries are dropped: {e})"
137
+ )
138
+
139
+ return evaluate(existing_content, proposed_content, pattern)
140
+
141
+
142
+ def emit_decision(decision: str, reason: str) -> None:
143
+ payload = {
144
+ "hookSpecificOutput": {
145
+ "hookEventName": "PreToolUse",
146
+ "permissionDecision": decision,
147
+ "permissionDecisionReason": reason,
148
+ }
149
+ }
150
+ sys.stdout.write(json.dumps(payload))
151
+
152
+
153
+ def main() -> int:
154
+ try:
155
+ event = json.load(sys.stdin)
156
+ except (json.JSONDecodeError, ValueError) as e:
157
+ # Fail closed: if we can't parse the event we can't confirm the write
158
+ # is safe, so deny rather than allow through a malformed event.
159
+ print(
160
+ f"guard_append_only: failed to parse hook event JSON, "
161
+ f"denying by default: {e}",
162
+ file=sys.stderr,
163
+ )
164
+ return 2
165
+
166
+ blocked, reason = check(event)
167
+ if blocked:
168
+ emit_decision("deny", reason)
169
+ return 0
170
+
171
+
172
+ if __name__ == "__main__":
173
+ sys.exit(main())
@@ -0,0 +1,40 @@
1
+ {
2
+ "_comment": "Project-specific custom patterns for the security-guidance plugin's free pattern layer. Complements (does not duplicate) the 25 built-in rules. Authored from the architect review on issue #524. JSON (not YAML) so the plugin's hook runtime never needs PyYAML. Globs use a leading wildcard because the hook matches against the absolute path with its leading slash stripped (file_path.lstrip('/') in check_patterns), and fnmatch anchors at the start.",
3
+ "patterns": [
4
+ {
5
+ "rule_name": "no-shell-true",
6
+ "regex": "shell\\s*=\\s*True",
7
+ "paths": ["*src/agentfluent/*.py"],
8
+ "exclude_paths": ["*/tests/*"],
9
+ "reminder": "AgentFluent never invokes a shell. Use list-form subprocess.run([...], shell=False); never shell=True. CLI args (--project/--agent/--session) must not reach a shell."
10
+ },
11
+ {
12
+ "rule_name": "no-dynamic-subprocess-args",
13
+ "regex": "subprocess\\.(run|Popen|call|check_output|check_call)\\(\\s*f[\"']",
14
+ "paths": ["*src/agentfluent/*.py"],
15
+ "exclude_paths": ["*/tests/*"],
16
+ "reminder": "Pass subprocess args as a list of literals/typed values, never an f-string or string-concatenated command. Building the command from interpolated input reintroduces injection even with shell=False."
17
+ },
18
+ {
19
+ "rule_name": "no-inprocess-http-client",
20
+ "regex": "(?:^|\\n)\\s*(import|from)\\s+(requests|httpx|urllib\\.request|urllib3|http\\.client|socket|aiohttp|ftplib|smtplib)\\b",
21
+ "paths": ["*src/agentfluent/*.py"],
22
+ "exclude_paths": ["*/tests/*", "*src/agentfluent/github/*"],
23
+ "reminder": "AgentFluent's only network egress is the gh CLI (Tier 3 GitHub signals). Do not import an in-process HTTP client. If you are adding a network feature, route it through the gh subprocess path or open a design discussion first."
24
+ },
25
+ {
26
+ "rule_name": "no-archive-extractall",
27
+ "substrings": [".extractall(", "tarfile.open(", "zipfile.ZipFile("],
28
+ "paths": ["*src/agentfluent/*.py"],
29
+ "exclude_paths": ["*/tests/*"],
30
+ "reminder": "AgentFluent reads JSONL, never archives. extractall() is a zip-slip / tar-slip path-traversal sink. If you genuinely need extraction, validate every member path stays within the target dir."
31
+ },
32
+ {
33
+ "rule_name": "no-render-secret-shaped-values",
34
+ "regex": "(print|log(ger)?\\.\\w+|console\\.print)\\([^)]*(api_key|api-key|secret|password)",
35
+ "paths": ["*src/agentfluent/*.py"],
36
+ "exclude_paths": ["*/tests/*"],
37
+ "reminder": "Session transcripts can contain secret-shaped values. Never print/log a variable named api_key/secret/password un-redacted. Redact at the boundary; see docs/SECURITY.md."
38
+ }
39
+ ]
40
+ }
@@ -6,7 +6,16 @@
6
6
  "hooks": [
7
7
  {
8
8
  "type": "command",
9
- "command": "python3 .claude/hooks/block_secret_reads.py"
9
+ "command": "python3 \"$CLAUDE_PROJECT_DIR/.claude/hooks/block_secret_reads.py\""
10
+ }
11
+ ]
12
+ },
13
+ {
14
+ "matcher": "Write",
15
+ "hooks": [
16
+ {
17
+ "type": "command",
18
+ "command": "python3 \"$CLAUDE_PROJECT_DIR/.claude/hooks/guard_append_only.py\""
10
19
  }
11
20
  ]
12
21
  }
@@ -17,7 +26,7 @@
17
26
  "hooks": [
18
27
  {
19
28
  "type": "command",
20
- "command": "python3 .claude/hooks/detect_secrets_in_output.py"
29
+ "command": "python3 \"$CLAUDE_PROJECT_DIR/.claude/hooks/detect_secrets_in_output.py\""
21
30
  }
22
31
  ]
23
32
  }
@@ -32,5 +41,13 @@
32
41
  ]
33
42
  }
34
43
  ]
44
+ },
45
+ "enabledPlugins": {
46
+ "security-guidance@claude-plugins-official": true
47
+ },
48
+ "env": {
49
+ "ENABLE_CODE_SECURITY_REVIEW": "0",
50
+ "SECURITY_REVIEW_MODEL": "claude-sonnet-4-6",
51
+ "SG_AGENTIC_MODEL": "claude-sonnet-4-6"
35
52
  }
36
53
  }
@@ -0,0 +1,204 @@
1
+ ---
2
+ name: release-loop
3
+ description: Run one routed iteration of the supervised dev loop over a backlog (milestone/label). Selects the next unblocked issue, routes it, drives plan→architect→implement→review→merge with human gates on uncertainty, and journals to the ledger. Invoke once per issue; re-invoke (or drive via /loop) for the next. Use when the user wants to work a backlog as a loop, "run the release loop", or "do the next issue".
4
+ ---
5
+
6
+ # Release Loop — orchestrator (ONE issue per invocation)
7
+
8
+ You are the orchestrator of a supervised dev loop. Each invocation handles exactly ONE
9
+ issue end-to-end, journals, and stops. State lives in the ledger, not your context — so a
10
+ fresh invocation resumes correctly. Read the project parameters in
11
+ `.claude/specs/prd-loop-engineering.md` §4.0.
12
+
13
+ ## 0. Load or initialize state
14
+ 1. Identify the active run (most recent `LEDGER_ROOT/<run>/`). If it already carries a
15
+ `RUN COMPLETE` sentinel (§9), report done and STOP — do not re-scan. If no run exists, ask
16
+ the user which milestone/label to run, then INITIALIZE per §7.5 of the spec.
17
+ 2. Read `queue.md` (note its `mode:` / `graduated-routes:` header and any `hold` rows) and the tail of `progress.md`.
18
+ 3. **Resume before selecting (spec §7.6).** If any row sits in an *interrupted* status —
19
+ non-terminal and NOT `queued`/`routed`/`hold` (i.e. `planning`/`plan-approved`/
20
+ `implementing`/`in-pr`/`in-review`) — a prior iteration was cut off. Reconcile it against
21
+ LIVE git/PR state as the source of truth — branch exists? PR open? already merged? CI
22
+ status? — plus the working tree (status is only a coarse anchor; git wins on conflict),
23
+ then re-enter the pipeline at the matching stage and FINISH that issue BEFORE selecting a
24
+ new one. This is what makes "one PR at a time" hold across `/clear`/compaction. A `hold`
25
+ row is NOT an interruption: skip it here, leave it held — it stays parked until the human
26
+ releases the hold and does not block working other issues.
27
+
28
+ ## 1. Select
29
+ **Budget cap (iteration start, retrospective).** Read `iteration-cap:` / `subagent-cap:` from the
30
+ `queue.md` header (both default `none` = uncapped). Cumulative iterations = the count of **distinct
31
+ issues at a terminal status** (`done`/`deferred`/`blocked`) in `queue.md` — never count
32
+ `progress.md` blocks (a `/clear`-resume re-enters an iteration and double-counts). Breach = that
33
+ count ≥ `iteration-cap`, OR the **prior** iteration's journaled `- Budget:` line (§12) shows
34
+ `subagent-runs` ≥ `subagent-cap`. On breach: **manual re-invoke is advisory** — journal + surface
35
+ it and proceed (the human who invoked is the budget authority); **the driver halts.** Inert while
36
+ both caps are `none`.
37
+
38
+ A row is **selectable** if its status is `queued`/`routed`, OR it is `blocked` on an unmet
39
+ dependency that has SINCE cleared (all its `Depends on` issues are now `done` — re-route it via
40
+ §2; this does NOT apply to a `blocked: too-large` park, which waits on a split). Among
41
+ selectable rows pick by `PRIORITY_LABELS` order, tiebreak issue-number ascending. If none are
42
+ selectable:
43
+ - If EVERY row is terminal (`done`/`deferred`/`blocked`), append the §9 `RUN COMPLETE —
44
+ <run-slug>` sentinel to `progress.md` (counts + any blocked/deferred items) and STOP
45
+ (convergence).
46
+ - Else if the only non-terminal rows are `hold`, report "<n> held — awaiting human
47
+ merge-release" and STOP **without** the sentinel (the run is not complete).
48
+ - Else (rows still blocked on open in-run dependencies) report what's pending and STOP
49
+ without the sentinel.
50
+ **Size guard:** before entering the pipeline, estimate scope from the issue body — if it
51
+ plausibly touches many files or spans multiple unrelated acceptance-criteria clusters (won't
52
+ fit one context window), mark it `blocked: too-large`, escalate to SCOPE_AGENT to split, and
53
+ go back to select. Aggressively offload reading/analysis to subagents (architect, AC-verifier)
54
+ within an iteration to conserve the parent's context.
55
+
56
+ ## 2. Triage / route (if not already routed)
57
+ Run §7.3 to set the row's **Route** (`code`/`research`/`docs`/`stub-defer`) and its **initial
58
+ Status** (Route and Status are distinct — §6.1): `stub-defer` → Status `deferred` (terminal);
59
+ an unmet dependency → Status `blocked` (parked; record the dep, or `too-large`, in Notes — the
60
+ Route is retained so the row resumes as that route when the dependency clears, §1); otherwise →
61
+ Status `routed`. If the Status is `deferred` or `blocked`, journal why and go back to §1 — do
62
+ not implement.
63
+
64
+ ## 3. Plan
65
+ Set the row status to `planning`. Fetch the issue (`gh issue view <N>`). Write
66
+ `issue-<N>.plan.md` (template in spec §6.3), copying acceptance criteria verbatim. Lighter for
67
+ research/docs.
68
+
69
+ **Value framing (opens the plan, route-scaled).** State *why this should exist* in
70
+ user terms — the question the architect/AC-verifier/code-review gates never ask (they check we
71
+ build the thing right, not that it's the right thing). You write it inline; it is not extra
72
+ ceremony. Scale it to the route:
73
+ - **`feat:`** — a compact user-story map: one backbone activity + 1–3 `as a <user>, I want
74
+ <capability>, so that <outcome>` stories. Each carries **who benefits**, its **prevalence**
75
+ (how often real configs/corpora actually hit it), and a **falsifier** — *what single
76
+ observation would show this feature is misdirected?* (e.g. "~0 matching instances in any real
77
+ corpus"). A story with no credible user, or no checkable falsifier, is a red flag.
78
+ - **`fix:`** — one line: who hits the bug, how often, what breaks without the fix.
79
+ - **`docs:`** — who reads it and what it unblocks.
80
+ - **`research:`** — the question, the downstream decision it informs, and what a **null result**
81
+ would mean (a null that changes nothing is a sign the question isn't worth asking).
82
+ - **`chore:` / `refactor:`** — one line: what internal tooling or quality this serves and why
83
+ now; no user-facing story required (state "internal tooling, no PyPI-visible change" if so).
84
+
85
+ **Discharge cheap falsifiers at plan time — don't just state them.** If a story's falsifier is
86
+ checkable *before* code (a grep / corpus / prevalence pass), RUN it now, or escalate; a
87
+ stated-but-unrun falsifier is not sufficient. This is the load-bearing step: the cheap corpus
88
+ pass is exactly what caught #437 — but only post-hoc. Defer discharge only when the check
89
+ genuinely requires the built feature.
90
+
91
+ **Source-fidelity check (any externally-cited justification).** If the issue's rationale leans on
92
+ an external source — a research-scout (`anthropic-feature-watch`) candidate, a linked article, a
93
+ postmortem — confirm the source actually supports the generalization the issue makes: **locus**
94
+ (does the incident occur on the surface this feature inspects?), **evidence base** (n, scope,
95
+ whether the source itself generalizes), and **current relevance** (already fixed upstream?
96
+ version-specific?). An issue that extrapolates past what its source establishes is misdirected
97
+ regardless of implementation quality. (The #437 lesson — decision D046.)
98
+
99
+ **When you can't articulate it, escalate — don't build.** If you cannot state a credible user
100
+ *and* a checkable falsifier, route the issue to SCOPE_AGENT (pm) BEFORE implementing; do not
101
+ proceed on a plan whose value story doesn't hold.
102
+
103
+ ## 4. Architect gate (conditional)
104
+ If any §7.2 trigger fires OR you are unsure about the design, invoke the DESIGN_AGENT with
105
+ the plan; address `blocking`/`important` concerns before coding. Skip for docs and trivial
106
+ research.
107
+
108
+ ## 5. Human gate (conditional — every mode)
109
+ The plan gate is **conditional in every mode** — `mode:` gates the merge gate only (§11), never
110
+ this one. It is **value-first**: present the §3 value framing (user-story map / value statement)
111
+ alongside the approach, and treat a **non-credible value story — no plausible user, or no
112
+ checkable falsifier — as itself a reason to STOP**, not just ambiguous ACs. Present the plan and
113
+ STOP for approval when: the value story doesn't hold; acceptance criteria are ambiguous; the
114
+ change is risky/irreversible; SCOPE/DESIGN agents disagree or punt; or you are otherwise unsure.
115
+ Otherwise proceed (note "auto-approved" + why in the journal). Route scope/value questions to
116
+ SCOPE_AGENT and design questions to DESIGN_AGENT BEFORE escalating to the human. On approval
117
+ (human or auto), advance the row to `plan-approved`.
118
+
119
+ ## 6. Implement (you, the parent thread)
120
+ Advance the row to `implementing`. Create the branch (`BRANCH_FMT`). Implement code + tests +
121
+ docs per the plan. TDD where it fits (write failing tests, commit, do not modify tests later).
122
+ Run `LINT_CMD`, `TYPE_CMD`, `TEST_CMD` until green. Do NOT stage unrelated pre-existing
123
+ working-tree changes.
124
+
125
+ ## 7. Verify done (independent, fresh context)
126
+ Run the AC-verifier (spec §7.4): a fresh check that the diff satisfies EVERY acceptance
127
+ criterion — verify state, not your claim. If gaps, fix and re-verify (max 2 rounds, else
128
+ escalate).
129
+
130
+ ## 8. Commit + PR
131
+ Commit with correct `COMMIT_CONV` scope. Open the PR; **replicate `PR_TEMPLATE` fully** in
132
+ the body; make the Security-review choice up front. Advance the row to `in-pr` and record the
133
+ PR number. Wait for CI; fix until green.
134
+
135
+ ## 9. Code review
136
+ Advance the row to `in-review`. Run CODE_REVIEW on the diff. Implement viable findings;
137
+ decline others with a one-line rationale; **verify recs were applied**. Bounded to 2 rounds —
138
+ contested findings escalate to the human, do not loop. Commit fixes.
139
+
140
+ ## 10. Security review (by route)
141
+ - `.claude/`-only change → run local `/security-review` (the labeled workflow excludes
142
+ `.claude/`; the local skill needs `git remote set-head origin -a` if it errors on
143
+ `origin/HEAD...`).
144
+ - Otherwise, if a sensitive surface is touched → apply `needs-security-review` ONLY now
145
+ (dev-complete). Skip for docs/no-surface changes.
146
+ Address findings ≥ the project's confidence bar.
147
+
148
+ ## 11. Merge
149
+ Read the run `mode` and `graduated-routes` from the `queue.md` header. The merge gate is the
150
+ **only** gate `mode` changes (§5 is conditional in every mode). A row is **auto-merge-eligible**
151
+ only when ALL of these hold:
152
+ - `mode: escalation-only`, AND
153
+ - the row's Route is listed in the header's `graduated-routes` field, AND
154
+ - the version bump is ≤ patch — a `docs`/`chore` change produces no bump, which qualifies, AND
155
+ - the row is **not** `hold`, AND
156
+ - none of the always-escalate conditions apply: a `feat:`/breaking change, a risky/irreversible
157
+ change, a touched security surface, or a contested review finding.
158
+
159
+ **Default-deny:** if route graduation or any always-escalate condition is uncertain, the row is
160
+ **not** auto-merge-eligible — fall back to the human merge gate.
161
+
162
+ If the row is **not** auto-merge-eligible — which includes *every* row under `mode: calibration`
163
+ (the default) and any `hold` row — STOP and ask the human before merging; never auto-merge.
164
+ **If the human holds the merge (now or in any later invocation),
165
+ WRITE the hold to the row before stopping** — set Status `hold` (record the reason in Notes) so
166
+ it persists across `/clear`; resume (step 0.3), §1, and this gate all key on Status `hold` and
167
+ honor it until the human clears it (restoring the row's prior status). When the row **is**
168
+ auto-merge-eligible (or the human has approved), and CI + security are green AND the row is not
169
+ `hold`:
170
+ squash-merge with an explicit `--subject` carrying the correct `COMMIT_CONV` scope,
171
+ `--delete-branch`. Confirm the issue closed.
172
+
173
+ ## 12. Journal + stop
174
+ Append the iteration block to `progress.md`, including a `- Budget:` line (spec §6.2):
175
+ `subagent-runs=<n>` · `gate-rounds=architect=<a>,code-review=<c>,ac-verify=<v>` ·
176
+ `wall-clock=<elapsed, includes gate-wait — not a cap input>` · `tokens=deferred` (computed
177
+ post-hoc by AgentFluent over the loop JSONL; the named slot keeps the line forward-stable).
178
+ Set the `queue.md` row to `done` (or `blocked`/`deferred` with reason); note newly-unblocked
179
+ issues. The ledger is gitignored — do NOT commit it (spec §6.4). STOP. (Driver re-invokes with
180
+ fresh context for the next issue.)
181
+
182
+ ## Escalation rubric (when unsure)
183
+ Scope/priority/requirements — including any plan whose value story lacks a credible user or a
184
+ checkable falsifier (§3) — → SCOPE_AGENT (pm), before implementing. Design/implementation →
185
+ DESIGN_AGENT. Escalate to the HUMAN only when those disagree/punt, ACs are unresolvable, an
186
+ action is destructive/irreversible, a review finding is contested, or the same step failed twice.
187
+
188
+ ## Guardrails
189
+ One PR at a time (no stacked PRs). **Stuck = the same error SIGNATURE recurs** — grep the FULL
190
+ `progress.md` (not just the tail) for the signature: an identical CI failure, or the same
191
+ tool+args failing again — NOT merely re-entering a status (a legitimate `/clear`-resume
192
+ re-enters `implementing` and must not be flagged). On a genuine repeat: stop, escalate, mark
193
+ `blocked`, move on. Respect any iteration/budget cap (`iteration-cap:`/`subagent-cap:` in the
194
+ `queue.md` header): checked at iteration start (§1) against the ledger — **advisory in manual
195
+ re-invoke (journaled + surfaced, not gating), halted by the driver**.
196
+
197
+ ## Tool surface — and what you must NOT do
198
+ This skill intentionally runs with the full session toolset (no `allowed-tools` restriction):
199
+ an orchestrator needs Write/Edit, Bash(git+gh+tests), Agent (pm/architect/AC-verifier), and
200
+ the built-in review skills. With that power come hard limits — never force-push; never bypass
201
+ failing CI (no `gh pr merge --admin`, never merge red); only `--delete-branch` the PR's own
202
+ branch; never `git add` unrelated pre-existing working-tree changes; never edit the
203
+ user-global SCOPE_AGENT/DESIGN_AGENT definitions. The C1 append-only guard and the
204
+ human/merge gates are the enforced backstops; the rest of this list is your contract.