diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index ba7d289..8131dd2 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -61,6 +61,8 @@ jobs: uses: dtolnay/rust-toolchain@1.97.0 with: targets: aarch64-apple-darwin + - name: Test + run: cargo test -- --test-threads=8 - name: Build run: cargo build --release --target aarch64-apple-darwin - uses: actions/upload-artifact@v4 @@ -76,6 +78,8 @@ jobs: uses: dtolnay/rust-toolchain@1.97.0 with: targets: x86_64-apple-darwin + - name: Test + run: cargo test -- --test-threads=8 - name: Build run: cargo build --release --target x86_64-apple-darwin - uses: actions/upload-artifact@v4 @@ -91,6 +95,8 @@ jobs: uses: dtolnay/rust-toolchain@1.97.0 with: targets: x86_64-pc-windows-msvc + - name: Test + run: cargo test -- --test-threads=8 - name: Build run: cargo build --release --target x86_64-pc-windows-msvc - uses: actions/upload-artifact@v4 diff --git a/.gitignore b/.gitignore index 513fa0f..d65bf2a 100644 --- a/.gitignore +++ b/.gitignore @@ -29,6 +29,6 @@ PLAN_NEXT.md PLAN_COMPLETED.md plan/ -# Wikifier runtime cache (state files like file_health.md ARE versioned) +# Wikifier runtime cache. Generated maps of the old Python package are not versioned. .wikifier_staging/ .coordinationhub/ diff --git a/AGENTS.md b/AGENTS.md index 3a3b138..785225f 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -43,6 +43,6 @@ Shards are one snapshot each under `.chisel/shards//`, configured by `CHI Scores are heuristics. Read `_meta.uniform_components` before trusting a composite risk score. `flakiness_trend` is present when a covering test has dated results in the current week and in an older week of the last 28 days. It does not change risk weights. `record_result` stores `recorded_at`. -Test filenames that produce edges include `test_*.py`, `*_test.py`, `*.test.ts`, `*.spec.ts`, `*_test.go`, and `*_test.rs`. +Test filenames that produce edges include `test_*.py`, `*_test.py`, `*.test.ts`, `*.spec.ts`, `*_test.go`, `*_test.rs`, and any `.rs` file under the top-level `tests/` directory. Rust functions marked `#[test]`, `#[tokio::test]`, `#[rstest]`, or `#[test_case]` are test units even when the file is not named like a test. `CHISEL_EXTRACTOR` or `extractor` in `.chisel/extensions.toml` names an executable. That program may use its own libraries. Chisel does not link them. The toml file is ignored unless the process was started with `--allow-repo-extractor` or `CHISEL_ALLOW_REPO_EXTRACTOR=1`. A tool argument cannot turn it on. The path must stay inside the project. `CHISEL_EXTRACTOR` overrides the file. A bad run falls back to the built-in scanner. A git ref that starts with `-` is rejected. diff --git a/ARCHITECTURE.md b/ARCHITECTURE.md index 6a296e0..4cb82b3 100644 --- a/ARCHITECTURE.md +++ b/ARCHITECTURE.md @@ -1,11 +1,49 @@ # Architecture -Chisel is a zero-dependency Rust program. `AGENTS.md` is the agent contract. +Chisel is a zero-dependency Rust program. `AGENTS.md` is the agent contract. `plan.md` records the layout and the rules that stay in force. -`src/engine` analyzes the worktree, answers queries, and records advisory locks. `src/extract` scans source files. `src/store.rs` reads and writes one snapshot, `.chisel/store`, with a temp file, fsync, and rename. It does not open `chisel.db` and it rejects `:memory:` and `file:` paths. +The crate exposes two binaries. `chisel` is `src/main.rs`. `chisel-mcp` is `src/bin/chisel-mcp.rs`. Both call `cli::entry`. When the program name is `chisel-mcp`, the entry inserts `serve-mcp` and speaks stdio MCP. `chisel serve` is HTTP only: `GET /health`, `GET /tools`, and `POST /call` on `127.0.0.1:8377`. There is no `/mcp` path. -Git is a subprocess. HTTP is `GET /health`, `GET /tools`, and `POST /call` on `127.0.0.1:8377`. There is no `/mcp` path. MCP is stdio Content-Length JSON-RPC from `chisel serve-mcp` or the `chisel-mcp` name of the same binary. +## Flow -Status values: `no_data`, `no_changes`, `no_edges`, `git_error`, `stale_db`, `error`, `unsupported`, `auto_queued`. +1. `analyze` lists code files through git (or a walk when ignored files are included), extracts units, and writes one snapshot. +2. Test files come from `looks_like_test` in `src/engine/mod.rs`. That is the only filename classifier. Rust files under the top-level `tests/` directory are test files. Functions with a `#[test]`-family attribute are test units even inside a source file. +3. Edges are calls and imports from those test units to code units. `suggest-tests`, `diff-impact`, `test-gaps`, and `risk-map` read the snapshot. +4. `update` recomputes changed files. A no-op update sets `edge_rebuild_skipped` and does not run `git blame` or per-function `git log -L`. +5. `risk-map` and `test-gaps` skip paths that `looks_like_test` accepts. A source file that also holds `#[test]` functions stays in those answers. The test functions themselves are not listed as untested code. -Do not pip-install this project. Do not store analysis in SQLite. +## Modules + +| Module | Responsibility | +|---|---| +| `src/cli.rs` | Flags, subcommands, `chisel run`, `chisel watch`. | +| `src/engine/analyze.rs` | Full and incremental scans, test units, edges. | +| `src/engine/query.rs` | Read tools: impact, suggestions, risk, gaps, triage. | +| `src/engine/locks.rs` | Advisory lock rows in the snapshot. | +| `src/extract` | Python syntax units, brace-language units, import and call deps. | +| `src/syntax.rs` | Built-in Python and JavaScript syntax tree used by the scanners. | +| `src/store.rs` | One snapshot file. Write to a temp file, fsync, rename. | +| `src/git.rs` | Porcelain via `std::process`. A ref that starts with `-` is rejected. | +| `src/impact.rs` | Churn, coupling, coverage gap, risk score. | +| `src/json.rs` | Parser, serializer, Python-compatible rounding for scores. | +| `src/hash.rs` | SHA-256 for file content. | +| `src/http.rs` | The small JSON HTTP server. | +| `src/mcp.rs` | Content-Length framing and the tool bridge. | +| `src/fswatch.rs` | OS change notification. `collect_files` is only compiled for the kqueue path. | +| `src/lock.rs` | Cross-process shared and exclusive locks. | +| `src/schemas.rs` | Tool list. Missing required arguments are `status: "error"`. | +| `src/next_steps.rs` | Hint rows attached to tool results. | + +Shards are one snapshot each under `.chisel/shards//`. Writes use the longest path prefix. Unscoped reads aggregate. Cross-shard test edges are written to the parent snapshot and overlaid for impact, suggestions, diff, risk coverage, and stale tests. + +## Storage and git + +`.chisel/store` is the snapshot. Chisel does not open `chisel.db`. `:memory:` and `file:` storage paths are rejected. `CHISEL_PROJECT_DIR` and `CHISEL_STORAGE_DIR` apply when the CLI flags are absent. + +Git is a subprocess. Locks are advisory records. They do not lock source files. + +## Status + +`no_data`, `no_changes`, `no_edges`, `git_error`, `stale_db`, `error`, `unsupported`, `auto_queued`. `no_data` and `stale_db` exit 0. `error` and `git_error` exit 1. + +Do not pip-install this project. Do not store analysis in SQLite. Do not add a crates.io dependency. diff --git a/CLAUDE.md b/CLAUDE.md index 153a4a4..0c05515 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -1,9 +1,11 @@ # Chisel -The product is the Rust crate in `src/`. Read `AGENTS.md` for how to call it. +The product is the Rust crate in `src/`. Read `AGENTS.md` for how to call it and `ARCHITECTURE.md` for the module map. -Build with `cargo build --release`. The binary is `chisel`. `chisel-mcp` is the same program. Storage is the snapshot `.chisel/store`. Chisel does not open `chisel.db`. `:memory:` and `file:` storage paths are rejected. +Build with `cargo build --release`. The `chisel` binary is `src/main.rs`. The `chisel-mcp` binary is `src/bin/chisel-mcp.rs`. Both call the same entry point. Storage is the snapshot `.chisel/store`. Chisel does not open `chisel.db`. `:memory:` and `file:` storage paths are rejected. -Do not pip-install Chisel. Do not use SQLite as the store. +Do not pip-install Chisel. Do not use SQLite as the store. `Cargo.toml` dependency tables stay empty. There is no `build.rs`. + +`looks_like_test` in `src/engine/mod.rs` is the only test-file classifier. Rust files under `tests/` are tests. `#[test]` functions are test units. Status values: `no_data`, `no_changes`, `no_edges`, `git_error`, `stale_db`, `error`, `unsupported`, `auto_queued`. `no_data` and `stale_db` exit 0. `error` and `git_error` exit 1. diff --git a/COMPLETE_PROJECT_DOCUMENTATION.md b/COMPLETE_PROJECT_DOCUMENTATION.md index 67d758f..9cdba02 100644 --- a/COMPLETE_PROJECT_DOCUMENTATION.md +++ b/COMPLETE_PROJECT_DOCUMENTATION.md @@ -1,9 +1,107 @@ # Chisel -The program you run is the Rust binary from `src/`. Read `AGENTS.md`. +Updated: 2026-11-03. + +Purpose: inventory of this repository. The program you run is the Rust binary from `src/`. Read `AGENTS.md` for calls and `ARCHITECTURE.md` for how the pieces fit. Storage is the snapshot `.chisel/store`. Chisel does not open `chisel.db`. `:memory:` and `file:` paths are rejected. Do not pip-install Chisel and do not use SQLite as the store. Status values: `no_data`, `no_changes`, `no_edges`, `git_error`, `stale_db`, `error`, `unsupported`, `auto_queued`. `no_data` and `stale_db` exit 0. `error` and `git_error` exit 1. + +`library.md`, `file_health.md`, and `file_health.json` were removed. They were generated maps of the deleted Python package. Nothing in this tree regenerates them. `monitored_paths.txt` was removed because it held one machine's absolute path and the binary does not read it. + +## Root + +| Path | Purpose | +|---|---| +| `README.md` | How to build and call the binary. | +| `AGENTS.md` | Agent contract: status values, flags, scale limits. | +| `CLAUDE.md` | Short orientation for agents that load this file first. | +| `ARCHITECTURE.md` | Module map and the analyze-to-query flow. | +| `plan.md` | Current layout and the dependency rule. Not a Python port schedule. | +| `CONTRIBUTING.md` | Build and test commands. | +| `CHANGELOG.md` | Release notes. Older entries mention the Python package. | +| `LLM_Development.md` | Historical session log. | +| `Cargo.toml` | Package `chisel`. Empty dependency tables. Two binaries. | +| `Cargo.lock` | Lockfile for the empty dependency graph. | +| `rust-toolchain.toml` | Pinned Rust channel. | +| `LICENSE` | MIT. | +| `.gitignore` | Build output, `.chisel/`, and the old `plan/` directory. | +| `exclude_patterns.txt` | Paths a watcher should skip. Not read by the Rust binary. | +| `GROK_CONNECTORS.md` | How to point an MCP client at `chisel-mcp`. | +| `skills/SKILL.md` | Skill text for calling the binary. | +| `.grok/skills/chisel/SKILL.md` | Same skill, installed for this workspace. | + +## `src/` + +| Path | Purpose | +|---|---| +| `src/lib.rs` | Crate root. | +| `src/main.rs` | `chisel` binary. | +| `src/bin/chisel-mcp.rs` | `chisel-mcp` binary. | +| `src/cli.rs` | Argv and dispatch, including `--version`. | +| `src/engine/mod.rs` | Engine type and `looks_like_test`. | +| `src/engine/analyze.rs` | `analyze` and `update`. | +| `src/engine/query.rs` | Read tools. | +| `src/engine/locks.rs` | Advisory locks. | +| `src/extract/mod.rs` | Scanner entry. | +| `src/extract/python.rs` | Python units. | +| `src/extract/brace.rs` | Brace languages, including Rust `#[test]`. | +| `src/extract/deps.rs` | Imports and calls. | +| `src/syntax.rs` | Python and JavaScript syntax tree. | +| `src/store.rs` | Snapshot file. | +| `src/git.rs` | Git subprocess. | +| `src/impact.rs` | Risk math. | +| `src/json.rs` | JSON. | +| `src/hash.rs` | SHA-256. | +| `src/http.rs` | HTTP server. | +| `src/mcp.rs` | Stdio MCP. | +| `src/fswatch.rs` | OS directory watch. | +| `src/lock.rs` | Process locks. | +| `src/schemas.rs` | Tool schemas. | +| `src/schemas_data.json` | Schema payload compiled into `schemas.rs`. | +| `src/next_steps.rs` | Follow-up hints. | + +## `tests/` + +| Path | Purpose | +|---|---| +| `tests/scale.rs` | Behaviour checks against temp git repos and the binary. | +| `tests/oracle_parity.rs` | Binary output against frozen oracle JSON. | +| `tests/surface_contract.rs` | Tool list shape. | +| `tests/extract_parity.rs` | Language fixtures, including Rust `#[test]` unit types. | +| `tests/durability.rs` | Snapshot publish under a killed writer. | +| `tests/rust_layout.rs` | Cargo layout fixture and a dogfood run on this repo. | +| `tests/fixtures/oracle/` | Frozen JSON for the Python-era oracle. | +| `tests/fixtures/languages/` | Small samples per language. | +| `tests/fixtures/rust_crate/` | `src/lib.rs`, `src/a.rs`, `tests/a.rs`, and a `Cargo.toml`. | + +## Other trees + +| Path | Purpose | +|---|---| +| `.github/workflows/ci.yml` | Test on Linux, macOS, and Windows. Release builds for the publish targets. | +| `.github/workflows/publish.yml` | Tagged release builds. | +| `docs/AGENT_PLAYBOOK.md` | Short pointer at `AGENTS.md`. | +| `docs/CUSTOM_EXTRACTORS.md` | External extractor program. | +| `docs/EXTRACTOR_ECOSYSTEM.md` | Where an external extractor fits. | +| `docs/LLM_CONTRACT.md` | Pointer at the status contract. | +| `docs/ZERO_DEPS.md` | Empty dependency tables. | +| `docs/chisel-demo.png` | Screenshot used by the README. | +| `examples/github-actions/chisel.yml` | Example workflow that builds this binary with Cargo. | +| `findings/` | Closed review notes. | +| `journal/2026/` | Dated notes from June 2026. | +| `Logged_issues/` | One closed issue note. | +| `wiki-local/` | Local notes. They still mention `schemas.py` in places. The Rust schemas are `src/schemas.rs`. | + +## Removed in this pass + +| Path | Why | +|---|---| +| `library.md` | Wikifier map of `chisel/*.py`. Those modules are gone, and this repo cannot regenerate the file. | +| `file_health.md` | Same age, same deleted tree. | +| `file_health.json` | Machine-readable copy of that matrix. | +| `pending_updates.md` | Empty. | +| `monitored_paths.txt` | Held `/home/aron/Documents/coding_projects/Chisel`. The binary does not read it. | diff --git a/Cargo.toml b/Cargo.toml index 1da5158..e626bea 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,6 +5,7 @@ edition = "2021" description = "Test impact analysis and code intelligence for LLM agents" license = "MIT" publish = false +autobins = false [[bin]] name = "chisel" @@ -12,7 +13,7 @@ path = "src/main.rs" [[bin]] name = "chisel-mcp" -path = "src/main.rs" +path = "src/bin/chisel-mcp.rs" [dependencies] diff --git a/LLM_Development.md b/LLM_Development.md index 5dd9728..a7f3cd7 100644 --- a/LLM_Development.md +++ b/LLM_Development.md @@ -4,6 +4,17 @@ Chronological record of development activity on the Chisel project. --- +## 2026-11-03 -- Cargo test discovery, docs, CI, version + +- One test-file classifier. Rust files under `tests/` and `#[test]`-family functions are test units. +- Removed `library.md`, `file_health.md`, `file_health.json`, and `pending_updates.md`. They described the deleted Python package or were empty. `plan.md`, `ARCHITECTURE.md`, `CLAUDE.md`, and `COMPLETE_PROJECT_DOCUMENTATION.md` now describe the Rust tree. +- `chisel` and `chisel-mcp` no longer share `src/main.rs`. `collect_files` is compiled only for the kqueue path. +- CI runs `cargo test` on macOS and Windows as well as Linux. +- `--version` prints the package version. Removed `monitored_paths.txt` (it held one local absolute path). +- Cleared the clippy warnings reported by `cargo clippy --all-targets`. + +--- + ## 2026-04-14 -- Review Thirteen fixes (kimi_review_2): risk_map crash, suggest_tests/diff_impact working_tree timeouts ### Summary diff --git a/README.md b/README.md index 179c10a..411488c 100644 --- a/README.md +++ b/README.md @@ -157,7 +157,7 @@ The same binary has been run on flask, requests, cJSON, quixbugs, and the Probat - **Zero dependencies** — the Rust binary links only the standard library. No crates.io crates. System `git` is required at runtime. - **Multi-language** — Python, JavaScript/TypeScript, Go, Rust, C#, Java, Kotlin, C/C++, Swift, PHP, Ruby, Dart -- **Framework-aware** — pytest, Jest, Go test, Rust #[test], Playwright, xUnit/NUnit/MSTest, JUnit (incl. parameterized), XCTest + Swift Testing, PHPUnit, RSpec, Minitest, gtest, Dart test +- **Framework-aware** — pytest, Jest, Go test, Rust `#[test]` (including `#[tokio::test]` and `#[rstest]`, in `tests/*.rs` and in source files), Playwright, xUnit/NUnit/MSTest, JUnit (incl. parameterized), XCTest + Swift Testing, PHPUnit, RSpec, Minitest, gtest, Dart test - **Incremental** — `update` recomputes changed files and newer commits. A no-op update sets `edge_rebuild_skipped` and does not run `git blame` or per-function `git log -L`. Above 2000 code files, file-level churn still runs and per-function `git log -L` does not (`unit_churn_skipped`). - **gitignore-aware** — ignored trees (vendored deps, build output, fixtures) are never scanned; untracked files still are (`CHISEL_INCLUDE_IGNORED=1` to override) - **Branch-aware** — `diff_impact` auto-detects feature branch vs main. Feature-branch co-change comes from `merge-base..HEAD`. `main` and `master` keep that list empty. diff --git a/file_health.json b/file_health.json deleted file mode 100644 index 5ac6f2b..0000000 --- a/file_health.json +++ /dev/null @@ -1,597 +0,0 @@ -{ - "version": 2, - "last_updated": "2026-06-11 16:02:39", - "entries": { - "plan/phase_15_agent_experience_plan_closed.md": { - "status": "🟢 Green", - "last_updated": "2026-06-11 13:42:03 AEST", - "reason": "runtime artifact or reviewed; now covered by exclude_patterns where applicable" - }, - "wiki-local/spec-project.md": { - "status": "🟢 Green", - "last_updated": "2026-06-11 13:55:11 AEST", - "reason": "reviewed in v0.12.0 pass or runtime artifact" - }, - "wiki-local/glossary.md": { - "status": "🟢 Green", - "last_updated": "2026-06-11 13:42:03 AEST", - "reason": "runtime artifact or reviewed; now covered by exclude_patterns where applicable" - }, - "wiki-local/index.md": { - "status": "🟢 Green", - "last_updated": "2026-06-11 13:42:04 AEST", - "reason": "runtime artifact or reviewed; now covered by exclude_patterns where applicable" - }, - "tests/test_engine.py": { - "status": "🟢 Green", - "last_updated": "2026-06-11 14:33:52 AEST", - "reason": "released in v0.13.0; docs current" - }, - "tests/test_next_steps.py": { - "status": "🟢 Green", - "last_updated": "2026-06-11 13:55:06 AEST", - "reason": "reviewed in v0.12.0 pass or runtime artifact" - }, - "tests/conftest.py": { - "status": "🟢 Green", - "last_updated": "2026-06-11 13:54:49 AEST", - "reason": "reviewed in v0.12.0 pass or runtime artifact" - }, - "tests/test_import_graph.py": { - "status": "🟢 Green", - "last_updated": "2026-06-11 13:55:01 AEST", - "reason": "reviewed in v0.12.0 pass or runtime artifact" - }, - "tests/test_engine_sharding.py": { - "status": "🟢 Green", - "last_updated": "2026-06-11 13:54:58 AEST", - "reason": "reviewed in v0.12.0 pass or runtime artifact" - }, - "tests/test_test_mapper.py": { - "status": "🟢 Green", - "last_updated": "2026-06-11 13:55:10 AEST", - "reason": "reviewed in v0.12.0 pass or runtime artifact" - }, - "tests/test_cli.py": { - "status": "🟢 Green", - "last_updated": "2026-06-11 14:33:52 AEST", - "reason": "released in v0.13.0; docs current" - }, - "tests/test_storage.py": { - "status": "🟢 Green", - "last_updated": "2026-06-11 13:55:09 AEST", - "reason": "reviewed in v0.12.0 pass or runtime artifact" - }, - "tests/test_impact.py": { - "status": "🟢 Green", - "last_updated": "2026-06-11 13:55:00 AEST", - "reason": "reviewed in v0.12.0 pass or runtime artifact" - }, - "tests/test_bootstrap.py": { - "status": "🟢 Green", - "last_updated": "2026-06-11 13:54:57 AEST", - "reason": "reviewed in v0.12.0 pass or runtime artifact" - }, - "tests/test_language_frameworks.py": { - "status": "🟢 Green", - "last_updated": "2026-06-11 13:55:02 AEST", - "reason": "reviewed in v0.12.0 pass or runtime artifact" - }, - "tests/test_ast_utils.py": { - "status": "🟢 Green", - "last_updated": "2026-06-11 13:54:56 AEST", - "reason": "reviewed in v0.12.0 pass or runtime artifact" - }, - "tests/test_mcp_stdio.py": { - "status": "🟢 Green", - "last_updated": "2026-06-11 13:55:04 AEST", - "reason": "reviewed in v0.12.0 pass or runtime artifact" - }, - "tests/__init__.py": { - "status": "🟢 Green", - "last_updated": "2026-06-11 13:54:54 AEST", - "reason": "reviewed in v0.12.0 pass or runtime artifact" - }, - "tests/test_project.py": { - "status": "🟢 Green", - "last_updated": "2026-06-11 13:55:07 AEST", - "reason": "reviewed in v0.12.0 pass or runtime artifact" - }, - "tests/test_llm_contract.py": { - "status": "🟢 Green", - "last_updated": "2026-06-11 13:55:03 AEST", - "reason": "reviewed in v0.12.0 pass or runtime artifact" - }, - "tests/fixtures/languages/csharp/BasicTests.cs": { - "status": "🟢 Green", - "last_updated": "2026-06-11 13:54:49 AEST", - "reason": "reviewed in v0.12.0 pass or runtime artifact" - }, - "tests/fixtures/languages/java/BasicTest.java": { - "status": "🟢 Green", - "last_updated": "2026-06-11 13:54:52 AEST", - "reason": "reviewed in v0.12.0 pass or runtime artifact" - }, - "tests/fixtures/languages/swift/BasicTests.swift": { - "status": "🟢 Green", - "last_updated": "2026-06-11 13:54:53 AEST", - "reason": "reviewed in v0.12.0 pass or runtime artifact" - }, - "tests/fixtures/languages/go/go.mod": { - "status": "🟢 Green", - "last_updated": "2026-06-11 13:54:51 AEST", - "reason": "reviewed in v0.12.0 pass or runtime artifact" - }, - "tests/fixtures/languages/go/basic_test.go": { - "status": "🟢 Green", - "last_updated": "2026-06-11 13:54:50 AEST", - "reason": "reviewed in v0.12.0 pass or runtime artifact" - }, - "tests/fixtures/languages/rust/basic.rs": { - "status": "🟢 Green", - "last_updated": "2026-06-11 13:54:53 AEST", - "reason": "reviewed in v0.12.0 pass or runtime artifact" - }, - "tests/bootstrap_stub.py": { - "status": "🟢 Green", - "last_updated": "2026-06-11 13:54:48 AEST", - "reason": "reviewed in v0.12.0 pass or runtime artifact" - }, - "tests/test_mcp_server.py": { - "status": "🟢 Green", - "last_updated": "2026-06-11 13:55:03 AEST", - "reason": "reviewed in v0.12.0 pass or runtime artifact" - }, - "tests/test_git_analyzer.py": { - "status": "🟢 Green", - "last_updated": "2026-06-11 13:54:59 AEST", - "reason": "reviewed in v0.12.0 pass or runtime artifact" - }, - "tests/test_metrics.py": { - "status": "🟢 Green", - "last_updated": "2026-06-11 13:55:05 AEST", - "reason": "reviewed in v0.12.0 pass or runtime artifact" - }, - "tests/test_static_hybrid.py": { - "status": "🟢 Green", - "last_updated": "2026-06-11 13:55:08 AEST", - "reason": "reviewed in v0.12.0 pass or runtime artifact" - }, - "tests/test_rwlock.py": { - "status": "🟢 Green", - "last_updated": "2026-06-11 13:55:07 AEST", - "reason": "reviewed in v0.12.0 pass or runtime artifact" - }, - "file_health.md": { - "status": "🟢 Green", - "last_updated": "2026-06-11 14:33:57 AEST", - "reason": "wikifier state file" - }, - "examples/chisel_bootstrap_example.py": { - "status": "🟢 Green", - "last_updated": "2026-06-11 13:45:51 AEST", - "reason": "build artifact, now excluded from monitoring" - }, - "examples/extractors/lsp_symbol_extractor.py": { - "status": "🟢 Green", - "last_updated": "2026-06-11 13:45:52 AEST", - "reason": "build artifact, now excluded from monitoring" - }, - "examples/extractors/swift_syntax_extractor.py": { - "status": "🟢 Green", - "last_updated": "2026-06-11 13:45:53 AEST", - "reason": "build artifact, now excluded from monitoring" - }, - "examples/extractors/tree_sitter_js_extractor.py": { - "status": "🟢 Green", - "last_updated": "2026-06-11 13:45:54 AEST", - "reason": "build artifact, now excluded from monitoring" - }, - "examples/github-actions/chisel.yml": { - "status": "🟢 Green", - "last_updated": "2026-06-11 13:45:54 AEST", - "reason": "build artifact, now excluded from monitoring" - }, - "CLAUDE.md": { - "status": "🟢 Green", - "last_updated": "2026-06-11 14:33:37 AEST", - "reason": "released in v0.13.0; docs current" - }, - "CONTRIBUTING.md": { - "status": "🟢 Green", - "last_updated": "2026-06-11 13:45:43 AEST", - "reason": "build artifact, now excluded from monitoring" - }, - ".github/workflows/ci.yml": { - "status": "🟢 Green", - "last_updated": "2026-06-11 13:46:02 AEST", - "reason": "build artifact, now excluded from monitoring" - }, - ".github/workflows/publish.yml": { - "status": "🟢 Green", - "last_updated": "2026-06-11 13:46:03 AEST", - "reason": "build artifact, now excluded from monitoring" - }, - "COMPLETE_PROJECT_DOCUMENTATION.md": { - "status": "🟢 Green", - "last_updated": "2026-06-11 14:33:37 AEST", - "reason": "released in v0.13.0; docs current" - }, - ".gitignore": { - "status": "🟢 Green", - "last_updated": "2026-06-11 14:01:08 AEST", - "reason": "release state settled" - }, - "GROK_CONNECTORS.md": { - "status": "🟢 Green", - "last_updated": "2026-06-11 13:46:04 AEST", - "reason": "build artifact, now excluded from monitoring" - }, - "ARCHITECTURE.md": { - "status": "🟢 Green", - "last_updated": "2026-06-11 13:44:56 AEST", - "reason": "build artifact, now excluded from monitoring" - }, - "scripts/check_version.py": { - "status": "🟢 Green", - "last_updated": "2026-06-11 13:54:46 AEST", - "reason": "reviewed in v0.12.0 pass or runtime artifact" - }, - "scripts/verify_static_hybrid.py": { - "status": "🟢 Green", - "last_updated": "2026-06-11 13:54:47 AEST", - "reason": "reviewed in v0.12.0 pass or runtime artifact" - }, - "scripts/benchmark_chisel.py": { - "status": "🟢 Green", - "last_updated": "2026-06-11 13:54:45 AEST", - "reason": "reviewed in v0.12.0 pass or runtime artifact" - }, - "docs/EXTRACTOR_ECOSYSTEM.md": { - "status": "🟢 Green", - "last_updated": "2026-06-11 13:45:49 AEST", - "reason": "build artifact, now excluded from monitoring" - }, - "docs/ZERO_DEPS.md": { - "status": "🟢 Green", - "last_updated": "2026-06-11 13:45:50 AEST", - "reason": "build artifact, now excluded from monitoring" - }, - "docs/CUSTOM_EXTRACTORS.md": { - "status": "🟢 Green", - "last_updated": "2026-06-11 13:45:48 AEST", - "reason": "build artifact, now excluded from monitoring" - }, - "docs/LLM_CONTRACT.md": { - "status": "🟢 Green", - "last_updated": "2026-06-11 13:45:49 AEST", - "reason": "build artifact, now excluded from monitoring" - }, - "docs/chisel-demo.png": { - "status": "🟢 Green", - "last_updated": "2026-06-11 13:45:47 AEST", - "reason": "build artifact, now excluded from monitoring" - }, - "docs/AGENT_PLAYBOOK.md": { - "status": "🟢 Green", - "last_updated": "2026-06-11 13:45:46 AEST", - "reason": "build artifact, now excluded from monitoring" - }, - "LLM_Development.md": { - "status": "🟢 Green", - "last_updated": "2026-06-11 14:33:41 AEST", - "reason": "released in v0.13.0; docs current" - }, - "PLAN_NEXT.md": { - "status": "🟢 Green", - "last_updated": "2026-06-11 13:54:41 AEST", - "reason": "reviewed in v0.12.0 pass or runtime artifact" - }, - "chisel/import_graph.py": { - "status": "🟢 Green", - "last_updated": "2026-06-11 13:45:06 AEST", - "reason": "build artifact, now excluded from monitoring" - }, - "chisel/ast_utils.py": { - "status": "🟢 Green", - "last_updated": "2026-06-11 13:44:58 AEST", - "reason": "build artifact, now excluded from monitoring" - }, - "chisel/llm_contract.py": { - "status": "🟢 Green", - "last_updated": "2026-06-11 13:45:08 AEST", - "reason": "build artifact, now excluded from monitoring" - }, - "chisel/test_mapper.py": { - "status": "🟢 Green", - "last_updated": "2026-06-11 14:33:36 AEST", - "reason": "released in v0.13.0; docs current" - }, - "chisel/mcp_stdio.py": { - "status": "🟢 Green", - "last_updated": "2026-06-11 13:45:09 AEST", - "reason": "build artifact, now excluded from monitoring" - }, - "chisel/risk_meta.py": { - "status": "🟢 Green", - "last_updated": "2026-06-11 13:45:30 AEST", - "reason": "build artifact, now excluded from monitoring" - }, - "chisel/next_steps.py": { - "status": "🟢 Green", - "last_updated": "2026-06-11 13:45:11 AEST", - "reason": "build artifact, now excluded from monitoring" - }, - "chisel/engine.py": { - "status": "🟢 Green", - "last_updated": "2026-06-11 14:33:12 AEST", - "reason": "release state settled for v0.13.0" - }, - "chisel/schemas.py": { - "status": "🟢 Green", - "last_updated": "2026-06-11 13:45:32 AEST", - "reason": "build artifact, now excluded from monitoring" - }, - "chisel/mcp_server.py": { - "status": "🟢 Green", - "last_updated": "2026-06-11 13:45:09 AEST", - "reason": "build artifact, now excluded from monitoring" - }, - "chisel/bootstrap.py": { - "status": "🟢 Green", - "last_updated": "2026-06-11 13:44:58 AEST", - "reason": "build artifact, now excluded from monitoring" - }, - "chisel/metrics.py": { - "status": "🟢 Green", - "last_updated": "2026-06-11 14:33:13 AEST", - "reason": "release state settled for v0.13.0" - }, - "chisel/__init__.py": { - "status": "🟢 Green", - "last_updated": "2026-06-11 14:33:13 AEST", - "reason": "release state settled for v0.13.0" - }, - "chisel/rwlock.py": { - "status": "🟢 Green", - "last_updated": "2026-06-11 13:45:31 AEST", - "reason": "build artifact, now excluded from monitoring" - }, - "chisel/git_analyzer.py": { - "status": "🟢 Green", - "last_updated": "2026-06-11 13:45:04 AEST", - "reason": "build artifact, now excluded from monitoring" - }, - "chisel/storage.py": { - "status": "🟢 Green", - "last_updated": "2026-06-11 13:45:34 AEST", - "reason": "build artifact, now excluded from monitoring" - }, - "chisel/impact.py": { - "status": "🟢 Green", - "last_updated": "2026-06-11 13:45:05 AEST", - "reason": "build artifact, now excluded from monitoring" - }, - "chisel/static_test_imports.py": { - "status": "🟢 Green", - "last_updated": "2026-06-11 13:45:33 AEST", - "reason": "build artifact, now excluded from monitoring" - }, - "chisel/project.py": { - "status": "🟢 Green", - "last_updated": "2026-06-11 14:33:14 AEST", - "reason": "release state settled for v0.13.0" - }, - "chisel/cli.py": { - "status": "🟢 Green", - "last_updated": "2026-06-11 14:33:11 AEST", - "reason": "release state settled for v0.13.0" - }, - "pyproject.toml": { - "status": "🟢 Green", - "last_updated": "2026-06-11 14:33:42 AEST", - "reason": "released in v0.13.0; docs current" - }, - "CHANGELOG.md": { - "status": "🟢 Green", - "last_updated": "2026-06-11 14:33:09 AEST", - "reason": "release state settled for v0.13.0" - }, - "dist/chisel_test_impact-0.8.1.tar.gz": { - "status": "🟢 Green", - "last_updated": "2026-06-11 13:45:45 AEST", - "reason": "build artifact, now excluded from monitoring" - }, - "dist/chisel_test_impact-0.8.1-py3-none-any.whl": { - "status": "🟢 Green", - "last_updated": "2026-06-11 13:45:44 AEST", - "reason": "build artifact, now excluded from monitoring" - }, - "README.md": { - "status": "🟢 Green", - "last_updated": "2026-06-11 13:54:43 AEST", - "reason": "reviewed in v0.12.0 pass or runtime artifact" - }, - "LICENSE": { - "status": "🟢 Green", - "last_updated": "2026-06-11 13:54:37 AEST", - "reason": "reviewed in v0.12.0 pass or runtime artifact" - }, - "pending_updates.md": { - "status": "🟢 Green", - "last_updated": "2026-06-11 14:33:58 AEST", - "reason": "wikifier state file" - }, - "monitored_paths.txt": { - "status": "🟢 Green", - "last_updated": "2026-06-11 13:54:39 AEST", - "reason": "reviewed in v0.12.0 pass or runtime artifact" - }, - ".claude/settings.local.json": { - "status": "🟢 Green", - "last_updated": "2026-06-11 13:45:41 AEST", - "reason": "build artifact, now excluded from monitoring" - }, - "exclude_patterns.txt": { - "status": "🟢 Green", - "last_updated": "2026-06-11 13:55:22 AEST", - "reason": "wikifier state file, self-referential" - }, - "library.md": { - "status": "🟢 Green", - "last_updated": "2026-06-11 14:33:40 AEST", - "reason": "released in v0.13.0; docs current" - }, - ".coordinationhub/coordination.db": { - "status": "🟢 Green", - "last_updated": "2026-06-11 14:33:38 AEST", - "reason": "released in v0.13.0; docs current" - }, - "tests/__pycache__/test_cli.cpython-314-pytest-9.0.2.pyc": { - "status": "🟢 Green", - "last_updated": "2026-06-11 14:33:49 AEST", - "reason": "released in v0.13.0; docs current" - }, - ".ruff_cache/0.15.6/2918868589294341649": { - "status": "🟢 Green", - "last_updated": "2026-06-11 14:33:45 AEST", - "reason": "released in v0.13.0; docs current" - }, - ".ruff_cache/0.15.6/1474866491082371723": { - "status": "🟢 Green", - "last_updated": "2026-06-11 14:33:45 AEST", - "reason": "released in v0.13.0; docs current" - }, - ".pytest_cache/v/cache/nodeids": { - "status": "🟢 Green", - "last_updated": "2026-06-11 14:33:44 AEST", - "reason": "released in v0.13.0; docs current" - }, - ".chisel/chisel.lock": { - "status": "🟢 Green", - "last_updated": "2026-06-11 14:33:10 AEST", - "reason": "release state settled for v0.13.0" - }, - ".chisel/chisel.db": { - "status": "🟢 Green", - "last_updated": "2026-06-11 14:33:09 AEST", - "reason": "release state settled for v0.13.0" - }, - "chisel/__pycache__/cli.cpython-314.pyc": { - "status": "🟢 Green", - "last_updated": "2026-06-11 14:33:15 AEST", - "reason": "release state settled for v0.13.0" - }, - "tests/test_gitignore_filter.py": { - "status": "🟢 Green", - "last_updated": "2026-06-11 14:33:53 AEST", - "reason": "released in v0.13.0; docs current" - }, - "Logged_issues/2026-06-11_directory-scoped-analyze-walks-all-tests.md": { - "status": "🟢 Green", - "last_updated": "2026-06-11 14:31:33 AEST", - "reason": "wiki and docs updated for v0.13.0" - }, - "tests/__pycache__/test_gitignore_filter.cpython-314-pytest-9.0.2.pyc": { - "status": "🟢 Green", - "last_updated": "2026-06-11 14:33:51 AEST", - "reason": "released in v0.13.0; docs current" - }, - "tests/__pycache__/test_engine.cpython-314-pytest-9.0.2.pyc": { - "status": "🟢 Green", - "last_updated": "2026-06-11 14:33:50 AEST", - "reason": "released in v0.13.0; docs current" - }, - ".pytest_cache/v/cache/lastfailed": { - "status": "🟢 Green", - "last_updated": "2026-06-11 14:33:43 AEST", - "reason": "released in v0.13.0; docs current" - }, - "chisel_test_impact.egg-info/PKG-INFO": { - "status": "🟢 Green", - "last_updated": "2026-06-11 14:33:22 AEST", - "reason": "release state settled for v0.13.0" - }, - "chisel_test_impact.egg-info/top_level.txt": { - "status": "🟢 Green", - "last_updated": "2026-06-11 14:33:35 AEST", - "reason": "released in v0.13.0; docs current" - }, - "chisel_test_impact.egg-info/dependency_links.txt": { - "status": "🟢 Green", - "last_updated": "2026-06-11 14:33:21 AEST", - "reason": "release state settled for v0.13.0" - }, - "chisel_test_impact.egg-info/SOURCES.txt": { - "status": "🟢 Green", - "last_updated": "2026-06-11 14:33:24 AEST", - "reason": "release state settled for v0.13.0" - }, - "chisel_test_impact.egg-info/requires.txt": { - "status": "🟢 Green", - "last_updated": "2026-06-11 14:33:23 AEST", - "reason": "release state settled for v0.13.0" - }, - "chisel_test_impact.egg-info/entry_points.txt": { - "status": "🟢 Green", - "last_updated": "2026-06-11 14:33:21 AEST", - "reason": "release state settled for v0.13.0" - }, - ".stele-context/stele_context.db-shm": { - "status": "🟢 Green", - "last_updated": "2026-06-11 14:33:48 AEST", - "reason": "released in v0.13.0; docs current" - }, - ".stele-context/stele_context.db-wal": { - "status": "🟢 Green", - "last_updated": "2026-06-11 14:33:48 AEST", - "reason": "released in v0.13.0; docs current" - }, - ".stele-context/indices/hnsw_index.json.zlib": { - "status": "🟢 Green", - "last_updated": "2026-06-11 14:33:46 AEST", - "reason": "released in v0.13.0; docs current" - }, - ".stele-context/stele_context.db": { - "status": "🟢 Green", - "last_updated": "2026-06-11 14:33:47 AEST", - "reason": "released in v0.13.0; docs current" - }, - "chisel/__pycache__/test_mapper.cpython-314.pyc": { - "status": "🟢 Green", - "last_updated": "2026-06-11 14:33:19 AEST", - "reason": "release state settled for v0.13.0" - }, - "chisel/__pycache__/engine.cpython-314.pyc": { - "status": "🟢 Green", - "last_updated": "2026-06-11 14:33:16 AEST", - "reason": "release state settled for v0.13.0" - }, - "chisel/__pycache__/project.cpython-314.pyc": { - "status": "🟢 Green", - "last_updated": "2026-06-11 14:33:18 AEST", - "reason": "release state settled for v0.13.0" - }, - "chisel/__pycache__/test_mapper.cpython-314-pytest-9.0.2.pyc": { - "status": "🟢 Green", - "last_updated": "2026-06-11 14:33:20 AEST", - "reason": "release state settled for v0.13.0" - }, - "chisel/__pycache__/metrics.cpython-314.pyc": { - "status": "🟢 Green", - "last_updated": "2026-06-11 14:33:17 AEST", - "reason": "release state settled for v0.13.0" - }, - "chisel/__pycache__/__init__.cpython-314.pyc": { - "status": "🟢 Green", - "last_updated": "2026-06-11 14:33:17 AEST", - "reason": "release state settled for v0.13.0" - }, - "skills/SKILL.md": { - "status": "🟡 Yellow", - "last_updated": "2026-06-11 16:02:39", - "reason": "New installable Claude Code skill distilling the Chisel agent protocol for v0.14.0", - "freshness_provenance": "upsert:🟡 Yellow (non-freshness path)" - } - } -} \ No newline at end of file diff --git a/file_health.md b/file_health.md deleted file mode 100644 index 0ecf240..0000000 --- a/file_health.md +++ /dev/null @@ -1,122 +0,0 @@ -# Documentation Health Matrix - -| File | Status | Last Updated | Reason / Intent | -|------|--------|--------------|-----------------| -| .chisel/chisel.db | 🟢 Green | 2026-06-11 14:33:09 AEST | release state settled for v0.13.0 | -| .chisel/chisel.lock | 🟢 Green | 2026-06-11 14:33:10 AEST | release state settled for v0.13.0 | -| .claude/settings.local.json | 🟢 Green | 2026-06-11 13:45:41 AEST | build artifact, now excluded from monitoring | -| .coordinationhub/coordination.db | 🟢 Green | 2026-06-11 14:33:38 AEST | released in v0.13.0; docs current | -| .github/workflows/ci.yml | 🟢 Green | 2026-06-11 13:46:02 AEST | build artifact, now excluded from monitoring | -| .github/workflows/publish.yml | 🟢 Green | 2026-06-11 13:46:03 AEST | build artifact, now excluded from monitoring | -| .gitignore | 🟢 Green | 2026-06-11 14:01:08 AEST | release state settled | -| .pytest_cache/v/cache/lastfailed | 🟢 Green | 2026-06-11 14:33:43 AEST | released in v0.13.0; docs current | -| .pytest_cache/v/cache/nodeids | 🟢 Green | 2026-06-11 14:33:44 AEST | released in v0.13.0; docs current | -| .ruff_cache/0.15.6/1474866491082371723 | 🟢 Green | 2026-06-11 14:33:45 AEST | released in v0.13.0; docs current | -| .ruff_cache/0.15.6/2918868589294341649 | 🟢 Green | 2026-06-11 14:33:45 AEST | released in v0.13.0; docs current | -| .stele-context/indices/hnsw_index.json.zlib | 🟢 Green | 2026-06-11 14:33:46 AEST | released in v0.13.0; docs current | -| .stele-context/stele_context.db | 🟢 Green | 2026-06-11 14:33:47 AEST | released in v0.13.0; docs current | -| .stele-context/stele_context.db-shm | 🟢 Green | 2026-06-11 14:33:48 AEST | released in v0.13.0; docs current | -| .stele-context/stele_context.db-wal | 🟢 Green | 2026-06-11 14:33:48 AEST | released in v0.13.0; docs current | -| ARCHITECTURE.md | 🟢 Green | 2026-06-11 13:44:56 AEST | build artifact, now excluded from monitoring | -| CHANGELOG.md | 🟢 Green | 2026-06-11 16:03:03 AEST | docs current for v0.14.0 | -| CLAUDE.md | 🟢 Green | 2026-06-11 14:33:37 AEST | released in v0.13.0; docs current | -| COMPLETE_PROJECT_DOCUMENTATION.md | 🟢 Green | 2026-06-11 16:03:06 AEST | docs current for v0.14.0 | -| CONTRIBUTING.md | 🟢 Green | 2026-06-11 13:45:43 AEST | build artifact, now excluded from monitoring | -| GROK_CONNECTORS.md | 🟢 Green | 2026-06-11 13:46:04 AEST | build artifact, now excluded from monitoring | -| LICENSE | 🟢 Green | 2026-06-11 13:54:37 AEST | reviewed in v0.12.0 pass or runtime artifact | -| LLM_Development.md | 🟢 Green | 2026-06-11 14:33:41 AEST | released in v0.13.0; docs current | -| Logged_issues/2026-06-11_directory-scoped-analyze-walks-all-tests.md | 🟢 Green | 2026-06-11 14:31:33 AEST | wiki and docs updated for v0.13.0 | -| PLAN_NEXT.md | 🟢 Green | 2026-06-11 13:54:41 AEST | reviewed in v0.12.0 pass or runtime artifact | -| README.md | 🟢 Green | 2026-06-11 16:16:21 AEST | README current for v0.14.0 tag | -| chisel/__init__.py | 🟢 Green | 2026-06-11 16:03:04 AEST | docs current for v0.14.0 | -| chisel/__pycache__/__init__.cpython-314.pyc | 🟢 Green | 2026-06-11 14:33:17 AEST | release state settled for v0.13.0 | -| chisel/__pycache__/cli.cpython-314.pyc | 🟢 Green | 2026-06-11 14:33:15 AEST | release state settled for v0.13.0 | -| chisel/__pycache__/engine.cpython-314.pyc | 🟢 Green | 2026-06-11 14:33:16 AEST | release state settled for v0.13.0 | -| chisel/__pycache__/metrics.cpython-314.pyc | 🟢 Green | 2026-06-11 14:33:17 AEST | release state settled for v0.13.0 | -| chisel/__pycache__/project.cpython-314.pyc | 🟢 Green | 2026-06-11 14:33:18 AEST | release state settled for v0.13.0 | -| chisel/__pycache__/test_mapper.cpython-314-pytest-9.0.2.pyc | 🟢 Green | 2026-06-11 14:33:20 AEST | release state settled for v0.13.0 | -| chisel/__pycache__/test_mapper.cpython-314.pyc | 🟢 Green | 2026-06-11 14:33:19 AEST | release state settled for v0.13.0 | -| chisel/ast_utils.py | 🟢 Green | 2026-06-11 13:44:58 AEST | build artifact, now excluded from monitoring | -| chisel/bootstrap.py | 🟢 Green | 2026-06-11 13:44:58 AEST | build artifact, now excluded from monitoring | -| chisel/cli.py | 🟢 Green | 2026-06-11 14:33:11 AEST | release state settled for v0.13.0 | -| chisel/engine.py | 🟢 Green | 2026-06-11 14:33:12 AEST | release state settled for v0.13.0 | -| chisel/git_analyzer.py | 🟢 Green | 2026-06-11 13:45:04 AEST | build artifact, now excluded from monitoring | -| chisel/impact.py | 🟢 Green | 2026-06-11 13:45:05 AEST | build artifact, now excluded from monitoring | -| chisel/import_graph.py | 🟢 Green | 2026-06-11 13:45:06 AEST | build artifact, now excluded from monitoring | -| chisel/llm_contract.py | 🟢 Green | 2026-06-11 13:45:08 AEST | build artifact, now excluded from monitoring | -| chisel/mcp_server.py | 🟢 Green | 2026-06-11 13:45:09 AEST | build artifact, now excluded from monitoring | -| chisel/mcp_stdio.py | 🟢 Green | 2026-06-11 13:45:09 AEST | build artifact, now excluded from monitoring | -| chisel/metrics.py | 🟢 Green | 2026-06-11 14:33:13 AEST | release state settled for v0.13.0 | -| chisel/next_steps.py | 🟢 Green | 2026-06-11 13:45:11 AEST | build artifact, now excluded from monitoring | -| chisel/project.py | 🟢 Green | 2026-06-11 14:33:14 AEST | release state settled for v0.13.0 | -| chisel/risk_meta.py | 🟢 Green | 2026-06-11 13:45:30 AEST | build artifact, now excluded from monitoring | -| chisel/rwlock.py | 🟢 Green | 2026-06-11 13:45:31 AEST | build artifact, now excluded from monitoring | -| chisel/schemas.py | 🟢 Green | 2026-06-11 13:45:32 AEST | build artifact, now excluded from monitoring | -| chisel/static_test_imports.py | 🟢 Green | 2026-06-11 13:45:33 AEST | build artifact, now excluded from monitoring | -| chisel/storage.py | 🟢 Green | 2026-06-11 13:45:34 AEST | build artifact, now excluded from monitoring | -| chisel/test_mapper.py | 🟢 Green | 2026-06-11 14:33:36 AEST | released in v0.13.0; docs current | -| chisel_test_impact.egg-info/PKG-INFO | 🟢 Green | 2026-06-11 14:33:22 AEST | release state settled for v0.13.0 | -| chisel_test_impact.egg-info/SOURCES.txt | 🟢 Green | 2026-06-11 14:33:24 AEST | release state settled for v0.13.0 | -| chisel_test_impact.egg-info/dependency_links.txt | 🟢 Green | 2026-06-11 14:33:21 AEST | release state settled for v0.13.0 | -| chisel_test_impact.egg-info/entry_points.txt | 🟢 Green | 2026-06-11 14:33:21 AEST | release state settled for v0.13.0 | -| chisel_test_impact.egg-info/requires.txt | 🟢 Green | 2026-06-11 14:33:23 AEST | release state settled for v0.13.0 | -| chisel_test_impact.egg-info/top_level.txt | 🟢 Green | 2026-06-11 14:33:35 AEST | released in v0.13.0; docs current | -| dist/chisel_test_impact-0.8.1-py3-none-any.whl | 🟢 Green | 2026-06-11 13:45:44 AEST | build artifact, now excluded from monitoring | -| dist/chisel_test_impact-0.8.1.tar.gz | 🟢 Green | 2026-06-11 13:45:45 AEST | build artifact, now excluded from monitoring | -| docs/AGENT_PLAYBOOK.md | 🟢 Green | 2026-06-11 16:03:01 AEST | docs current for v0.14.0 | -| docs/CUSTOM_EXTRACTORS.md | 🟢 Green | 2026-06-11 13:45:48 AEST | build artifact, now excluded from monitoring | -| docs/EXTRACTOR_ECOSYSTEM.md | 🟢 Green | 2026-06-11 13:45:49 AEST | build artifact, now excluded from monitoring | -| docs/LLM_CONTRACT.md | 🟢 Green | 2026-06-11 13:45:49 AEST | build artifact, now excluded from monitoring | -| docs/ZERO_DEPS.md | 🟢 Green | 2026-06-11 13:45:50 AEST | build artifact, now excluded from monitoring | -| docs/chisel-demo.png | 🟢 Green | 2026-06-11 13:45:47 AEST | build artifact, now excluded from monitoring | -| examples/chisel_bootstrap_example.py | 🟢 Green | 2026-06-11 13:45:51 AEST | build artifact, now excluded from monitoring | -| examples/extractors/lsp_symbol_extractor.py | 🟢 Green | 2026-06-11 13:45:52 AEST | build artifact, now excluded from monitoring | -| examples/extractors/swift_syntax_extractor.py | 🟢 Green | 2026-06-11 13:45:53 AEST | build artifact, now excluded from monitoring | -| examples/extractors/tree_sitter_js_extractor.py | 🟢 Green | 2026-06-11 13:45:54 AEST | build artifact, now excluded from monitoring | -| examples/github-actions/chisel.yml | 🟢 Green | 2026-06-11 13:45:54 AEST | build artifact, now excluded from monitoring | -| exclude_patterns.txt | 🟢 Green | 2026-06-11 13:55:22 AEST | wikifier state file, self-referential | -| file_health.md | 🟢 Green | 2026-06-11 14:33:57 AEST | wikifier state file | -| library.md | 🟢 Green | 2026-06-11 14:33:40 AEST | released in v0.13.0; docs current | -| monitored_paths.txt | 🟢 Green | 2026-06-11 13:54:39 AEST | reviewed in v0.12.0 pass or runtime artifact | -| pending_updates.md | 🟢 Green | 2026-06-11 14:33:58 AEST | wikifier state file | -| plan/phase_15_agent_experience_plan_closed.md | 🟢 Green | 2026-06-11 13:42:03 AEST | runtime artifact or reviewed; now covered by exclude_patterns where applicable | -| pyproject.toml | 🟢 Green | 2026-06-11 16:03:05 AEST | docs current for v0.14.0 | -| scripts/benchmark_chisel.py | 🟢 Green | 2026-06-11 13:54:45 AEST | reviewed in v0.12.0 pass or runtime artifact | -| scripts/check_version.py | 🟢 Green | 2026-06-11 13:54:46 AEST | reviewed in v0.12.0 pass or runtime artifact | -| scripts/verify_static_hybrid.py | 🟢 Green | 2026-06-11 13:54:47 AEST | reviewed in v0.12.0 pass or runtime artifact | -| skills/SKILL.md | 🟢 Green | 2026-06-11 16:03:00 AEST | docs current for v0.14.0 | -| tests/__init__.py | 🟢 Green | 2026-06-11 13:54:54 AEST | reviewed in v0.12.0 pass or runtime artifact | -| tests/__pycache__/test_cli.cpython-314-pytest-9.0.2.pyc | 🟢 Green | 2026-06-11 14:33:49 AEST | released in v0.13.0; docs current | -| tests/__pycache__/test_engine.cpython-314-pytest-9.0.2.pyc | 🟢 Green | 2026-06-11 14:33:50 AEST | released in v0.13.0; docs current | -| tests/__pycache__/test_gitignore_filter.cpython-314-pytest-9.0.2.pyc | 🟢 Green | 2026-06-11 14:33:51 AEST | released in v0.13.0; docs current | -| tests/bootstrap_stub.py | 🟢 Green | 2026-06-11 13:54:48 AEST | reviewed in v0.12.0 pass or runtime artifact | -| tests/conftest.py | 🟢 Green | 2026-06-11 13:54:49 AEST | reviewed in v0.12.0 pass or runtime artifact | -| tests/fixtures/languages/csharp/BasicTests.cs | 🟢 Green | 2026-06-11 13:54:49 AEST | reviewed in v0.12.0 pass or runtime artifact | -| tests/fixtures/languages/go/basic_test.go | 🟢 Green | 2026-06-11 13:54:50 AEST | reviewed in v0.12.0 pass or runtime artifact | -| tests/fixtures/languages/go/go.mod | 🟢 Green | 2026-06-11 13:54:51 AEST | reviewed in v0.12.0 pass or runtime artifact | -| tests/fixtures/languages/java/BasicTest.java | 🟢 Green | 2026-06-11 13:54:52 AEST | reviewed in v0.12.0 pass or runtime artifact | -| tests/fixtures/languages/rust/basic.rs | 🟢 Green | 2026-06-11 13:54:53 AEST | reviewed in v0.12.0 pass or runtime artifact | -| tests/fixtures/languages/swift/BasicTests.swift | 🟢 Green | 2026-06-11 13:54:53 AEST | reviewed in v0.12.0 pass or runtime artifact | -| tests/test_ast_utils.py | 🟢 Green | 2026-06-11 13:54:56 AEST | reviewed in v0.12.0 pass or runtime artifact | -| tests/test_bootstrap.py | 🟢 Green | 2026-06-11 13:54:57 AEST | reviewed in v0.12.0 pass or runtime artifact | -| tests/test_cli.py | 🟢 Green | 2026-06-11 14:33:52 AEST | released in v0.13.0; docs current | -| tests/test_engine.py | 🟢 Green | 2026-06-11 14:33:52 AEST | released in v0.13.0; docs current | -| tests/test_engine_sharding.py | 🟢 Green | 2026-06-11 13:54:58 AEST | reviewed in v0.12.0 pass or runtime artifact | -| tests/test_git_analyzer.py | 🟢 Green | 2026-06-11 13:54:59 AEST | reviewed in v0.12.0 pass or runtime artifact | -| tests/test_gitignore_filter.py | 🟢 Green | 2026-06-11 14:33:53 AEST | released in v0.13.0; docs current | -| tests/test_impact.py | 🟢 Green | 2026-06-11 13:55:00 AEST | reviewed in v0.12.0 pass or runtime artifact | -| tests/test_import_graph.py | 🟢 Green | 2026-06-11 13:55:01 AEST | reviewed in v0.12.0 pass or runtime artifact | -| tests/test_language_frameworks.py | 🟢 Green | 2026-06-11 13:55:02 AEST | reviewed in v0.12.0 pass or runtime artifact | -| tests/test_llm_contract.py | 🟢 Green | 2026-06-11 13:55:03 AEST | reviewed in v0.12.0 pass or runtime artifact | -| tests/test_mcp_server.py | 🟢 Green | 2026-06-11 13:55:03 AEST | reviewed in v0.12.0 pass or runtime artifact | -| tests/test_mcp_stdio.py | 🟢 Green | 2026-06-11 13:55:04 AEST | reviewed in v0.12.0 pass or runtime artifact | -| tests/test_metrics.py | 🟢 Green | 2026-06-11 13:55:05 AEST | reviewed in v0.12.0 pass or runtime artifact | -| tests/test_next_steps.py | 🟢 Green | 2026-06-11 13:55:06 AEST | reviewed in v0.12.0 pass or runtime artifact | -| tests/test_project.py | 🟢 Green | 2026-06-11 13:55:07 AEST | reviewed in v0.12.0 pass or runtime artifact | -| tests/test_rwlock.py | 🟢 Green | 2026-06-11 13:55:07 AEST | reviewed in v0.12.0 pass or runtime artifact | -| tests/test_static_hybrid.py | 🟢 Green | 2026-06-11 13:55:08 AEST | reviewed in v0.12.0 pass or runtime artifact | -| tests/test_storage.py | 🟢 Green | 2026-06-11 13:55:09 AEST | reviewed in v0.12.0 pass or runtime artifact | -| tests/test_test_mapper.py | 🟢 Green | 2026-06-11 13:55:10 AEST | reviewed in v0.12.0 pass or runtime artifact | -| wiki-local/glossary.md | 🟢 Green | 2026-06-11 13:42:03 AEST | runtime artifact or reviewed; now covered by exclude_patterns where applicable | -| wiki-local/index.md | 🟢 Green | 2026-06-11 13:42:04 AEST | runtime artifact or reviewed; now covered by exclude_patterns where applicable | -| wiki-local/spec-project.md | 🟢 Green | 2026-06-11 13:55:11 AEST | reviewed in v0.12.0 pass or runtime artifact | diff --git a/library.md b/library.md deleted file mode 100644 index 15f3b83..0000000 --- a/library.md +++ /dev/null @@ -1,782 +0,0 @@ -# Library & Imports Map (auto-generated by wikifier update-maps) - -> This file is regenerated. Manual edits will be overwritten. -> Run `wikifier record-change library.md "..."` if you need to annotate. - -## File Tree - -> Every parsed/tracked file, organised by folder. Descriptions come from -> the agent wiki (file_health). The dependency graph is further below. - -```text -Chisel/ (98 files) -├── .chisel/ -│ ├── chisel.db — reviewed in v0.12.0 pass or runtime artifact -│ └── chisel.lock — reviewed in v0.12.0 pass or runtime artifact -├── .claude/ -│ └── settings.local.json — build artifact, now excluded from monitoring -├── .coordinationhub/ -│ └── coordination.db — release state settled -├── .github/ -│ └── workflows/ -│ ├── ci.yml — build artifact, now excluded from monitoring -│ └── publish.yml — build artifact, now excluded from monitoring -├── .pytest_cache/ -│ └── v/ -│ └── cache/ -│ └── nodeids — release state settled -├── .ruff_cache/ -│ └── 0.15.6/ -│ ├── 1474866491082371723 — reviewed in v0.12.0 pass or runtime artifact -│ └── 2918868589294341649 — reviewed in v0.12.0 pass or runtime artifact -├── Logged_issues/ -│ └── 2026-06-11_directory-scoped-analyze-walks-all-tests.md — wiki and docs updated for v0.13.0 -├── chisel/ -│ ├── __pycache__/ -│ │ └── cli.cpython-314.pyc — reviewed in v0.12.0 pass or runtime artifact -│ ├── __init__.py — wiki and docs updated for v0.13.0 -│ ├── ast_utils.py — build artifact, now excluded from monitoring -│ ├── bootstrap.py — build artifact, now excluded from monitoring -│ ├── cli.py — wiki and docs updated for v0.13.0 -│ ├── engine.py — wiki and docs updated for v0.13.0 -│ ├── git_analyzer.py — build artifact, now excluded from monitoring -│ ├── impact.py — build artifact, now excluded from monitoring -│ ├── import_graph.py — build artifact, now excluded from monitoring -│ ├── llm_contract.py — build artifact, now excluded from monitoring -│ ├── mcp_server.py — build artifact, now excluded from monitoring -│ ├── mcp_stdio.py — build artifact, now excluded from monitoring -│ ├── metrics.py — build artifact, now excluded from monitoring -│ ├── next_steps.py — build artifact, now excluded from monitoring -│ ├── project.py — wiki and docs updated for v0.13.0 -│ ├── risk_meta.py — build artifact, now excluded from monitoring -│ ├── rwlock.py — build artifact, now excluded from monitoring -│ ├── schemas.py — build artifact, now excluded from monitoring -│ ├── static_test_imports.py — build artifact, now excluded from monitoring -│ ├── storage.py — build artifact, now excluded from monitoring -│ └── test_mapper.py — wiki and docs updated for v0.13.0 -├── dist/ -│ ├── chisel_test_impact-0.8.1-py3-none-any.whl — build artifact, now excluded from monitoring -│ └── chisel_test_impact-0.8.1.tar.gz — build artifact, now excluded from monitoring -├── docs/ -│ ├── AGENT_PLAYBOOK.md — build artifact, now excluded from monitoring -│ ├── CUSTOM_EXTRACTORS.md — build artifact, now excluded from monitoring -│ ├── EXTRACTOR_ECOSYSTEM.md — build artifact, now excluded from monitoring -│ ├── LLM_CONTRACT.md — build artifact, now excluded from monitoring -│ ├── ZERO_DEPS.md — build artifact, now excluded from monitoring -│ └── chisel-demo.png — build artifact, now excluded from monitoring -├── examples/ -│ ├── extractors/ -│ │ ├── lsp_symbol_extractor.py — build artifact, now excluded from monitoring -│ │ ├── swift_syntax_extractor.py — build artifact, now excluded from monitoring -│ │ └── tree_sitter_js_extractor.py — build artifact, now excluded from monitoring -│ ├── github-actions/ -│ │ └── chisel.yml — build artifact, now excluded from monitoring -│ └── chisel_bootstrap_example.py — build artifact, now excluded from monitoring -├── plan/ -│ └── phase_15_agent_experience_plan_closed.md — runtime artifact or reviewed; now covered by exclude_patterns where applicable -├── scripts/ -│ ├── benchmark_chisel.py — reviewed in v0.12.0 pass or runtime artifact -│ ├── check_version.py — reviewed in v0.12.0 pass or runtime artifact -│ └── verify_static_hybrid.py — reviewed in v0.12.0 pass or runtime artifact -├── tests/ -│ ├── __pycache__/ -│ │ └── test_cli.cpython-314-pytest-9.0.2.pyc — reviewed in v0.12.0 pass or runtime artifact -│ ├── fixtures/ -│ │ └── languages/ -│ │ ├── csharp/ -│ │ │ └── BasicTests.cs — reviewed in v0.12.0 pass or runtime artifact -│ │ ├── go/ -│ │ │ ├── basic_test.go — reviewed in v0.12.0 pass or runtime artifact -│ │ │ └── go.mod — reviewed in v0.12.0 pass or runtime artifact -│ │ ├── java/ -│ │ │ └── BasicTest.java — reviewed in v0.12.0 pass or runtime artifact -│ │ ├── rust/ -│ │ │ └── basic.rs — reviewed in v0.12.0 pass or runtime artifact -│ │ └── swift/ -│ │ └── BasicTests.swift — reviewed in v0.12.0 pass or runtime artifact -│ ├── __init__.py — reviewed in v0.12.0 pass or runtime artifact -│ ├── bootstrap_stub.py — reviewed in v0.12.0 pass or runtime artifact -│ ├── conftest.py — reviewed in v0.12.0 pass or runtime artifact -│ ├── test_ast_utils.py — reviewed in v0.12.0 pass or runtime artifact -│ ├── test_bootstrap.py — reviewed in v0.12.0 pass or runtime artifact -│ ├── test_cli.py — wiki and docs updated for v0.13.0 -│ ├── test_engine.py — wiki and docs updated for v0.13.0 -│ ├── test_engine_sharding.py — reviewed in v0.12.0 pass or runtime artifact -│ ├── test_git_analyzer.py — reviewed in v0.12.0 pass or runtime artifact -│ ├── test_gitignore_filter.py — wiki and docs updated for v0.13.0 -│ ├── test_impact.py — reviewed in v0.12.0 pass or runtime artifact -│ ├── test_import_graph.py — reviewed in v0.12.0 pass or runtime artifact -│ ├── test_language_frameworks.py — reviewed in v0.12.0 pass or runtime artifact -│ ├── test_llm_contract.py — reviewed in v0.12.0 pass or runtime artifact -│ ├── test_mcp_server.py — reviewed in v0.12.0 pass or runtime artifact -│ ├── test_mcp_stdio.py — reviewed in v0.12.0 pass or runtime artifact -│ ├── test_metrics.py — reviewed in v0.12.0 pass or runtime artifact -│ ├── test_next_steps.py — reviewed in v0.12.0 pass or runtime artifact -│ ├── test_project.py — reviewed in v0.12.0 pass or runtime artifact -│ ├── test_rwlock.py — reviewed in v0.12.0 pass or runtime artifact -│ ├── test_static_hybrid.py — reviewed in v0.12.0 pass or runtime artifact -│ ├── test_storage.py — reviewed in v0.12.0 pass or runtime artifact -│ └── test_test_mapper.py — reviewed in v0.12.0 pass or runtime artifact -├── wiki-local/ -│ ├── glossary.md — runtime artifact or reviewed; now covered by exclude_patterns where applicable -│ ├── index.md — runtime artifact or reviewed; now covered by exclude_patterns where applicable -│ └── spec-project.md — reviewed in v0.12.0 pass or runtime artifact -├── .gitignore — release state settled -├── ARCHITECTURE.md — build artifact, now excluded from monitoring -├── CHANGELOG.md — wiki and docs updated for v0.13.0 -├── CLAUDE.md — wiki and docs updated for v0.13.0 -├── COMPLETE_PROJECT_DOCUMENTATION.md — wiki and docs updated for v0.13.0 -├── CONTRIBUTING.md — build artifact, now excluded from monitoring -├── GROK_CONNECTORS.md — build artifact, now excluded from monitoring -├── LICENSE — reviewed in v0.12.0 pass or runtime artifact -├── LLM_Development.md — wiki and docs updated for v0.13.0 -├── PLAN_NEXT.md — reviewed in v0.12.0 pass or runtime artifact -├── README.md — reviewed in v0.12.0 pass or runtime artifact -├── exclude_patterns.txt — wikifier state file, self-referential -├── file_health.md — release state settled -├── library.md — reviewed in v0.12.0 pass or runtime artifact -├── monitored_paths.txt — reviewed in v0.12.0 pass or runtime artifact -├── pending_updates.md — release state settled -└── pyproject.toml — wiki and docs updated for v0.13.0 -``` - -## Dependency Graph (Mermaid) - -```mermaid -graph TD - subgraph chisel - chisel_ast_utils_py["ast_utils.py"] - chisel_bootstrap_py["bootstrap.py"] - chisel_cli_py["cli.py"] - chisel_engine_py["engine.py"] - chisel_git_analyzer_py["git_analyzer.py"] - chisel_impact_py["impact.py"] - chisel_import_graph_py["import_graph.py"] - chisel_mcp_server_py["mcp_server.py"] - chisel_mcp_stdio_py["mcp_stdio.py"] - chisel_metrics_py["metrics.py"] - chisel_project_py["project.py"] - chisel_risk_meta_py["risk_meta.py"] - chisel_rwlock_py["rwlock.py"] - chisel_schemas_py["schemas.py"] - chisel_static_test_imports_py["static_test_imports.py"] - chisel_storage_py["storage.py"] - chisel_test_mapper_py["test_mapper.py"] - end - subgraph examples - examples_extractors_lsp_symbol_extractor_py["lsp_symbol_extractor.py"] - examples_extractors_swift_syntax_extractor_py["swift_syntax_extractor.py"] - examples_extractors_tree_sitter_js_extractor_py["tree_sitter_js_extractor.py"] - end - subgraph root - CHISEL_BOOTSTRAP["CHISEL_BOOTSTRAP"] - end - subgraph scripts - scripts_benchmark_chisel_py["benchmark_chisel.py"] - scripts_check_version_py["check_version.py"] - scripts_verify_static_hybrid_py["verify_static_hybrid.py"] - end - subgraph tests - tests_conftest_py["conftest.py"] - tests_test_ast_utils_py["test_ast_utils.py"] - tests_test_bootstrap_py["test_bootstrap.py"] - tests_test_cli_py["test_cli.py"] - tests_test_engine_py["test_engine.py"] - tests_test_engine_sharding_py["test_engine_sharding.py"] - tests_test_git_analyzer_py["test_git_analyzer.py"] - tests_test_gitignore_filter_py["test_gitignore_filter.py"] - tests_test_impact_py["test_impact.py"] - tests_test_import_graph_py["test_import_graph.py"] - tests_test_language_frameworks_py["test_language_frameworks.py"] - tests_test_llm_contract_py["test_llm_contract.py"] - tests_test_mcp_server_py["test_mcp_server.py"] - tests_test_mcp_stdio_py["test_mcp_stdio.py"] - tests_test_metrics_py["test_metrics.py"] - tests_test_next_steps_py["test_next_steps.py"] - tests_test_project_py["test_project.py"] - tests_test_rwlock_py["test_rwlock.py"] - tests_test_static_hybrid_py["test_static_hybrid.py"] - tests_test_storage_py["test_storage.py"] - tests_test_test_mapper_py["test_test_mapper.py"] - end - subgraph External - argparse["argparse"] - ast["ast"] - asyncio["asyncio"] - builtins["builtins"] - chisel["chisel"] - chisel_ast_utils["chisel.ast_utils"] - chisel_bootstrap["chisel.bootstrap"] - chisel_cli["chisel.cli"] - chisel_engine["chisel.engine"] - chisel_git_analyzer["chisel.git_analyzer"] - chisel_impact["chisel.impact"] - chisel_import_graph["chisel.import_graph"] - chisel_llm_contract["chisel.llm_contract"] - chisel_mcp_server["chisel.mcp_server"] - chisel_mcp_stdio["chisel.mcp_stdio"] - chisel_metrics["chisel.metrics"] - chisel_next_steps["chisel.next_steps"] - chisel_project["chisel.project"] - chisel_risk_meta["chisel.risk_meta"] - chisel_rwlock["chisel.rwlock"] - chisel_schemas["chisel.schemas"] - chisel_static_test_imports["chisel.static_test_imports"] - chisel_storage["chisel.storage"] - chisel_test_mapper["chisel.test_mapper"] - collections["collections"] - concurrent_futures["concurrent.futures"] - contextlib["contextlib"] - ctypes["ctypes"] - ctypes_wintypes["ctypes.wintypes"] - dataclasses["dataclasses"] - datetime["datetime"] - fcntl["fcntl"] - functools["functools"] - hashlib["hashlib"] - http_server["http.server"] - importlib["importlib"] - itertools["itertools"] - json["json"] - logging["logging"] - math["math"] - mcp_server["mcp.server"] - mcp_server_stdio["mcp.server.stdio"] - mcp_types["mcp.types"] - msvcrt["msvcrt"] - os["os"] - pathlib["pathlib"] - pytest["pytest"] - re["re"] - sqlite3["sqlite3"] - statistics["statistics"] - subprocess["subprocess"] - swift_syntax["swift_syntax"] - swift_syntax_parser["swift_syntax.parser"] - sys["sys"] - tempfile["tempfile"] - textwrap["textwrap"] - threading["threading"] - time["time"] - tomllib["tomllib"] - tree_sitter["tree_sitter"] - tree_sitter_javascript["tree_sitter_javascript"] - tree_sitter_typescript["tree_sitter_typescript"] - types["types"] - unittest["unittest"] - unittest_mock["unittest.mock"] - urllib_error["urllib.error"] - urllib_parse["urllib.parse"] - urllib_request["urllib.request"] - uuid["uuid"] - end - chisel_ast_utils_py -.-> ast - chisel_ast_utils_py -.-> hashlib - chisel_ast_utils_py -.-> re - chisel_ast_utils_py -.-> dataclasses - chisel_ast_utils_py -.-> functools - chisel_ast_utils_py -.-> pathlib - chisel_bootstrap_py -.-> importlib - chisel_bootstrap_py -.-> os - chisel_bootstrap_py -. ? .-> CHISEL_BOOTSTRAP - chisel_cli_py -.-> argparse - chisel_cli_py -.-> json - chisel_cli_py -.-> os - chisel_cli_py -.-> re - chisel_cli_py -.-> subprocess - chisel_cli_py -.-> sys - chisel_cli_py -.-> tempfile - chisel_cli_py -.-> chisel_engine - chisel_cli_py -.-> chisel_mcp_server - chisel_cli_py -.-> chisel_mcp_stdio - chisel_engine_py -.-> json - chisel_engine_py -.-> logging - chisel_engine_py -.-> os - chisel_engine_py -.-> threading - chisel_engine_py -.-> time - chisel_engine_py -.-> uuid - chisel_engine_py -.-> tomllib - chisel_engine_py -.-> contextlib - chisel_engine_py -.-> chisel_ast_utils - chisel_engine_py -.-> chisel_bootstrap - chisel_engine_py -.-> chisel_git_analyzer - chisel_engine_py -.-> chisel_import_graph - chisel_engine_py -.-> chisel_impact - chisel_engine_py -.-> chisel_metrics - chisel_engine_py -.-> chisel_project - chisel_engine_py -.-> chisel_risk_meta - chisel_engine_py -.-> chisel_rwlock - chisel_engine_py -.-> chisel_storage - chisel_engine_py -.-> chisel_test_mapper - chisel_engine_py -.-> concurrent_futures - chisel_git_analyzer_py -.-> re - chisel_git_analyzer_py -.-> subprocess - chisel_git_analyzer_py -.-> datetime - chisel_impact_py -.-> os - chisel_impact_py -.-> re - chisel_impact_py -.-> collections - chisel_impact_py -.-> datetime - chisel_impact_py -.-> chisel_ast_utils - chisel_impact_py -.-> chisel_metrics - chisel_impact_py -.-> chisel_static_test_imports - chisel_import_graph_py -.-> os - chisel_import_graph_py -.-> re - chisel_import_graph_py -.-> chisel_test_mapper - chisel_mcp_server_py -.-> json - chisel_mcp_server_py -.-> logging - chisel_mcp_server_py -.-> os - chisel_mcp_server_py -.-> re - chisel_mcp_server_py -.-> threading - chisel_mcp_server_py -.-> http_server - chisel_mcp_server_py -.-> chisel_engine - chisel_mcp_server_py -.-> chisel_next_steps - chisel_mcp_server_py -.-> chisel_schemas - chisel_mcp_server_py -.-> concurrent_futures - chisel_mcp_stdio_py -.-> asyncio - chisel_mcp_stdio_py -.-> json - chisel_mcp_stdio_py -.-> logging - chisel_mcp_stdio_py -.-> os - chisel_mcp_stdio_py -.-> sys - chisel_mcp_stdio_py -.-> chisel_engine - chisel_mcp_stdio_py -.-> chisel_mcp_server - chisel_mcp_stdio_py -.-> chisel_schemas - chisel_mcp_stdio_py -.-> mcp_server - chisel_mcp_stdio_py -.-> mcp_server_stdio - chisel_mcp_stdio_py -.-> mcp_types - chisel_metrics_py -.-> math - chisel_metrics_py -.-> collections - chisel_metrics_py -.-> datetime - chisel_metrics_py -.-> itertools - chisel_project_py -.-> os - chisel_project_py -.-> subprocess - chisel_project_py -.-> sys - chisel_project_py -.-> ctypes - chisel_project_py -.-> ctypes_wintypes - chisel_project_py -.-> msvcrt - chisel_project_py -.-> fcntl - chisel_project_py -.-> contextlib - chisel_risk_meta_py -.-> collections - chisel_risk_meta_py -.-> chisel_metrics - chisel_rwlock_py -.-> threading - chisel_rwlock_py -.-> contextlib - chisel_schemas_py -.-> chisel_llm_contract - chisel_static_test_imports_py -.-> logging - chisel_static_test_imports_py -.-> os - chisel_static_test_imports_py -.-> pathlib - chisel_static_test_imports_py -.-> chisel_ast_utils - chisel_static_test_imports_py -.-> chisel_import_graph - chisel_static_test_imports_py -.-> chisel_test_mapper - chisel_storage_py -.-> json - chisel_storage_py -.-> logging - chisel_storage_py -.-> os - chisel_storage_py -.-> sqlite3 - chisel_storage_py -.-> threading - chisel_storage_py -.-> time - chisel_storage_py -.-> datetime - chisel_storage_py -.-> pathlib - chisel_storage_py -.-> statistics - chisel_test_mapper_py -.-> ast - chisel_test_mapper_py -.-> os - chisel_test_mapper_py -.-> re - chisel_test_mapper_py -.-> pathlib - chisel_test_mapper_py -.-> chisel_ast_utils - chisel_test_mapper_py -.-> chisel_project - examples_extractors_lsp_symbol_extractor_py -.-> json - examples_extractors_lsp_symbol_extractor_py -.-> subprocess - examples_extractors_lsp_symbol_extractor_py -.-> threading - examples_extractors_lsp_symbol_extractor_py -.-> urllib_parse - examples_extractors_lsp_symbol_extractor_py -.-> chisel_ast_utils - examples_extractors_swift_syntax_extractor_py -.-> chisel_ast_utils - examples_extractors_swift_syntax_extractor_py -.-> swift_syntax - examples_extractors_swift_syntax_extractor_py -.-> swift_syntax_parser - examples_extractors_tree_sitter_js_extractor_py -.-> tree_sitter_javascript - examples_extractors_tree_sitter_js_extractor_py -.-> tree_sitter_typescript - examples_extractors_tree_sitter_js_extractor_py -.-> chisel_ast_utils - examples_extractors_tree_sitter_js_extractor_py -.-> tree_sitter - scripts_benchmark_chisel_py -.-> os - scripts_benchmark_chisel_py -.-> subprocess - scripts_benchmark_chisel_py -.-> sys - scripts_benchmark_chisel_py -.-> tempfile - scripts_benchmark_chisel_py -.-> time - scripts_benchmark_chisel_py -.-> pathlib - scripts_benchmark_chisel_py -.-> chisel_engine - scripts_check_version_py -.-> sys - scripts_check_version_py -.-> tomllib - scripts_check_version_py -.-> chisel - scripts_check_version_py -.-> pathlib - scripts_verify_static_hybrid_py -.-> subprocess - scripts_verify_static_hybrid_py -.-> sys - scripts_verify_static_hybrid_py -.-> tempfile - scripts_verify_static_hybrid_py -.-> pathlib - scripts_verify_static_hybrid_py -.-> chisel_engine - tests_conftest_py -.-> os - tests_conftest_py -.-> subprocess - tests_conftest_py -.-> pytest - tests_conftest_py -.-> chisel_storage - tests_test_ast_utils_py -.-> hashlib - tests_test_ast_utils_py -.-> textwrap - tests_test_ast_utils_py -.-> pytest - tests_test_ast_utils_py -.-> chisel_ast_utils - tests_test_bootstrap_py -.-> sys - tests_test_bootstrap_py -.-> pathlib - tests_test_bootstrap_py -.-> chisel_engine - tests_test_cli_py -.-> json - tests_test_cli_py -.-> unittest_mock - tests_test_cli_py -.-> chisel_cli - tests_test_engine_py -.-> os - tests_test_engine_py -.-> time - tests_test_engine_py -.-> pytest - tests_test_engine_py -.-> math - tests_test_engine_py -.-> subprocess - tests_test_engine_py -.-> unittest_mock - tests_test_engine_py -.-> chisel_engine - tests_test_engine_py -.-> chisel_ast_utils - tests_test_engine_sharding_py -.-> os - tests_test_engine_sharding_py -.-> pytest - tests_test_engine_sharding_py -.-> threading - tests_test_engine_sharding_py -.-> unittest_mock - tests_test_engine_sharding_py -.-> chisel_engine - tests_test_engine_sharding_py -.-> chisel_schemas - tests_test_git_analyzer_py -.-> pytest - tests_test_git_analyzer_py -.-> datetime - tests_test_git_analyzer_py -.-> chisel_git_analyzer - tests_test_git_analyzer_py -.-> chisel_metrics - tests_test_impact_py -.-> pytest - tests_test_impact_py -.-> chisel_impact - tests_test_impact_py -.-> unittest_mock - tests_test_import_graph_py -.-> pytest - tests_test_import_graph_py -.-> chisel_import_graph - tests_test_import_graph_py -.-> chisel_test_mapper - tests_test_language_frameworks_py -.-> pytest - tests_test_language_frameworks_py -.-> chisel_ast_utils - tests_test_language_frameworks_py -.-> chisel_test_mapper - tests_test_llm_contract_py -.-> chisel_llm_contract - tests_test_mcp_server_py -.-> json - tests_test_mcp_server_py -.-> urllib_request - tests_test_mcp_server_py -.-> urllib_error - tests_test_mcp_server_py -.-> pytest - tests_test_mcp_server_py -.-> chisel_mcp_server - tests_test_mcp_server_py -.-> chisel_engine - tests_test_mcp_server_py -.-> chisel_schemas - tests_test_mcp_stdio_py -.-> builtins - tests_test_mcp_stdio_py -.-> importlib - tests_test_mcp_stdio_py -.-> sys - tests_test_mcp_stdio_py -.-> pytest - tests_test_mcp_stdio_py -.-> asyncio - tests_test_mcp_stdio_py -.-> json - tests_test_mcp_stdio_py -.-> types - tests_test_mcp_stdio_py -.-> unittest - tests_test_mcp_stdio_py -.-> chisel_mcp_stdio - tests_test_mcp_stdio_py -.-> chisel_mcp_server - tests_test_mcp_stdio_py -.-> chisel - tests_test_mcp_stdio_py -.-> mcp_types - tests_test_mcp_stdio_py -.-> chisel_engine - tests_test_metrics_py -.-> pytest - tests_test_metrics_py -.-> datetime - tests_test_metrics_py -.-> chisel_metrics - tests_test_next_steps_py -.-> chisel_next_steps - tests_test_project_py -.-> os - tests_test_project_py -.-> subprocess - tests_test_project_py -.-> sys - tests_test_project_py -.-> threading - tests_test_project_py -.-> pytest - tests_test_project_py -.-> chisel_project - tests_test_rwlock_py -.-> threading - tests_test_rwlock_py -.-> time - tests_test_rwlock_py -.-> pytest - tests_test_rwlock_py -.-> chisel_rwlock - tests_test_static_hybrid_py -.-> chisel_impact - tests_test_storage_py -.-> sqlite3 - tests_test_storage_py -.-> time - tests_test_test_mapper_py -.-> os - tests_test_test_mapper_py -.-> pytest - tests_test_test_mapper_py -.-> chisel_ast_utils - tests_test_test_mapper_py -.-> chisel_test_mapper - tests_test_gitignore_filter_py -.-> os - tests_test_gitignore_filter_py -.-> pytest - tests_test_gitignore_filter_py -.-> chisel_engine - tests_test_gitignore_filter_py -.-> chisel_project - tests_test_gitignore_filter_py -.-> chisel_test_mapper - classDef external fill:#eeeeee,stroke:#888888,stroke-dasharray: 3 3 - class argparse,ast,asyncio,builtins,chisel,chisel_ast_utils,chisel_bootstrap,chisel_cli,chisel_engine,chisel_git_analyzer,chisel_impact,chisel_import_graph,chisel_llm_contract,chisel_mcp_server,chisel_mcp_stdio,chisel_metrics,chisel_next_steps,chisel_project,chisel_risk_meta,chisel_rwlock,chisel_schemas,chisel_static_test_imports,chisel_storage,chisel_test_mapper,collections,concurrent_futures,contextlib,ctypes,ctypes_wintypes,dataclasses,datetime,fcntl,functools,hashlib,http_server,importlib,itertools,json,logging,math,mcp_server,mcp_server_stdio,mcp_types,msvcrt,os,pathlib,pytest,re,sqlite3,statistics,subprocess,swift_syntax,swift_syntax_parser,sys,tempfile,textwrap,threading,time,tomllib,tree_sitter,tree_sitter_javascript,tree_sitter_typescript,types,unittest,unittest_mock,urllib_error,urllib_parse,urllib_request,uuid external -``` - -## Resolved Dependencies - -| Source | Import → Resolved | Confidence | -|--------|-------------------|------------| -| chisel/ast_utils.py | ast → ast | medium | -| chisel/ast_utils.py | dataclasses → dataclasses | medium | -| chisel/ast_utils.py | functools → functools | medium | -| chisel/ast_utils.py | hashlib → hashlib | medium | -| chisel/ast_utils.py | pathlib → pathlib | medium | -| chisel/ast_utils.py | re → re | medium | -| chisel/bootstrap.py | importlib → importlib | medium | -| chisel/bootstrap.py | name → CHISEL_BOOTSTRAP | low | -| chisel/bootstrap.py | os → os | medium | -| chisel/cli.py | argparse → argparse | medium | -| chisel/cli.py | chisel.engine → chisel.engine | medium | -| chisel/cli.py | chisel.mcp_server → chisel.mcp_server | medium | -| chisel/cli.py | chisel.mcp_stdio → chisel.mcp_stdio | medium | -| chisel/cli.py | json → json | medium | -| chisel/cli.py | os → os | medium | -| chisel/cli.py | re → re | medium | -| chisel/cli.py | subprocess → subprocess | medium | -| chisel/cli.py | sys → sys | medium | -| chisel/cli.py | tempfile → tempfile | medium | -| chisel/engine.py | chisel.ast_utils → chisel.ast_utils | medium | -| chisel/engine.py | chisel.bootstrap → chisel.bootstrap | medium | -| chisel/engine.py | chisel.git_analyzer → chisel.git_analyzer | medium | -| chisel/engine.py | chisel.impact → chisel.impact | medium | -| chisel/engine.py | chisel.import_graph → chisel.import_graph | medium | -| chisel/engine.py | chisel.metrics → chisel.metrics | medium | -| chisel/engine.py | chisel.project → chisel.project | medium | -| chisel/engine.py | chisel.risk_meta → chisel.risk_meta | medium | -| chisel/engine.py | chisel.rwlock → chisel.rwlock | medium | -| chisel/engine.py | chisel.storage → chisel.storage | medium | -| chisel/engine.py | chisel.test_mapper → chisel.test_mapper | medium | -| chisel/engine.py | concurrent.futures → concurrent.futures | medium | -| chisel/engine.py | contextlib → contextlib | medium | -| chisel/engine.py | json → json | medium | -| chisel/engine.py | logging → logging | medium | -| chisel/engine.py | os → os | medium | -| chisel/engine.py | threading → threading | medium | -| chisel/engine.py | time → time | medium | -| chisel/engine.py | tomllib → tomllib | medium | -| chisel/engine.py | uuid → uuid | medium | -| chisel/git_analyzer.py | datetime → datetime | medium | -| chisel/git_analyzer.py | re → re | medium | -| chisel/git_analyzer.py | subprocess → subprocess | medium | -| chisel/impact.py | chisel.ast_utils → chisel.ast_utils | medium | -| chisel/impact.py | chisel.metrics → chisel.metrics | medium | -| chisel/impact.py | chisel.static_test_imports → chisel.static_test_imports | medium | -| chisel/impact.py | collections → collections | medium | -| chisel/impact.py | datetime → datetime | medium | -| chisel/impact.py | os → os | medium | -| chisel/impact.py | re → re | medium | -| chisel/import_graph.py | chisel.test_mapper → chisel.test_mapper | medium | -| chisel/import_graph.py | os → os | medium | -| chisel/import_graph.py | re → re | medium | -| chisel/mcp_server.py | chisel.engine → chisel.engine | medium | -| chisel/mcp_server.py | chisel.next_steps → chisel.next_steps | medium | -| chisel/mcp_server.py | chisel.schemas → chisel.schemas | medium | -| chisel/mcp_server.py | concurrent.futures → concurrent.futures | medium | -| chisel/mcp_server.py | http.server → http.server | medium | -| chisel/mcp_server.py | json → json | medium | -| chisel/mcp_server.py | logging → logging | medium | -| chisel/mcp_server.py | os → os | medium | -| chisel/mcp_server.py | re → re | medium | -| chisel/mcp_server.py | threading → threading | medium | -| chisel/mcp_stdio.py | asyncio → asyncio | medium | -| chisel/mcp_stdio.py | chisel.engine → chisel.engine | medium | -| chisel/mcp_stdio.py | chisel.mcp_server → chisel.mcp_server | medium | -| chisel/mcp_stdio.py | chisel.schemas → chisel.schemas | medium | -| chisel/mcp_stdio.py | json → json | medium | -| chisel/mcp_stdio.py | logging → logging | medium | -| chisel/mcp_stdio.py | mcp.server → mcp.server | medium | -| chisel/mcp_stdio.py | mcp.server.stdio → mcp.server.stdio | medium | -| chisel/mcp_stdio.py | mcp.types → mcp.types | medium | -| chisel/mcp_stdio.py | os → os | medium | -| chisel/mcp_stdio.py | sys → sys | medium | -| chisel/metrics.py | collections → collections | medium | -| chisel/metrics.py | datetime → datetime | medium | -| chisel/metrics.py | itertools → itertools | medium | -| chisel/metrics.py | math → math | medium | -| chisel/project.py | contextlib → contextlib | medium | -| chisel/project.py | ctypes → ctypes | medium | -| chisel/project.py | ctypes.wintypes → ctypes.wintypes | medium | -| chisel/project.py | fcntl → fcntl | medium | -| chisel/project.py | msvcrt → msvcrt | medium | -| chisel/project.py | os → os | medium | -| chisel/project.py | subprocess → subprocess | medium | -| chisel/project.py | sys → sys | medium | -| chisel/risk_meta.py | chisel.metrics → chisel.metrics | medium | -| chisel/risk_meta.py | collections → collections | medium | -| chisel/rwlock.py | contextlib → contextlib | medium | -| chisel/rwlock.py | threading → threading | medium | -| chisel/schemas.py | chisel.llm_contract → chisel.llm_contract | medium | -| chisel/static_test_imports.py | chisel.ast_utils → chisel.ast_utils | medium | -| chisel/static_test_imports.py | chisel.import_graph → chisel.import_graph | medium | -| chisel/static_test_imports.py | chisel.test_mapper → chisel.test_mapper | medium | -| chisel/static_test_imports.py | logging → logging | medium | -| chisel/static_test_imports.py | os → os | medium | -| chisel/static_test_imports.py | pathlib → pathlib | medium | -| chisel/storage.py | datetime → datetime | medium | -| chisel/storage.py | json → json | medium | -| chisel/storage.py | logging → logging | medium | -| chisel/storage.py | os → os | medium | -| chisel/storage.py | pathlib → pathlib | medium | -| chisel/storage.py | sqlite3 → sqlite3 | medium | -| chisel/storage.py | statistics → statistics | medium | -| chisel/storage.py | threading → threading | medium | -| chisel/storage.py | time → time | medium | -| chisel/test_mapper.py | ast → ast | medium | -| chisel/test_mapper.py | chisel.ast_utils → chisel.ast_utils | medium | -| chisel/test_mapper.py | chisel.project → chisel.project | medium | -| chisel/test_mapper.py | os → os | medium | -| chisel/test_mapper.py | pathlib → pathlib | medium | -| chisel/test_mapper.py | re → re | medium | -| examples/extractors/lsp_symbol_extractor.py | chisel.ast_utils → chisel.ast_utils | medium | -| examples/extractors/lsp_symbol_extractor.py | json → json | medium | -| examples/extractors/lsp_symbol_extractor.py | subprocess → subprocess | medium | -| examples/extractors/lsp_symbol_extractor.py | threading → threading | medium | -| examples/extractors/lsp_symbol_extractor.py | urllib.parse → urllib.parse | medium | -| examples/extractors/swift_syntax_extractor.py | chisel.ast_utils → chisel.ast_utils | medium | -| examples/extractors/swift_syntax_extractor.py | swift_syntax → swift_syntax | medium | -| examples/extractors/swift_syntax_extractor.py | swift_syntax.parser → swift_syntax.parser | medium | -| examples/extractors/tree_sitter_js_extractor.py | chisel.ast_utils → chisel.ast_utils | medium | -| examples/extractors/tree_sitter_js_extractor.py | tree_sitter → tree_sitter | medium | -| examples/extractors/tree_sitter_js_extractor.py | tree_sitter_javascript → tree_sitter_javascript | medium | -| examples/extractors/tree_sitter_js_extractor.py | tree_sitter_typescript → tree_sitter_typescript | medium | -| scripts/benchmark_chisel.py | chisel.engine → chisel.engine | medium | -| scripts/benchmark_chisel.py | os → os | medium | -| scripts/benchmark_chisel.py | pathlib → pathlib | medium | -| scripts/benchmark_chisel.py | subprocess → subprocess | medium | -| scripts/benchmark_chisel.py | sys → sys | medium | -| scripts/benchmark_chisel.py | tempfile → tempfile | medium | -| scripts/benchmark_chisel.py | time → time | medium | -| scripts/check_version.py | chisel → chisel | medium | -| scripts/check_version.py | pathlib → pathlib | medium | -| scripts/check_version.py | sys → sys | medium | -| scripts/check_version.py | tomllib → tomllib | medium | -| scripts/verify_static_hybrid.py | chisel.engine → chisel.engine | medium | -| scripts/verify_static_hybrid.py | pathlib → pathlib | medium | -| scripts/verify_static_hybrid.py | subprocess → subprocess | medium | -| scripts/verify_static_hybrid.py | sys → sys | medium | -| scripts/verify_static_hybrid.py | tempfile → tempfile | medium | -| tests/conftest.py | chisel.storage → chisel.storage | medium | -| tests/conftest.py | os → os | medium | -| tests/conftest.py | pytest → pytest | medium | -| tests/conftest.py | subprocess → subprocess | medium | -| tests/test_ast_utils.py | chisel.ast_utils → chisel.ast_utils | medium | -| tests/test_ast_utils.py | hashlib → hashlib | medium | -| tests/test_ast_utils.py | pytest → pytest | medium | -| tests/test_ast_utils.py | textwrap → textwrap | medium | -| tests/test_bootstrap.py | chisel.engine → chisel.engine | medium | -| tests/test_bootstrap.py | pathlib → pathlib | medium | -| tests/test_bootstrap.py | sys → sys | medium | -| tests/test_cli.py | chisel.cli → chisel.cli | medium | -| tests/test_cli.py | json → json | medium | -| tests/test_cli.py | unittest.mock → unittest.mock | medium | -| tests/test_engine.py | chisel.ast_utils → chisel.ast_utils | medium | -| tests/test_engine.py | chisel.engine → chisel.engine | medium | -| tests/test_engine.py | math → math | medium | -| tests/test_engine.py | os → os | medium | -| tests/test_engine.py | pytest → pytest | medium | -| tests/test_engine.py | subprocess → subprocess | medium | -| tests/test_engine.py | time → time | medium | -| tests/test_engine.py | unittest.mock → unittest.mock | medium | -| tests/test_engine_sharding.py | chisel.engine → chisel.engine | medium | -| tests/test_engine_sharding.py | chisel.schemas → chisel.schemas | medium | -| tests/test_engine_sharding.py | os → os | medium | -| tests/test_engine_sharding.py | pytest → pytest | medium | -| tests/test_engine_sharding.py | threading → threading | medium | -| tests/test_engine_sharding.py | unittest.mock → unittest.mock | medium | -| tests/test_git_analyzer.py | chisel.git_analyzer → chisel.git_analyzer | medium | -| tests/test_git_analyzer.py | chisel.metrics → chisel.metrics | medium | -| tests/test_git_analyzer.py | datetime → datetime | medium | -| tests/test_git_analyzer.py | pytest → pytest | medium | -| tests/test_gitignore_filter.py | chisel.engine → chisel.engine | medium | -| tests/test_gitignore_filter.py | chisel.project → chisel.project | medium | -| tests/test_gitignore_filter.py | chisel.test_mapper → chisel.test_mapper | medium | -| tests/test_gitignore_filter.py | os → os | medium | -| tests/test_gitignore_filter.py | pytest → pytest | medium | -| tests/test_impact.py | chisel.impact → chisel.impact | medium | -| tests/test_impact.py | pytest → pytest | medium | -| tests/test_impact.py | unittest.mock → unittest.mock | medium | -| tests/test_import_graph.py | chisel.import_graph → chisel.import_graph | medium | -| tests/test_import_graph.py | chisel.test_mapper → chisel.test_mapper | medium | -| tests/test_import_graph.py | pytest → pytest | medium | -| tests/test_language_frameworks.py | chisel.ast_utils → chisel.ast_utils | medium | -| tests/test_language_frameworks.py | chisel.test_mapper → chisel.test_mapper | medium | -| tests/test_language_frameworks.py | pytest → pytest | medium | -| tests/test_llm_contract.py | chisel.llm_contract → chisel.llm_contract | medium | -| tests/test_mcp_server.py | chisel.engine → chisel.engine | medium | -| tests/test_mcp_server.py | chisel.mcp_server → chisel.mcp_server | medium | -| tests/test_mcp_server.py | chisel.schemas → chisel.schemas | medium | -| tests/test_mcp_server.py | json → json | medium | -| tests/test_mcp_server.py | pytest → pytest | medium | -| tests/test_mcp_server.py | urllib.error → urllib.error | medium | -| tests/test_mcp_server.py | urllib.request → urllib.request | medium | -| tests/test_mcp_stdio.py | "chisel.mcp_stdio" → chisel.mcp_stdio | low | -| tests/test_mcp_stdio.py | asyncio → asyncio | medium | -| tests/test_mcp_stdio.py | builtins → builtins | medium | -| tests/test_mcp_stdio.py | chisel → chisel | medium | -| tests/test_mcp_stdio.py | chisel.engine → chisel.engine | medium | -| tests/test_mcp_stdio.py | chisel.mcp_server → chisel.mcp_server | medium | -| tests/test_mcp_stdio.py | chisel.mcp_stdio → chisel.mcp_stdio | medium | -| tests/test_mcp_stdio.py | importlib → importlib | medium | -| tests/test_mcp_stdio.py | json → json | medium | -| tests/test_mcp_stdio.py | mcp.types → mcp.types | medium | -| tests/test_mcp_stdio.py | pytest → pytest | medium | -| tests/test_mcp_stdio.py | sys → sys | medium | -| tests/test_mcp_stdio.py | types → types | medium | -| tests/test_mcp_stdio.py | unittest → unittest | medium | -| tests/test_metrics.py | chisel.metrics → chisel.metrics | medium | -| tests/test_metrics.py | datetime → datetime | medium | -| tests/test_metrics.py | pytest → pytest | medium | -| tests/test_next_steps.py | chisel.next_steps → chisel.next_steps | medium | -| tests/test_project.py | chisel.project → chisel.project | medium | -| tests/test_project.py | os → os | medium | -| tests/test_project.py | pytest → pytest | medium | -| tests/test_project.py | subprocess → subprocess | medium | -| tests/test_project.py | sys → sys | medium | -| tests/test_project.py | threading → threading | medium | -| tests/test_rwlock.py | chisel.rwlock → chisel.rwlock | medium | -| tests/test_rwlock.py | pytest → pytest | medium | -| tests/test_rwlock.py | threading → threading | medium | -| tests/test_rwlock.py | time → time | medium | -| tests/test_static_hybrid.py | chisel.impact → chisel.impact | medium | -| tests/test_storage.py | sqlite3 → sqlite3 | medium | -| tests/test_storage.py | time → time | medium | -| tests/test_test_mapper.py | chisel.ast_utils → chisel.ast_utils | medium | -| tests/test_test_mapper.py | chisel.test_mapper → chisel.test_mapper | medium | -| tests/test_test_mapper.py | os → os | medium | -| tests/test_test_mapper.py | pytest → pytest | medium | - -## Circular Dependencies - -✅ No circular dependencies detected in the current dependency graph. - -## ACS Risk Snapshot - -**Scored edges**: 3 | **avg_confidence**: 0.05 | **low-confidence**: 3 (threshold 0.65) -**Top risk reasons**: base:low:3, conditional_context:3, tag:control_flow:3, tag:ternary:3 -**Sample low-confidence edges**: - 1. Base low (0.05). opaque dynamic expression; conditional context; control_flow semantics; ternary semantics; trace: ControlFlowDetector=if; TernaryDetector=, "").strip() - if not name: - return. Recommendation: Opaque or high-complexity dynamic resolution — refactor to static import or introduce explicit runtime guard; current edge is unsuitable for static analysis tooling. - 2. Base low (0.05). creative registry_map (LDSI/CDIA); conditional context; control_flow semantics; ternary semantics; trace: ControlFlowDetector=try; TernaryDetector=importlib.import_module("chisel.mcp_stdio") -. Recommendation: Extremely creative dynamic (tagged template / registry map / multi-cond feature / call-produced path) — CDIA detectors fired; supply static mappings or treat as runtime-only for dependency graphs. - 3. Base low (0.05). creative registry_map (LDSI/CDIA); conditional context; control_flow semantics; ternary semantics; trace: ControlFlowDetector=try; TernaryDetector=importlib.import_module("chisel.mcp_stdio") -. Recommendation: Extremely creative dynamic (tagged template / registry map / multi-cond feature / call-produced path) — CDIA detectors fired; supply static mappings or treat as runtime-only for dependency graphs. - -## Reverse Dependencies ("Who depends on me") - -**Targets with dependents**: 2 | **Total reverse edges**: 2 - -**High-impact modules (most reverse dependents)**: -- `CHISEL_BOOTSTRAP` ← 1 files depend on it (e.g. chisel/bootstrap.py) -- `chisel.mcp_stdio` ← 1 files depend on it (e.g. tests/test_mcp_stdio.py) - -## Barrel Expansions - -**Total via-barrel imports**: 0 -**Files using barrel re-exports**: 0 -**Max observed barrel depth**: 0 - -(No barrel usage detected in this run) - -## Conditional & Dynamic Intelligence - -**Conditional imports detected**: 3 -**Dynamic imports detected**: 3 - -Sample conditional imports (fragile / feature-flagged paths): -- `chisel/bootstrap.py` → `name` (ctx: ) -- `tests/test_mcp_stdio.py` → `"chisel.mcp_stdio"` (ctx: ) -- `tests/test_mcp_stdio.py` → `"chisel.mcp_stdio"` (ctx: ) -Sample dynamic imports (runtime / template-driven): -- `chisel/bootstrap.py` → `name` (type: expression) -- `tests/test_mcp_stdio.py` → `"chisel.mcp_stdio"` (type: static) -- `tests/test_mcp_stdio.py` → `"chisel.mcp_stdio"` (type: static) diff --git a/monitored_paths.txt b/monitored_paths.txt deleted file mode 100644 index 0a6f9cd..0000000 --- a/monitored_paths.txt +++ /dev/null @@ -1 +0,0 @@ -/home/aron/Documents/coding_projects/Chisel diff --git a/pending_updates.md b/pending_updates.md deleted file mode 100644 index e69de29..0000000 diff --git a/plan.md b/plan.md index f85a304..de9c68e 100644 --- a/plan.md +++ b/plan.md @@ -1,297 +1,58 @@ -# Chisel Rust rewrite +# Chisel -Status: specification. Execution is `plan/rust_finish.md`. The port is not finished until that file's scale bar and the cutover check below are both true. +The product is the Rust crate in `src/`. `chisel` and `chisel-mcp` are two binaries over that crate. Storage is the snapshot `.chisel/store`. There is no Python package in this tree. -This document is the long-term rewrite of Chisel into Rust. The reason for the rewrite is faster checks and faster tool calls for agents. Calendar time for the port is unlimited. Build pace stays slow. +`PLAN_NEXT.md` is not part of this repository. It was a Python-era backlog name. Do not look for it, and do not treat a missing file as unfinished work. Cross-shard test edges and `flakiness_trend` are implemented in the Rust engine. `AGENTS.md` is the call contract. -`PLAN_NEXT.md` stays the Python feature backlog (cross-shard edges, flakiness trends). Those items are not part of the parity port. A speed improvement may land only after the feature it touches already matches Python. +Checked-in result checks are the JSON files under `tests/fixtures/oracle`. They were frozen from the old Python tool. New behaviour is covered by the Rust tests, including a Cargo-layout fixture at `tests/fixtures/rust_crate`. -The Python tree remains the oracle until a feature matches. Nothing in `chisel/` is deleted as part of standing this plan up. +## What the binary does -## Goal +Chisel links tests to code units, mixes in git churn and ownership, and answers `suggest-tests`, `diff-impact`, `test-gaps`, and `risk-map`. Scores are heuristics. Read `_meta.uniform_components` before trusting a composite risk score. -Agents spend less time waiting on Chisel. A check (`diff_impact`, `suggest_tests`, `risk_map`, `triage`, `test_gaps`, and the other read tools) and a refresh (`analyze`, `update`) should return sooner than the Python process does, with the same answers. +Status values: `no_data`, `no_changes`, `no_edges`, `git_error`, `stale_db`, `error`, `unsupported`, `auto_queued`. `no_data` and `stale_db` exit 0. `error` and `git_error` exit 1. -One static binary, `chisel`, plus the `chisel-mcp` name for the same binary. Same 26 MCP tools, same 28 CLI subcommands, same status envelopes, same risk math. The crate list is empty. Persistence, JSON, HTTP, stdio MCP, and source extraction are first-party modules. +## Layout -## How each feature is built - -Every feature goes through two gates, in order. The second gate does not start until the first has passed. - -1. **Match.** Output matches Python 0.15.1 on the fixtures: same ids, scores, statuses, labels, and exit codes. Key order in JSON may differ. -2. **Improve.** Make that feature faster for an agent without changing the matched output. Record what got faster and how it was measured. If an improvement would change a result, it waits. - -Improvements that serve the goal, once the matching feature is green: - -- Process startup. A static binary instead of a Python interpreter, for one-shot CLI calls agents spawn themselves. -- Warm MCP calls. The stdio and HTTP servers stay up. Time is the tool body, not import time. -- The snapshot store. Typed in-memory maps instead of parsing SQL on every read. -- Fewer `git` process spawns for the same porcelain result: batch where the output is identical, reuse a parse, skip work `update` already knows is unchanged. -- Scanners and edge building. Same units and same edges, less CPU between files. -- Parallel `git blame` and other subprocess work only where the merged result matches the serial one. - -`git log -L` and `git blame` still shell out to system `git`. Their runtime is a floor for cold history work. The improvement there is calling them less often and overlapping independent calls, not reimplementing git. Read-tool latency on an already-built snapshot is where agents feel the port first. - -## Zero-dependency rule - -This rewrite is compliant with a complete zero-dep rule. The equivalents of `sqlite3`, `ast`, `http.server`, and `json` are part of Chisel, not libraries Chisel depends on. Zero-dep limits what the program pulls in. It does not limit how much first-party code we write. - -`Cargo.toml` has `dependencies = []` and `dev-dependencies = []` for the life of the port. CI fails the build if either table is non-empty. There is no `build.rs`. There is no vendored third-party C or Rust. - -### Allowed - -| Piece | Why it is still zero-dep | +| Path | Role | |---|---| -| Rust standard library | Same role as the Python standard library. `std::fs`, `std::net`, `std::process`, `std::thread`, `std::io`, `std::env`. | -| First-party JSON module | Replaces `json`. Written and reviewed in this repo. | -| First-party HTTP/1.1 server | Replaces `http.server`. Uses `std::net::TcpListener` only. | -| First-party stdio MCP framing | Replaces the optional `mcp` package and any Rust MCP SDK. The MCP spec is a document, not a dependency. | -| First-party snapshot store | Replaces `sqlite3` for the queries Chisel actually runs. Not a copy of SQLite. | -| First-party language scanners | Replace `ast` for Python and the regex extractors for the other eleven languages. | -| First-party argv parser | Replaces argparse. Twenty-eight subcommands, no `clap`. | -| `extern "C"` for `flock` and `LockFileEx` | The OS call, declared in our code. Rust `std` on Unix already links the system libc, and on Windows already links `kernel32`. We do not add the `libc` crate or the `windows-sys` crate. | -| System `git` via `std::process` | Same runtime requirement as today. Not compiled in. Not a crate. | -| Optional extractor program named by an environment variable | User-supplied process speaking JSON over stdio. Not linked, not shipped. Same posture as `CHISEL_BOOTSTRAP` living outside the wheel. | - -### Forbidden - -These would break the rule even if they are convenient or "small": - -- Any crates.io crate, including dev-only crates (`serde`, `regex`, `libc`, `nix`, `clap`, `tokio`, `rmcp`, `rusqlite`, `tempfile`, `proptest`, `criterion`). -- A vendored SQLite amalgamation, tree-sitter grammar, or any other third-party source dropped into the tree. -- `build.rs` that compiles C or fetches code. -- PyO3, an embedded CPython, or calling Python `ast` at runtime. -- In-process dynamic libraries as the plugin mechanism. -- A PyPI runtime dependency for the stdio server. Stdio MCP moves into the core binary, which is stricter than today's optional `[mcp]` extra. - -### Trusted base - -What remains trusted, on purpose: - -- The pinned Rust compiler and its standard library (the toolchain, analogous to installing CPython). -- The operating system C library that `std` already links, used only for the two lock calls above. -- The `git` executable the user already has. - -A snapshot store we can read in one sitting is the supply-chain trade. We own its crash behavior, and the tests for that behavior are part of the store phase, not a follow-up. - -## What stays the same - -- Tool names and JSON shapes from `chisel/schemas.py`, including `next_steps`. -- Status values: `no_data`, `no_changes`, `no_edges`, `git_error`, `stale_db`, plus the existing tool-specific statuses. -- CLI subcommands and exit codes. `error` and `git_error` exit 1. `chisel run` forwards the test runner's exit code. -- Binary names: `chisel` and `chisel-mcp` (the second is the same binary, or a symlink to it). -- Risk weights in `chisel/risk_meta.py` (`churn` 0.35, `coupling` 0.25, `coverage_gap` 0.15, `coverage_depth` 0.10, `author_concentration` 0.10, `test_instability` 0.05), plus `hidden_risk_factor` (max 0.15) and `new_file_boost` (0.5). One function applies them. -- Banker's rounding wherever Python calls `round`. Rust `f64::round` is not a substitute. Scores keep today's 4-place and 6-place rounding. -- Code-unit ids: `{relative_path}:{name}:{unit_type}`. -- Orphan test edges survive code-unit deletion, because `stale_tests` reads them. -- Environment variables: `CHISEL_PROJECT_DIR`, `CHISEL_STORAGE_DIR` (reject `:memory:` and `file:` URIs), `CHISEL_SHARDS`, `CHISEL_INCLUDE_IGNORED`. -- Git invocations: `log --numstat`, `blame --porcelain`, `log -L`, `merge-base`, `diff --name-only`, `ls-files`. Numstat fields accept digits or `-`. Non-UTF-8 output is replaced, not fatal. -- Thresholds: inline `auto_update` skips above 50 changed files. `analyze` with `force` on more than 300 code files queues a background job. -- Advisory file locks (the six tools) stay advisory records in the store. They do not lock file contents on disk. -- Shards: one store per shard, queries aggregate, writes route by longest path prefix. Cross-shard test edges stay a known gap. - -## What changes - -- The on-disk database. Rust writes a snapshot file. It does not open `chisel.db`. Existing projects migrate by running `chisel analyze` again. The Python file is left in place and ignored. -- `CHISEL_BOOTSTRAP` as a Python import path. The replacement is `CHISEL_EXTRACTOR`, an executable. The core never imports a language runtime. -- Thread-local "current shard" mutation. A shard is an explicit handle. -- `optimize_storage` rewrites the snapshot. There is no WAL to checkpoint. The tool stays, so agents keep a stable name. -- Stdio MCP no longer needs a separate install extra. -- Agent-facing latency, after parity, as described under "How each feature is built." - -## Repository layout - -One crate, added beside the Python package. No workspace members. - -```text -rust-toolchain.toml pinned compiler -Cargo.toml [dependencies] empty, [dev-dependencies] empty -src/lib.rs -src/main.rs argv dispatch -src/json.rs -src/http.rs -src/mcp.rs stdio Content-Length JSON-RPC -src/lock.rs flock / LockFileEx -src/store.rs snapshot, 17 record sets, indexes -src/extract/mod.rs -src/extract/python.rs -src/extract/scanners.rs the other eleven languages -src/git.rs -src/impact.rs pure formulas and ranking -src/engine.rs 26 tools, jobs, shards -src/cli.rs -src/bin/chisel.rs -tests/ cargo tests only, std temp dirs -``` - -`src/` here means the crate root once the crate exists. The Python package `chisel/` is untouched until cutover. - -## Store - -Chisel's storage is 17 record sets and a fixed list of lookups (by id, by file path, by pair, plus joins from edges to units). The access rule is already `ProcessLock`: readers hold a shared lock, a writer holds an exclusive lock, and those modes do not overlap. The store relies on that rule. It does not implement SQL, a page cache, or a write-ahead log. - -### Files - -Inside the storage directory (project `.chisel/` by default, or `CHISEL_STORAGE_DIR`): - -- `chisel.lock`. Same flock protocol as `project.py`. Shared for reads, exclusive for writes. A Python process and a Rust process exclude each other if they share a storage directory. -- `store`. The published snapshot. -- `store.tmp`. The file being written. Deleted on the next open if it is still present. - -Shard data lives under `shards//` with its own `store` and `chisel.lock`. - -### Publish - -1. Mutate in-memory maps for the duration of the tool call. -2. Serialize the live records to `store.tmp`. -3. `fsync` the temp file. -4. Atomically rename `store.tmp` onto `store`. -5. `fsync` the directory. -6. Release the exclusive lock. - -A crash before the rename leaves the previous `store` in place. One flush per tool call, including `chisel run`, so recording many test results does not rewrite once per row. +| `src/main.rs` | `chisel` binary. Debug-only durability helpers stay here. | +| `src/bin/chisel-mcp.rs` | `chisel-mcp` binary. Same entry as `chisel serve-mcp`. | +| `src/cli.rs` | Argv parser and command dispatch. | +| `src/engine` | Analyze, update, queries, advisory locks. | +| `src/extract` | Built-in scanners. One test-file classifier lives in `src/engine/mod.rs` (`looks_like_test`). | +| `src/store.rs` | Snapshot read and write. Temp file, fsync, rename. | +| `src/git.rs` | `git` subprocess. | +| `src/impact.rs` | Risk math. | +| `src/json.rs` | JSON parser and serializer. | +| `src/http.rs` | `GET /health`, `GET /tools`, `POST /call`. No `/mcp` path. | +| `src/mcp.rs` | Stdio Content-Length JSON-RPC. | +| `src/fswatch.rs` | `inotify`, `kqueue`, or `ReadDirectoryChangesW`, then the caller checks git. | +| `src/lock.rs` | `flock` on Unix, `LockFileEx` on Windows. | +| `src/schemas.rs` | The 26 tool names and input schemas. | -Readers load `store` under the shared lock. Indexes are hash maps for the primary key and the secondary keys `storage.py` already uses (`file_path`, `code_id`, `test_id`, import target, co-change partner). Joins are lookups in those maps. +## Test discovery -Deleted code units do not delete edges that point at them. +`looks_like_test` is the only path classifier. The hint printed when analyze finds no tests uses that same pattern list. -`mtime` on `file_hashes` is informational. Content change is decided by SHA-256. A mismatch with a Python-written float must not, by itself, force a re-extract. Rust does not read the Python database, so this only matters if a future import path copies those rows. +Cargo integration tests are `.rs` files under the top-level `tests/` directory, including `tests/a.rs`. Rust functions marked `#[test]`, `#[tokio::test]`, `#[rstest]`, or `#[test_case]` are test units even when the surrounding file is source. A source file that also contains `#[test]` stays a code file for `risk-map` and `test-gaps`. The test functions themselves are not reported as untested code. -### Record sets +## Dependency rule -Match the current tables, without SQL types: +`Cargo.toml` keeps `[dependencies]` and `[dev-dependencies]` empty. There is no `build.rs`. CI fails the build if either table is non-empty. The toolchain is pinned in `rust-toolchain.toml` and in `.github/workflows/ci.yml`. -`code_units`, `test_units`, `test_edges`, `commits`, `commit_files`, `blame_cache`, `co_changes`, `churn_stats`, `file_hashes`, `import_edges` (with `confidence`), `branch_co_changes`, `test_results`, `meta`, `file_locks`, `bg_jobs`, `job_events`, `static_test_imports`. +Allowed: the Rust standard library, first-party modules in `src/`, `extern "C"` for the two lock calls, system `git`, and an optional extractor program named by `CHISEL_EXTRACTOR` or by `.chisel/extensions.toml` when the process was started with `--allow-repo-extractor`. -The snapshot header carries a magic string, a format version, and a generation counter. Format version 1 is the 0.15.1 record set. Later changes bump the version and migrate inside this module. +Not allowed: crates.io crates, a vendored SQLite amalgamation or tree-sitter grammar, `build.rs`, or an in-process plugin loader. -### Tests that define "durable" +## CI -- Two processes: a writer blocks until the reader's shared lock drops, and the reader never observes a torn file. -- `kill -9` of a writer after `store.tmp` exists and before the rename. The next open still loads the previous snapshot and removes the temp file. -- Round-trip every record set, including an edge whose code id has been deleted. -- Reject `:memory:` and `file:` storage directories. - -## JSON - -A module for the values this program reads and writes. - -- Objects, arrays, strings, numbers, booleans, null. -- Duplicate object keys: last value wins, matching Python `json`. -- Integers that fit in `i64` stay integers. Other numbers are `f64`. -- Reject `NaN`, `Infinity`, and `-Infinity` on parse and on write. -- Standard string escapes, explicit UTF-8, a nesting depth cap, a byte-size cap. -- Emit UTF-8. Stdio tool payloads use the same pretty-print the Python server uses (`indent=2`). HTTP responses may be compact, matching `json.dumps` without indent in `mcp_server.py`. - -Acceptance: fixtures taken from real tool results and from `schemas.py` parse and re-serialize without changing meaning. A corpus of hostile inputs (deep nesting, truncated strings, bad UTF-8) returns errors and does not panic. - -## HTTP - -`std::net::TcpListener`, default `127.0.0.1:8377`, bounded thread pool (32 workers, queue depth in line with today's 128). - -- `GET /tools` returns the tool schema list. -- `GET /health` returns `{"status":"ok"}`. -- `POST /call` reads a `Content-Length` body, dispatches, and writes JSON. -- Unknown paths return 404 JSON. Bad JSON and bad arguments return 400. Unknown tool returns 404. Uncaught tool failure returns 500. -- One request per connection, matching `BaseHTTPRequestHandler`'s default HTTP/1.0 behavior. -- Body size cap. No TLS, no HTTP/2, no chunked transfer in version 1 of this server. Clients that speak to `chisel serve` today send `Content-Length`. - -## Stdio MCP - -Frame messages the way a language server does: - -```text -Content-Length: \r\n -\r\n - -``` - -Implement `initialize`, the initialized notification, `tools/list`, and `tools/call`. `tools/call` runs the engine on a worker thread and returns one text content item whose text is `{"result": ..., "next_steps": ...}` when hints exist. Log to stderr only. Stdout is the protocol stream. - -Do not implement resources, prompts, sampling, OAuth, or server tasks. Unknown methods return a JSON-RPC method-not-found error. - -Acceptance: a small stdio script drives `initialize`, `tools/list` (26 tools), and one `tools/call`, and the payload matches the Python server for the same engine state. - -## Extraction - -Output type is the current `CodeUnit`: file, name, unit type, line start, line end. - -### Python - -Target the `ast` walk in `_extract_python_ast`, not a full Python compiler. - -- Track strings (including triple-quoted and f-strings), comments, and bracket depth. -- Recognize `def`, `async def`, and `class` only at bracket depth zero, at the start of a logical line. -- Methods are named `ClassName.method` when the function is a direct member of the class. A function nested inside a method keeps its own name. That matches `parent_map` in the Python extractor. -- Start line is the `def` or `class` line, not the decorator line. -- End line is the last line of the block. -- On a scan failure, use the indent fallback that `_extract_python_regex` already implements. - -Acceptance: the cases in `tests/test_ast_utils.py` and the language fixtures produce the same units as Python 0.15.1. - -### Other languages - -Hand-written scanners for JavaScript, TypeScript, Go, Rust, C#, Java, Kotlin, C, C++, Swift, PHP, Ruby, and Dart, covering the same unit kinds the current extractors emit. Share one brace matcher that ignores braces inside strings and comments. - -The JS/TS dependency scanner covers static `import` / `require`, destructuring, ESM defaults, and the dynamic forms (`variable`, template, concat, conditional, `eval`) with the current confidence values. The lookbehind in the assignment scanner is written as "previous character is not an identifier character." No regex crate. - -Test-framework detection stays as it is: pytest, Jest/Mocha/Vitest, Playwright, Go test, Rust `#[test]`, xUnit/NUnit/MSTest, JUnit including parameterized forms, XCTest and Swift Testing, PHPUnit, RSpec, Minitest, gtest, Dart test. - -`CHISEL_EXTRACTOR`, when set, is run as a subprocess for a file. It receives the path and language on stdin as JSON and returns a JSON list of units. A non-zero exit or bad JSON falls back to the built-in scanner and records a warning on the analyze result. The core binary does not load plugins. - -## Git - -`git.rs` shells out with `std::process::Command`. Parsers cover the porcelain listed above. Lossy UTF-8 matches today's `errors="replace"`. Missing `git`, a non-repo, and a failed diff return `git_error` with `cwd`, `project_dir`, and a hint. They do not return an empty list. - -## Engine behavior worth pinning - -- Read tools take the shared process lock. Writes, `analyze`, `update`, and `record_result` take the exclusive lock. -- `update` reprocesses files whose SHA-256 changed and skips the edge rebuild when nothing changed (`edge_rebuild_skipped: true`). -- `analyze` and test discovery skip gitignored trees unless `CHISEL_INCLUDE_IGNORED=1`. Non-git projects are unfiltered. -- Per-function `git log -L` runs only for changed files, and is skipped entirely above 2000 code files. File-level churn still runs. -- Commits that touch more than 50 files are skipped for co-change. -- Single-author history halves the adaptive co-change threshold, with a floor of 1. -- `diff_impact` is branch-aware unless `ref` is set. Untracked code files are included. `working_tree` uses the static import overlay. -- `suggest_tests` source labels stay `direct`, `co_change`, `import_graph`, `static_require`, `hybrid`, `fallback`, `working_tree`. -- Background jobs are one cooperative thread per process. Cancel is a flag checked between files. `job_status` reads the store. -- `auto_update` runs an inline `update` when the store is stale, and records `auto_update_skip_reason` when a job is running or more than 50 files changed. - -## Phases - -Each phase ends when its match checks pass against the Python oracle on the language fixtures and on a small git fixture. The speed pass for that phase comes after the match, and it must keep the same output. Do not start the next phase by deleting Python. Do not skip a match to chase a timing number. - -1. **Toolchain and guard.** Crate with an empty dependency table, pinned toolchain, CI check that both dependency tables are empty, `cargo test` that builds. No behavior yet. -2. **JSON.** Parser, serializer, hostile-input tests, fixtures from real payloads. -3. **Lock and store.** Snapshot publish, the record sets, the durability tests, storage-dir rejection. -4. **Extractors.** Python scanner plus the other languages, diffed against current unit lists. Dynamic-require confidence included. -5. **Git parsers.** Numstat, blame, `log -L`, encoding replacement, `git_error` shape. -6. **Pure impact.** Churn, coupling threshold, quantize boundaries, `compose_risk_score`, proximity weights. Golden JSON for the banker's-rounding edges (values at `n + 0.5` in the last kept digit). -7. **Engine writes.** `analyze`, `update`, edges, `record_result`, incremental hashes, `chisel run` for pytest and Jest output. -8. **Engine reads.** The remaining tools, including `triage`, `working_tree`, and `auto_update` skip reasons. -9. **CLI.** All 28 subcommands and exit codes. -10. **HTTP, then stdio MCP.** Schema list parity and one live tool call on each transport. -11. **Jobs, shards, advisory locks.** Thread-pool behavior, unknown shard error, two shards aggregated on an unscoped read. -12. **Cutover.** Release binaries (Linux musl, macOS arm64, macOS x64, Windows). `chisel-mcp` on `PATH` is enough for current MCP configs. Document that 0.15 databases are not read and that `chisel analyze` rebuilds the snapshot. Only after this phase may the Python package become a thin launcher, and only if we still want a `pip` install path. The launcher must exec the binary and must not reimplement tools. +`.github/workflows/ci.yml` runs `cargo test` on Linux, macOS, and Windows. Release builds still cover Linux musl, macOS arm64, macOS x64, and Windows. `chisel --version` prints the package version from `Cargo.toml`. ## Out of scope -- Cross-shard test-edge routing. -- Flakiness trends over weeks. -- A general regex engine, a general SQL engine, or a full Python compiler. -- Tree-sitter, LSP, TLS, HTTP/2. +- Reading `chisel.db`, `:memory:`, or `file:` storage paths. +- A general regex engine, a general SQL engine, or a full language compiler. +- Tree-sitter, LSP, TLS, or HTTP/2. - Replacing `git`. -- Reading `chisel.db`. -- Changing risk weights, tool names, or id format in the same effort as the match. -- A speed change that alters a matched result. That is a later, separate decision. - -## Cutover check - -The port is ready to replace the Python entry points when all of the following are true: - -- `cargo metadata` shows no dependencies. -- The durability tests pass. -- Extractor output matches on the current fixtures. -- Risk golden files match, including banker's rounding. -- `tools/list` advertises the same 26 tools as `schemas.py`. -- A fixture repo produces the same `diff_impact`, `suggest_tests`, and `risk_map` JSON as Python 0.15.1, ignoring key order. -- `chisel run -- pytest` records results and forwards the pytest exit code. +- Changing risk weights in the same change as a discovery fix. diff --git a/src/bin/chisel-mcp.rs b/src/bin/chisel-mcp.rs new file mode 100644 index 0000000..0ca746b --- /dev/null +++ b/src/bin/chisel-mcp.rs @@ -0,0 +1,10 @@ +fn main() { + let code = match chisel::cli::entry() { + Ok(c) => c, + Err(e) => { + eprintln!("Error: {e}"); + 1 + } + }; + std::process::exit(code); +} diff --git a/src/cli.rs b/src/cli.rs index 66817b1..fd26cd1 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -576,9 +576,31 @@ pub fn argv_is_mcp_binary(argv0: &str) -> bool { std::path::Path::new(argv0).file_stem().and_then(|s| s.to_str()) == Some("chisel-mcp") } +pub fn version() { + println!("chisel {}", env!("CARGO_PKG_VERSION")); +} + +pub fn entry() -> Result { + let mut argv: Vec = env::args().skip(1).collect(); + if argv.iter().any(|a| a == "--version") { + version(); + return Ok(0); + } + let argv0 = env::args().next().unwrap_or_default(); + if argv_is_mcp_binary(&argv0) { + argv.insert(0, "serve-mcp".into()); + } + if argv.is_empty() || argv.iter().any(|a| a == "-h" || a == "--help") && argv.len() == 1 { + help(); + return Ok(0); + } + let args = parse(&argv)?; + run(&args) +} + pub fn help() { println!("Chisel — test impact analysis and code intelligence"); - println!("usage: chisel [--json] [--project-dir DIR] [--storage-dir DIR] ..."); + println!("usage: chisel [--json] [--version] [--project-dir DIR] [--storage-dir DIR] ..."); println!("commands: analyze, impact, suggest-tests, churn, ownership, coupling, risk-map,"); println!(" stale-tests, history, who-reviews, diff-impact, update, test-gaps, record-result,"); println!(" run, watch, stats, start-job, job-status, cancel-job, triage, serve, serve-mcp,"); diff --git a/src/engine/analyze.rs b/src/engine/analyze.rs index 39df63a..f970f69 100644 --- a/src/engine/analyze.rs +++ b/src/engine/analyze.rs @@ -113,7 +113,7 @@ impl Engine { } } let score = impact::churn_score(&dates, now); - churn_rows.push(churn_row(rel, "", count, authors.len() as i64, ins, del, &last, score)); + churn_rows.push(churn_row(rel, "", ChurnCounts { commits: count, authors: authors.len() as i64, insertions: ins, deletions: del }, &last, score)); } if files.len() <= 2000 { let tasks = unit_log_tasks(&files, &units_by_file); @@ -126,7 +126,7 @@ impl Engine { let func_dates: Vec = func_commits.iter().map(|c| c.date.clone()).collect(); let func_score = impact::churn_score(&func_dates, now); let func_count = func_dates.iter().filter(|d| impact::parse_iso_unix(d).is_some()).count() as i64; - churn_rows.push(churn_row(&task.0, &task.1, func_count, 1, 0, 0, "", func_score)); + churn_rows.push(churn_row(&task.0, &task.1, ChurnCounts { commits: func_count, authors: 1, insertions: 0, deletions: 0 }, "", func_score)); } } self.store.doc.insert("churn", Value::Array(churn_rows)); @@ -354,7 +354,7 @@ impl Engine { } } let score = impact::churn_score(&dates, now); - churn_rows.push(churn_row(rel, "", count, authors.len() as i64, ins, del, &last, score)); + churn_rows.push(churn_row(rel, "", ChurnCounts { commits: count, authors: authors.len() as i64, insertions: ins, deletions: del }, &last, score)); } if !skip_unit { let stored_units = self.store.array("code_units"); @@ -375,7 +375,7 @@ impl Engine { let func_dates: Vec = func_commits.iter().map(|c| c.date.clone()).collect(); let func_score = impact::churn_score(&func_dates, now); let func_count = func_dates.iter().filter(|d| impact::parse_iso_unix(d).is_some()).count() as i64; - churn_rows.push(churn_row(&task.0, &task.1, func_count, 1, 0, 0, "", func_score)); + churn_rows.push(churn_row(&task.0, &task.1, ChurnCounts { commits: func_count, authors: 1, insertions: 0, deletions: 0 }, "", func_score)); } } self.store.doc.insert("churn", Value::Array(churn_rows)); @@ -420,6 +420,14 @@ impl Engine { let changed_tests: Vec = changed.iter().filter(|p| looks_like_test(p)).cloned().collect(); let changed_sources: Vec = changed.iter().filter(|p| !looks_like_test(p)).cloned().collect(); let mut tests_to_rebuild = changed_tests.clone(); + let prior_tests = self.store.array("test_units"); + for path in &changed_sources { + let fresh = units_by_file.get(path).is_some_and(|units| units.iter().any(|u| is_test_unit_type(&u.unit_type))); + let prior = prior_tests.iter().any(|u| u.get("file_path").and_then(|v| v.as_str()) == Some(path.as_str())); + if fresh || prior { + tests_to_rebuild.push(path.clone()); + } + } if !changed_sources.is_empty() || !removed.is_empty() { let known_tests = self.store.array("test_units"); for edge in self.store.array("test_edges") { @@ -553,33 +561,31 @@ impl Engine { pub(super) fn build_tests_and_edges(&mut self, files: &[String], units_by_file: &BTreeMap>) -> (Vec, Vec, Vec) { let mut test_units = Vec::new(); - let mut test_files = Vec::new(); for rel in files { - if !looks_like_test(rel) { + let units = units_by_file.get(rel).cloned().unwrap_or_default(); + let as_file = looks_like_test(rel); + let framework = if rel.ends_with(".py") { + "pytest" + } else if rel.ends_with(".go") { + "go" + } else { + "test" + }; + let selected: Vec = if as_file { + units.into_iter().filter(|u| { + extract::is_test_name(&u.name, framework) || is_test_unit_type(&u.unit_type) + }).collect() + } else { + units.into_iter().filter(|u| is_test_unit_type(&u.unit_type)).collect() + }; + if !as_file && selected.is_empty() { continue; } - test_files.push(rel.clone()); let abs = self.project_dir.join(rel); let content = fs::read_to_string(&abs).unwrap_or_default(); self.source_reads += 1; let hash = sha256_hex(content.as_bytes()); - let framework = if rel.ends_with(".py") { "pytest" } else if rel.ends_with(".go") { "go" } else { "test" }; - let units = units_by_file.get(rel).cloned().unwrap_or_default(); - let before_n = test_units.len(); - for u in units { - if extract::is_test_name(&u.name, framework) || u.unit_type == "test_case" || u.unit_type == "test_suite" { - test_units.push(Value::object(vec![ - ("id".into(), Value::String(format!("{rel}:{}", u.name))), - ("file_path".into(), Value::String(rel.clone())), - ("name".into(), Value::String(u.name.clone())), - ("framework".into(), Value::String(framework.into())), - ("line_start".into(), Value::Int(u.line_start as i64)), - ("line_end".into(), Value::Int(u.line_end as i64)), - ("content_hash".into(), Value::String(hash.clone())), - ])); - } - } - if test_units.len() == before_n { + if selected.is_empty() { let stem = rel.rsplit(['/', '\\']).next().unwrap_or(rel); test_units.push(Value::object(vec![ ("id".into(), Value::String(format!("{rel}:{stem}"))), @@ -590,6 +596,18 @@ impl Engine { ("line_end".into(), Value::Int(1)), ("content_hash".into(), Value::String(hash)), ])); + continue; + } + for u in selected { + test_units.push(Value::object(vec![ + ("id".into(), Value::String(format!("{rel}:{}", u.name))), + ("file_path".into(), Value::String(rel.clone())), + ("name".into(), Value::String(u.name.clone())), + ("framework".into(), Value::String(framework.into())), + ("line_start".into(), Value::Int(u.line_start as i64)), + ("line_end".into(), Value::Int(u.line_end as i64)), + ("content_hash".into(), Value::String(hash.clone())), + ])); } } let code_units = self.store.array("code_units"); @@ -643,12 +661,11 @@ impl Engine { } pub(super) fn build_import_edges(&self, files: &[String]) -> Vec { - let tests = self.test_file_set(); let code_units = self.store.array("code_units"); let mut edges = Vec::new(); let mut seen = BTreeSet::new(); for rel in files { - if tests.contains(rel) { + if looks_like_test(rel) { continue; } let content = fs::read_to_string(self.project_dir.join(rel)).unwrap_or_default(); diff --git a/src/engine/mod.rs b/src/engine/mod.rs index 1631631..a5d495b 100644 --- a/src/engine/mod.rs +++ b/src/engine/mod.rs @@ -38,11 +38,9 @@ pub(crate) fn shard_name_for<'a>(file: &str, shards: &'a [(String, String)]) -> let mut best: Option<&str> = None; let mut best_len = 0usize; for (name, prefix) in shards { - if file == prefix.as_str() || file.starts_with(&format!("{prefix}/")) { - if prefix.len() >= best_len { - best = Some(name.as_str()); - best_len = prefix.len(); - } + if (file == prefix.as_str() || file.starts_with(&format!("{prefix}/"))) && prefix.len() >= best_len { + best = Some(name.as_str()); + best_len = prefix.len(); } } best @@ -264,9 +262,17 @@ impl Engine { } +/// Filename patterns `looks_like_test` accepts. Rust `#[test]` functions are separate: they are units, not names. +pub(crate) fn test_file_patterns() -> &'static str { + "test_*.py, *_test.py, *_test.go, *_test.rs, tests/*.rs, *_test.dart, *_spec.rb, test_*.rb, *.test.js, *.test.jsx, *.test.ts, *.test.tsx, *.spec.js, *.spec.jsx, *.spec.ts, *.spec.tsx, *Test.cs, *Tests.cs, *Test.java, Test*.java, *Test.kt, *Test.swift, *Tests.swift, *Test.php" +} + pub(crate) fn analyze_hint(test_files: i64) -> String { if test_files == 0 { - "No test files matched test_*.py, *_test.py, *_test.go, *_test.rs, *.test.ts, *.test.js, *.spec.ts, or *.spec.js. For large repos, use start_job with kind='analyze' to avoid MCP timeouts.".into() + format!( + "No test files matched {}. Rust #[test] functions are still recorded when a file is not named like a test. For large repos, use start_job with kind='analyze' to avoid MCP timeouts.", + test_file_patterns() + ) } else { "For large repos, use start_job with kind='analyze' to avoid MCP timeouts.".into() } @@ -342,7 +348,7 @@ pub(crate) fn flakiness_trend(results: &[Value], test_id: &str, now: f64) -> Opt continue; }; let age = now - when; - if age < 0.0 || age >= WINDOW { + if !(0.0..WINDOW).contains(&age) { continue; } let bucket = (age / WEEK) as usize; @@ -443,14 +449,21 @@ pub(crate) fn now_secs() -> f64 { impact::now_unix() } -pub(crate) fn churn_row(rel: &str, unit: &str, count: i64, authors: i64, ins: i64, del: i64, last: &str, score: f64) -> Value { +pub(crate) struct ChurnCounts { + pub commits: i64, + pub authors: i64, + pub insertions: i64, + pub deletions: i64, +} + +pub(crate) fn churn_row(rel: &str, unit: &str, counts: ChurnCounts, last: &str, score: f64) -> Value { Value::object(vec![ ("file_path".into(), Value::String(rel.into())), ("unit_name".into(), Value::String(unit.into())), - ("commit_count".into(), Value::Int(count)), - ("distinct_authors".into(), Value::Int(authors)), - ("total_insertions".into(), Value::Int(ins)), - ("total_deletions".into(), Value::Int(del)), + ("commit_count".into(), Value::Int(counts.commits)), + ("distinct_authors".into(), Value::Int(counts.authors)), + ("total_insertions".into(), Value::Int(counts.insertions)), + ("total_deletions".into(), Value::Int(counts.deletions)), ("last_changed".into(), if last.is_empty() { Value::Null } else { Value::String(last.into()) }), ("churn_score".into(), Value::Float(score)), ]) @@ -468,6 +481,16 @@ pub(crate) fn unit_obj(rel: &str, u: &CodeUnit, hash: &str) -> Value { ]) } +pub(crate) fn is_test_unit_type(unit_type: &str) -> bool { + unit_type == "test_case" || unit_type == "test_suite" +} + +pub(crate) fn is_test_unit_row(unit: &Value) -> bool { + unit.get("unit_type").and_then(|v| v.as_str()).is_some_and(is_test_unit_type) +} + +/// One classifier for "this path is a test file". +/// Cargo integration tests are any `.rs` file under the top-level `tests/` directory. pub(crate) fn looks_like_test(path: &str) -> bool { let name = path.rsplit(['/', '\\']).next().unwrap_or(path); let ext_ok = |suffix: &str| name.ends_with(suffix); @@ -494,6 +517,15 @@ pub(crate) fn looks_like_test(path: &str) -> bool { || name.ends_with("Test.swift") || name.ends_with("Tests.swift") || name.ends_with("Test.php") + || rust_integration_test(path, name) +} + +fn rust_integration_test(path: &str, name: &str) -> bool { + if !name.ends_with(".rs") { + return false; + } + let normalized = path.replace('\\', "/"); + normalized.split('/').next() == Some("tests") } pub(crate) fn resolved_import_files(deps: &[Dep], units: &[Value]) -> BTreeSet { diff --git a/src/engine/query.rs b/src/engine/query.rs index c112982..d1a2276 100644 --- a/src/engine/query.rs +++ b/src/engine/query.rs @@ -44,8 +44,7 @@ impl Engine { if !self.has_data() { return Ok(Self::no_data()); } - let tests = self.test_file_set(); - let mut files: Vec = self.store.array("churn").into_iter().filter_map(|c| c.get("file_path").and_then(|v| v.as_str()).map(|s| s.to_string())).filter(|p| !tests.contains(p)).collect(); + let mut files: Vec = self.store.array("churn").into_iter().filter_map(|c| c.get("file_path").and_then(|v| v.as_str()).map(|s| s.to_string())).filter(|p| !looks_like_test(p)).collect(); files.sort(); files.dedup(); let query_min: i64 = store::meta_get(&self.store.doc, "co_change_query_min").and_then(|s| s.parse().ok()).unwrap_or(1); @@ -121,9 +120,9 @@ impl Engine { } } partners.sort_by(|a, b| b.1.cmp(&a.1).then(a.2.cmp(&b.2)).then(a.0.cmp(&b.0))); - let mut import_neighbors: Vec = neighbor_map.get(f).map(|s| s.iter().cloned().filter(|n| self.project_dir.join(n).exists()).collect()).unwrap_or_default(); + let mut import_neighbors: Vec = neighbor_map.get(f).map(|s| s.iter().filter(|n| self.project_dir.join(n.as_str()).exists()).cloned().collect()).unwrap_or_default(); import_neighbors.sort(); - let file_units = self.store.units_by_file(f); + let file_units: Vec = self.store.units_by_file(f).into_iter().filter(|u| !is_test_unit_row(u)).collect(); let mut tested_lines = 0i64; let mut total_lines = 0i64; let mut tested_units = 0i64; @@ -1424,13 +1423,12 @@ impl Engine { } pub(super) fn gap_rows(&self) -> Vec { - let tests = self.test_file_set(); let edges = self.store.array("test_edges"); let churn = self.store.array("churn"); let mut gaps = Vec::new(); for u in self.store.array("code_units") { let path = u.get("file_path").and_then(|v| v.as_str()).unwrap_or(""); - if tests.contains(path) { continue; } + if looks_like_test(path) || is_test_unit_row(&u) { continue; } let id = u.get("id").and_then(|v| v.as_str()).unwrap_or(""); if edges.iter().any(|e| e.get("code_id").and_then(|v| v.as_str()) == Some(id)) { continue; @@ -1675,41 +1673,58 @@ impl Engine { let history = self.history_rows(file); if history.is_empty() { return Vec::new(); } let now = impact::now_unix(); - let mut stats: BTreeMap = BTreeMap::new(); + struct ReviewerTotals { + commits: i64, + email: String, + insertions: i64, + deletions: i64, + last_date: String, + score: f64, + last_ts: f64, + } + let mut stats: BTreeMap = BTreeMap::new(); for c in &history { let author = c.get("author").and_then(|v| v.as_str()).unwrap_or("").to_string(); let email = c.get("author_email").and_then(|v| v.as_str()).unwrap_or("").to_string(); let date = c.get("date").and_then(|v| v.as_str()).unwrap_or("").to_string(); let ins = c.get("insertions").and_then(|v| v.as_i64()).unwrap_or(0); let del = c.get("deletions").and_then(|v| v.as_i64()).unwrap_or(0); - let slot = stats.entry(author).or_insert((0, String::new(), 0, 0, String::new(), 0.0, 0.0)); - slot.0 += 1; - if !email.is_empty() { slot.1 = email; } - slot.2 += ins; - slot.3 += del; + let slot = stats.entry(author).or_insert(ReviewerTotals { + commits: 0, + email: String::new(), + insertions: 0, + deletions: 0, + last_date: String::new(), + score: 0.0, + last_ts: 0.0, + }); + slot.commits += 1; + if !email.is_empty() { slot.email = email; } + slot.insertions += ins; + slot.deletions += del; if let Some(ts) = impact::parse_iso_unix(&date) { - if date > slot.4 { - slot.4 = date.clone(); - slot.6 = ts; + if date > slot.last_date { + slot.last_date = date.clone(); + slot.last_ts = ts; } let days = ((now - ts) / 86400.0).max(0.0); - slot.5 += 1.0 / (1.0 + days); + slot.score += 1.0 / (1.0 + days); } else { - slot.5 += 0.01; + slot.score += 0.01; } } - let total: f64 = stats.values().map(|s| s.5).sum(); - let mut rows: Vec = stats.into_iter().map(|(author, (commits, email, ins, del, last, score, last_ts))| { - let days = if last_ts > 0.0 { Some(crate::json::py_round((now - last_ts) / 86400.0, 0) as i64) } else { None }; - let pct = if total > 0.0 { score / total * 100.0 } else { 0.0 }; + let total: f64 = stats.values().map(|s| s.score).sum(); + let mut rows: Vec = stats.into_iter().map(|(author, totals)| { + let days = if totals.last_ts > 0.0 { Some(crate::json::py_round((now - totals.last_ts) / 86400.0, 0) as i64) } else { None }; + let pct = if total > 0.0 { totals.score / total * 100.0 } else { 0.0 }; Value::object(vec![ ("author".into(), Value::String(author)), - ("author_email".into(), Value::String(email)), - ("recent_commits".into(), Value::Int(commits)), - ("last_commit_date".into(), Value::String(last)), + ("author_email".into(), Value::String(totals.email)), + ("recent_commits".into(), Value::Int(totals.commits)), + ("last_commit_date".into(), Value::String(totals.last_date)), ("days_since_last_commit".into(), days.map(Value::Int).unwrap_or(Value::Null)), - ("insertions".into(), Value::Int(ins)), - ("deletions".into(), Value::Int(del)), + ("insertions".into(), Value::Int(totals.insertions)), + ("deletions".into(), Value::Int(totals.deletions)), ("percentage".into(), Value::Float(crate::json::py_round(pct, 2))), ("role".into(), Value::String("suggested_reviewer".into())), ]) diff --git a/src/extract/brace.rs b/src/extract/brace.rs index 82d0c92..845c37f 100644 --- a/src/extract/brace.rs +++ b/src/extract/brace.rs @@ -61,7 +61,10 @@ pub(super) fn extract_brace(file_path: &str, content: &str, lang: &str) -> Vec Option<(String, String)> { } } if lang == "rust" { - if let Some(name) = rust_fn_name(t) { + let body = strip_leading_rust_attrs(t); + if let Some(name) = rust_fn_name(body) { return Some((name, "function".into())); } } @@ -168,10 +172,10 @@ fn keyword_name(t: &str, keys: &[&str]) -> Option { // modifiers before the keyword: "public class Foo" if let Some(idx) = t.find(k) { let before = t[..idx].trim(); - if before.split_whitespace().all(is_modifier) && !before.is_empty() || before.is_empty() { - if idx == 0 || t.as_bytes().get(idx - 1).is_some_and(|b| b.is_ascii_whitespace()) { - return ident(&t[idx + k.len()..]); - } + if (before.split_whitespace().all(is_modifier) && !before.is_empty() || before.is_empty()) + && (idx == 0 || t.as_bytes().get(idx - 1).is_some_and(|b| b.is_ascii_whitespace())) + { + return ident(&t[idx + k.len()..]); } } } @@ -208,6 +212,67 @@ fn go_func_name(t: &str) -> Option { } } +fn strip_leading_rust_attrs(t: &str) -> &str { + let mut s = t.trim(); + while s.starts_with("#[") { + let Some(end) = s.find(']') else { break }; + s = s[end + 1..].trim_start(); + } + s +} + +fn rust_attr_head(line: &str) -> Option<&str> { + let rest = line.trim().strip_prefix("#[")?.trim_start(); + let end = rest.find(']')?; + let head = rest[..end].split('(').next()?.trim(); + if head.is_empty() { None } else { Some(head) } +} + +fn is_rust_test_attr(head: &str) -> bool { + head == "test" + || head == "rstest" + || head == "test_case" + || head.ends_with("::test") + || head.ends_with("::rstest") + || head.ends_with("::test_case") +} + +fn rust_test_above(lines: &[String], index: usize) -> bool { + if line_has_rust_test_attr(&lines[index]) { + return true; + } + let mut i = index; + while i > 0 { + i -= 1; + let t = lines[i].trim(); + if t.is_empty() || t.starts_with("//") { + continue; + } + if let Some(head) = rust_attr_head(t) { + if is_rust_test_attr(head) { + return true; + } + continue; + } + return false; + } + false +} + +fn line_has_rust_test_attr(line: &str) -> bool { + let mut rest = line.trim(); + while let Some(start) = rest.find("#[") { + let after = &rest[start..]; + let Some(head) = rust_attr_head(after) else { break }; + if is_rust_test_attr(head) { + return true; + } + let Some(end) = after.find(']') else { break }; + rest = after[end + 1..].trim_start(); + } + false +} + fn rust_fn_name(t: &str) -> Option { let mut s = t; for prefix in ["pub(crate) ", "pub(super) ", "pub ", "async ", "const ", "unsafe ", "extern "] { diff --git a/src/extract/deps.rs b/src/extract/deps.rs index 0042910..c5be15d 100644 --- a/src/extract/deps.rs +++ b/src/extract/deps.rs @@ -530,20 +530,6 @@ pub fn proximity_weight(test_path: &str, code_path: &str) -> f64 { } } -pub fn is_test_file(path: &str) -> bool { - let name = path.rsplit(['/', '\\']).next().unwrap_or(path); - if name.starts_with("test_") && name.ends_with(".py") { - return true; - } - if name.ends_with("_test.py") || name.ends_with("_test.go") { - return true; - } - if name.ends_with("Test.java") || name.ends_with("Tests.cs") || name.ends_with("Tests.swift") { - return true; - } - false -} - pub fn is_test_name(name: &str, framework: &str) -> bool { let bare = name.rsplit('.').next().unwrap_or(name); match framework { diff --git a/src/extract/mod.rs b/src/extract/mod.rs index 0ff9b2d..8901dae 100644 --- a/src/extract/mod.rs +++ b/src/extract/mod.rs @@ -5,7 +5,7 @@ mod brace; mod deps; mod python; -pub use deps::{extract_deps, Dep, is_test_file, is_test_name, matches_import_path, matches_relative_import, proximity_weight}; +pub use deps::{extract_deps, Dep, is_test_name, matches_import_path, matches_relative_import, proximity_weight}; #[derive(Clone, Debug, PartialEq, Eq)] pub struct CodeUnit { diff --git a/src/fswatch.rs b/src/fswatch.rs index be5110f..13ab751 100644 --- a/src/fswatch.rs +++ b/src/fswatch.rs @@ -2,6 +2,7 @@ //! Linux uses inotify, macOS uses kqueue, Windows uses a change notification. //! A timeout with no event returns false. The caller still checks git. +#[cfg(any(target_os = "linux", target_os = "macos", target_os = "freebsd"))] use std::fs; use std::io; use std::path::Path; @@ -29,10 +30,12 @@ pub fn wait_for_change(root: &Path, timeout: Duration) -> io::Result { { let _ = (root, timeout); Ok(false) } } +#[cfg(any(target_os = "linux", target_os = "macos", target_os = "freebsd"))] fn skip_dir(name: &str) -> bool { matches!(name, ".git" | ".chisel" | "node_modules" | "target" | ".next") } +#[cfg(any(target_os = "linux", target_os = "macos", target_os = "freebsd"))] fn collect_dirs(root: &Path, out: &mut Vec) { out.push(root.to_path_buf()); let Ok(read) = fs::read_dir(root) else { return }; @@ -49,6 +52,7 @@ fn collect_dirs(root: &Path, out: &mut Vec) { } } +#[cfg(any(target_os = "macos", target_os = "freebsd"))] fn collect_files(root: &Path, out: &mut Vec) { let Ok(read) = fs::read_dir(root) else { return }; for entry in read.flatten() { @@ -88,7 +92,7 @@ fn wait_inotify(root: &Path, timeout: Duration) -> io::Result { let mut dirs = Vec::new(); collect_dirs(root, &mut dirs); if dirs.len() > 4000 { - return Err(io::Error::new(io::ErrorKind::Other, "too many directories to watch")); + return Err(io::Error::other("too many directories to watch")); } let fd = unsafe { inotify_init1(IN_CLOEXEC) }; if fd < 0 { @@ -146,7 +150,7 @@ fn wait_kqueue(root: &Path, timeout: Duration) -> io::Result { collect_dirs(root, &mut paths); collect_files(root, &mut paths); if paths.len() > 4000 { - return Err(io::Error::new(io::ErrorKind::Other, "too many paths to watch")); + return Err(io::Error::other("too many paths to watch")); } let mut files = Vec::new(); for path in &paths { diff --git a/src/git.rs b/src/git.rs index 74be186..0bdbdc8 100644 --- a/src/git.rs +++ b/src/git.rs @@ -170,6 +170,10 @@ fn finish(args: &[&str], output: Output) -> Result { Ok(String::from_utf8_lossy(&output.stdout).into_owned()) } +fn utc_z(date: &str) -> String { + date.strip_suffix("+00:00").map(|prefix| format!("{prefix}Z")).unwrap_or_else(|| date.to_string()) +} + pub fn parse_log_output(raw: &str) -> Vec { let mut commits = Vec::new(); for block in raw.split("COMMIT_START") { @@ -187,7 +191,7 @@ pub fn parse_log_output(raw: &str) -> Vec { hash: parts[0].into(), author: parts[1].into(), author_email: parts[2].into(), - date: parts[3].into(), + date: utc_z(parts[3]), message: parts[4].into(), files: Vec::new(), }; diff --git a/src/impact.rs b/src/impact.rs index be50510..9646486 100644 --- a/src/impact.rs +++ b/src/impact.rs @@ -29,24 +29,26 @@ pub fn weight(name: &str) -> f64 { } } -pub fn compose_risk_score( - churn_norm: f64, - coupling_norm: f64, - coverage_gap: f64, - coverage_depth: f64, - author_concentration: f64, - test_instability: f64, - hidden_risk_factor: f64, - new_file_boost: f64, -) -> f64 { - weight("churn") * churn_norm - + weight("coupling") * coupling_norm - + weight("coverage_gap") * coverage_gap - + weight("coverage_depth") * coverage_depth - + weight("author_concentration") * author_concentration - + weight("test_instability") * test_instability - + hidden_risk_factor - + new_file_boost +pub struct RiskTerms { + pub churn_norm: f64, + pub coupling_norm: f64, + pub coverage_gap: f64, + pub coverage_depth: f64, + pub author_concentration: f64, + pub test_instability: f64, + pub hidden_risk_factor: f64, + pub new_file_boost: f64, +} + +pub fn compose_risk_score(terms: &RiskTerms) -> f64 { + weight("churn") * terms.churn_norm + + weight("coupling") * terms.coupling_norm + + weight("coverage_gap") * terms.coverage_gap + + weight("coverage_depth") * terms.coverage_depth + + weight("author_concentration") * terms.author_concentration + + weight("test_instability") * terms.test_instability + + terms.hidden_risk_factor + + terms.new_file_boost } pub fn hidden_risk(dynamic_edge_count: f64) -> f64 { @@ -209,7 +211,16 @@ pub fn score_file(inp: &RiskInputs) -> Value { } else { 0.0 }; - let risk = compose_risk_score(churn_norm, coupling, gap, depth, inp.author_conc, inp.instability, hidden, new_boost); + let risk = compose_risk_score(&RiskTerms { + churn_norm, + coupling_norm: coupling, + coverage_gap: gap, + coverage_depth: depth, + author_concentration: inp.author_conc, + test_instability: inp.instability, + hidden_risk_factor: hidden, + new_file_boost: new_boost, + }); let partners = Value::Array(inp.coupling_partners.iter().take(3).map(|(f, n)| { Value::object(vec![ ("file".into(), Value::String(f.clone())), diff --git a/src/json.rs b/src/json.rs index 230648d..9541fb9 100644 --- a/src/json.rs +++ b/src/json.rs @@ -551,7 +551,7 @@ pub fn py_round(x: f64, digits: i32) -> f64 { } pub fn quantize_gap(value: f64) -> f64 { - (py_round(value * 20.0, 0) / 20.0).min(1.0).max(0.0) + (py_round(value * 20.0, 0) / 20.0).clamp(0.0, 1.0) } #[cfg(test)] diff --git a/src/lock.rs b/src/lock.rs index 75c5749..34d5fd7 100644 --- a/src/lock.rs +++ b/src/lock.rs @@ -14,7 +14,7 @@ impl ProcessLock { pub fn open(dir: &Path) -> io::Result { std::fs::create_dir_all(dir)?; let path = dir.join("chisel.lock"); - let file = OpenOptions::new().read(true).write(true).create(true).open(&path)?; + let file = OpenOptions::new().read(true).write(true).create(true).truncate(false).open(&path)?; Ok(ProcessLock { _file: file, path }) } diff --git a/src/main.rs b/src/main.rs index 4da3799..4265609 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,4 +1,5 @@ use chisel::cli; +#[cfg(debug_assertions)] use std::env; #[cfg(debug_assertions)] use chisel::json::Value; @@ -25,25 +26,17 @@ fn main() { } fn real_main() -> Result { - let mut argv: Vec = env::args().skip(1).collect(); - #[cfg(debug_assertions)] - if argv.first().map(|s| s.as_str()) == Some("internal-pause-before-rename") { - return pause_before_rename(&argv); - } #[cfg(debug_assertions)] - if argv.first().map(|s| s.as_str()) == Some("internal-read-loop") { - return read_loop(&argv); - } - let argv0 = env::args().next().unwrap_or_default(); - if cli::argv_is_mcp_binary(&argv0) { - argv.insert(0, "serve-mcp".into()); - } - if argv.is_empty() || argv.iter().any(|a| a == "-h" || a == "--help") && argv.len() == 1 { - cli::help(); - return Ok(0); + { + let argv: Vec = env::args().skip(1).collect(); + if argv.first().map(|s| s.as_str()) == Some("internal-pause-before-rename") { + return pause_before_rename(&argv); + } + if argv.first().map(|s| s.as_str()) == Some("internal-read-loop") { + return read_loop(&argv); + } } - let args = cli::parse(&argv)?; - cli::run(&args) + cli::entry() } #[cfg(debug_assertions)] diff --git a/src/mcp.rs b/src/mcp.rs index 89e66c2..0707f81 100644 --- a/src/mcp.rs +++ b/src/mcp.rs @@ -48,10 +48,10 @@ pub fn serve(project: &Path, storage: Option<&Path>) -> Result<(), String> { }; let method = msg.get("method").and_then(|v| v.as_str()).unwrap_or(""); let id = msg.get("id").cloned(); - if method.starts_with("notifications/") || method == "initialized" || id.is_none() && method != "initialize" { - if method == "initialized" || method.starts_with("notifications/") { - continue; - } + if (method.starts_with("notifications/") || method == "initialized" || id.is_none() && method != "initialize") + && (method == "initialized" || method.starts_with("notifications/")) + { + continue; } let result = match method { "initialize" => Value::object(vec![ diff --git a/src/schemas.rs b/src/schemas.rs index a1ba5d6..e50bcbe 100644 --- a/src/schemas.rs +++ b/src/schemas.rs @@ -1,4 +1,4 @@ -//! The 26 tool names, in the same order as `chisel/schemas.py`. +//! The 26 tool names and their input schemas. //! Schema bodies are the Python descriptions and parameter objects, embedded //! so the binary does not import Python at runtime. diff --git a/src/syntax.rs b/src/syntax.rs index 246d196..1d42381 100644 --- a/src/syntax.rs +++ b/src/syntax.rs @@ -53,10 +53,10 @@ fn parse_python(source: &str) -> Node { let mut stack: Vec<(usize, usize, String)> = Vec::new(); let mut i = 0; while i < lines.len() { - let (clean, next_depth) = strip_line(&lines[i], depth); + let (clean, next_depth) = strip_line(lines[i], depth); let trimmed = clean.trim(); if depth == 0 { - let indent = indent_of(&lines[i]); + let indent = indent_of(lines[i]); stack.retain(|(_, ind, _)| *ind < indent); if let Some(name) = class_name(trimmed) { let end = block_end(&lines, i, indent); @@ -191,10 +191,9 @@ fn class_name(trimmed: &str) -> Option { fn def_name(trimmed: &str) -> Option<(String, bool)> { let (rest, is_async) = if let Some(r) = trimmed.strip_prefix("async def ") { (r, true) - } else if let Some(r) = trimmed.strip_prefix("def ") { - (r, false) } else { - return None; + let r = trimmed.strip_prefix("def ")?; + (r, false) }; let name: String = rest.chars().take_while(|c| c.is_ascii_alphanumeric() || *c == '_').collect(); if name.is_empty() { None } else { Some((name, is_async)) } diff --git a/tests/extract_parity.rs b/tests/extract_parity.rs index 91106ac..e9ca597 100644 --- a/tests/extract_parity.rs +++ b/tests/extract_parity.rs @@ -41,9 +41,9 @@ fn language_fixtures_match_python_0_15_1() { ( "rust/basic.rs", vec![ - ("function", 2, 4, "test_addition"), - ("function", 7, 9, "test_async_fetch"), - ("function", 12, 14, "test_with_fixture"), + ("test_case", 2, 4, "test_addition"), + ("test_case", 7, 9, "test_async_fetch"), + ("test_case", 12, 14, "test_with_fixture"), ("function", 16, 18, "plain_helper"), ], ), diff --git a/tests/fixtures/rust_crate/Cargo.toml b/tests/fixtures/rust_crate/Cargo.toml new file mode 100644 index 0000000..9bebed9 --- /dev/null +++ b/tests/fixtures/rust_crate/Cargo.toml @@ -0,0 +1,6 @@ +[package] +name = "sample_crate" +version = "0.1.0" +edition = "2021" + +[dependencies] diff --git a/tests/fixtures/rust_crate/src/a.rs b/tests/fixtures/rust_crate/src/a.rs new file mode 100644 index 0000000..90a1ae1 --- /dev/null +++ b/tests/fixtures/rust_crate/src/a.rs @@ -0,0 +1,13 @@ +pub fn add_values(left: i32, right: i32) -> i32 { + left + right +} + +#[cfg(test)] +mod unit_checks { + use super::add_values; + + #[test] + fn checks_add_values() { + assert_eq!(add_values(1, 1), 2); + } +} diff --git a/tests/fixtures/rust_crate/src/lib.rs b/tests/fixtures/rust_crate/src/lib.rs new file mode 100644 index 0000000..98cce68 --- /dev/null +++ b/tests/fixtures/rust_crate/src/lib.rs @@ -0,0 +1,5 @@ +pub mod a; + +pub fn unused_root() -> i32 { + 0 +} diff --git a/tests/fixtures/rust_crate/tests/a.rs b/tests/fixtures/rust_crate/tests/a.rs new file mode 100644 index 0000000..9720d89 --- /dev/null +++ b/tests/fixtures/rust_crate/tests/a.rs @@ -0,0 +1,21 @@ +use sample_crate::a::add_values; + +fn helper_not_a_test() -> i32 { + 1 +} + +#[test] +fn checks_add_values_from_crate_tests() { + assert_eq!(add_values(2, 3), 5); + let _ = helper_not_a_test(); +} + +#[tokio::test] +async fn checks_add_values_async() { + assert_eq!(add_values(4, 1), 5); +} + +#[rstest] +fn checks_add_values_cases() { + assert_eq!(add_values(0, 0), 0); +} diff --git a/tests/oracle_parity.rs b/tests/oracle_parity.rs index 07f43f3..fad45a7 100644 --- a/tests/oracle_parity.rs +++ b/tests/oracle_parity.rs @@ -43,6 +43,8 @@ fn write_fixture(dir: &Path) { git(dir, &["init", "-b", "main"]); git(dir, &["config", "user.email", "a@example.com"]); git(dir, &["config", "user.name", "Solo"]); + git(dir, &["config", "commit.gpgsign", "false"]); + git(dir, &["config", "core.autocrlf", "false"]); for (date, msg, paths) in [ ("2024-01-15T12:00:00+00:00", "init", vec!["."]), ("2024-02-15T12:00:00+00:00", "touch", vec!["src/calc.py", "src/extra.py"]), @@ -145,6 +147,7 @@ fn read_tools_match_python_oracle() { assert!(analyze.contains("code_files_scanned")); } +#[cfg(unix)] #[test] fn run_records_pytest_and_forwards_exit_code() { let dir = scratch("run"); diff --git a/tests/rust_layout.rs b/tests/rust_layout.rs new file mode 100644 index 0000000..4eea564 --- /dev/null +++ b/tests/rust_layout.rs @@ -0,0 +1,185 @@ +//! Cargo-layout recognition and a dogfood check on this repository. + +use std::fs; +use std::path::{Path, PathBuf}; +use std::process::Command; +use std::sync::atomic::{AtomicU64, Ordering}; +use std::time::{SystemTime, UNIX_EPOCH}; + +fn scratch(name: &str) -> PathBuf { + static N: AtomicU64 = AtomicU64::new(0); + let n = N.fetch_add(1, Ordering::Relaxed); + let t = SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_nanos(); + let p = std::env::temp_dir().join(format!("chisel-rust-{name}-{t}-{n}")); + fs::create_dir_all(&p).unwrap(); + p +} + +fn git(dir: &Path, args: &[&str]) { + let status = Command::new("git").args(args).current_dir(dir).status().unwrap(); + assert!(status.success(), "git {args:?}"); +} + +fn init_repo(dir: &Path) { + git(dir, &["init", "-b", "main"]); + git(dir, &["config", "user.email", "a@example.com"]); + git(dir, &["config", "user.name", "Solo"]); + git(dir, &["config", "commit.gpgsign", "false"]); + git(dir, &["config", "core.autocrlf", "false"]); +} + +fn commit_all(dir: &Path, msg: &str) { + git(dir, &["add", "-A"]); + let status = Command::new("git") + .args(["commit", "-m", msg]) + .env("GIT_AUTHOR_DATE", "2024-03-01T00:00:00+00:00") + .env("GIT_COMMITTER_DATE", "2024-03-01T00:00:00+00:00") + .current_dir(dir) + .status() + .unwrap(); + assert!(status.success(), "{msg}"); +} + +fn copy_tree(from: &Path, to: &Path) { + fs::create_dir_all(to).unwrap(); + for entry in fs::read_dir(from).unwrap() { + let entry = entry.unwrap(); + let dest = to.join(entry.file_name()); + let kind = entry.file_type().unwrap(); + if kind.is_dir() { + copy_tree(&entry.path(), &dest); + } else { + fs::copy(entry.path(), dest).unwrap(); + } + } +} + +fn names_in(doc: &chisel::json::Value, key: &str) -> Vec { + doc.get(key) + .and_then(|v| v.as_array()) + .unwrap_or(&[]) + .iter() + .filter_map(|row| row.get("name").and_then(|v| v.as_str()).map(|s| s.to_string())) + .collect() +} + +fn paths_in(doc: &chisel::json::Value, key: &str) -> Vec { + doc.get(key) + .and_then(|v| v.as_array()) + .unwrap_or(&[]) + .iter() + .filter_map(|row| row.get("file_path").and_then(|v| v.as_str()).map(|s| s.to_string())) + .collect() +} + +#[test] +fn cargo_layout_links_tests_and_skips_them_as_code() { + let dir = scratch("crate"); + let fixture = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("tests/fixtures/rust_crate"); + copy_tree(&fixture, &dir); + init_repo(&dir); + commit_all(&dir, "init"); + let store = dir.join("store"); + let mut engine = chisel::Engine::open(&dir, Some(&store)).unwrap(); + let summary = engine.analyze().unwrap(); + let summary_text = chisel::json::to_string(&summary).unwrap(); + assert!(summary_text.contains("\"test_files_found\":2") || summary_text.contains("\"test_files_found\": 2"), "{summary_text}"); + let snap = chisel::json::parse(&fs::read_to_string(store.join("store")).unwrap()).unwrap(); + let test_names = names_in(&snap, "test_units"); + assert!(test_names.iter().any(|n| n == "checks_add_values"), "{test_names:?}"); + assert!(test_names.iter().any(|n| n == "checks_add_values_from_crate_tests"), "{test_names:?}"); + assert!(test_names.iter().any(|n| n == "checks_add_values_async"), "{test_names:?}"); + assert!(test_names.iter().any(|n| n == "checks_add_values_cases"), "{test_names:?}"); + assert!(!test_names.iter().any(|n| n == "helper_not_a_test"), "{test_names:?}"); + assert!(!test_names.iter().any(|n| n == "add_values"), "{test_names:?}"); + + let suggested = engine.suggest_tests("src/a.rs").unwrap(); + let suggested_text = chisel::json::to_string(&suggested).unwrap(); + assert!(suggested_text.contains("tests/a.rs"), "{suggested_text}"); + assert!(suggested_text.contains("checks_add_values"), "{suggested_text}"); + + fs::write( + dir.join("src/a.rs"), + fs::read_to_string(dir.join("src/a.rs")).unwrap() + "\n// touched\n", + ) + .unwrap(); + let impact = engine.diff_impact().unwrap(); + let impact_text = chisel::json::to_string(&impact).unwrap(); + assert!(impact_text.contains("tests/a.rs"), "{impact_text}"); + + let gaps = engine.dispatch("test_gaps", &chisel::json::Value::object(vec![])).unwrap(); + let gap_list = gaps.as_array().unwrap_or(&[]); + assert!(gap_list.iter().all(|row| { + let path = row.get("file_path").and_then(|v| v.as_str()).unwrap_or(""); + let name = row.get("name").and_then(|v| v.as_str()).unwrap_or(""); + !path.starts_with("tests/") && name != "checks_add_values" && name != "helper_not_a_test" + }), "{gap_list:?}"); + assert!(gap_list.iter().any(|row| row.get("name").and_then(|v| v.as_str()) == Some("unused_root")), "{gap_list:?}"); + + let risk = engine.risk_map().unwrap(); + let risk_paths = paths_in(&risk, "files"); + assert!(risk_paths.iter().all(|path| !path.starts_with("tests/")), "{risk_paths:?}"); + assert!(risk_paths.iter().any(|path| path == "src/a.rs"), "{risk_paths:?}"); +} + +#[test] +fn empty_project_hint_names_the_rust_tests_directory() { + let dir = scratch("hint"); + fs::write(dir.join("lib.rs"), "pub fn value() -> i32 { 1 }\n").unwrap(); + init_repo(&dir); + commit_all(&dir, "init"); + let mut engine = chisel::Engine::open(&dir, Some(&dir.join("store"))).unwrap(); + let summary = chisel::json::to_string(&engine.analyze().unwrap()).unwrap(); + assert!(summary.contains("tests/*.rs"), "{summary}"); + assert!(summary.contains("#[test]"), "{summary}"); + assert!(summary.contains("test_*.py") && summary.contains("*.spec.ts"), "{summary}"); +} + +fn mcp_binary() -> PathBuf { + let chisel = PathBuf::from(env!("CARGO_BIN_EXE_chisel")); + let name = match chisel.extension().and_then(|ext| ext.to_str()) { + Some(ext) if ext.eq_ignore_ascii_case("exe") => "chisel-mcp.exe", + _ => "chisel-mcp", + }; + chisel.with_file_name(name) +} + +#[test] +fn version_flag_prints_the_package_version() { + let version = env!("CARGO_PKG_VERSION"); + let bins = [PathBuf::from(env!("CARGO_BIN_EXE_chisel")), mcp_binary()]; + for bin in &bins { + let out = Command::new(bin).arg("--version").output().unwrap(); + assert!(out.status.success(), "{} {}", bin.display(), String::from_utf8_lossy(&out.stderr)); + let text = String::from_utf8_lossy(&out.stdout); + assert!(text.contains(version), "{}: {text}", bin.display()); + } + let unknown = Command::new(env!("CARGO_BIN_EXE_chisel")).arg("--not-a-real-flag").output().unwrap(); + assert!(!unknown.status.success()); + let err = String::from_utf8_lossy(&unknown.stderr); + assert!(err.contains("unknown or unimplemented flag"), "{err}"); +} + +#[test] +fn this_repo_suggests_a_rust_test_for_analyze() { + let root = PathBuf::from(env!("CARGO_MANIFEST_DIR")); + let store = scratch("dogfood"); + let mut engine = chisel::Engine::open(&root, Some(&store)).unwrap(); + engine.analyze().unwrap(); + let suggested = engine.suggest_tests("src/engine/analyze.rs").unwrap(); + let rows = suggested.as_array().expect("suggest-tests list"); + assert!(rows.iter().any(|row| { + let path = row.get("file_path").and_then(|v| v.as_str()).unwrap_or(""); + let rest = path.strip_prefix("tests/").unwrap_or(""); + rest.ends_with(".rs") && !rest.contains('/') + }), "{suggested:?}"); + let risk = engine.risk_map().unwrap(); + for path in paths_in(&risk, "files") { + assert!(!path.starts_with("tests/"), "risk-map listed a test path as code: {path}"); + } + let gaps = engine.dispatch("test_gaps", &chisel::json::Value::object(vec![])).unwrap(); + for row in gaps.as_array().unwrap_or(&[]) { + let path = row.get("file_path").and_then(|v| v.as_str()).unwrap_or(""); + assert!(!path.starts_with("tests/"), "test-gaps listed a test path as code: {path}"); + } +} diff --git a/tests/scale.rs b/tests/scale.rs index dfe2f84..a53e925 100644 --- a/tests/scale.rs +++ b/tests/scale.rs @@ -25,6 +25,8 @@ fn init_repo(dir: &Path) { git(dir, &["init", "-b", "main"]); git(dir, &["config", "user.email", "a@example.com"]); git(dir, &["config", "user.name", "Solo"]); + git(dir, &["config", "commit.gpgsign", "false"]); + git(dir, &["config", "core.autocrlf", "false"]); } fn commit_all(dir: &Path, msg: &str) { @@ -191,6 +193,7 @@ fn batch_record_flushes_once_and_jest_parses() { assert_eq!(after, before + 1); } +#[cfg(unix)] #[test] fn cli_run_records_jest_stdout() { let dir = scratch("jestcli"); @@ -531,6 +534,7 @@ fn project_dir_env_and_stdio_call_and_long_prefix() { assert!(text.contains("next_steps"), "{text}"); } +#[cfg(unix)] #[test] fn chisel_run_pytest_flushes_once() { let dir = scratch("pyflush"); @@ -860,7 +864,7 @@ fn unscoped_reads_aggregate_and_update_uses_longest_prefix() { let front = fs::read_to_string(store.join("shards/frontend/store")).unwrap(); let root = fs::read_to_string(store.join("store")).unwrap_or_default(); let new_hash = chisel::hash::sha256_hex(b"def w():\n return 9\n"); - assert!(web.contains(&format!("\"file_path\":\"frontend/web/w.py\"")) && web.contains(&new_hash), "longest prefix did not store the updated file: {web}"); + assert!(web.contains("\"file_path\":\"frontend/web/w.py\"") && web.contains(&new_hash), "longest prefix did not store the updated file: {web}"); assert!(!front.contains("\"file_path\":\"frontend/web/w.py\""), "shorter prefix stored the longer-prefix file"); assert!(!root.contains("\"file_path\":\"frontend/web/w.py\""), "root snapshot stored a shard file"); let diff = engine.diff_impact().unwrap(); @@ -1037,12 +1041,14 @@ fn record_result_changes_failure_rate_and_instability() { assert!(text.contains("\"test_instability\":1") || text.contains("\"test_instability\": 1"), "{text}"); } +#[cfg(unix)] fn write_exec(path: &Path, body: &str) { fs::write(path, body).unwrap(); let status = Command::new("chmod").args(["+x"]).arg(path).status().unwrap(); assert!(status.success()); } +#[cfg(unix)] #[test] fn extractor_failure_falls_back_and_success_replaces_units() { let dir = scratch("ext"); @@ -1089,6 +1095,7 @@ fn extractor_failure_falls_back_and_success_replaces_units() { assert!(!snap.contains("\"name\":\"a\""), "builtin unit leaked: {snap}"); } +#[cfg(unix)] #[test] fn extensions_toml_runs_an_external_program_without_an_env_var() { let dir = scratch("ext-toml"); @@ -1121,6 +1128,7 @@ fn extensions_toml_runs_an_external_program_without_an_env_var() { assert!(!snap.contains("\"name\":\"a\""), "{snap}"); } +#[cfg(unix)] #[test] fn repo_extractor_is_skipped_unless_allowed() { let dir = scratch("ext-skip"); @@ -1235,6 +1243,7 @@ fn lock_check_reloads_a_lock_taken_by_another_handle() { assert!(err.is_err(), "negative ttl should fail"); } +#[cfg(unix)] #[test] fn tool_argument_cannot_run_a_repo_extractor() { let dir = scratch("ext-arg"); @@ -1269,6 +1278,7 @@ fn tool_argument_cannot_run_a_repo_extractor() { assert!(marker.exists(), "cli opt-in did not run the extractor"); } +#[cfg(unix)] #[test] fn repo_extractor_outside_the_project_is_rejected() { let dir = scratch("ext-out"); @@ -1324,7 +1334,7 @@ fn oversized_mcp_body_is_discarded_and_the_next_message_is_read() { use std::io::Cursor; let header = format!("Content-Length: {}\r\n\r\n", 8 * 1024 * 1024 + 1); let mut bytes = header.into_bytes(); - bytes.extend(std::iter::repeat(b'x').take(8 * 1024 * 1024 + 1)); + bytes.extend(std::iter::repeat_n(b'x', 8 * 1024 * 1024 + 1)); let next = b"Content-Length: 2\r\n\r\n{}"; bytes.extend_from_slice(next); let mut input = Cursor::new(bytes); @@ -1630,29 +1640,32 @@ fn flakiness_trend_uses_week_buckets_and_pytest_ids_join() { let planted_doc = chisel::json::parse(&planted).unwrap(); let add_again = planted_doc.as_array().unwrap().iter().find(|r| r.get("test_id").and_then(|v| v.as_str()) == Some("tests/test_calc.py:test_add")).unwrap(); assert!(add_again.get("failure_rate").and_then(|v| v.as_f64()).unwrap() > 0.0, "{add_again:?}"); - let pytest = dir.join("pytest"); - fs::write(&pytest, "#!/bin/sh\necho 'tests/test_calc.py::test_add FAILED'\nexit 1\n").unwrap(); - let _ = Command::new("chmod").arg("+x").arg(&pytest).status(); - let bin = env!("CARGO_BIN_EXE_chisel"); - let run = Command::new(bin) - .args(["run", "--project-dir"]) - .arg(&dir) - .arg("--storage-dir") - .arg(&store) - .arg("--") - .arg(&pytest) - .env("CHISEL_NOW", now.to_string()) - .current_dir(&dir) - .output() - .unwrap(); - assert_eq!(run.status.code(), Some(1), "{}", String::from_utf8_lossy(&run.stderr)); - let snap = fs::read_to_string(store.join("store")).unwrap(); - assert!(snap.contains("tests/test_calc.py:test_add"), "{snap}"); - assert!(!snap.contains("tests/test_calc.py::test_add"), "{snap}"); - let after = chisel_at(&dir, &store, &["suggest-tests", "src/calc.py"], &now.to_string()); - let after_doc = chisel::json::parse(&after).unwrap(); - let joined = after_doc.as_array().unwrap().iter().find(|r| r.get("test_id").and_then(|v| v.as_str()) == Some("tests/test_calc.py:test_add")).unwrap(); - assert!(joined.get("failure_rate").and_then(|v| v.as_f64()).unwrap() > 0.0, "{joined:?}"); + #[cfg(unix)] + { + let pytest = dir.join("pytest"); + fs::write(&pytest, "#!/bin/sh\necho 'tests/test_calc.py::test_add FAILED'\nexit 1\n").unwrap(); + let _ = Command::new("chmod").arg("+x").arg(&pytest).status(); + let bin = env!("CARGO_BIN_EXE_chisel"); + let run = Command::new(bin) + .args(["run", "--project-dir"]) + .arg(&dir) + .arg("--storage-dir") + .arg(&store) + .arg("--") + .arg(&pytest) + .env("CHISEL_NOW", now.to_string()) + .current_dir(&dir) + .output() + .unwrap(); + assert_eq!(run.status.code(), Some(1), "{}", String::from_utf8_lossy(&run.stderr)); + let snap = fs::read_to_string(store.join("store")).unwrap(); + assert!(snap.contains("tests/test_calc.py:test_add"), "{snap}"); + assert!(!snap.contains("tests/test_calc.py::test_add"), "{snap}"); + let after = chisel_at(&dir, &store, &["suggest-tests", "src/calc.py"], &now.to_string()); + let after_doc = chisel::json::parse(&after).unwrap(); + let joined = after_doc.as_array().unwrap().iter().find(|r| r.get("test_id").and_then(|v| v.as_str()) == Some("tests/test_calc.py:test_add")).unwrap(); + assert!(joined.get("failure_rate").and_then(|v| v.as_f64()).unwrap() > 0.0, "{joined:?}"); + } } #[test]