feat: implement analyze, complexity, and coverage analyzer methods - #9
Conversation
Implements the three required Gaze protocol v1.1.0 methods (issue #4), building on the taxonomy and discovery from #3. - analyze: Python side-effect detector covering the original taxonomy types that have a Python analogue plus 10 new Python-specific types; ambiguous calls reported (never dropped), Go-era no-analogue types never fabricated - complexity: McCabe cyclomatic complexity lifted from gaze-py with Apache-2.0 provenance + change notice (no radon) - coverage: coverage.py data parser (coverage.json/.coverage), confined to the discovered set, never runs tests - shared safe-file/enumeration/package helpers with Constitution-V resource bounds (S_ISREG guard, size cap, AST-depth budget) and deterministic byte-identical output - wire analyze/complexity/coverage into the JSON-RPC server; initialize capabilities unchanged - add coverage>=7.0,<8 runtime dependency; 321 tests, fixtures, and OpenSpec change artifacts; sync README and AGENTS docs Assisted-by: claude-opus Generated with AI assistance (claude-opus)
Align spec artifacts with the shared-helper architecture after the review-council refactor: - name is_analyzable_file as the centralized stat/S_ISREG/byte-cap guard (iter_source_files for detector+complexity; coverage calls it directly) - scope derive_package to detector+complexity (coverage carries no package) - document compute_complexity as the public entry point (private _cyclomatic_complexity helper) - reword tasks.md 3.16 to scope the ambiguity guarantee to unknown name calls + enumerated dynamic constructs Docs-only; no requirement headers or scenarios changed.
Address review-council findings across three iterations: Safety & resilience: - centralize the safe-file guard (is_analyzable_file) so the stat/S_ISREG/ byte-cap runs before Coverage.analysis2() on every path (Constitution V) - coverage: config_file=False to ignore untrusted repo coverage config - broaden coverage catches to include RecursionError/MemoryError/ValueError - correct the _is_regular_file symlink comment (rejects non-regular files; paths stay root-confined via _confine_path) Detection accuracy: - drop 'input' from pure-builtins (real stdin effect -> ambiguous) - gate FileSystemWrite co-emit on write-mode opens Performance: - bisect-based O(log S) line-to-function coverage counting Server & cleanup: - generic handler returns 'Internal error' (no str(err) on the wire) - _discover reuses _validate_analysis_params; delete dead _BOOL_OPS Tests (321 -> 341, coverage 93.82% -> 95.77%): - real fault injection for stat-error paths (no self-mocking) - full-equality detector golden + parametrized FileNotFoundError->-32602 - depth-budget guards exercised via in-memory AST injection (source nesting hits MAXINDENT=100 before MAX_AST_DEPTH=200) - remove orphaned fixtures; make coverage ordering fixture discriminating Docs: - README Limits & Troubleshooting section + gaze-py/NOTICE provenance line - AGENTS.md method-delivery parity
jflowers
left a comment
There was a problem hiding this comment.
Review Council Verdict: APPROVE
Note: This review could not be submitted as a formal GitHub APPROVE because GitHub prohibits an author from approving their own pull request (HTTP 422). It is therefore posted as a COMMENT. Original council verdict: APPROVE.
Generated by /uf.review-council (AI-assisted). Gaze quality analysis was skipped at the requester's direction — snake-eyes is a Python project and the Go gaze binary cannot analyze it.
Reviewers: 9 discovered divisor-* personas, all invoked — unanimous APPROVE.
Iterations: 3 (2 rounds of fixes). Scope: full branch diff vs main (42 files).
Per-persona verdicts (final iteration)
| Reviewer | Verdict |
|---|---|
| divisor-adversary (security/resilience) | APPROVE |
| divisor-architect (structure/conventions) | APPROVE (alignment 9/10) |
| divisor-guard (intent/governance) | APPROVE (all 5 constitution principles PASS) |
| divisor-testing (test quality) | APPROVE |
| divisor-sre (ops/efficiency) | APPROVE |
| divisor-scribe (technical docs) | APPROVE |
| divisor-curator (doc/content triage) | APPROVE |
| divisor-envoy (PR/comms) | APPROVE |
| divisor-herald (blog/announcements) | APPROVE |
Iteration history
- Iteration 1 — REQUEST CHANGES. 5 HIGH, 11 MEDIUM, ~18 LOW consolidated findings. Notable HIGH: coverage.py bypassed the centralized safe-file reader (byte-cap applied after
Coverage.analysis2()already read untrusted source — Constitution V DoS);coverage.Coverage()auto-loaded untrusted repo config (config_file=True) — non-determinism + plugin code-exec surface; self-mocking tautology tests left safety branches uncovered; detector effect tests were presence-only (no golden);FileNotFoundError -> -32602untested at the JSON-RPC boundary. - Iteration 2 — REQUEST CHANGES. All iteration-1 findings resolved; 7 APPROVE, 2 MEDIUM blockers surfaced: (1) depth-budget guard tests were misattributed — nested source hits CPython's tokenizer
MAXINDENT=100beforeMAX_AST_DEPTH=200, so the snake-eyes guards never executed; (2) the README "Limits & Troubleshooting" section reported added had not actually landed. - Iteration 3 — APPROVE. Both blockers fixed: depth guards now covered via in-memory AST injection (mutation-resistant); README section added and verified line-by-line against source.
coverage.pyreached 100% coverage.
Changes made in response to the council (pushed, 2 commits)
3c31f8a— docs: reconcile analysis-methods spec artifacts with implementation (shared-helper architecture now accurately described).640593a— fix: harden analysis methods per review council findings: centralizedis_analyzable_file()guard (byte-cap beforeanalysis2());config_file=Falsefor deterministic, plugin-free coverage parsing; broadened graceful-degradation exception handling; O(log S) bisect coverage counting; real fault-injection + golden +-32602boundary + depth-guard tests; README Limits & Troubleshooting; write-mode-gatedFileSystemWrite;input()no longer treated as pure; DRY/zero-waste cleanups.
Local CI-parity (all green): ruff check + ruff format --check + mypy pass; pytest 341 passed, coverage 95.77% (protected gate 85%).
Outstanding / non-blocking
- [MEDIUM — human governance ruling required]
symtable.proposal.md/design.mdand Constitution III literally namesymtable, but the implementation usesast.Global/ast.Nonlocal(nosymtableimport). The code satisfies Principle III in substance (Python-native, no reimplemented semantics). Requires a human decision — amend Constitution III wording, or integratesymtable. Excluded from automated fixes at requester direction. - [LOW — deferred]
.coverageSQLite container not byte-capped beforecov.load()(pre-existing; capping the aggregate DB could over-restrict legitimate large projects). - [LOW — optional] Directly mutation-pin the
_EffectVisitor/_ComplexityVisitordepth guards (the shared guard is already mutation-tested). - [LOW — optional] Extract a
read_and_parse()helper to dedupe ~4 lines between_sharedandcoverage. - [INFO] No CHANGELOG (pre-existing convention — history lives in
openspec/changes/); frame any future release note as "48-type taxonomy, of which 42 are detected in Python (6 Go-only concepts retained as vocabulary)." - [INFO]
CoverageWarning: module-not-measuredat pytest teardown (import-timing; does not affect the gate).
3-iteration council process; all actionable findings remediated and verified. The two commits above are already pushed to this PR branch.
PR Cost Report: #9Session: New session - 2026-08-28T21:01:42.217ZID:
Timeline: 2026-08-28 17:01:42 — 2026-08-29 17:16:16 Child sessions: 52 — additional cost: $87.15 Child session breakdown
Session: Skip gaze testing for PythonID:
Timeline: 2026-08-29 15:14:47 — 2026-08-29 17:07:46 Child sessions: 24 — additional cost: $60.68 Child session breakdown
Grand Total: $271.82 across 2 session tree(s) (78 sessions) |
Summary
Implements the three required Gaze analyzer-protocol v1.1.0 methods for Snake Eyes
(issue #4):
analyze(Python side-effect detection),complexity(per-functionMcCabe cyclomatic complexity), and
coverage(parse coverage.py data withoutexecuting tests). These methods previously returned method-not-found (-32601); they
now return protocol-shaped results.
initializecapabilities are unchanged{discover:true, test_mapping:false, classify_signals:false, streaming:false}.
Python-specific types (ErrorSignal, GeneratorYield, ContainerMutation,
StreamOutput, AsyncGeneratorYield, MetaprogrammingMutation, DescriptorEffect,
ResourceManagement, ImportSideEffect, MonkeyPatch); reports ambiguous constructs
rather than dropping them; never fabricates effects for the 6 Go-only taxonomy
types (retained only as vocabulary).
and §4(b) change notices retained; no radon).
the discovered set, and degrades gracefully on missing/malformed data.
coverage>=7.0,<8.MAX_AST_DEPTH bounds on every read path; deterministic byte-identical output.
How to Test
Run the CI-parity gate suite locally:
Expected: all green — 321 tests pass, project coverage 93.82% (per-module:
complexity.py 100%, detector.py 91%, coverage.py 93%). Acceptance scenarios in
openspec/changes/analysis-methods/specs/**/*.mdare covered bytests/test_analysis_methods.py (JSON-RPC e2e), test_detector*.py (all 10 new + 6 P0
types), test_complexity*.py (hand-derived McCabe oracle), and test_coverage*.py
(coverage.json + .coverage lookup, confinement, graceful degradation).
How to Demo
Snake Eyes is spawned by Gaze as a subprocess; drive it via JSON-RPC over stdio:
Send
initialize(capabilities unchanged), thenanalyze/complexity/coveragewith
{"root_path":"<abs>","patterns":["./..."]}and observe protocol-shapedfunctions[]results. Point it at tests/fixtures/effects/ for side-effect detection,or a project with a coverage.json/.coverage for coverage mapping.
Key Files Changed
src/snake_eyes/analysis/detector.py(+1775) — side-effect detector:analyze_path/analyze_source, all effect families, 10 new types, ambiguity fallback,
resource/S_ISREG bounds, gaze-py provenance.
src/snake_eyes/coverage.py(+360) — parse_coverage: coverage.json + .coverage(analysis2) lookup, discovered-set confinement, graceful degradation.
src/snake_eyes/analysis/_shared.py(+177) — shared safe-file reader, functionenumeration, package derivation, MAX_FILE_BYTES/MAX_AST_DEPTH/BROADENED_EXCEPTIONS.
src/snake_eyes/analysis/complexity.py(+166) — McCabe complexity (lifted gaze-py,no radon).
src/snake_eyes/server.py(+55) — analyze/complexity/coverage handlers wired intoDEFAULT_DISPATCH; initialize unchanged.
src/snake_eyes/analysis/__init__.py— re-export Effect/FunctionRecord/function_record_to_dict.
pyproject.toml,uv.lock— add pinnedcoverage>=7.0,<8.README.md,AGENTS.md— doc-sync (methods implemented; radon rejected; modulesdelivered).
tests/— 12 test modules + fixtures under tests/fixtures/{effects,coverage}.openspec/changes/analysis-methods/— proposal, design, tasks, 6 capability specs(spec-review + code-review passed).
This PR was generated by /uf.finale (AI-assisted).