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.
- agentfluent-0.10.0/.claude/claude-security-guidance.md +69 -0
- agentfluent-0.10.0/.claude/hooks/guard_append_only.py +173 -0
- agentfluent-0.10.0/.claude/security-patterns.json +40 -0
- {agentfluent-0.8.0 → agentfluent-0.10.0}/.claude/settings.json +19 -2
- agentfluent-0.10.0/.claude/skills/release-loop/SKILL.md +204 -0
- agentfluent-0.10.0/.claude/specs/agent-sdk-session-format-findings.md +312 -0
- agentfluent-0.10.0/.claude/specs/analysis/2026-05-30-v08-dogfood/analysis.md +186 -0
- agentfluent-0.10.0/.claude/specs/analysis/2026-06-05-v09-dogfood/analysis.md +164 -0
- agentfluent-0.10.0/.claude/specs/analysis/2026-06-05-v09-dogfood/analyze.json +127576 -0
- agentfluent-0.10.0/.claude/specs/analysis/2026-06-05-v09-dogfood/analyze.table.txt +2823 -0
- agentfluent-0.10.0/.claude/specs/analysis/407-calibration/calibration.md +196 -0
- agentfluent-0.10.0/.claude/specs/analysis/407-calibration/detections.tsv +196 -0
- agentfluent-0.10.0/.claude/specs/analysis/407-calibration/export.py +126 -0
- agentfluent-0.10.0/.claude/specs/analysis/407-calibration/sample.py +40 -0
- agentfluent-0.10.0/.claude/specs/analysis/407-calibration/tune.py +45 -0
- agentfluent-0.10.0/.claude/specs/backlog-v0.9.md +458 -0
- {agentfluent-0.8.0 → agentfluent-0.10.0}/.claude/specs/decisions.md +171 -0
- {agentfluent-0.8.0 → agentfluent-0.10.0}/.claude/specs/prd-advanced-tool-use-diagnostics.md +11 -6
- agentfluent-0.10.0/.claude/specs/prd-agent-sdk-discovery.md +135 -0
- agentfluent-0.10.0/.claude/specs/prd-loop-engineering.md +757 -0
- agentfluent-0.10.0/.claude/specs/prd-pricing-genai-migration.md +151 -0
- agentfluent-0.10.0/.claude/specs/prd-v0.10.md +227 -0
- agentfluent-0.10.0/.claude/specs/prd-v0.9.md +225 -0
- agentfluent-0.10.0/.claude/specs/value-review-437-verbosity.md +182 -0
- {agentfluent-0.8.0 → agentfluent-0.10.0}/.github/workflows/ci.yml +1 -1
- {agentfluent-0.8.0 → agentfluent-0.10.0}/.github/workflows/claude-review.yml +1 -1
- {agentfluent-0.8.0 → agentfluent-0.10.0}/.github/workflows/release-please.yml +1 -1
- {agentfluent-0.8.0 → agentfluent-0.10.0}/.github/workflows/security-review.yml +9 -2
- {agentfluent-0.8.0 → agentfluent-0.10.0}/.gitignore +8 -0
- agentfluent-0.10.0/.release-please-manifest.json +3 -0
- {agentfluent-0.8.0 → agentfluent-0.10.0}/CHANGELOG.md +59 -0
- {agentfluent-0.8.0 → agentfluent-0.10.0}/CLAUDE.md +43 -6
- {agentfluent-0.8.0 → agentfluent-0.10.0}/CONTRIBUTING.md +18 -0
- {agentfluent-0.8.0 → agentfluent-0.10.0}/PKG-INFO +32 -12
- {agentfluent-0.8.0 → agentfluent-0.10.0}/README.md +31 -11
- agentfluent-0.10.0/docs/COST_MODEL.md +35 -0
- agentfluent-0.10.0/docs/EFFICIENCY_DIAGNOSTICS_RESEARCH.md +130 -0
- {agentfluent-0.8.0 → agentfluent-0.10.0}/docs/GLOSSARY.md +349 -2
- {agentfluent-0.8.0 → agentfluent-0.10.0}/docs/ROADMAP.md +43 -2
- agentfluent-0.10.0/images/demo-analyze.svg +426 -0
- agentfluent-0.10.0/images/demo-config-check.svg +136 -0
- agentfluent-0.10.0/images/demo-diagnostics.svg +445 -0
- agentfluent-0.10.0/images/demo-diff.svg +466 -0
- agentfluent-0.10.0/images/demo-subagents.svg +167 -0
- {agentfluent-0.8.0 → agentfluent-0.10.0}/pyproject.toml +4 -1
- agentfluent-0.10.0/research/agent-sdk-probe/FINDINGS.md +607 -0
- agentfluent-0.10.0/research/agent-sdk-probe/README.md +107 -0
- agentfluent-0.10.0/research/agent-sdk-probe/agent.py +269 -0
- agentfluent-0.10.0/research/agent-sdk-probe/fixture.txt +7 -0
- agentfluent-0.10.0/research/agent-sdk-probe/probe.py +38 -0
- agentfluent-0.10.0/research/agent-sdk-probe/run_matrix.py +208 -0
- agentfluent-0.10.0/research/agent-sdk-probe/sampledata/code.py +7 -0
- agentfluent-0.10.0/research/agent-sdk-probe/sampledata/data.csv +6 -0
- agentfluent-0.10.0/research/agent-sdk-probe/sampledata/notes.txt +12 -0
- {agentfluent-0.8.0 → agentfluent-0.10.0}/src/agentfluent/agents/extractor.py +3 -0
- {agentfluent-0.8.0 → agentfluent-0.10.0}/src/agentfluent/agents/models.py +33 -0
- agentfluent-0.10.0/src/agentfluent/analytics/agent_metrics.py +288 -0
- {agentfluent-0.8.0 → agentfluent-0.10.0}/src/agentfluent/analytics/pipeline.py +112 -2
- {agentfluent-0.8.0 → agentfluent-0.10.0}/src/agentfluent/analytics/pricing.py +56 -16
- {agentfluent-0.8.0 → agentfluent-0.10.0}/src/agentfluent/analytics/tokens.py +23 -10
- {agentfluent-0.8.0 → agentfluent-0.10.0}/src/agentfluent/cli/commands/analyze.py +22 -1
- {agentfluent-0.8.0 → agentfluent-0.10.0}/src/agentfluent/cli/commands/explain.py +17 -2
- {agentfluent-0.8.0 → agentfluent-0.10.0}/src/agentfluent/cli/commands/report_renderers.py +29 -8
- {agentfluent-0.8.0 → agentfluent-0.10.0}/src/agentfluent/cli/formatters/diff_table.py +30 -7
- agentfluent-0.10.0/src/agentfluent/cli/formatters/helpers.py +218 -0
- {agentfluent-0.8.0 → agentfluent-0.10.0}/src/agentfluent/cli/formatters/table.py +65 -4
- agentfluent-0.10.0/src/agentfluent/config/hook_inspector.py +142 -0
- {agentfluent-0.8.0 → agentfluent-0.10.0}/src/agentfluent/config/models.py +51 -0
- agentfluent-0.10.0/src/agentfluent/config/retention.py +179 -0
- {agentfluent-0.8.0 → agentfluent-0.10.0}/src/agentfluent/core/parser.py +34 -1
- {agentfluent-0.8.0 → agentfluent-0.10.0}/src/agentfluent/core/paths.py +18 -0
- {agentfluent-0.8.0 → agentfluent-0.10.0}/src/agentfluent/core/session.py +60 -2
- {agentfluent-0.8.0 → agentfluent-0.10.0}/src/agentfluent/diagnostics/_complexity.py +68 -0
- agentfluent-0.10.0/src/agentfluent/diagnostics/agent_audit.py +163 -0
- {agentfluent-0.8.0 → agentfluent-0.10.0}/src/agentfluent/diagnostics/aggregation.py +3 -0
- {agentfluent-0.8.0 → agentfluent-0.10.0}/src/agentfluent/diagnostics/correlator.py +372 -13
- {agentfluent-0.8.0 → agentfluent-0.10.0}/src/agentfluent/diagnostics/model_routing.py +47 -46
- {agentfluent-0.8.0 → agentfluent-0.10.0}/src/agentfluent/diagnostics/models.py +7 -3
- {agentfluent-0.8.0 → agentfluent-0.10.0}/src/agentfluent/diagnostics/parent_workload.py +2 -1
- {agentfluent-0.8.0 → agentfluent-0.10.0}/src/agentfluent/diagnostics/pipeline.py +48 -3
- {agentfluent-0.8.0 → agentfluent-0.10.0}/src/agentfluent/diagnostics/signals.py +26 -11
- agentfluent-0.10.0/src/agentfluent/diagnostics/tool_orchestration.py +183 -0
- {agentfluent-0.8.0 → agentfluent-0.10.0}/src/agentfluent/diagnostics/trace_signals.py +215 -15
- {agentfluent-0.8.0 → agentfluent-0.10.0}/src/agentfluent/diff/compute.py +23 -0
- {agentfluent-0.8.0 → agentfluent-0.10.0}/src/agentfluent/diff/models.py +39 -0
- {agentfluent-0.8.0 → agentfluent-0.10.0}/src/agentfluent/glossary/loader.py +32 -0
- {agentfluent-0.8.0 → agentfluent-0.10.0}/src/agentfluent/glossary/models.py +2 -0
- {agentfluent-0.8.0 → agentfluent-0.10.0}/src/agentfluent/glossary/terms.yaml +313 -2
- {agentfluent-0.8.0 → agentfluent-0.10.0}/src/agentfluent/traces/models.py +41 -0
- {agentfluent-0.8.0 → agentfluent-0.10.0}/src/agentfluent/traces/parser.py +36 -9
- agentfluent-0.10.0/tests/fixtures/hooks/inline_bash_timing.yaml +15 -0
- agentfluent-0.10.0/tests/fixtures/hooks/no_post_hook.yaml +9 -0
- agentfluent-0.10.0/tests/fixtures/hooks/secrets_hook.py +18 -0
- agentfluent-0.10.0/tests/fixtures/hooks/timing_hook.py +15 -0
- agentfluent-0.10.0/tests/fixtures/nested_session/README.md +43 -0
- agentfluent-0.10.0/tests/fixtures/nested_session/nested-session-1/subagents/agent-leaf0001.jsonl +4 -0
- agentfluent-0.10.0/tests/fixtures/nested_session/nested-session-1/subagents/agent-leaf0001.meta.json +1 -0
- agentfluent-0.10.0/tests/fixtures/nested_session/nested-session-1/subagents/agent-worker001.jsonl +6 -0
- agentfluent-0.10.0/tests/fixtures/nested_session/nested-session-1/subagents/agent-worker001.meta.json +1 -0
- agentfluent-0.10.0/tests/fixtures/nested_session/nested-session-1.jsonl +4 -0
- {agentfluent-0.8.0 → agentfluent-0.10.0}/tests/fixtures/report/analyze_snapshot.json +8 -2
- {agentfluent-0.8.0 → agentfluent-0.10.0}/tests/fixtures/report/expected_report.md +6 -4
- agentfluent-0.10.0/tests/fixtures/sdk_session/README.md +62 -0
- agentfluent-0.10.0/tests/fixtures/sdk_session/sdk-main-1/subagents/agent-child0000001.jsonl +2 -0
- agentfluent-0.10.0/tests/fixtures/sdk_session/sdk-main-1/subagents/agent-child0000001.meta.json +5 -0
- agentfluent-0.10.0/tests/fixtures/sdk_session/sdk-main-1.jsonl +4 -0
- {agentfluent-0.8.0 → agentfluent-0.10.0}/tests/unit/cli/conftest.py +11 -0
- {agentfluent-0.8.0 → agentfluent-0.10.0}/tests/unit/cli/test_active_duration_display.py +66 -0
- agentfluent-0.10.0/tests/unit/cli/test_agent_duration_cell.py +128 -0
- {agentfluent-0.8.0 → agentfluent-0.10.0}/tests/unit/cli/test_axis_json_contract.py +30 -0
- agentfluent-0.10.0/tests/unit/cli/test_cleanup_warning_cli.py +96 -0
- {agentfluent-0.8.0 → agentfluent-0.10.0}/tests/unit/cli/test_diff_table_signed.py +17 -0
- agentfluent-0.10.0/tests/unit/cli/test_environment_warning_banner.py +77 -0
- {agentfluent-0.8.0 → agentfluent-0.10.0}/tests/unit/cli/test_explain.py +5 -2
- {agentfluent-0.8.0 → agentfluent-0.10.0}/tests/unit/cli/test_json_output.py +6 -0
- {agentfluent-0.8.0 → agentfluent-0.10.0}/tests/unit/cli/test_report_golden.py +10 -0
- {agentfluent-0.8.0 → agentfluent-0.10.0}/tests/unit/cli/test_report_renderers.py +27 -1
- {agentfluent-0.8.0 → agentfluent-0.10.0}/tests/unit/diff/test_compute.py +129 -1
- {agentfluent-0.8.0 → agentfluent-0.10.0}/tests/unit/test_agent_metrics.py +164 -1
- {agentfluent-0.8.0 → agentfluent-0.10.0}/tests/unit/test_agent_models.py +34 -0
- agentfluent-0.10.0/tests/unit/test_append_only_guard.py +267 -0
- agentfluent-0.10.0/tests/unit/test_complexity.py +51 -0
- {agentfluent-0.8.0 → agentfluent-0.10.0}/tests/unit/test_correlator.py +321 -8
- {agentfluent-0.8.0 → agentfluent-0.10.0}/tests/unit/test_diagnostics_pipeline.py +200 -1
- {agentfluent-0.8.0 → agentfluent-0.10.0}/tests/unit/test_extractor.py +5 -0
- {agentfluent-0.8.0 → agentfluent-0.10.0}/tests/unit/test_glossary_loader.py +35 -0
- agentfluent-0.10.0/tests/unit/test_hook_inspector.py +137 -0
- {agentfluent-0.8.0 → agentfluent-0.10.0}/tests/unit/test_parent_workload_cost.py +18 -2
- {agentfluent-0.8.0 → agentfluent-0.10.0}/tests/unit/test_parser.py +131 -0
- {agentfluent-0.8.0 → agentfluent-0.10.0}/tests/unit/test_paths.py +9 -0
- agentfluent-0.10.0/tests/unit/test_pipeline.py +281 -0
- {agentfluent-0.8.0 → agentfluent-0.10.0}/tests/unit/test_pricing.py +78 -8
- agentfluent-0.10.0/tests/unit/test_retention.py +183 -0
- agentfluent-0.10.0/tests/unit/test_sdk_session_fixture.py +123 -0
- {agentfluent-0.8.0 → agentfluent-0.10.0}/tests/unit/test_session_models.py +49 -0
- {agentfluent-0.8.0 → agentfluent-0.10.0}/tests/unit/test_tokens.py +38 -3
- agentfluent-0.10.0/tests/unit/test_tool_inventory_audit.py +204 -0
- agentfluent-0.10.0/tests/unit/test_tool_orchestration.py +193 -0
- {agentfluent-0.8.0 → agentfluent-0.10.0}/tests/unit/test_trace_signals.py +207 -1
- {agentfluent-0.8.0 → agentfluent-0.10.0}/tests/unit/test_traces_fixtures.py +9 -0
- agentfluent-0.10.0/tests/unit/test_traces_nested_fixture.py +110 -0
- {agentfluent-0.8.0 → agentfluent-0.10.0}/tests/unit/test_traces_parser.py +113 -1
- {agentfluent-0.8.0 → agentfluent-0.10.0}/uv.lock +221 -2
- agentfluent-0.8.0/.claude/agents/tester.md +0 -167
- agentfluent-0.8.0/.release-please-manifest.json +0 -3
- agentfluent-0.8.0/images/demo-analyze.svg +0 -374
- agentfluent-0.8.0/images/demo-config-check.svg +0 -132
- agentfluent-0.8.0/images/demo-diagnostics.svg +0 -421
- agentfluent-0.8.0/images/demo-diff.svg +0 -362
- agentfluent-0.8.0/images/demo-subagents.svg +0 -167
- agentfluent-0.8.0/src/agentfluent/analytics/agent_metrics.py +0 -152
- agentfluent-0.8.0/src/agentfluent/cli/formatters/helpers.py +0 -113
- agentfluent-0.8.0/src/agentfluent/diagnostics/agent_audit.py +0 -60
- agentfluent-0.8.0/tests/unit/test_pipeline.py +0 -85
- {agentfluent-0.8.0 → agentfluent-0.10.0}/.claude/agents/anthropic-research.md +0 -0
- {agentfluent-0.8.0 → agentfluent-0.10.0}/.claude/agents/candidate-verifier.md +0 -0
- {agentfluent-0.8.0 → agentfluent-0.10.0}/.claude/hooks/block_secret_reads.py +0 -0
- {agentfluent-0.8.0 → agentfluent-0.10.0}/.claude/hooks/detect_secrets_in_output.py +0 -0
- {agentfluent-0.8.0 → agentfluent-0.10.0}/.claude/skills/promote-candidates/SKILL.md +0 -0
- {agentfluent-0.8.0 → agentfluent-0.10.0}/.claude/specs/.gitkeep +0 -0
- {agentfluent-0.8.0 → agentfluent-0.10.0}/.claude/specs/analysis/2026-04-29-agent-portfolio-analysis.md +0 -0
- {agentfluent-0.8.0 → agentfluent-0.10.0}/.claude/specs/analysis/2026-05-17-v07-dogfood-analysis.md +0 -0
- {agentfluent-0.8.0 → agentfluent-0.10.0}/.claude/specs/analysis/333-error-pattern-precision/findings.md +0 -0
- {agentfluent-0.8.0 → agentfluent-0.10.0}/.claude/specs/analysis/333-error-pattern-precision/labels.tsv +0 -0
- {agentfluent-0.8.0 → agentfluent-0.10.0}/.claude/specs/analysis/333-error-pattern-precision/sample.tsv +0 -0
- {agentfluent-0.8.0 → agentfluent-0.10.0}/.claude/specs/analysis/333-error-pattern-precision/script.py +0 -0
- {agentfluent-0.8.0 → agentfluent-0.10.0}/.claude/specs/analysis/402-calibration/calibration.md +0 -0
- {agentfluent-0.8.0 → agentfluent-0.10.0}/.claude/specs/analysis/402-calibration/pairs.tsv +0 -0
- {agentfluent-0.8.0 → agentfluent-0.10.0}/.claude/specs/backlog-mvp.md +0 -0
- {agentfluent-0.8.0 → agentfluent-0.10.0}/.claude/specs/backlog-v0.3.md +0 -0
- {agentfluent-0.8.0 → agentfluent-0.10.0}/.claude/specs/backlog-v0.5.md +0 -0
- {agentfluent-0.8.0 → agentfluent-0.10.0}/.claude/specs/backlog-v0.6.md +0 -0
- {agentfluent-0.8.0 → agentfluent-0.10.0}/.claude/specs/backlog-v0.7.md +0 -0
- {agentfluent-0.8.0 → agentfluent-0.10.0}/.claude/specs/backlog-v0.8.md +0 -0
- {agentfluent-0.8.0 → agentfluent-0.10.0}/.claude/specs/plan-100-mcp-assessment.md +0 -0
- {agentfluent-0.8.0 → agentfluent-0.10.0}/.claude/specs/plan-116-mcp-extraction.md +0 -0
- {agentfluent-0.8.0 → agentfluent-0.10.0}/.claude/specs/prd-context-fork-loop-detection.md +0 -0
- {agentfluent-0.8.0 → agentfluent-0.10.0}/.claude/specs/prd-date-range-filtering.md +0 -0
- {agentfluent-0.8.0 → agentfluent-0.10.0}/.claude/specs/prd-glossary.md +0 -0
- {agentfluent-0.8.0 → agentfluent-0.10.0}/.claude/specs/prd-mvp.md +0 -0
- {agentfluent-0.8.0 → agentfluent-0.10.0}/.claude/specs/prd-quality-axis.md +0 -0
- {agentfluent-0.8.0 → agentfluent-0.10.0}/.claude/specs/prd-research-pipeline-phase-3.md +0 -0
- {agentfluent-0.8.0 → agentfluent-0.10.0}/.claude/specs/prd-tier3-github-enrichment.md +0 -0
- {agentfluent-0.8.0 → agentfluent-0.10.0}/.claude/specs/prd-tool-name-normalization.md +0 -0
- {agentfluent-0.8.0 → agentfluent-0.10.0}/.claude/specs/prd-v0.3.md +0 -0
- {agentfluent-0.8.0 → agentfluent-0.10.0}/.claude/specs/prd-v0.5.md +0 -0
- {agentfluent-0.8.0 → agentfluent-0.10.0}/.claude/specs/prd-v0.6.md +0 -0
- {agentfluent-0.8.0 → agentfluent-0.10.0}/.claude/specs/prd-v0.7.md +0 -0
- {agentfluent-0.8.0 → agentfluent-0.10.0}/.claude/specs/prd-v0.8.md +0 -0
- {agentfluent-0.8.0 → agentfluent-0.10.0}/.claude/specs/research/anthropic-feature-watch.md +0 -0
- {agentfluent-0.8.0 → agentfluent-0.10.0}/.claude/specs/research-update-2026-04-15.md +0 -0
- {agentfluent-0.8.0 → agentfluent-0.10.0}/.claude/specs/v0.4-scope-review.md +0 -0
- {agentfluent-0.8.0 → agentfluent-0.10.0}/.github/ISSUE_TEMPLATE/bug_report.yml +0 -0
- {agentfluent-0.8.0 → agentfluent-0.10.0}/.github/ISSUE_TEMPLATE/feature_request.yml +0 -0
- {agentfluent-0.8.0 → agentfluent-0.10.0}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
- {agentfluent-0.8.0 → agentfluent-0.10.0}/.github/dependabot.yml +0 -0
- {agentfluent-0.8.0 → agentfluent-0.10.0}/.github/workflows/dependabot-auto-merge.yml +0 -0
- {agentfluent-0.8.0 → agentfluent-0.10.0}/.github/workflows/milestone-docs-issue.yml +0 -0
- {agentfluent-0.8.0 → agentfluent-0.10.0}/.github/workflows/pr-template-check.yml +0 -0
- {agentfluent-0.8.0 → agentfluent-0.10.0}/.python-version +0 -0
- {agentfluent-0.8.0 → agentfluent-0.10.0}/LICENSE +0 -0
- {agentfluent-0.8.0 → agentfluent-0.10.0}/SECURITY.md +0 -0
- {agentfluent-0.8.0 → agentfluent-0.10.0}/docs/AGENT_ANALYTICS_RESEARCH.md +0 -0
- {agentfluent-0.8.0 → agentfluent-0.10.0}/docs/RAG_OVER_TOOLS_RESEARCH.md +0 -0
- {agentfluent-0.8.0 → agentfluent-0.10.0}/docs/SECURITY.md +0 -0
- {agentfluent-0.8.0 → agentfluent-0.10.0}/docs/codefluent_cli_review_042526.md +0 -0
- {agentfluent-0.8.0 → agentfluent-0.10.0}/images/archive/v0.3/demo-analyze.svg +0 -0
- {agentfluent-0.8.0 → agentfluent-0.10.0}/images/archive/v0.3/demo-config-check.svg +0 -0
- {agentfluent-0.8.0 → agentfluent-0.10.0}/images/archive/v0.3/demo-diagnostics.svg +0 -0
- {agentfluent-0.8.0 → agentfluent-0.10.0}/images/archive/v0.3/demo-subagents.svg +0 -0
- {agentfluent-0.8.0 → agentfluent-0.10.0}/release-please-config.json +0 -0
- {agentfluent-0.8.0 → agentfluent-0.10.0}/scripts/calibration/README.md +0 -0
- {agentfluent-0.8.0 → agentfluent-0.10.0}/scripts/calibration/build_notebook.py +0 -0
- {agentfluent-0.8.0 → agentfluent-0.10.0}/scripts/calibration/quality_labels.json +0 -0
- {agentfluent-0.8.0 → agentfluent-0.10.0}/scripts/calibration/threshold_validation.ipynb +0 -0
- {agentfluent-0.8.0 → agentfluent-0.10.0}/scripts/generate_glossary_md.py +0 -0
- {agentfluent-0.8.0 → agentfluent-0.10.0}/scripts/generate_readme_screenshots.py +0 -0
- {agentfluent-0.8.0 → agentfluent-0.10.0}/src/agentfluent/__init__.py +0 -0
- {agentfluent-0.8.0 → agentfluent-0.10.0}/src/agentfluent/agents/__init__.py +0 -0
- {agentfluent-0.8.0 → agentfluent-0.10.0}/src/agentfluent/analytics/__init__.py +0 -0
- {agentfluent-0.8.0 → agentfluent-0.10.0}/src/agentfluent/analytics/tools.py +0 -0
- {agentfluent-0.8.0 → agentfluent-0.10.0}/src/agentfluent/cli/__init__.py +0 -0
- {agentfluent-0.8.0 → agentfluent-0.10.0}/src/agentfluent/cli/_time_args.py +0 -0
- {agentfluent-0.8.0 → agentfluent-0.10.0}/src/agentfluent/cli/commands/__init__.py +0 -0
- {agentfluent-0.8.0 → agentfluent-0.10.0}/src/agentfluent/cli/commands/config_check.py +0 -0
- {agentfluent-0.8.0 → agentfluent-0.10.0}/src/agentfluent/cli/commands/diff_cmd.py +0 -0
- {agentfluent-0.8.0 → agentfluent-0.10.0}/src/agentfluent/cli/commands/list_cmd.py +0 -0
- {agentfluent-0.8.0 → agentfluent-0.10.0}/src/agentfluent/cli/commands/report.py +0 -0
- {agentfluent-0.8.0 → agentfluent-0.10.0}/src/agentfluent/cli/exit_codes.py +0 -0
- {agentfluent-0.8.0 → agentfluent-0.10.0}/src/agentfluent/cli/formatters/__init__.py +0 -0
- {agentfluent-0.8.0 → agentfluent-0.10.0}/src/agentfluent/cli/formatters/json_output.py +0 -0
- {agentfluent-0.8.0 → agentfluent-0.10.0}/src/agentfluent/cli/main.py +0 -0
- {agentfluent-0.8.0 → agentfluent-0.10.0}/src/agentfluent/config/__init__.py +0 -0
- {agentfluent-0.8.0 → agentfluent-0.10.0}/src/agentfluent/config/mcp_discovery.py +0 -0
- {agentfluent-0.8.0 → agentfluent-0.10.0}/src/agentfluent/config/scanner.py +0 -0
- {agentfluent-0.8.0 → agentfluent-0.10.0}/src/agentfluent/config/scoring.py +0 -0
- {agentfluent-0.8.0 → agentfluent-0.10.0}/src/agentfluent/core/__init__.py +0 -0
- {agentfluent-0.8.0 → agentfluent-0.10.0}/src/agentfluent/core/discovery.py +0 -0
- {agentfluent-0.8.0 → agentfluent-0.10.0}/src/agentfluent/core/filtering.py +0 -0
- {agentfluent-0.8.0 → agentfluent-0.10.0}/src/agentfluent/core/timeutil.py +0 -0
- {agentfluent-0.8.0 → agentfluent-0.10.0}/src/agentfluent/diagnostics/__init__.py +0 -0
- {agentfluent-0.8.0 → agentfluent-0.10.0}/src/agentfluent/diagnostics/_clustering.py +0 -0
- {agentfluent-0.8.0 → agentfluent-0.10.0}/src/agentfluent/diagnostics/_git_helpers.py +0 -0
- {agentfluent-0.8.0 → agentfluent-0.10.0}/src/agentfluent/diagnostics/builtin_actions.py +0 -0
- {agentfluent-0.8.0 → agentfluent-0.10.0}/src/agentfluent/diagnostics/delegation.py +0 -0
- {agentfluent-0.8.0 → agentfluent-0.10.0}/src/agentfluent/diagnostics/git_signals.py +0 -0
- {agentfluent-0.8.0 → agentfluent-0.10.0}/src/agentfluent/diagnostics/github_signals.py +0 -0
- {agentfluent-0.8.0 → agentfluent-0.10.0}/src/agentfluent/diagnostics/mcp_assessment.py +0 -0
- {agentfluent-0.8.0 → agentfluent-0.10.0}/src/agentfluent/diagnostics/quality_signals.py +0 -0
- {agentfluent-0.8.0 → agentfluent-0.10.0}/src/agentfluent/diff/__init__.py +0 -0
- {agentfluent-0.8.0 → agentfluent-0.10.0}/src/agentfluent/diff/loader.py +0 -0
- {agentfluent-0.8.0 → agentfluent-0.10.0}/src/agentfluent/github/__init__.py +0 -0
- {agentfluent-0.8.0 → agentfluent-0.10.0}/src/agentfluent/github/cache.py +0 -0
- {agentfluent-0.8.0 → agentfluent-0.10.0}/src/agentfluent/github/client.py +0 -0
- {agentfluent-0.8.0 → agentfluent-0.10.0}/src/agentfluent/github/consent.py +0 -0
- {agentfluent-0.8.0 → agentfluent-0.10.0}/src/agentfluent/github/detection.py +0 -0
- {agentfluent-0.8.0 → agentfluent-0.10.0}/src/agentfluent/github/models.py +0 -0
- {agentfluent-0.8.0 → agentfluent-0.10.0}/src/agentfluent/glossary/__init__.py +0 -0
- {agentfluent-0.8.0 → agentfluent-0.10.0}/src/agentfluent/glossary/render.py +0 -0
- {agentfluent-0.8.0 → agentfluent-0.10.0}/src/agentfluent/py.typed +0 -0
- {agentfluent-0.8.0 → agentfluent-0.10.0}/src/agentfluent/traces/__init__.py +0 -0
- {agentfluent-0.8.0 → agentfluent-0.10.0}/src/agentfluent/traces/discovery.py +0 -0
- {agentfluent-0.8.0 → agentfluent-0.10.0}/src/agentfluent/traces/linker.py +0 -0
- {agentfluent-0.8.0 → agentfluent-0.10.0}/src/agentfluent/traces/retry.py +0 -0
- {agentfluent-0.8.0 → agentfluent-0.10.0}/tests/__init__.py +0 -0
- {agentfluent-0.8.0 → agentfluent-0.10.0}/tests/_builders.py +0 -0
- {agentfluent-0.8.0 → agentfluent-0.10.0}/tests/conftest.py +0 -0
- {agentfluent-0.8.0 → agentfluent-0.10.0}/tests/fixtures/.gitkeep +0 -0
- {agentfluent-0.8.0 → agentfluent-0.10.0}/tests/fixtures/agents/empty_prompt.md +0 -0
- {agentfluent-0.8.0 → agentfluent-0.10.0}/tests/fixtures/agents/no_frontmatter.md +0 -0
- {agentfluent-0.8.0 → agentfluent-0.10.0}/tests/fixtures/agents/no_tools.md +0 -0
- {agentfluent-0.8.0 → agentfluent-0.10.0}/tests/fixtures/agents/vague_description.md +0 -0
- {agentfluent-0.8.0 → agentfluent-0.10.0}/tests/fixtures/agents/well_configured.md +0 -0
- {agentfluent-0.8.0 → agentfluent-0.10.0}/tests/fixtures/mcp/claude_user_only.json +0 -0
- {agentfluent-0.8.0 → agentfluent-0.10.0}/tests/fixtures/mcp/claude_user_with_disabled.json +0 -0
- {agentfluent-0.8.0 → agentfluent-0.10.0}/tests/fixtures/parent_workload_session.jsonl +0 -0
- {agentfluent-0.8.0 → agentfluent-0.10.0}/tests/fixtures/session_basic.jsonl +0 -0
- {agentfluent-0.8.0 → agentfluent-0.10.0}/tests/fixtures/session_block_per_line.jsonl +0 -0
- {agentfluent-0.8.0 → agentfluent-0.10.0}/tests/fixtures/session_malformed.jsonl +0 -0
- {agentfluent-0.8.0 → agentfluent-0.10.0}/tests/fixtures/session_skip_types.jsonl +0 -0
- {agentfluent-0.8.0 → agentfluent-0.10.0}/tests/fixtures/session_streaming_dupes.jsonl +0 -0
- {agentfluent-0.8.0 → agentfluent-0.10.0}/tests/fixtures/session_with_agent.jsonl +0 -0
- {agentfluent-0.8.0 → agentfluent-0.10.0}/tests/fixtures/session_with_tool_calls.jsonl +0 -0
- {agentfluent-0.8.0 → agentfluent-0.10.0}/tests/fixtures/subagents/agent-basic.jsonl +0 -0
- {agentfluent-0.8.0 → agentfluent-0.10.0}/tests/fixtures/subagents/agent-empty.jsonl +0 -0
- {agentfluent-0.8.0 → agentfluent-0.10.0}/tests/fixtures/subagents/agent-errors.jsonl +0 -0
- {agentfluent-0.8.0 → agentfluent-0.10.0}/tests/fixtures/subagents/agent-large.jsonl +0 -0
- {agentfluent-0.8.0 → agentfluent-0.10.0}/tests/fixtures/subagents/agent-malformed.jsonl +0 -0
- {agentfluent-0.8.0 → agentfluent-0.10.0}/tests/fixtures/subagents/agent-retry.jsonl +0 -0
- {agentfluent-0.8.0 → agentfluent-0.10.0}/tests/fixtures/subagents/agent-streaming-dupes.jsonl +0 -0
- {agentfluent-0.8.0 → agentfluent-0.10.0}/tests/fixtures/subagents/agent-stuck.jsonl +0 -0
- {agentfluent-0.8.0 → agentfluent-0.10.0}/tests/integration/__init__.py +0 -0
- {agentfluent-0.8.0 → agentfluent-0.10.0}/tests/integration/test_agent_extraction.py +0 -0
- {agentfluent-0.8.0 → agentfluent-0.10.0}/tests/integration/test_analytics.py +0 -0
- {agentfluent-0.8.0 → agentfluent-0.10.0}/tests/integration/test_analyze_session_scope.py +0 -0
- {agentfluent-0.8.0 → agentfluent-0.10.0}/tests/integration/test_config_assessment.py +0 -0
- {agentfluent-0.8.0 → agentfluent-0.10.0}/tests/integration/test_diagnostics.py +0 -0
- {agentfluent-0.8.0 → agentfluent-0.10.0}/tests/integration/test_git_signals_real.py +0 -0
- {agentfluent-0.8.0 → agentfluent-0.10.0}/tests/integration/test_real_sessions.py +0 -0
- {agentfluent-0.8.0 → agentfluent-0.10.0}/tests/integration/test_subagent_traces.py +0 -0
- {agentfluent-0.8.0 → agentfluent-0.10.0}/tests/unit/__init__.py +0 -0
- {agentfluent-0.8.0 → agentfluent-0.10.0}/tests/unit/cli/__init__.py +0 -0
- {agentfluent-0.8.0 → agentfluent-0.10.0}/tests/unit/cli/_recommendation_helpers.py +0 -0
- {agentfluent-0.8.0 → agentfluent-0.10.0}/tests/unit/cli/test_analyze_github_flag.py +0 -0
- {agentfluent-0.8.0 → agentfluent-0.10.0}/tests/unit/cli/test_analyze_session_scope.py +0 -0
- {agentfluent-0.8.0 → agentfluent-0.10.0}/tests/unit/cli/test_analyze_since_until.py +0 -0
- {agentfluent-0.8.0 → agentfluent-0.10.0}/tests/unit/cli/test_axis_labels.py +0 -0
- {agentfluent-0.8.0 → agentfluent-0.10.0}/tests/unit/cli/test_claude_config_dir.py +0 -0
- {agentfluent-0.8.0 → agentfluent-0.10.0}/tests/unit/cli/test_cost_labeling.py +0 -0
- {agentfluent-0.8.0 → agentfluent-0.10.0}/tests/unit/cli/test_deep_diagnostics_formatting.py +0 -0
- {agentfluent-0.8.0 → agentfluent-0.10.0}/tests/unit/cli/test_diagnostics_default.py +0 -0
- {agentfluent-0.8.0 → agentfluent-0.10.0}/tests/unit/cli/test_diagnostics_smoke.py +0 -0
- {agentfluent-0.8.0 → agentfluent-0.10.0}/tests/unit/cli/test_diff_by_model_table.py +0 -0
- {agentfluent-0.8.0 → agentfluent-0.10.0}/tests/unit/cli/test_diff_cmd.py +0 -0
- {agentfluent-0.8.0 → agentfluent-0.10.0}/tests/unit/cli/test_diff_summary.py +0 -0
- {agentfluent-0.8.0 → agentfluent-0.10.0}/tests/unit/cli/test_exit_codes.py +0 -0
- {agentfluent-0.8.0 → agentfluent-0.10.0}/tests/unit/cli/test_glossary_footer.py +0 -0
- {agentfluent-0.8.0 → agentfluent-0.10.0}/tests/unit/cli/test_help.py +0 -0
- {agentfluent-0.8.0 → agentfluent-0.10.0}/tests/unit/cli/test_json_alias.py +0 -0
- {agentfluent-0.8.0 → agentfluent-0.10.0}/tests/unit/cli/test_list_since_until.py +0 -0
- {agentfluent-0.8.0 → agentfluent-0.10.0}/tests/unit/cli/test_min_severity_filter.py +0 -0
- {agentfluent-0.8.0 → agentfluent-0.10.0}/tests/unit/cli/test_offload_candidates_formatting.py +0 -0
- {agentfluent-0.8.0 → agentfluent-0.10.0}/tests/unit/cli/test_output_modes.py +0 -0
- {agentfluent-0.8.0 → agentfluent-0.10.0}/tests/unit/cli/test_parse_warnings.py +0 -0
- {agentfluent-0.8.0 → agentfluent-0.10.0}/tests/unit/cli/test_recommendations_top_n.py +0 -0
- {agentfluent-0.8.0 → agentfluent-0.10.0}/tests/unit/cli/test_report_cmd.py +0 -0
- {agentfluent-0.8.0 → agentfluent-0.10.0}/tests/unit/cli/test_report_pipeline.py +0 -0
- {agentfluent-0.8.0 → agentfluent-0.10.0}/tests/unit/cli/test_tier3_degraded_banner.py +0 -0
- {agentfluent-0.8.0 → agentfluent-0.10.0}/tests/unit/cli/test_time_args.py +0 -0
- {agentfluent-0.8.0 → agentfluent-0.10.0}/tests/unit/cli/test_verbose_signal_message.py +0 -0
- {agentfluent-0.8.0 → agentfluent-0.10.0}/tests/unit/diff/__init__.py +0 -0
- {agentfluent-0.8.0 → agentfluent-0.10.0}/tests/unit/test_agent_audit.py +0 -0
- {agentfluent-0.8.0 → agentfluent-0.10.0}/tests/unit/test_clustering.py +0 -0
- {agentfluent-0.8.0 → agentfluent-0.10.0}/tests/unit/test_core_filtering.py +0 -0
- {agentfluent-0.8.0 → agentfluent-0.10.0}/tests/unit/test_dedup.py +0 -0
- {agentfluent-0.8.0 → agentfluent-0.10.0}/tests/unit/test_delegation.py +0 -0
- {agentfluent-0.8.0 → agentfluent-0.10.0}/tests/unit/test_delegation_yaml_draft.py +0 -0
- {agentfluent-0.8.0 → agentfluent-0.10.0}/tests/unit/test_diagnostics.py +0 -0
- {agentfluent-0.8.0 → agentfluent-0.10.0}/tests/unit/test_discovery.py +0 -0
- {agentfluent-0.8.0 → agentfluent-0.10.0}/tests/unit/test_git_signals.py +0 -0
- {agentfluent-0.8.0 → agentfluent-0.10.0}/tests/unit/test_github_cache.py +0 -0
- {agentfluent-0.8.0 → agentfluent-0.10.0}/tests/unit/test_github_client.py +0 -0
- {agentfluent-0.8.0 → agentfluent-0.10.0}/tests/unit/test_github_consent.py +0 -0
- {agentfluent-0.8.0 → agentfluent-0.10.0}/tests/unit/test_github_detection.py +0 -0
- {agentfluent-0.8.0 → agentfluent-0.10.0}/tests/unit/test_github_signals_ci_failure.py +0 -0
- {agentfluent-0.8.0 → agentfluent-0.10.0}/tests/unit/test_github_signals_density.py +0 -0
- {agentfluent-0.8.0 → agentfluent-0.10.0}/tests/unit/test_glossary_drift.py +0 -0
- {agentfluent-0.8.0 → agentfluent-0.10.0}/tests/unit/test_glossary_render.py +0 -0
- {agentfluent-0.8.0 → agentfluent-0.10.0}/tests/unit/test_mcp_assessment.py +0 -0
- {agentfluent-0.8.0 → agentfluent-0.10.0}/tests/unit/test_mcp_discovery.py +0 -0
- {agentfluent-0.8.0 → agentfluent-0.10.0}/tests/unit/test_model_routing.py +0 -0
- {agentfluent-0.8.0 → agentfluent-0.10.0}/tests/unit/test_parent_workload_cluster.py +0 -0
- {agentfluent-0.8.0 → agentfluent-0.10.0}/tests/unit/test_parent_workload_extract.py +0 -0
- {agentfluent-0.8.0 → agentfluent-0.10.0}/tests/unit/test_quality_signals.py +0 -0
- {agentfluent-0.8.0 → agentfluent-0.10.0}/tests/unit/test_recommendation_aggregation.py +0 -0
- {agentfluent-0.8.0 → agentfluent-0.10.0}/tests/unit/test_scanner.py +0 -0
- {agentfluent-0.8.0 → agentfluent-0.10.0}/tests/unit/test_scoring.py +0 -0
- {agentfluent-0.8.0 → agentfluent-0.10.0}/tests/unit/test_signals.py +0 -0
- {agentfluent-0.8.0 → agentfluent-0.10.0}/tests/unit/test_smoke.py +0 -0
- {agentfluent-0.8.0 → agentfluent-0.10.0}/tests/unit/test_timeutil.py +0 -0
- {agentfluent-0.8.0 → agentfluent-0.10.0}/tests/unit/test_tools.py +0 -0
- {agentfluent-0.8.0 → agentfluent-0.10.0}/tests/unit/test_traces_discovery.py +0 -0
- {agentfluent-0.8.0 → agentfluent-0.10.0}/tests/unit/test_traces_linker.py +0 -0
- {agentfluent-0.8.0 → agentfluent-0.10.0}/tests/unit/test_traces_models.py +0 -0
- {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
|
|
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
|
|
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.
|