From 68a9d1a2ebac6ad0bd6f642d1540ceeae3178705 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Tue, 3 Nov 2026 05:49:43 +0000 Subject: [PATCH] Check impl items and resolve workspace member crates. Rust wiki checks now include pub methods and associated items on a public type, plus items written in a trait impl for that type. pub(crate), pub(super), pub(self), and pub(in ...) stay out, as do impl items on a restricted type. Workspace members, including globs, supply crate names, and crate:: stays inside the member that owns the file. Co-authored-by: Aron Amos --- AGENTS.md | 4 +- AGENTS.md.wiki.md | 2 +- CHANGELOG.md | 3 +- CHANGELOG.md.wiki.md | 2 +- CLAUDE.md | 2 +- CLAUDE.md.wiki.md | 2 +- README.md | 4 +- README.md.wiki.md | 2 +- file_health.json | 2 +- file_health.md | 26 +- journal/2026/11/03.md | 32 ++ skills/run.md | 6 +- skills/run.md.wiki.md | 2 +- src/artifacts.rs.wiki.md | 2 +- src/cache.rs.wiki.md | 2 +- src/cache_commit.rs.wiki.md | 2 + src/jsonutil.rs.wiki.md | 2 +- src/parse.rs | 533 +++++++++++++++++++++++++++++--- src/parse.rs.wiki.md | 2 +- src/project_lock.rs.wiki.md | 2 +- tests/behaviour_gaps.rs | 106 ++++++- tests/behaviour_gaps.rs.wiki.md | 4 + tests/protocol_v07.rs | 10 +- tests/protocol_v07.rs.wiki.md | 2 +- 24 files changed, 677 insertions(+), 79 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index 9f64bdd..d116569 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -32,8 +32,8 @@ Prefer the CLI when the connected MCP server is still `python3 -m wikifier.mcp.s - Do not hand-edit `library.md`, `file_health.md`, `file_health.json`, or `pending_updates.md`. `update-maps` rewrites `library.md`. Health and pending change only through the commands. They take `.wikifier_staging/.lock`. - `init` copies `index.html` only when it is missing, and it does not clobber an existing path list. `diagnostics.html` stays in this repo. `index.v2.html` and `diagnostics.v2.html` are comparison pages and are not deployed. - This repo's map roots are `src/` and `tests/` (`map_paths.txt`). `monitored_paths.txt` is the extra watch list for docs and `wikifier.sh`. -- The import scan is comment-masked regex, plus named JS/TS re-exports to depth 8 (`parser: "regex+reexport"`). An indented Python import is conditional. `import()` is dynamic. Rust resolves `mod`, `crate::`, `self::`, `super::`, and the root `Cargo.toml` package or lib name (`-` to `_`). `crate::` stays in that file's crate (`tests/*.rs` and `src/bin/*.rs` included). The package or lib name points at the library, which is how `src/main.rs` and `tests/*.rs` show up as dependents. The accuracy gate is `accuracy_fixtures_match_frozen_edges` (the frozen layouts, including a Cargo crate). Do not describe the scan as a CPython `ast` walk, and do not claim it has been run on Babylon.js. It does not read tsconfig paths, package exports, or workspaces. -- `mark_green` checks Python, JS/TS, and Rust public names against the wiki. Rust names are top-level `pub` items, not `pub(crate)` and not methods inside `impl`. Go, C/C++, C#, Java, and Markdown come back `unchecked`. That is expected. +- The import scan is comment-masked regex, plus named JS/TS re-exports to depth 8 (`parser: "regex+reexport"`). An indented Python import is conditional. `import()` is dynamic. Rust resolves `mod`, `crate::`, `self::`, `super::`, and package or lib names from the root `Cargo.toml` and from `[workspace] members` (globs included, `-` to `_`). `crate::` stays in that file's crate (`tests/*.rs`, `src/bin/*.rs`, and each workspace member). A package or lib name points at that member's library, which is how `src/main.rs` and `tests/*.rs` show up as dependents. The accuracy gate is `accuracy_fixtures_match_frozen_edges` (the frozen layouts, including a Cargo crate). Do not describe the scan as a CPython `ast` walk, and do not claim it has been run on Babylon.js. It does not read tsconfig paths, package exports, or npm workspaces. +- `mark_green` checks Python, JS/TS, and Rust public names against the wiki. Rust names are unrestricted `pub` items, `pub` methods and associated items on a public type, and trait-impl items for a public type. `pub(crate)`, `pub(super)`, `pub(self)`, and `pub(in ...)` stay out. Go, C/C++, C#, Java, and Markdown come back `unchecked`. That is expected. - `wikifier serve` binds `127.0.0.1:8787`. Do not bind port 8080. - Do not set the project root to a parent that contains several repos. - Git: honor hooks. Do not use `--no-verify`. Do not commit `.cursor/`, `target/`, or gitignored leftovers (`wikifier/*.pyc`, `build/`, `dist/`, `.venv-build/`). diff --git a/AGENTS.md.wiki.md b/AGENTS.md.wiki.md index 1c875fe..5ae6fd9 100644 --- a/AGENTS.md.wiki.md +++ b/AGENTS.md.wiki.md @@ -2,4 +2,4 @@ **Role:** Entry point for any coding agent in this repository. -It points at `skills/run.md` (protocol v0.7) and names the live Rust binary, the daily loop, and the Python-era records kept under `Findings/archive/`. Map roots stay `src/` and `tests/`. The import scan stays comment-masked regex plus named re-exports. Rust also resolves `crate::`, `self::`, `super::`, and the root Cargo.toml package or lib name, so `src/main.rs` and `tests/*.rs` are dependents of the library modules they import. `mark-green` checks Rust top-level `pub` names. +It points at `skills/run.md` (protocol v0.7) and names the live Rust binary, the daily loop, and the Python-era records kept under `Findings/archive/`. Map roots stay `src/` and `tests/`. The import scan stays comment-masked regex plus named re-exports. Rust also resolves `crate::`, `self::`, `super::`, root crate names, and `[workspace] members`, so `src/main.rs` and `tests/*.rs` are dependents of the library modules they import. `mark-green` checks Rust `pub` names, impl methods, and trait-impl items. diff --git a/CHANGELOG.md b/CHANGELOG.md index ef299a3..54983b0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -33,7 +33,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - This repo's import-map roots are `src/` and `tests/`. - The import scan remains comment-masked regex plus named JS/TS re-exports to depth 8 (`parser: "regex+reexport"`). It is not a CPython `ast` walk, and it has not been run on Babylon.js. The accuracy gate is `accuracy_fixtures_match_frozen_edges`, now including a Cargo crate layout. - Rust `mod`, `crate::`, `self::`, `super::`, and the root `Cargo.toml` package or lib name resolve to files. Hyphens in the package name become underscores. `crate::` stays inside the file's crate (`tests/*.rs` and `src/bin/*.rs` are separate crates). The package or lib name points at the library, so binaries and integration tests are dependents of the library modules they import. -- `mark-green` checks Rust top-level `pub` items (`fn`, `struct`, `enum`, `trait`, `mod`, `type`, `const`, `static`, and `pub use` names) the same way it checks Python and JS/TS names. `pub(crate)` and items inside `impl` stay out of that set. A Rust file can be `verified` Green. Go, C/C++, C#, and Java stay `unchecked`. +- `mark-green` checks Rust unrestricted `pub` items, including methods and associated items inside inherent impls and the items written in a trait impl for a public type. `pub(crate)`, `pub(super)`, `pub(self)`, and `pub(in ...)` stay out of that set. A Rust file can be `verified` Green. Go, C/C++, C#, and Java stay `unchecked`. +- Rust import names come from the root package and from `[workspace] members`, including globs. `use member_crate::...` points at that member. `crate::` stays inside the member that owns the file. - Tests cover `WIKIFIER_MCP_DEADLINE_S`, `WIKIFIER_CHECK_CHANGES_MAX`, an `import_cache.sqlite` import (including `sqlite_present_unread`), and `actionable_low_conf_edges`. - CI pins Rust 1.83.0 (`dtolnay/rust-toolchain@1.83.0`). Ubuntu, macOS, and Windows all run `cargo test --locked`, and a Windows failure fails the workflow. diff --git a/CHANGELOG.md.wiki.md b/CHANGELOG.md.wiki.md index 7455466..ab38179 100644 --- a/CHANGELOG.md.wiki.md +++ b/CHANGELOG.md.wiki.md @@ -2,4 +2,4 @@ **Role:** Keep a Changelog history for package 4.7.x. -The 4.7.0 section is the record of that release, including the Python package that shipped then. Unreleased notes describe the Rust binary: map artifacts, dependency flags, wiki checks, deletion, background commands, the dashboard serve routes, MCP error reporting, `heal-stubs --dry-run`, and `init DIR`. The import scan is recorded as `regex+reexport`. Unreleased notes that Rust crate-name imports resolve to the library, that the unreproducible lookup benchmark was removed, that Rust top-level `pub` names can be verified Green, and that the MCP deadline, check-changes cap, sqlite import, and low-confidence edges have tests. CI pins Rust 1.83.0 and requires Ubuntu, macOS, and Windows. CI checks the empty dependency list without Python. +The 4.7.0 section is the record of that release, including the Python package that shipped then. Unreleased notes describe the Rust binary: map artifacts, dependency flags, wiki checks, deletion, background commands, the dashboard serve routes, MCP error reporting, `heal-stubs --dry-run`, and `init DIR`. The import scan is recorded as `regex+reexport`. Unreleased notes that Rust crate-name imports resolve to the library and to `[workspace] members`, that the unreproducible lookup benchmark was removed, that Rust `pub` names including impl methods can be verified Green, and that the MCP deadline, check-changes cap, sqlite import, and low-confidence edges have tests. CI pins Rust 1.83.0 and requires Ubuntu, macOS, and Windows. CI checks the empty dependency list without Python. diff --git a/CLAUDE.md b/CLAUDE.md index ff369c4..3872190 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -60,7 +60,7 @@ Pipeline: **scan → parse → resolve → cache → health → artifacts**, imp - `src/api.rs` — the library surface: `session_bootstrap`, `check_changes`, `prepare_edit`, `suggest_next_actions`, `record_change`, `record_changes`, `record_deletion`, `mark_green`, `verify_wikis`, `update_maps` / `update_maps_run`, `dependency_report`, `load_dependents`, `cycles`, `init_project`, `serve_loop`, `monitor_loop`, `daemon_ctl`. `check_changes` watches mapped files, Green files, and `monitored_paths.txt`. `record_changes` reads `git status` (git is optional). - `src/scope.rs` — project root, `map_paths.txt`, `monitored_paths.txt`, excludes, and the file walk. A missing configured map directory is a scope error. Three or more child projects make the root a container and the walk stops. -- `src/parse.rs` — comment-masked regex scans for Python, JS/TS, Rust, Go, C/C++, C#, and Java. Named JS/TS re-exports are followed to depth 8 (`via_barrel`). An indented Python import is `conditional`. `import()` is `dynamic`. Rust resolves `mod`, `crate::`, `self::`, `super::`, and the root `Cargo.toml` package or lib name (`-` to `_`). `crate::` stays in the file's crate; the package or lib name points at the library. This is not a CPython `ast` parser and it does not read tsconfig paths, package `exports`, or workspaces. The accuracy gate is `accuracy_fixtures_match_frozen_edges`. +- `src/parse.rs` — comment-masked regex scans for Python, JS/TS, Rust, Go, C/C++, C#, and Java. Named JS/TS re-exports are followed to depth 8 (`via_barrel`). An indented Python import is `conditional`. `import()` is `dynamic`. Rust resolves `mod`, `crate::`, `self::`, `super::`, and package or lib names from the root `Cargo.toml` and `[workspace] members` (`-` to `_`, globs included). `crate::` stays in the file's crate; a member name points at that member's library. This is not a CPython `ast` parser and it does not read tsconfig paths, package `exports`, or npm workspaces. The accuracy gate is `accuracy_fixtures_match_frozen_edges`. - `src/cache.rs` + `src/cache_commit.rs` — text index at `.wikifier_staging/wk_cache` (per-file payload, `by_src`, `by_dst`, notes, barrels). One commit per `update-maps`. Size and mtime skip unchanged files. `--full` rehashes. `src/legacy_cache.rs` imports an empty cache from `import_cache.sqlite` (the `sqlite3` CLI) or `import_cache.json`. - `src/artifacts.rs` — `file_health.json` (schema version 2) plus the `file_health.md` view, pending lines, and the journal. Identical journal retries within 120 seconds are skipped. `mark_green` stores `public_symbols`, `wiki_hash`, and `source_mtime_ns`, and refuses a Python or JS/TS wiki that misses the current public names, still mentions a removed name in backticks, or is unchanged after the public surface changed. - `src/maps_out.rs` — `library.md` (a file tree, a Mermaid block capped at 80 edges, and the dependency table) and sharded folder cards at `.wikifier_staging/maps/_root.json` and `maps/{prefix}/_index.json`. diff --git a/CLAUDE.md.wiki.md b/CLAUDE.md.wiki.md index a0d766a..c32c9c0 100644 --- a/CLAUDE.md.wiki.md +++ b/CLAUDE.md.wiki.md @@ -2,4 +2,4 @@ **Role:** Repo guidance for an agent working in this tree. -Hard constraints: empty Cargo dependencies, no hand-edits of `library.md` or the health and pending files, and the human dashboard stays secondary. The Python-era plan logs live under `Findings/archive/`. It points agents at `AGENTS.md` and `skills/run.md`. The architecture section names the Rust modules under `src/`, the library file tree, and this repo's map roots `src/` and `tests/`. The parse note includes Rust `crate::`, `self::`, `super::`, the Cargo.toml crate name, and `rust_public_symbols` for wiki checks. JSON failures use `success: false`. +Hard constraints: empty Cargo dependencies, no hand-edits of `library.md` or the health and pending files, and the human dashboard stays secondary. The Python-era plan logs live under `Findings/archive/`. It points agents at `AGENTS.md` and `skills/run.md`. The architecture section names the Rust modules under `src/`, the library file tree, and this repo's map roots `src/` and `tests/`. The parse note includes Rust `crate::`, `self::`, `super::`, Cargo.toml crate names, `[workspace] members`, and `rust_public_symbols` for wiki checks. JSON failures use `success: false`. diff --git a/README.md b/README.md index 009d525..c8b0713 100644 --- a/README.md +++ b/README.md @@ -26,7 +26,7 @@ Works from small scripts to large monorepos. **Deep import/include maps** (zero- |----------|------------|--------| | Python | `.py` | comment-masked regex; relative imports and same-tree module paths; an indented import is flagged conditional | | JavaScript / TypeScript | `.js` `.ts` `.jsx` `.tsx` `.mjs` `.cjs` `.mts` `.cts` | comment-masked regex; named re-exports followed to depth 8; `import()` flagged dynamic | -| Rust | `.rs` | `mod`, `use`, `crate::`, `self::`, `super::`, and the root `Cargo.toml` package or lib name (`-` becomes `_`). A name points at the library; `crate::` stays inside the file's own crate, including `tests/` and `src/bin/` | +| Rust | `.rs` | `mod`, `use`, `crate::`, `self::`, `super::`, and package or lib names from the root `Cargo.toml` and from `[workspace] members` (globs included). `-` becomes `_`. A member name points at that member's library. `crate::` stays inside the crate that owns the file, including `tests/` and `src/bin/` | | Go | `.go` | `import` / import blocks | | C / C++ | `.c` `.h` `.cpp` `.cc` `.cxx` `.hpp` `.hh` | `#include` (local + system) | | C# | `.cs` | `using` namespaces | @@ -104,7 +104,7 @@ Advanced intel as needed: `list_paths` (names-only folder expand), `get_dependen - **Import analysis** — comment-masked regex for Python, JS/TS, Rust, Go, C/C++, C#, and Java. Named JS/TS re-exports are followed to depth 8. Edges carry conditional, dynamic, and via-barrel flags, and a confidence read from those flags. - **Incremental pipeline** — `update-maps` parses files whose size or mtime changed, then writes the `wk_cache` text index, reverse edges, `library.md`, and sharded folder cards. - **Two path lists** — `map_paths.txt` = map package roots; `monitored_paths.txt` = extra watch list (docs, scripts). `check-changes` always watches mapped files and anything ever marked Green, so a narrow list never hides edits. -- **Green you can trust** — `mark-green` checks the wiki against Python, JS/TS, and Rust public symbols and refuses when a public name is missing, a removed name is still in backticks, or the public surface changed while the wiki file did not. Rust names are top-level `pub` items: `fn`, `struct`, `enum`, `trait`, `mod`, `type`, `const`, `static`, and the names a `pub use` exports. `pub(crate)` and items inside `impl` are not in that set. Go, C/C++, C#, and Java are recorded as unchecked. `verify-wikis` re-checks every Green wiki. `health --summary` includes `green_breakdown`. +- **Green you can trust** — `mark-green` checks the wiki against Python, JS/TS, and Rust public symbols and refuses when a public name is missing, a removed name is still in backticks, or the public surface changed while the wiki file did not. Rust names are unrestricted `pub` items, including methods and associated items on a public type and the items written in a trait impl for that type. `pub(crate)`, `pub(super)`, `pub(self)`, and `pub(in ...)` stay out of that set. Go, C/C++, C#, and Java are recorded as unchecked. `verify-wikis` re-checks every Green wiki. `health --summary` includes `green_breakdown`. - **Partial-map honesty** — `--max-files` returns `map_complete=false` and `files_remaining_dirty`. Run the same command again to resume. Already-parsed files are stat-skipped. - **Cache** — `.wikifier_staging/wk_cache`. `wikifier cache-status` reports the backend and the file count. An empty cache imports `import_cache.sqlite` through the `sqlite3` CLI, or `import_cache.json`. - **Selective agent work** — health and `suggest-next` stay on 🔴 and actionable 🟡. Diagnostics list ACS reason codes and `actionable_low_conf_edges`. diff --git a/README.md.wiki.md b/README.md.wiki.md index d7b59d6..cc0cf2d 100644 --- a/README.md.wiki.md +++ b/README.md.wiki.md @@ -2,4 +2,4 @@ **Role:** The public entry point for the std-only Rust program. -It tells agents to build with `cargo build --release`, start in this repo at `AGENTS.md`, and follow `skills/run.md`. Bootstrap a target with `wikifier init`. The language table describes the comment-masked regex scans, including Rust crate-name imports. The lookup benchmark that cited a missing `scripts/benchmark_lookup.py` is gone. The performance table that remains says its numbers are from the Python package. `mark-green` checks Rust top-level `pub` names as well as Python and JS/TS. MCP is `wikifier mcp`. +It tells agents to build with `cargo build --release`, start in this repo at `AGENTS.md`, and follow `skills/run.md`. Bootstrap a target with `wikifier init`. The language table describes the comment-masked regex scans, including Rust crate-name imports. The lookup benchmark that cited a missing `scripts/benchmark_lookup.py` is gone. The performance table that remains says its numbers are from the Python package. `mark-green` checks Rust `pub` items, impl methods, and trait-impl items, as well as Python and JS/TS. Rust also reads `[workspace] members`. MCP is `wikifier mcp`. diff --git a/file_health.json b/file_health.json index fdfa347..64c196a 100644 --- a/file_health.json +++ b/file_health.json @@ -1 +1 @@ -{"entries":{"AGENTS.md":{"last_updated":"2026-11-03 05:34:35","reason":"Address the 3 Nov review findings (subid=review-2026-11-03)","source_content_hash":"sha256:6f68ebef06e7d09223b625a0e1f96bb94de5ddfc1ccb03649df64edbd35fcc83","source_mtime_ns":"1793684038394977086","status":"🟢 Green","verification":"unchecked","wiki_hash":"sha256:109e3acac311f59909ef8b43dd1287904a70e27c50bce5b98229a44aa8d49e02"},"CHANGELOG.md":{"last_updated":"2026-11-03 05:34:35","reason":"Address the 3 Nov review findings (subid=review-2026-11-03)","source_content_hash":"sha256:e8ebc4db70717e7816a8c6282b66a19f70e99e66270d6e166436bbc8efcf80fd","source_mtime_ns":"1793683924472409471","status":"🟢 Green","verification":"unchecked","wiki_hash":"sha256:b48347653b8beaaaf269916f07f1229d32714780de3371534ae9a1f31bf5f254"},"CLAUDE.md":{"last_updated":"2026-11-03 05:34:35","reason":"Address the 3 Nov review findings (subid=review-2026-11-03)","source_content_hash":"sha256:e7b296172e1b71a3051863b86830cbf6367a01b13c85ba3f94c447dcfc60abf4","source_mtime_ns":"1793684038394977086","status":"🟢 Green","verification":"unchecked","wiki_hash":"sha256:1c4309d8e8dedd8ae9dc03f3b808bb7f3c582900d30f4f6cbf64931f2f4a63fe"},"CONTRIBUTING.md":{"last_updated":"2026-11-03 05:34:35","reason":"Address the 3 Nov review findings (subid=review-2026-11-03)","source_content_hash":"sha256:d090280cd3d1c3ab1cf342d69392471ed3835512ff7381bf4409bada57bbe89c","source_mtime_ns":"1793683924316411256","status":"🟢 Green","verification":"unchecked","wiki_hash":"sha256:ec0bd9342b8e9dfeefaabc8dcddeb577f6bd4f1c760d7286b21d709c2a372b29"},"Findings/M5-Dogfood-Assessment-Report.md":{"last_updated":"2026-07-09 05:02:27","reason":"kept; stale yellow cleared","source_content_hash":"sha256:238435188b1699e5bcf4753b62a6fe55cb064413a291259f0b696b5bd3c1692f","source_mtime_ns":"1780519956000000000","status":"🟢 Green"},"Findings/M5-Dogfood-Progress.md":{"last_updated":"2026-11-03 08:06:46","reason":"Wiki marks this file as the historical M5 log (subid=protocol-v07)","source_content_hash":"sha256:ef55167e819806060dfd7dfe7cdfd4d02024ca97706a7faa243710e2630b65fe","source_mtime_ns":"1793408707259550076","status":"🟢 Green","verification":"unchecked","wiki_hash":"sha256:b6b1fa4bbee0c7e8c4035cd925f6d07e1221ba483e3472878d5b88236c5cf7f9"},"Findings/M5.1-cross-hardening-analysis.md":{"last_updated":"2026-07-09 05:02:27","reason":"kept; stale yellow cleared","source_content_hash":"sha256:a2c2414e5b6c9b1a802bec8734a7924850d546cd7746a8087216f3685ca36cb4","source_mtime_ns":"1780519762000000000","status":"🟢 Green"},"Findings/Milestones-Overview.md":{"last_updated":"2026-07-09 05:02:27","reason":"kept; stale yellow cleared","source_content_hash":"sha256:f8b7030ec8980774cf3a2560ad7aee6537263eadf8374e650718f7c6793c6d50","source_mtime_ns":"1780800604000000000","status":"🟢 Green"},"Findings/p6_real_world_validation_report.md":{"last_updated":"2026-07-09 05:02:27","reason":"kept; stale yellow cleared","source_content_hash":"sha256:0468224675888f2747674324726c0ea9a5dddded39d9a1a50f22fc3357dafc0f","source_mtime_ns":"1780520008000000000","status":"🟢 Green"},"README.md":{"last_updated":"2026-11-03 05:34:35","reason":"Address the 3 Nov review findings (subid=review-2026-11-03)","source_content_hash":"sha256:9f1e7c23b08ea002d053ee722f979628e0d0bd98b79fc5ad7dc5ee122af2aef7","source_mtime_ns":"1793683696766187580","status":"🟢 Green","verification":"unchecked","wiki_hash":"sha256:e2c2b383144e498adad61999d960fb6d862ec35220a5bf3b563784582199b49f"},"docs/spec.md":{"last_updated":"2026-11-03 08:00:11","reason":"Name the Rust binary and protocol v0.7 in the living principles (subid=protocol-v07)","source_content_hash":"sha256:31fe6b3b3d705628692eb0512cf44cbb21861ccfc5bb02afdd46d796ec066c6e","source_mtime_ns":"1793653008580824356","status":"🟢 Green","verification":"unchecked","wiki_hash":"sha256:a58b929d25d510f15f9fe176d7823e852f6d089e398accc2e22e1ed189f260ac"},"skills/run.md":{"last_updated":"2026-11-03 05:34:35","reason":"Address the 3 Nov review findings (subid=review-2026-11-03)","source_content_hash":"sha256:51a921f43bf2eb02d9fba6c66ae0c9209fc1316aeb3b48f38b8035038dcf7e39","source_mtime_ns":"1793683697246186161","status":"🟢 Green","verification":"unchecked","wiki_hash":"sha256:327e668717490617ac1b1285d36d0a699044918fb932504b23fc8b4c254cb74f"},"src/api.rs":{"last_updated":"2026-11-03 05:34:35","public_symbols":"MapRun\nVERSION\nMCP_TOOLS\nupdate_maps\nupdate_maps_opts\nupdate_maps_run\nmapped_edges\nload_dependencies\ndependency_report\nload_dependents\nlist_paths\nsession_bootstrap\nsuggest_next_actions\ncheck_changes\nprepare_edit\nrecord_change\nmark_green\nhealth\nread_health\nread_journal\ncache_status\nvalidate\ncycles\nverify_wikis\nrecord_changes\nrecord_changes_opts\nrecord_deletion\nheal_stubs\nprune_health\nprune_health_opts\nprune_pending\nstale_wikis\nprune_barrels\nmetrics_snapshot\nhealing_stats\nresolution_diagnostics\nbarrel_reports\nincremental_status\nserve_once\nserve_loop\nmonitor_loop\ndaemon_ctl\ndispatch\nCLI_COMMANDS\nensure_root","reason":"Address the 3 Nov review findings (subid=review-2026-11-03)","source_content_hash":"sha256:bb9cda81c00a44543c1888c3b0e7f493390d9baee9b8675ad4b1580799e53ca8","source_mtime_ns":"1793684003575439733","status":"🟢 Green","verification":"verified","wiki_hash":"sha256:69ac1101639d23ac6893adf37e501a820e277ace0ddeaf3c2c80afba28557e54"},"src/artifacts.rs":{"last_updated":"2026-11-03 05:35:24","public_symbols":"HealthRow\nread_health\nwrite_health\nupsert_health_row\nJournalEntry\njournal_path\nread_journal\nparse_journal\nJournalWrite\nappend_journal\nread_pending\nfolder_card","reason":"Wiki names the public health and journal types (subid=review-w3)","source_content_hash":"sha256:37b29254a15e6c09e76a54d1c5a7d0aa7fc54b25044e17db84f345e55cbfa790","source_mtime_ns":"1793678007374624809","status":"🟢 Green","verification":"verified","wiki_hash":"sha256:8b48f279108946f7861b81cd31e632977037c1c4fc84b7b17cbbd99eb2e74730"},"src/cache.rs":{"last_updated":"2026-11-03 05:35:24","public_symbols":"check_consistency\nrebuild\nConsistency\nSession\npayload_path\nby_src_path\ncommit_pending_path\nload_dependencies\nInternalEdge\nconfidence_of\nformat_edge_line\nDependencyView\nload_dependency_view\nload_dependents\nlist_prefix\nall_paths\nFileUpdate\nupsert_file\nremove_file\nindexed_rows\npayload_bytes\nstat_ns\nedges\nrel_string\nwrite_barrel_record\nprune_barrel_files","reason":"Wiki names the cache index surface (subid=review-w3)","source_content_hash":"sha256:79faa2b50bc95a573a1bd3555bcafbca271221af61849201df999cb229b4be63","source_mtime_ns":"1793678007370625780","status":"🟢 Green","verification":"verified","wiki_hash":"sha256:5b6aed776d0ef82fb156bed8a6a507f9823ef6f2a873a19916bcd69f3900e106"},"src/cache_commit.rs":{"last_updated":"2026-11-03 05:34:35","public_symbols":"Consistency\nSession\ncheck_consistency\nrebuild","reason":"Address the 3 Nov review findings (subid=review-2026-11-03)","source_content_hash":"sha256:71ce0df880b4ba70abac47509ee22f03a4835d51f4ca91cd9249720abd5833e8","source_mtime_ns":"1793683981823718483","status":"🟢 Green","verification":"verified","wiki_hash":"sha256:7eea56868007852f1bdc2eaca8f53687eb265a243958fe9681deb38279307526"},"src/jsonutil.rs":{"last_updated":"2026-11-03 05:34:35","public_symbols":"Json\nparse\nparse_jsonc\nstrip_json_comments","reason":"Address the 3 Nov review findings (subid=review-2026-11-03)","source_content_hash":"sha256:18a53e0aca984c6ba4e58132ec21a194cc08733a5e7ad4fc22c003c04a5174fd","source_mtime_ns":"1793683981803718735","status":"🟢 Green","verification":"verified","wiki_hash":"sha256:0b67d6238457fa9e27bc3cd657576648f311bcb5095db70df8f5607deb699996"},"src/legacy_cache.rs":{"last_updated":"2026-11-03 05:34:35","public_symbols":"import_if_empty","reason":"Address the 3 Nov review findings (subid=review-2026-11-03)","source_content_hash":"sha256:5bfe43e2a43b138f28d5daeecb8576f90a9c82bf01569f4d9d3e84339f1a2507","source_mtime_ns":"1793683981803718735","status":"🟢 Green","verification":"verified","wiki_hash":"sha256:a6fe6c79185a46cc4b30d215475bab28f517f6e5a6fd7bce6423c2792814aac8"},"src/lib.rs":{"last_updated":"2026-11-03 05:35:24","public_symbols":"api\nartifacts\ncache\njsonutil\nmaps_out\nmcp\nparse\nscope\nsha256\ntimeutil\ncache_status\ncheck_changes\nhealth\nlist_paths\nload_dependencies\nload_dependents\nmapped_edges\nmark_green\nprepare_edit\nread_health\nread_journal\nrecord_change\nsession_bootstrap\nsuggest_next_actions\nupdate_maps\nVERSION\nJson","reason":"Wiki names the crate re-exports (subid=review-w3)","source_content_hash":"sha256:d3afbbbe2faf0f68c7e4c4148f8aec42a728b1c529c8615689798a1a9f9b039d","source_mtime_ns":"1793678007370625780","status":"🟢 Green","verification":"verified","wiki_hash":"sha256:d7e771a3ff63ab55a7967195e3b403dd75d5ae020a383adc56f0e5c09d7b03a0"},"src/main.rs":{"last_updated":"2026-11-03 05:34:35","reason":"Address the 3 Nov review findings (subid=review-2026-11-03)","source_content_hash":"sha256:c29545879d40673183e8e9477dc2f80ec56326a1f33f19a640350f207a7a94e6","source_mtime_ns":"1793683990351610198","status":"🟢 Green","verification":"verified","wiki_hash":"sha256:db6e1cd5beeb90e5a65b3e00a37ee2c73ea4c53750771151e655459a1b823ff5"},"src/maps_out.rs":{"last_updated":"2026-11-03 05:35:24","public_symbols":"write\nwrite_status\nread_status","reason":"Wiki names read_status (subid=review-w3)","source_content_hash":"sha256:6f4523bfae50356ce8c90f2c840f04a79eb99c4ce889e985dd80432cba35d410","source_mtime_ns":"1793678007370625780","status":"🟢 Green","verification":"verified","wiki_hash":"sha256:d441c395fe565828a87c54d5545e4c89e4ec4cfc49308a08525e6dcf3c744e75"},"src/mcp.rs":{"last_updated":"2026-11-03 05:34:35","public_symbols":"tool_schema\nhandle_message\nserve_stdio","reason":"Address the 3 Nov review findings (subid=review-2026-11-03)","source_content_hash":"sha256:2b6a92af8522c7a5047fe64beee86bf1301992ec07d2e65263fe93e593bfc3c5","source_mtime_ns":"1793683982335712018","status":"🟢 Green","verification":"verified","wiki_hash":"sha256:8414b1757bf3acc39a66a52443e9625cb027173b2783d996e39e9f707a9a1933"},"src/parse.rs":{"last_updated":"2026-11-03 05:34:35","public_symbols":"RawImport\nKind\nScan\nlanguage\nread_utf8\nscan_file_warned\nscan_file\nresolve_project\nrust_public_symbols\nresolve_python_spec\nFactEdge\nFileFacts\nAnalysis\nanalyze_files\nanalyze_changed\nedges_for_files","reason":"Address the 3 Nov review findings (subid=review-2026-11-03)","source_content_hash":"sha256:364a6fe8cdc730f57c8be17f92d8adc704bfcc1ac8a101b935f40f4231c3e447","source_mtime_ns":"1793683982995703683","status":"🟢 Green","verification":"verified","wiki_hash":"sha256:34dbd74cd48200e734284c5b999ffd93965537cee71444261151d81647ea8e5d"},"src/proc.rs":{"last_updated":"2026-11-03 07:58:59","reason":"Initial stub","source_content_hash":"sha256:a4751d64c912e3eb9b5608410b1555a0495ee7114174ea03500fffd5336ea776","status":"🟡 Yellow","verification":"no_wiki"},"src/project_lock.rs":{"last_updated":"2026-11-03 05:35:15","public_symbols":"ProjectLock","reason":"Wiki names ProjectLock after Rust symbol checks (subid=review-w3)","source_content_hash":"sha256:d7f0448fccc96b9f9f4294f0abadb9044ee24551418180530eafd331b71b4ab5","source_mtime_ns":"1793678007370625780","status":"🟢 Green","verification":"verified","wiki_hash":"sha256:892ea30f94deaf281fd74d94946c81991c90e32c633e2f4fc1cf868ec4c0b60d"},"src/scope.rs":{"last_updated":"2026-11-03 07:58:59","reason":"Initial stub","source_content_hash":"sha256:4d80b5432823864d0b95db68a558cd25aa0fe7d35b3f5796fbbdf107bee709aa","status":"🟡 Yellow","verification":"no_wiki"},"src/sha256.rs":{"last_updated":"2026-11-03 07:58:59","reason":"Initial stub","source_content_hash":"sha256:a68691e08d8260937e0f6c2534faf02ef58bb8332efa2cdd1b0a59f8fdf0d77d","status":"🟡 Yellow","verification":"no_wiki"},"src/timeutil.rs":{"last_updated":"2026-11-03 07:58:59","reason":"Initial stub","source_content_hash":"sha256:a90359c6ab1c816b7d7b2f9ce12c799d3f6c8d560de0ed491c7f069be6fbaa14","status":"🟡 Yellow","verification":"no_wiki"},"tests/behaviour_gaps.rs":{"last_updated":"2026-11-03 05:34:35","reason":"Address the 3 Nov review findings (subid=review-2026-11-03)","source_content_hash":"sha256:a97b21cd9a5c7faa265b8537920f140935718c02c0d79c81c63b17a74cd830a5","source_mtime_ns":"1793684003387442177","status":"🟢 Green","verification":"verified","wiki_hash":"sha256:0b89bd19ab27fb71a6975e099d5d6ac5c47b4a24cd3caab85dcff9ebce75f43e"},"tests/cache_and_artifacts.rs":{"last_updated":"2026-11-03 07:58:59","reason":"Initial stub","source_content_hash":"sha256:e147bf88f37a4748bde1c20657b911da6ca329b99867d854eea6f30a7562fd30","status":"🟡 Yellow","verification":"no_wiki"},"tests/cli_surface.rs":{"last_updated":"2026-11-03 07:58:59","reason":"Initial stub","source_content_hash":"sha256:c88cd894134edacd5cfca1895d38fc25b1f12ebfd86d7be98033863238b5414c","status":"🟡 Yellow","verification":"no_wiki"},"tests/contract_gaps.rs":{"last_updated":"2026-11-03 07:58:59","reason":"Initial stub","source_content_hash":"sha256:ece7c1aa0d6c311280d6f93a902d8faa9e4645295730299598cabfec469ed56d","status":"🟡 Yellow","verification":"no_wiki"},"tests/dogfood.rs":{"last_updated":"2026-11-03 05:34:35","reason":"Address the 3 Nov review findings (subid=review-2026-11-03)","source_content_hash":"sha256:4efe688e0e9b58c8f0b0ca986dad90dfd837c4e36816579353023ede19044d3a","source_mtime_ns":"1793683337392806763","status":"🟢 Green","verification":"verified","wiki_hash":"sha256:9546cc2062f19584c348419322f07d25d353c7f8ba0257e992372f63d798c020"},"tests/parity.rs":{"last_updated":"2026-11-03 05:34:35","reason":"Address the 3 Nov review findings (subid=review-2026-11-03)","source_content_hash":"sha256:4eb986e4a3f922d917672f2df45644726f1f3a8cd132b0bf089740c91678a6d0","source_mtime_ns":"1793683314773282057","status":"🟢 Green","verification":"verified","wiki_hash":"sha256:32753c244642df6e08f8578c6203f77225363783c15bc57c33a84f03989f2378"},"tests/protocol_v07.rs":{"last_updated":"2026-11-03 05:34:35","reason":"Address the 3 Nov review findings (subid=review-2026-11-03)","source_content_hash":"sha256:6d20f5a2375f7a06c6dc12e4d4ad3aea75bdca4b724e73cc6cdedc346f122c04","source_mtime_ns":"1793683731550052721","status":"🟢 Green","verification":"verified","wiki_hash":"sha256:c382f216277633704637f8b520e3814b7ae53fb3fce1b0d3c24af1238a286190"},"tests/robustness.rs":{"last_updated":"2026-11-03 07:58:59","reason":"Initial stub","source_content_hash":"sha256:1216cf8aa62c31215bba2107580e3c6824bed736ebd212670f68d8a33ecf0103","status":"🟡 Yellow","verification":"no_wiki"},"tests/skip_maps.rs":{"last_updated":"2026-11-03 07:58:59","reason":"Initial stub","source_content_hash":"sha256:4c5706c7ce384e330517848e51c690369d71e00b22b25ddfa19c9485f9988c43","status":"🟡 Yellow","verification":"no_wiki"},"wikifier.sh":{"last_updated":"2026-11-03 08:06:46","reason":"Wiki describes the Rust binary launcher (subid=protocol-v07)","source_content_hash":"sha256:4f467e2117e8c361882054998cb0506717d9555e2aa570f0e176fc18d9c2f4aa","source_mtime_ns":"1793410024331635951","status":"🟢 Green","verification":"unchecked","wiki_hash":"sha256:3876708eaec60862b9b8b6f73e2ae87e4abca2f228c456f973ddb7c9ee36854a"}},"version":2} \ No newline at end of file +{"entries":{"AGENTS.md":{"last_updated":"2026-11-03 05:49:16","reason":"Check impl items and resolve workspace member crates (subid=impl-workspace)","source_content_hash":"sha256:4c3c9a11f3c42c26325dfcb0f5b907814fc536165abd8f8c4139684077445151","source_mtime_ns":"1793684917066518484","status":"🟢 Green","verification":"unchecked","wiki_hash":"sha256:d5907471b6831952e34e64a033d471bb0264ef4dee92151ca2c0eaf40b1d16ef"},"CHANGELOG.md":{"last_updated":"2026-11-03 05:49:16","reason":"Check impl items and resolve workspace member crates (subid=impl-workspace)","source_content_hash":"sha256:c4d7b852c1656257acf0a79ba7623bf69d58b8517a4a3e2d6b0649b55d41c9ff","source_mtime_ns":"1793684916746519688","status":"🟢 Green","verification":"unchecked","wiki_hash":"sha256:43a7a238cc155970024675795f4d1f1fc0000e22cd22898ca7206c57dc111e67"},"CLAUDE.md":{"last_updated":"2026-11-03 05:49:16","reason":"Check impl items and resolve workspace member crates (subid=impl-workspace)","source_content_hash":"sha256:a3e8e8efe945e955c91fe596e51ecd0dcec709be5e9f397134abf46dab29a3bd","source_mtime_ns":"1793684916630520123","status":"🟢 Green","verification":"unchecked","wiki_hash":"sha256:afc87f10c92c7cf033d4a5af38ff318ccc1d8ff069b88cab22b3563552ea071b"},"CONTRIBUTING.md":{"last_updated":"2026-11-03 05:34:35","reason":"Address the 3 Nov review findings (subid=review-2026-11-03)","source_content_hash":"sha256:d090280cd3d1c3ab1cf342d69392471ed3835512ff7381bf4409bada57bbe89c","source_mtime_ns":"1793683924316411256","status":"🟢 Green","verification":"unchecked","wiki_hash":"sha256:ec0bd9342b8e9dfeefaabc8dcddeb577f6bd4f1c760d7286b21d709c2a372b29"},"Findings/M5-Dogfood-Assessment-Report.md":{"last_updated":"2026-07-09 05:02:27","reason":"kept; stale yellow cleared","source_content_hash":"sha256:238435188b1699e5bcf4753b62a6fe55cb064413a291259f0b696b5bd3c1692f","source_mtime_ns":"1780519956000000000","status":"🟢 Green"},"Findings/M5-Dogfood-Progress.md":{"last_updated":"2026-11-03 08:06:46","reason":"Wiki marks this file as the historical M5 log (subid=protocol-v07)","source_content_hash":"sha256:ef55167e819806060dfd7dfe7cdfd4d02024ca97706a7faa243710e2630b65fe","source_mtime_ns":"1793408707259550076","status":"🟢 Green","verification":"unchecked","wiki_hash":"sha256:b6b1fa4bbee0c7e8c4035cd925f6d07e1221ba483e3472878d5b88236c5cf7f9"},"Findings/M5.1-cross-hardening-analysis.md":{"last_updated":"2026-07-09 05:02:27","reason":"kept; stale yellow cleared","source_content_hash":"sha256:a2c2414e5b6c9b1a802bec8734a7924850d546cd7746a8087216f3685ca36cb4","source_mtime_ns":"1780519762000000000","status":"🟢 Green"},"Findings/Milestones-Overview.md":{"last_updated":"2026-07-09 05:02:27","reason":"kept; stale yellow cleared","source_content_hash":"sha256:f8b7030ec8980774cf3a2560ad7aee6537263eadf8374e650718f7c6793c6d50","source_mtime_ns":"1780800604000000000","status":"🟢 Green"},"Findings/p6_real_world_validation_report.md":{"last_updated":"2026-07-09 05:02:27","reason":"kept; stale yellow cleared","source_content_hash":"sha256:0468224675888f2747674324726c0ea9a5dddded39d9a1a50f22fc3357dafc0f","source_mtime_ns":"1780520008000000000","status":"🟢 Green"},"README.md":{"last_updated":"2026-11-03 05:49:16","reason":"Check impl items and resolve workspace member crates (subid=impl-workspace)","source_content_hash":"sha256:f27b8eaecf2f176d90593fb971d8b4fd6bce24128317136840e5dfdc9ed67d9f","source_mtime_ns":"1793684917046518560","status":"🟢 Green","verification":"unchecked","wiki_hash":"sha256:725a9cdfc3d48d9a343a319b192be15d107543a64246438ef72e83e710cb0667"},"docs/spec.md":{"last_updated":"2026-11-03 08:00:11","reason":"Name the Rust binary and protocol v0.7 in the living principles (subid=protocol-v07)","source_content_hash":"sha256:31fe6b3b3d705628692eb0512cf44cbb21861ccfc5bb02afdd46d796ec066c6e","source_mtime_ns":"1793653008580824356","status":"🟢 Green","verification":"unchecked","wiki_hash":"sha256:a58b929d25d510f15f9fe176d7823e852f6d089e398accc2e22e1ed189f260ac"},"skills/run.md":{"last_updated":"2026-11-03 05:49:16","reason":"Check impl items and resolve workspace member crates (subid=impl-workspace)","source_content_hash":"sha256:9ba8d1ee8bd5bc3552a6d27a30c8c1f46253599fa6cecac1fdca44c0cfc7c1a0","source_mtime_ns":"1793684917474516945","status":"🟢 Green","verification":"unchecked","wiki_hash":"sha256:5f6e7868ee9adee68bd634352413338e9a367eac640c623545bcad60f64b6389"},"src/api.rs":{"last_updated":"2026-11-03 05:34:35","public_symbols":"MapRun\nVERSION\nMCP_TOOLS\nupdate_maps\nupdate_maps_opts\nupdate_maps_run\nmapped_edges\nload_dependencies\ndependency_report\nload_dependents\nlist_paths\nsession_bootstrap\nsuggest_next_actions\ncheck_changes\nprepare_edit\nrecord_change\nmark_green\nhealth\nread_health\nread_journal\ncache_status\nvalidate\ncycles\nverify_wikis\nrecord_changes\nrecord_changes_opts\nrecord_deletion\nheal_stubs\nprune_health\nprune_health_opts\nprune_pending\nstale_wikis\nprune_barrels\nmetrics_snapshot\nhealing_stats\nresolution_diagnostics\nbarrel_reports\nincremental_status\nserve_once\nserve_loop\nmonitor_loop\ndaemon_ctl\ndispatch\nCLI_COMMANDS\nensure_root","reason":"Address the 3 Nov review findings (subid=review-2026-11-03)","source_content_hash":"sha256:bb9cda81c00a44543c1888c3b0e7f493390d9baee9b8675ad4b1580799e53ca8","source_mtime_ns":"1793684003575439733","status":"🟢 Green","verification":"verified","wiki_hash":"sha256:69ac1101639d23ac6893adf37e501a820e277ace0ddeaf3c2c80afba28557e54"},"src/artifacts.rs":{"last_updated":"2026-11-03 05:49:23","public_symbols":"HealthRow\nread_health\nwrite_health\nupsert_health_row\nJournalEntry\njournal_path\nread_journal\nparse_journal\nJournalWrite\nappend_journal\nread_pending\nfolder_card\nnew\nkeep_baseline_from","reason":"Wiki names impl items (subid=impl-workspace)","source_content_hash":"sha256:37b29254a15e6c09e76a54d1c5a7d0aa7fc54b25044e17db84f345e55cbfa790","source_mtime_ns":"1793678007374624809","status":"🟢 Green","verification":"verified","wiki_hash":"sha256:75bf0f72cc4bd57b6178dc6a0a58b8bf97bc3e7265485a28102093b967f20181"},"src/cache.rs":{"last_updated":"2026-11-03 05:49:23","public_symbols":"check_consistency\nrebuild\nConsistency\nSession\npayload_path\nby_src_path\ncommit_pending_path\nload_dependencies\nInternalEdge\nconfidence_of\nformat_edge_line\nDependencyView\nload_dependency_view\nload_dependents\nlist_prefix\nall_paths\nFileUpdate\nupsert_file\nremove_file\nindexed_rows\npayload_bytes\nstat_ns\nedges\nrel_string\nwrite_barrel_record\nprune_barrel_files\nconfidence\nacs_reason","reason":"Wiki names impl items (subid=impl-workspace)","source_content_hash":"sha256:79faa2b50bc95a573a1bd3555bcafbca271221af61849201df999cb229b4be63","source_mtime_ns":"1793678007370625780","status":"🟢 Green","verification":"verified","wiki_hash":"sha256:afb7493ff8742682f5f3c743da8919408502e6a6e75bba9998eb59cbbaf1c02a"},"src/cache_commit.rs":{"last_updated":"2026-11-03 05:49:23","public_symbols":"Consistency\nSession\ncheck_consistency\nrebuild\nopen\nknown\ndsts_of\nnote_stat\nrels\nupsert\nremove\nstage_barrel\ncommit","reason":"Wiki names impl items (subid=impl-workspace)","source_content_hash":"sha256:71ce0df880b4ba70abac47509ee22f03a4835d51f4ca91cd9249720abd5833e8","source_mtime_ns":"1793684429116993727","status":"🟢 Green","verification":"verified","wiki_hash":"sha256:c6f3fbdcd6aafe62de119c302e09e11ae0ea208681c52166cfcf79de80bb5f6c"},"src/jsonutil.rs":{"last_updated":"2026-11-03 05:49:23","public_symbols":"Json\nparse\nparse_jsonc\nstrip_json_comments\nobj\nfrom_pairs\nget\nas_str\nas_bool\nas_array\nas_object\ninsert\nfmt","reason":"Wiki names impl items (subid=impl-workspace)","source_content_hash":"sha256:18a53e0aca984c6ba4e58132ec21a194cc08733a5e7ad4fc22c003c04a5174fd","source_mtime_ns":"1793684429116993727","status":"🟢 Green","verification":"verified","wiki_hash":"sha256:fb4cffba8b14a179dd90f201b61df2608ca78a9775e9646e7f011f1b9fa07c63"},"src/legacy_cache.rs":{"last_updated":"2026-11-03 05:34:35","public_symbols":"import_if_empty","reason":"Address the 3 Nov review findings (subid=review-2026-11-03)","source_content_hash":"sha256:5bfe43e2a43b138f28d5daeecb8576f90a9c82bf01569f4d9d3e84339f1a2507","source_mtime_ns":"1793683981803718735","status":"🟢 Green","verification":"verified","wiki_hash":"sha256:a6fe6c79185a46cc4b30d215475bab28f517f6e5a6fd7bce6423c2792814aac8"},"src/lib.rs":{"last_updated":"2026-11-03 05:35:24","public_symbols":"api\nartifacts\ncache\njsonutil\nmaps_out\nmcp\nparse\nscope\nsha256\ntimeutil\ncache_status\ncheck_changes\nhealth\nlist_paths\nload_dependencies\nload_dependents\nmapped_edges\nmark_green\nprepare_edit\nread_health\nread_journal\nrecord_change\nsession_bootstrap\nsuggest_next_actions\nupdate_maps\nVERSION\nJson","reason":"Wiki names the crate re-exports (subid=review-w3)","source_content_hash":"sha256:d3afbbbe2faf0f68c7e4c4148f8aec42a728b1c529c8615689798a1a9f9b039d","source_mtime_ns":"1793678007370625780","status":"🟢 Green","verification":"verified","wiki_hash":"sha256:d7e771a3ff63ab55a7967195e3b403dd75d5ae020a383adc56f0e5c09d7b03a0"},"src/main.rs":{"last_updated":"2026-11-03 05:34:35","reason":"Address the 3 Nov review findings (subid=review-2026-11-03)","source_content_hash":"sha256:c29545879d40673183e8e9477dc2f80ec56326a1f33f19a640350f207a7a94e6","source_mtime_ns":"1793683990351610198","status":"🟢 Green","verification":"verified","wiki_hash":"sha256:db6e1cd5beeb90e5a65b3e00a37ee2c73ea4c53750771151e655459a1b823ff5"},"src/maps_out.rs":{"last_updated":"2026-11-03 05:35:24","public_symbols":"write\nwrite_status\nread_status","reason":"Wiki names read_status (subid=review-w3)","source_content_hash":"sha256:6f4523bfae50356ce8c90f2c840f04a79eb99c4ce889e985dd80432cba35d410","source_mtime_ns":"1793678007370625780","status":"🟢 Green","verification":"verified","wiki_hash":"sha256:d441c395fe565828a87c54d5545e4c89e4ec4cfc49308a08525e6dcf3c744e75"},"src/mcp.rs":{"last_updated":"2026-11-03 05:34:35","public_symbols":"tool_schema\nhandle_message\nserve_stdio","reason":"Address the 3 Nov review findings (subid=review-2026-11-03)","source_content_hash":"sha256:2b6a92af8522c7a5047fe64beee86bf1301992ec07d2e65263fe93e593bfc3c5","source_mtime_ns":"1793683982335712018","status":"🟢 Green","verification":"verified","wiki_hash":"sha256:8414b1757bf3acc39a66a52443e9625cb027173b2783d996e39e9f707a9a1933"},"src/parse.rs":{"last_updated":"2026-11-03 05:49:16","public_symbols":"RawImport\nKind\nScan\nlanguage\nread_utf8\nscan_file_warned\nscan_file\nresolve_project\nrust_public_symbols\nresolve_python_spec\nFactEdge\nFileFacts\nAnalysis\nanalyze_files\nanalyze_changed\nedges_for_files","reason":"Check impl items and resolve workspace member crates (subid=impl-workspace)","source_content_hash":"sha256:a188a97f91505c56c5c7a30ef0cab6ab3d627905a7a6aea76c077897a97bd969","source_mtime_ns":"1793684824322776381","status":"🟢 Green","verification":"verified","wiki_hash":"sha256:c70af52c81a4dfffb364cba13ca21873491e83e1c9c5cee31ab2bab2bc379fbb"},"src/proc.rs":{"last_updated":"2026-11-03 07:58:59","reason":"Initial stub","source_content_hash":"sha256:a4751d64c912e3eb9b5608410b1555a0495ee7114174ea03500fffd5336ea776","status":"🟡 Yellow","verification":"no_wiki"},"src/project_lock.rs":{"last_updated":"2026-11-03 05:49:23","public_symbols":"ProjectLock\ndrop\nacquire","reason":"Wiki names impl items (subid=impl-workspace)","source_content_hash":"sha256:d7f0448fccc96b9f9f4294f0abadb9044ee24551418180530eafd331b71b4ab5","source_mtime_ns":"1793678007370625780","status":"🟢 Green","verification":"verified","wiki_hash":"sha256:fccc8d547f30262ff64d506d842472623e828ebe39fde71532a683c6956329d2"},"src/scope.rs":{"last_updated":"2026-11-03 07:58:59","reason":"Initial stub","source_content_hash":"sha256:4d80b5432823864d0b95db68a558cd25aa0fe7d35b3f5796fbbdf107bee709aa","status":"🟡 Yellow","verification":"no_wiki"},"src/sha256.rs":{"last_updated":"2026-11-03 07:58:59","reason":"Initial stub","source_content_hash":"sha256:a68691e08d8260937e0f6c2534faf02ef58bb8332efa2cdd1b0a59f8fdf0d77d","status":"🟡 Yellow","verification":"no_wiki"},"src/timeutil.rs":{"last_updated":"2026-11-03 07:58:59","reason":"Initial stub","source_content_hash":"sha256:a90359c6ab1c816b7d7b2f9ce12c799d3f6c8d560de0ed491c7f069be6fbaa14","status":"🟡 Yellow","verification":"no_wiki"},"tests/behaviour_gaps.rs":{"last_updated":"2026-11-03 05:49:16","reason":"Check impl items and resolve workspace member crates (subid=impl-workspace)","source_content_hash":"sha256:e77dbec8b9cfcc9f81edcfc6937f407a80a1cd054899bb54c61680444497892a","source_mtime_ns":"1793684893298602299","status":"🟢 Green","verification":"verified","wiki_hash":"sha256:b9d20a310ca48ffd858e6d0d4e49b5e0568ecb57dc5ad981eb54398562864691"},"tests/cache_and_artifacts.rs":{"last_updated":"2026-11-03 07:58:59","reason":"Initial stub","source_content_hash":"sha256:e147bf88f37a4748bde1c20657b911da6ca329b99867d854eea6f30a7562fd30","status":"🟡 Yellow","verification":"no_wiki"},"tests/cli_surface.rs":{"last_updated":"2026-11-03 07:58:59","reason":"Initial stub","source_content_hash":"sha256:c88cd894134edacd5cfca1895d38fc25b1f12ebfd86d7be98033863238b5414c","status":"🟡 Yellow","verification":"no_wiki"},"tests/contract_gaps.rs":{"last_updated":"2026-11-03 07:58:59","reason":"Initial stub","source_content_hash":"sha256:ece7c1aa0d6c311280d6f93a902d8faa9e4645295730299598cabfec469ed56d","status":"🟡 Yellow","verification":"no_wiki"},"tests/dogfood.rs":{"last_updated":"2026-11-03 05:34:35","reason":"Address the 3 Nov review findings (subid=review-2026-11-03)","source_content_hash":"sha256:4efe688e0e9b58c8f0b0ca986dad90dfd837c4e36816579353023ede19044d3a","source_mtime_ns":"1793683337392806763","status":"🟢 Green","verification":"verified","wiki_hash":"sha256:9546cc2062f19584c348419322f07d25d353c7f8ba0257e992372f63d798c020"},"tests/parity.rs":{"last_updated":"2026-11-03 05:34:35","reason":"Address the 3 Nov review findings (subid=review-2026-11-03)","source_content_hash":"sha256:4eb986e4a3f922d917672f2df45644726f1f3a8cd132b0bf089740c91678a6d0","source_mtime_ns":"1793683314773282057","status":"🟢 Green","verification":"verified","wiki_hash":"sha256:32753c244642df6e08f8578c6203f77225363783c15bc57c33a84f03989f2378"},"tests/protocol_v07.rs":{"last_updated":"2026-11-03 05:49:16","reason":"Check impl items and resolve workspace member crates (subid=impl-workspace)","source_content_hash":"sha256:401a1187e9f8e7322ebf3658985fea0912d907959a3c28b00642a65ea875340c","source_mtime_ns":"1793684781738827745","status":"🟢 Green","verification":"verified","wiki_hash":"sha256:b38f4a13e316cefd56070f822980b4f83d9cdacb850ab1978de48ed35c630ec8"},"tests/robustness.rs":{"last_updated":"2026-11-03 07:58:59","reason":"Initial stub","source_content_hash":"sha256:1216cf8aa62c31215bba2107580e3c6824bed736ebd212670f68d8a33ecf0103","status":"🟡 Yellow","verification":"no_wiki"},"tests/skip_maps.rs":{"last_updated":"2026-11-03 07:58:59","reason":"Initial stub","source_content_hash":"sha256:4c5706c7ce384e330517848e51c690369d71e00b22b25ddfa19c9485f9988c43","status":"🟡 Yellow","verification":"no_wiki"},"wikifier.sh":{"last_updated":"2026-11-03 08:06:46","reason":"Wiki describes the Rust binary launcher (subid=protocol-v07)","source_content_hash":"sha256:4f467e2117e8c361882054998cb0506717d9555e2aa570f0e176fc18d9c2f4aa","source_mtime_ns":"1793410024331635951","status":"🟢 Green","verification":"unchecked","wiki_hash":"sha256:3876708eaec60862b9b8b6f73e2ae87e4abca2f228c456f973ddb7c9ee36854a"}},"version":2} \ No newline at end of file diff --git a/file_health.md b/file_health.md index 378a78d..8a00024 100644 --- a/file_health.md +++ b/file_health.md @@ -2,41 +2,41 @@ | File | Status | Last Updated | Reason / Intent | |------|--------|--------------|-----------------| -| AGENTS.md | 🟢 Green | 2026-11-03 05:34:35 | Address the 3 Nov review findings (subid=review-2026-11-03) | -| CHANGELOG.md | 🟢 Green | 2026-11-03 05:34:35 | Address the 3 Nov review findings (subid=review-2026-11-03) | -| CLAUDE.md | 🟢 Green | 2026-11-03 05:34:35 | Address the 3 Nov review findings (subid=review-2026-11-03) | +| AGENTS.md | 🟢 Green | 2026-11-03 05:49:16 | Check impl items and resolve workspace member crates (subid=impl-workspace) | +| CHANGELOG.md | 🟢 Green | 2026-11-03 05:49:16 | Check impl items and resolve workspace member crates (subid=impl-workspace) | +| CLAUDE.md | 🟢 Green | 2026-11-03 05:49:16 | Check impl items and resolve workspace member crates (subid=impl-workspace) | | CONTRIBUTING.md | 🟢 Green | 2026-11-03 05:34:35 | Address the 3 Nov review findings (subid=review-2026-11-03) | | Findings/M5-Dogfood-Assessment-Report.md | 🟢 Green | 2026-07-09 05:02:27 | kept; stale yellow cleared | | Findings/M5-Dogfood-Progress.md | 🟢 Green | 2026-11-03 08:06:46 | Wiki marks this file as the historical M5 log (subid=protocol-v07) | | Findings/M5.1-cross-hardening-analysis.md | 🟢 Green | 2026-07-09 05:02:27 | kept; stale yellow cleared | | Findings/Milestones-Overview.md | 🟢 Green | 2026-07-09 05:02:27 | kept; stale yellow cleared | | Findings/p6_real_world_validation_report.md | 🟢 Green | 2026-07-09 05:02:27 | kept; stale yellow cleared | -| README.md | 🟢 Green | 2026-11-03 05:34:35 | Address the 3 Nov review findings (subid=review-2026-11-03) | +| README.md | 🟢 Green | 2026-11-03 05:49:16 | Check impl items and resolve workspace member crates (subid=impl-workspace) | | docs/spec.md | 🟢 Green | 2026-11-03 08:00:11 | Name the Rust binary and protocol v0.7 in the living principles (subid=protocol-v07) | -| skills/run.md | 🟢 Green | 2026-11-03 05:34:35 | Address the 3 Nov review findings (subid=review-2026-11-03) | +| skills/run.md | 🟢 Green | 2026-11-03 05:49:16 | Check impl items and resolve workspace member crates (subid=impl-workspace) | | src/api.rs | 🟢 Green | 2026-11-03 05:34:35 | Address the 3 Nov review findings (subid=review-2026-11-03) | -| src/artifacts.rs | 🟢 Green | 2026-11-03 05:35:24 | Wiki names the public health and journal types (subid=review-w3) | -| src/cache.rs | 🟢 Green | 2026-11-03 05:35:24 | Wiki names the cache index surface (subid=review-w3) | -| src/cache_commit.rs | 🟢 Green | 2026-11-03 05:34:35 | Address the 3 Nov review findings (subid=review-2026-11-03) | -| src/jsonutil.rs | 🟢 Green | 2026-11-03 05:34:35 | Address the 3 Nov review findings (subid=review-2026-11-03) | +| src/artifacts.rs | 🟢 Green | 2026-11-03 05:49:23 | Wiki names impl items (subid=impl-workspace) | +| src/cache.rs | 🟢 Green | 2026-11-03 05:49:23 | Wiki names impl items (subid=impl-workspace) | +| src/cache_commit.rs | 🟢 Green | 2026-11-03 05:49:23 | Wiki names impl items (subid=impl-workspace) | +| src/jsonutil.rs | 🟢 Green | 2026-11-03 05:49:23 | Wiki names impl items (subid=impl-workspace) | | src/legacy_cache.rs | 🟢 Green | 2026-11-03 05:34:35 | Address the 3 Nov review findings (subid=review-2026-11-03) | | src/lib.rs | 🟢 Green | 2026-11-03 05:35:24 | Wiki names the crate re-exports (subid=review-w3) | | src/main.rs | 🟢 Green | 2026-11-03 05:34:35 | Address the 3 Nov review findings (subid=review-2026-11-03) | | src/maps_out.rs | 🟢 Green | 2026-11-03 05:35:24 | Wiki names read_status (subid=review-w3) | | src/mcp.rs | 🟢 Green | 2026-11-03 05:34:35 | Address the 3 Nov review findings (subid=review-2026-11-03) | -| src/parse.rs | 🟢 Green | 2026-11-03 05:34:35 | Address the 3 Nov review findings (subid=review-2026-11-03) | +| src/parse.rs | 🟢 Green | 2026-11-03 05:49:16 | Check impl items and resolve workspace member crates (subid=impl-workspace) | | src/proc.rs | 🟡 Yellow | 2026-11-03 07:58:59 | Initial stub | -| src/project_lock.rs | 🟢 Green | 2026-11-03 05:35:15 | Wiki names ProjectLock after Rust symbol checks (subid=review-w3) | +| src/project_lock.rs | 🟢 Green | 2026-11-03 05:49:23 | Wiki names impl items (subid=impl-workspace) | | src/scope.rs | 🟡 Yellow | 2026-11-03 07:58:59 | Initial stub | | src/sha256.rs | 🟡 Yellow | 2026-11-03 07:58:59 | Initial stub | | src/timeutil.rs | 🟡 Yellow | 2026-11-03 07:58:59 | Initial stub | -| tests/behaviour_gaps.rs | 🟢 Green | 2026-11-03 05:34:35 | Address the 3 Nov review findings (subid=review-2026-11-03) | +| tests/behaviour_gaps.rs | 🟢 Green | 2026-11-03 05:49:16 | Check impl items and resolve workspace member crates (subid=impl-workspace) | | tests/cache_and_artifacts.rs | 🟡 Yellow | 2026-11-03 07:58:59 | Initial stub | | tests/cli_surface.rs | 🟡 Yellow | 2026-11-03 07:58:59 | Initial stub | | tests/contract_gaps.rs | 🟡 Yellow | 2026-11-03 07:58:59 | Initial stub | | tests/dogfood.rs | 🟢 Green | 2026-11-03 05:34:35 | Address the 3 Nov review findings (subid=review-2026-11-03) | | tests/parity.rs | 🟢 Green | 2026-11-03 05:34:35 | Address the 3 Nov review findings (subid=review-2026-11-03) | -| tests/protocol_v07.rs | 🟢 Green | 2026-11-03 05:34:35 | Address the 3 Nov review findings (subid=review-2026-11-03) | +| tests/protocol_v07.rs | 🟢 Green | 2026-11-03 05:49:16 | Check impl items and resolve workspace member crates (subid=impl-workspace) | | tests/robustness.rs | 🟡 Yellow | 2026-11-03 07:58:59 | Initial stub | | tests/skip_maps.rs | 🟡 Yellow | 2026-11-03 07:58:59 | Initial stub | | wikifier.sh | 🟢 Green | 2026-11-03 08:06:46 | Wiki describes the Rust binary launcher (subid=protocol-v07) | diff --git a/journal/2026/11/03.md b/journal/2026/11/03.md index 422e6a3..565d1bd 100644 --- a/journal/2026/11/03.md +++ b/journal/2026/11/03.md @@ -254,3 +254,35 @@ **File:** tests/protocol_v07.rs **Reason:** Address the 3 Nov review findings (subid=review-2026-11-03) +## [2026-11-03 05:49:16] record-change +**File:** AGENTS.md +**Reason:** Check impl items and resolve workspace member crates (subid=impl-workspace) + +## [2026-11-03 05:49:16] record-change +**File:** CHANGELOG.md +**Reason:** Check impl items and resolve workspace member crates (subid=impl-workspace) + +## [2026-11-03 05:49:16] record-change +**File:** CLAUDE.md +**Reason:** Check impl items and resolve workspace member crates (subid=impl-workspace) + +## [2026-11-03 05:49:16] record-change +**File:** README.md +**Reason:** Check impl items and resolve workspace member crates (subid=impl-workspace) + +## [2026-11-03 05:49:16] record-change +**File:** skills/run.md +**Reason:** Check impl items and resolve workspace member crates (subid=impl-workspace) + +## [2026-11-03 05:49:16] record-change +**File:** src/parse.rs +**Reason:** Check impl items and resolve workspace member crates (subid=impl-workspace) + +## [2026-11-03 05:49:16] record-change +**File:** tests/behaviour_gaps.rs +**Reason:** Check impl items and resolve workspace member crates (subid=impl-workspace) + +## [2026-11-03 05:49:16] record-change +**File:** tests/protocol_v07.rs +**Reason:** Check impl items and resolve workspace member crates (subid=impl-workspace) + diff --git a/skills/run.md b/skills/run.md index 0d0fbf6..22ee8b0 100644 --- a/skills/run.md +++ b/skills/run.md @@ -73,7 +73,7 @@ Never skip record_change after an edit; never skip mark_green after refreshing t ### Wiki verification (mark_green) -`mark_green` compares the file's public symbols (Python: `__all__` or top-level public functions/classes; JS/TS: exports; Rust: top-level `pub` items) with those recorded at the previous mark-green, and refuses (`error_type: "WikiVerificationError"`, `issues[]`) when: +`mark_green` compares the file's public symbols (Python: `__all__` or top-level public functions/classes; JS/TS: exports; Rust: unrestricted `pub` items, impl methods, and trait-impl items) with those recorded at the previous mark-green, and refuses (`error_type: "WikiVerificationError"`, `issues[]`) when: | Issue code | Meaning | |---|---| @@ -157,8 +157,8 @@ The program is a std-only Rust binary under `src/`. `Cargo.toml` has an empty `[ - `dependencies --full` returns `imports[]` with `confidence_score`, `confidence_reasons`, `acs`, `is_dynamic`, `is_conditional`, and `in_cycle`. Compact rows always include `conditional`, `dynamic`, `via_barrel`, and `confidence`. - `record_deletion` appends the journal line, marks the health row Red, drops the file from the graph, and removes its pending line. The file does not have to exist. - `record_changes` matches reasons by exact path, then the longest `dir/` prefix, then the longest glob (`*` and `?`). `--only`, `--green`, `--dry-run`, and deletions are applied. `--green` lists verification failures and leaves those files Yellow. -- `mark_green` extracts Python top-level `def`/`class` names (or `__all__`), JS/TS `export` names, and Rust top-level `pub` items (`fn`, `struct`, `enum`, `trait`, `mod`, `type`, `const`, `static`, and `pub use` names). Each current name must appear in the wiki. `pub(crate)` and items inside `impl` are not Rust public names. With a previous baseline, a removed name still wrapped in backticks is `wiki_mentions_removed_symbols`, and a changed public surface with an unchanged wiki hash is `wiki_not_updated_for_api_change`. Go, C/C++, C#, and Java have no symbol extractor, so their rows are `unchecked`. `health --summary` includes `green_breakdown`. A successful mark removes that file's pending line. -- The scan ignores comments and strings. It does not use CPython `ast`. It does not read tsconfig `paths`, package `exports`, or workspace maps. Named re-exports are the barrel follow. Rust reads the root `Cargo.toml` `[package].name` and `[lib].name` only (hyphens become underscores) and resolves `crate::`, `self::`, `super::`, that crate name, and `mod` to files. `crate::` stays in the file's crate (`tests/*.rs` and `src/bin/*.rs` are their own crates). A package or lib name points at the library. Accuracy is `accuracy_fixtures_match_frozen_edges`, including the Rust crate layout. This parser has not been run on Babylon.js. +- `mark_green` extracts Python top-level `def`/`class` names (or `__all__`), JS/TS `export` names, and Rust unrestricted `pub` items (`fn`, `struct`, `enum`, `trait`, `mod`, `type`, `const`, `static`, and `pub use` names), plus `pub` methods and associated items on a public type and the items written in a trait impl for that type. Each current name must appear in the wiki. `pub(crate)`, `pub(super)`, `pub(self)`, and `pub(in ...)` are not Rust public names. With a previous baseline, a removed name still wrapped in backticks is `wiki_mentions_removed_symbols`, and a changed public surface with an unchanged wiki hash is `wiki_not_updated_for_api_change`. Go, C/C++, C#, and Java have no symbol extractor, so their rows are `unchecked`. `health --summary` includes `green_breakdown`. A successful mark removes that file's pending line. +- The scan ignores comments and strings. It does not use CPython `ast`. It does not read tsconfig `paths`, package `exports`, or npm workspace maps. Named re-exports are the barrel follow. Rust reads the root `Cargo.toml` `[package].name`, `[lib].name`, and `[workspace] members` (globs included; hyphens become underscores). It resolves `crate::`, `self::`, `super::`, those crate names, and `mod` to files. `crate::` stays in the file's crate (`tests/*.rs` and `src/bin/*.rs` are their own crates, and a workspace member does not share `crate::` with another member). A package or lib name points at that member's library. Accuracy is `accuracy_fixtures_match_frozen_edges`, including the Rust crate layout. This parser has not been run on Babylon.js. - MCP `tools/call` sets `isError` when the payload has `success: false`. `resources/read` and `prompts/get` answer. A call waits at most `WIKIFIER_MCP_DEADLINE_S` seconds (default 60) and then returns `timed_out` and `may_still_complete`. An identical journal action, file, and reason inside 120 seconds sets `duplicate_skipped` and does not append another line. - Commands that write health, pending, the map, or the cache take `.wikifier_staging/.lock` before the cache lock. The lock is re-entrant on the same thread, waits 30 seconds, and is stolen when the recorded pid is dead. A timeout returns `error_type: "LockTimeoutError"` and `timed_out: true`. - `init` keeps a path list that already has a non-comment entry. It seeds `src/` only when a list is empty and `src/` exists. It copies `index.html` only when that file is absent, and it writes `.wikifier/marker`. diff --git a/skills/run.md.wiki.md b/skills/run.md.wiki.md index 0494c95..59c22fc 100644 --- a/skills/run.md.wiki.md +++ b/skills/run.md.wiki.md @@ -2,6 +2,6 @@ **Role:** Agent Protocol v0.7. This is the contract for the daily loop. `AGENTS.md` is the repository entry point and points here. -Part 1 is the session loop: bootstrap, check-changes, suggest-next, prepare-edit, record-change, mark-green, update-maps. Part 2 is the reference for health, wiki verification, batch recording, dependencies, and commands. Wiki verification covers Python, JS/TS, and Rust top-level `pub` items. +Part 1 is the session loop: bootstrap, check-changes, suggest-next, prepare-edit, record-change, mark-green, update-maps. Part 2 is the reference for health, wiki verification, batch recording, dependencies, and commands. Wiki verification covers Python, JS/TS, and Rust `pub` items including impl methods and trait-impl items. Rust import names include `[workspace] members`. The implementation note records how the std-only Rust binary fills that contract: `wk_cache`, regex-plus-reexport parsing, Rust paths from `crate::` / `self::` / `super::` / the root Cargo.toml crate name, map artifacts, deletion, MCP errors, the project lock, and a standing `serve` that feeds the dashboard. Daemon actions are `start`, `stop`, `status`, and `run`. `init DIR`, `verify-wikis DIR`, and `search-journal --file` apply. The protocol version stays v0.7. The live version command is `wikifier --version`. diff --git a/src/artifacts.rs.wiki.md b/src/artifacts.rs.wiki.md index d07035d..3439dd5 100644 --- a/src/artifacts.rs.wiki.md +++ b/src/artifacts.rs.wiki.md @@ -6,4 +6,4 @@ `append_journal` skips an identical action, file, and reason inside 120 seconds. `folder_card` reads `.wikifier_staging/maps/_root.json` or a directory `_index.json`. -Public names: `HealthRow`, `read_health`, `write_health`, `upsert_health_row`, `JournalEntry`, `journal_path`, `read_journal`, `parse_journal`, `JournalWrite`, `append_journal`, `read_pending`, `folder_card`. +Public names: `HealthRow`, `new`, `keep_baseline_from`, `read_health`, `write_health`, `upsert_health_row`, `JournalEntry`, `journal_path`, `read_journal`, `parse_journal`, `JournalWrite`, `append_journal`, `read_pending`, `folder_card`. diff --git a/src/cache.rs.wiki.md b/src/cache.rs.wiki.md index f1a9a21..f24e539 100644 --- a/src/cache.rs.wiki.md +++ b/src/cache.rs.wiki.md @@ -6,4 +6,4 @@ Each mapped file has a payload, a `by_src` edge list, a `by_dst` edge list, note Confidence is computed when an edge is read: dynamic 0.4, conditional 0.55, via barrel 0.7, static 0.9. The ACS reason is `dynamic_literal`, `conditional_guard`, `via_barrel`, or `resolved_static`. -Public names: `check_consistency`, `rebuild`, `Consistency`, `Session`, `payload_path`, `by_src_path`, `commit_pending_path`, `load_dependencies`, `InternalEdge`, `confidence_of`, `format_edge_line`, `DependencyView`, `load_dependency_view`, `load_dependents`, `list_prefix`, `all_paths`, `FileUpdate`, `upsert_file`, `remove_file`, `indexed_rows`, `payload_bytes`, `stat_ns`, `edges`, `rel_string`, `write_barrel_record`, `prune_barrel_files`. +Public names: `check_consistency`, `rebuild`, `Consistency`, `Session`, `payload_path`, `by_src_path`, `commit_pending_path`, `load_dependencies`, `InternalEdge`, `confidence`, `acs_reason`, `confidence_of`, `format_edge_line`, `DependencyView`, `load_dependency_view`, `load_dependents`, `list_prefix`, `all_paths`, `FileUpdate`, `upsert_file`, `remove_file`, `indexed_rows`, `payload_bytes`, `stat_ns`, `edges`, `rel_string`, `write_barrel_record`, `prune_barrel_files`. diff --git a/src/cache_commit.rs.wiki.md b/src/cache_commit.rs.wiki.md index ba5895e..6916418 100644 --- a/src/cache_commit.rs.wiki.md +++ b/src/cache_commit.rs.wiki.md @@ -3,3 +3,5 @@ **Role:** One commit for a map run. The `Session` edits the index in memory and writes `by_src` before `by_dst`, with the generation file last. A crash leaves `commit.pending`. Unresolved notes are stored as `U`, the raw specifier, the line, and a reason. Removing a file also drops its barrel record on commit. The cache lock is `wk.lock`. `check_consistency` compares the forward and reverse indexes. `rebuild` rewrites them. `Consistency` is that comparison's result. + +`Session` methods: `open`, `known`, `dsts_of`, `note_stat`, `rels`, `upsert`, `remove`, `stage_barrel`, `commit`. diff --git a/src/jsonutil.rs.wiki.md b/src/jsonutil.rs.wiki.md index f301af3..4c82ed7 100644 --- a/src/jsonutil.rs.wiki.md +++ b/src/jsonutil.rs.wiki.md @@ -4,4 +4,4 @@ `parse` reads a JSON document. `parse_jsonc` and `strip_json_comments` accept a tsconfig-style document with comments. `Json` is the value: null, bool, int, float, string, array, and object. -Public names: `Json`, `parse`, `parse_jsonc`, `strip_json_comments`. +Public names: `Json`, `obj`, `from_pairs`, `get`, `as_str`, `as_bool`, `as_array`, `as_object`, `insert`, `fmt`, `parse`, `parse_jsonc`, `strip_json_comments`. diff --git a/src/parse.rs b/src/parse.rs index c66de0d..6ab4eba 100644 --- a/src/parse.rs +++ b/src/parse.rs @@ -182,11 +182,18 @@ fn defining_file(resolver: &Resolver, scans: &BTreeMap, file: &str None } +struct RustLib { + names: Vec, + root_file: String, + dir: String, + exported: bool, +} + struct Resolver { files: BTreeSet, ts_paths: Vec<(String, String, String)>, packages: Vec, - rust_crates: Vec, + rust_libs: Vec, } struct Package { @@ -200,8 +207,8 @@ impl Resolver { let files: BTreeSet = rels.iter().cloned().collect(); let ts_paths = load_ts_paths(root); let packages = load_packages(root); - let rust_crates = load_rust_crate_names(root); - Resolver { files, ts_paths, packages, rust_crates } + let rust_libs = load_rust_libs(root); + Resolver { files, ts_paths, packages, rust_libs } } fn resolve(&self, from_rel: &str, spec: &str, lang: &str, inline_depth: usize) -> Option { @@ -328,10 +335,10 @@ impl Resolver { let dir = root.as_deref().map(rust_submodule_dir).unwrap_or_else(|| PathBuf::from("src")); return self.rust_walk(&dir, &segments[1..], root.as_deref()); } - if self.rust_crates.iter().any(|name| name == head) { - let root = self.library_root_file(); - let dir = root.as_deref().map(rust_submodule_dir).unwrap_or_else(|| PathBuf::from("src")); - return self.rust_walk(&dir, &segments[1..], root.as_deref()); + if let Some(lib) = self.lib_by_name(head) { + let root_file = lib.root_file.clone(); + let dir = rust_submodule_dir(&root_file); + return self.rust_walk(&dir, &segments[1..], Some(&root_file)); } let mut dir = rust_submodule_dir(from_rel); let mut depth = inline_depth; @@ -366,7 +373,7 @@ impl Resolver { if matches!(head, "crate" | "self" | "super") { return true; } - if self.rust_crates.iter().any(|name| name == head) { + if self.lib_by_name(head).is_some() { return true; } self.rust_child_exists(&rust_submodule_dir(from_rel), head) @@ -451,14 +458,39 @@ impl Resolver { } } + fn lib_by_name(&self, name: &str) -> Option<&RustLib> { + self.rust_libs.iter().find(|lib| lib.exported && lib.names.iter().any(|have| have == name)) + } + + fn package_for(&self, from_rel: &str) -> Option<&RustLib> { + let mut best: Option<&RustLib> = None; + for lib in &self.rust_libs { + let matches = lib.dir.is_empty() || from_rel == lib.dir || from_rel.starts_with(&(lib.dir.clone() + "/")); + if !matches { + continue; + } + if best.as_ref().map(|have| lib.dir.len() > have.dir.len()).unwrap_or(true) { + best = Some(lib); + } + } + best + } + fn file_crate_root(&self, from_rel: &str) -> Option { - if let Some(root) = separate_crate_root(from_rel) { + let package_dir = self.package_for(from_rel).map(|lib| lib.dir.clone()).unwrap_or_default(); + if let Some(root) = separate_in_package(from_rel, &package_dir) { if self.files.contains(&root) { return Some(root); } } - if from_rel == "src/main.rs" { - return Some("src/main.rs".into()); + let main_rs = if package_dir.is_empty() { "src/main.rs".to_string() } else { format!("{package_dir}/src/main.rs") }; + if from_rel == main_rs { + return Some(main_rs); + } + if let Some(lib) = self.package_for(from_rel) { + if self.files.contains(&lib.root_file) { + return Some(lib.root_file.clone()); + } } self.library_root_file() } @@ -1506,26 +1538,43 @@ fn rust_submodule_dir(from_rel: &str) -> PathBuf { parent.join(stem) } -fn separate_crate_root(from_rel: &str) -> Option { - let parts: Vec<&str> = from_rel.split('/').filter(|part| !part.is_empty()).collect(); +fn separate_in_package(from_rel: &str, dir: &str) -> Option { + let rest = if dir.is_empty() { + from_rel.to_string() + } else { + from_rel.strip_prefix(&format!("{dir}/"))?.to_string() + }; + let parts: Vec<&str> = rest.split('/').filter(|part| !part.is_empty()).collect(); if parts.len() >= 2 && matches!(parts[0], "tests" | "examples" | "benches") { let stem = parts[1].trim_end_matches(".rs"); - return Some(format!("{}/{stem}.rs", parts[0])); + return Some(join_rel(dir, &format!("{}/{stem}.rs", parts[0]))); } if parts.len() >= 3 && parts[0] == "src" && parts[1] == "bin" { let stem = parts[2].trim_end_matches(".rs"); - return Some(format!("src/bin/{stem}.rs")); + return Some(join_rel(dir, &format!("src/bin/{stem}.rs"))); } None } -/// Top-level `pub` items. `pub(crate)` and items inside `impl` or a function are not part of this set. +fn join_rel(dir: &str, rest: &str) -> String { + if dir.is_empty() { rest.to_string() } else { format!("{dir}/{rest}") } +} + +/// Public Rust names: unrestricted `pub` items, `pub` methods and associated items on a +/// public type, and items written in a trait impl for a public type. +/// `pub(crate)`, `pub(super)`, `pub(self)`, and `pub(in ...)` stay out. Items inside a +/// function or a nested module stay out. pub fn rust_public_symbols(text: &str) -> Vec { let b = text.as_bytes(); let mut symbols = Vec::new(); + let mut public_types = BTreeSet::new(); + let mut known_types = BTreeSet::new(); + let mut pending: Vec<(String, String)> = Vec::new(); + let mut impls: Vec<(i32, bool, String)> = Vec::new(); let mut i = 0; let mut brace: i32 = 0; while i < b.len() { + impls.retain(|(depth, _, _)| brace >= *depth); if b[i].is_ascii_whitespace() { i += 1; continue; @@ -1552,31 +1601,211 @@ pub fn rust_public_symbols(text: &str) -> Vec { i += 1; continue; } + let at_impl_body = impls.last().is_some_and(|(depth, _, _)| brace == *depth); + if brace == 0 && starts_word(b, i, "impl") { + if let Some((brace_at, trait_impl, type_name)) = read_impl_header(b, i + 4) { + brace += 1; + impls.push((brace, trait_impl, type_name)); + i = brace_at + 1; + continue; + } + } if brace == 0 && starts_word(b, i, "pub") { - let after = i + 3; - if after < b.len() && b[after] == b'(' { - i = after; + if pub_is_restricted(b, i) { + let after = skip_restricted_pub(b, i); + if let Some((name, end)) = read_type_name(b, after) { + known_types.insert(name); + i = end; + continue; + } + i = after.max(i + 3); continue; } - if let Some((names, end)) = rust_pub_names(b, after) { - for name in names { - if !name.is_empty() && !symbols.iter().any(|have: &String| have == &name) { - symbols.push(name); + if let Some((names, end)) = rust_pub_names(b, i + 3) { + if let Some((type_name, _)) = read_type_name(b, i + 3) { + if names.iter().any(|name| name == &type_name) { + public_types.insert(type_name.clone()); + known_types.insert(type_name); } } + for name in names { + push_unique(&mut symbols, name); + } i = end; continue; } } + if brace == 0 { + if let Some((name, end)) = read_type_name(b, i) { + known_types.insert(name); + i = end; + continue; + } + } + if at_impl_body { + if let Some((_, trait_impl, type_name)) = impls.last() { + let type_name = type_name.clone(); + if *trait_impl { + if let Some((name, end)) = read_assoc_item(b, i) { + pending.push((type_name, name)); + i = end; + continue; + } + } else if starts_word(b, i, "pub") && !pub_is_restricted(b, i) { + if let Some((names, end)) = rust_pub_names(b, i + 3) { + for name in names { + pending.push((type_name.clone(), name)); + } + i = end; + continue; + } + } + } + } if let Some((_, end)) = read_ident(b, i) { i = end; continue; } i += 1; } + for (type_name, name) in pending { + let visible = public_types.contains(&type_name) || !known_types.contains(&type_name); + if visible { + push_unique(&mut symbols, name); + } + } symbols } +fn push_unique(symbols: &mut Vec, name: String) { + if !name.is_empty() && !symbols.iter().any(|have| have == &name) { + symbols.push(name); + } +} + +fn pub_is_restricted(b: &[u8], i: usize) -> bool { + let j = skip_noise(b, i + 3); + j < b.len() && b[j] == b'(' +} + +fn skip_restricted_pub(b: &[u8], i: usize) -> usize { + let mut j = skip_noise(b, i + 3); + if j >= b.len() || b[j] != b'(' { + return i + 3; + } + let mut depth = 0i32; + while j < b.len() { + if b[j] == b'(' { + depth += 1; + } else if b[j] == b')' { + depth -= 1; + j += 1; + if depth == 0 { + break; + } + continue; + } + j += 1; + } + j +} + +fn read_type_name(b: &[u8], start: usize) -> Option<(String, usize)> { + let mut i = skip_noise(b, start); + if starts_word(b, i, "unsafe") { + i = skip_noise(b, i + "unsafe".len()); + } + for word in ["struct", "enum", "union", "trait", "type"] { + if starts_word(b, i, word) { + let (name, end) = read_ident(b, skip_noise(b, i + word.len()))?; + return Some((name, end)); + } + } + None +} + +fn read_impl_header(b: &[u8], start: usize) -> Option<(usize, bool, String)> { + let mut i = skip_noise(b, start); + let mut angle = 0i32; + let mut trait_impl = false; + let mut in_where = false; + let mut last_ident = String::new(); + while i < b.len() { + if b[i].is_ascii_whitespace() { + i += 1; + continue; + } + if b[i] == b'/' && i + 1 < b.len() && (b[i + 1] == b'/' || b[i + 1] == b'*') { + i = skip_rust_comment(b, i); + continue; + } + if b[i] == b'\'' { + i = skip_rust_char_or_lifetime(b, i); + continue; + } + if b[i] == b'<' { + angle += 1; + i += 1; + continue; + } + if b[i] == b'>' { + angle -= 1; + i += 1; + continue; + } + if b[i] == b'{' && angle <= 0 { + if last_ident.is_empty() { + return None; + } + return Some((i, trait_impl, last_ident)); + } + if angle == 0 && !in_where && starts_word(b, i, "where") { + in_where = true; + i += "where".len(); + continue; + } + if angle == 0 && !in_where && starts_word(b, i, "for") { + trait_impl = true; + i += 3; + continue; + } + if let Some((ident, end)) = read_ident(b, i) { + if angle == 0 && !in_where && ident != "const" && ident != "unsafe" && ident != "async" { + last_ident = ident; + } + i = end; + continue; + } + i += 1; + } + None +} + +fn read_assoc_item(b: &[u8], start: usize) -> Option<(String, usize)> { + let mut i = skip_noise(b, start); + if starts_word(b, i, "unsafe") { + i = skip_noise(b, i + "unsafe".len()); + } + if starts_word(b, i, "async") { + i = skip_noise(b, i + "async".len()); + } + if starts_word(b, i, "const") { + let next = skip_noise(b, i + "const".len()); + if starts_word(b, next, "fn") { + i = next; + } else { + let (name, end) = read_ident(b, next)?; + return Some((name, end)); + } + } + if starts_word(b, i, "fn") || starts_word(b, i, "type") { + let word = if starts_word(b, i, "fn") { "fn" } else { "type" }; + let (name, end) = read_ident(b, skip_noise(b, i + word.len()))?; + return Some((name, end)); + } + None +} + fn rust_pub_names(b: &[u8], start: usize) -> Option<(Vec, usize)> { let mut i = skip_noise(b, start); loop { @@ -1670,17 +1899,86 @@ fn use_export_names(b: &[u8], start: usize) -> Option<(Vec, usize)> { Some((names, end)) } -fn load_rust_crate_names(root: &Path) -> Vec { - let text = fs::read_to_string(root.join("Cargo.toml")).unwrap_or_default(); +fn load_rust_libs(root: &Path) -> Vec { + let root_text = fs::read_to_string(root.join("Cargo.toml")).unwrap_or_default(); + let (members, exclude) = workspace_arrays(&root_text); + let mut dirs: Vec = Vec::new(); + if cargo_package_name(&root_text).is_some() { + dirs.push(String::new()); + } + for dir in expand_member_dirs(root, &members, &exclude) { + if !dirs.contains(&dir) { + dirs.push(dir); + } + } + // Excluded packages stay in the list so `crate::` does not jump to another crate, + // but their names are not importable from outside. + for dir in expand_member_dirs(root, &exclude, &[]) { + if !dirs.contains(&dir) { + dirs.push(dir); + } + } + let mut libs = Vec::new(); + for dir in dirs { + let exported = dir.is_empty() || expand_member_dirs(root, &members, &exclude).contains(&dir) || (members.is_empty() && dir.is_empty()); + if let Some(lib) = rust_lib_at(root, &dir, exported && !exclude_matches(&exclude, &dir)) { + libs.push(lib); + } + } + libs +} + +fn exclude_matches(exclude: &[String], dir: &str) -> bool { + !dir.is_empty() && exclude.iter().any(|pat| cargo_glob_match(pat, dir)) +} + +fn rust_lib_at(root: &Path, dir: &str, exported: bool) -> Option { + let text = fs::read_to_string(root.join(dir).join("Cargo.toml")).ok()?; + let (package, lib_name, lib_path) = cargo_identity(&text); + if package.is_empty() && lib_name.is_empty() { + return None; + } + let lib_on_disk = root.join(dir).join("src/lib.rs").is_file(); + let main_on_disk = root.join(dir).join("src/main.rs").is_file(); + let has_lib = lib_path.is_some() || lib_on_disk; + let root_file = if let Some(path) = lib_path { + join_rel(dir, &path) + } else if lib_on_disk { + join_rel(dir, "src/lib.rs") + } else if main_on_disk { + join_rel(dir, "src/main.rs") + } else { + return None; + }; + let mut names = Vec::new(); + if !package.is_empty() { + names.push(package.replace('-', "_")); + } + if !lib_name.is_empty() { + let normalized = lib_name.replace('-', "_"); + if !names.contains(&normalized) { + names.push(normalized); + } + } + Some(RustLib { names, root_file, dir: dir.to_string(), exported: exported && has_lib }) +} + +fn cargo_package_name(text: &str) -> Option { + let (package, _, _) = cargo_identity(text); + if package.is_empty() { None } else { Some(package) } +} + +fn cargo_identity(text: &str) -> (String, String, Option) { let mut section = String::new(); let mut package = String::new(); let mut lib_name = String::new(); + let mut lib_path = None; for line in text.lines() { let line = line.split('#').next().unwrap_or("").trim(); if line.is_empty() { continue; } - if line.starts_with('[') && line.ends_with(']') { + if line.starts_with('[') && line.ends_with(']') && !line.starts_with("[[") { section = line[1..line.len() - 1].trim().to_string(); continue; } @@ -1688,30 +1986,183 @@ fn load_rust_crate_names(root: &Path) -> Vec { continue; } let Some((key, value)) = line.split_once('=') else { continue }; - if key.trim() != "name" { + let key = key.trim(); + let value = unquote_toml(value.trim()); + if value.is_empty() { continue; } - let value = value.trim().trim_matches(|c| c == '"' || c == '\''); - if value.is_empty() { + if section == "package" && key == "name" && package.is_empty() { + package = value; + } else if section == "lib" && key == "name" && lib_name.is_empty() { + lib_name = value; + } else if section == "lib" && key == "path" && lib_path.is_none() { + lib_path = Some(value); + } + } + (package, lib_name, lib_path) +} + +fn unquote_toml(value: &str) -> String { + value.trim().trim_matches(|c| c == '"' || c == '\'').to_string() +} + +fn workspace_arrays(text: &str) -> (Vec, Vec) { + let mut section = String::new(); + let mut members = Vec::new(); + let mut exclude = Vec::new(); + let mut key = String::new(); + let mut buf = String::new(); + let mut collecting = false; + for line in text.lines() { + let line = line.split('#').next().unwrap_or("").trim(); + if line.is_empty() { continue; } - if section == "package" && package.is_empty() { - package = value.to_string(); - } else if section == "lib" && lib_name.is_empty() { - lib_name = value.to_string(); + if line.starts_with('[') && line.ends_with(']') && !line.starts_with("[[") { + section = line[1..line.len() - 1].trim().to_string(); + collecting = false; + continue; + } + if section != "workspace" { + continue; + } + if collecting { + buf.push(' '); + buf.push_str(line); + if line.contains(']') { + let items = quoted_strings(&buf); + if key == "members" { members = items; } + else if key == "exclude" { exclude = items; } + collecting = false; + } + continue; + } + let Some((raw_key, value)) = line.split_once('=') else { continue }; + let raw_key = raw_key.trim(); + if raw_key != "members" && raw_key != "exclude" { + continue; + } + if value.contains('[') && !value.contains(']') { + key = raw_key.to_string(); + buf = value.to_string(); + collecting = true; + } else { + let items = quoted_strings(value); + if raw_key == "members" { members = items; } + else if raw_key == "exclude" { exclude = items; } } } - let mut names = Vec::new(); - if !package.is_empty() { - names.push(package.replace('-', "_")); + (members, exclude) +} + +fn quoted_strings(text: &str) -> Vec { + let b = text.as_bytes(); + let mut out = Vec::new(); + let mut i = 0; + while i < b.len() { + if b[i] == b'"' || b[i] == b'\'' { + let quote = b[i]; + i += 1; + let start = i; + while i < b.len() && b[i] != quote { + i += 1; + } + out.push(String::from_utf8_lossy(&b[start..i]).into_owned()); + if i < b.len() { + i += 1; + } + continue; + } + i += 1; } - if !lib_name.is_empty() { - let normalized = lib_name.replace('-', "_"); - if !names.contains(&normalized) { - names.push(normalized); + out +} + +fn expand_member_dirs(root: &Path, patterns: &[String], exclude: &[String]) -> Vec { + let mut found = Vec::new(); + for pattern in patterns { + let pattern = pattern.trim().trim_matches('/').to_string(); + if pattern.is_empty() || pattern == "." { + if root.join("Cargo.toml").is_file() { + found.push(String::new()); + } + continue; + } + if pattern.contains('*') || pattern.contains('?') { + for dir in cargo_package_dirs(root) { + if cargo_glob_match(&pattern, &dir) && !exclude_matches(exclude, &dir) && !found.contains(&dir) { + found.push(dir); + } + } + } else if root.join(&pattern).join("Cargo.toml").is_file() && !exclude_matches(exclude, &pattern) && !found.contains(&pattern) { + found.push(pattern); + } + } + found +} + +fn cargo_package_dirs(root: &Path) -> Vec { + let mut out = Vec::new(); + fn walk(root: &Path, dir: &Path, out: &mut Vec) { + let Ok(entries) = fs::read_dir(dir) else { return }; + for entry in entries.flatten() { + let path = entry.path(); + if !path.is_dir() { + continue; + } + let name = entry.file_name().to_string_lossy().to_string(); + if name.starts_with('.') || name == "target" || name == "node_modules" { + continue; + } + if path.join("Cargo.toml").is_file() { + if let Ok(rel) = path.strip_prefix(root) { + let rel = rel.components().map(|c| c.as_os_str().to_string_lossy()).collect::>().join("/"); + if !rel.is_empty() { + out.push(rel); + } + } + } + walk(root, &path, out); + } + } + walk(root, root, &mut out); + out.sort(); + out.dedup(); + out +} + +fn cargo_glob_match(pattern: &str, path: &str) -> bool { + let pat: Vec<&str> = pattern.split('/').filter(|part| !part.is_empty()).collect(); + let segs: Vec<&str> = path.split('/').filter(|part| !part.is_empty()).collect(); + fn rec(pat: &[&str], segs: &[&str]) -> bool { + if pat.is_empty() { + return segs.is_empty(); + } + if pat[0] == "**" { + return rec(&pat[1..], segs) || (!segs.is_empty() && rec(pat, &segs[1..])); + } + if segs.is_empty() { + return false; + } + segment_glob(pat[0], segs[0]) && rec(&pat[1..], &segs[1..]) + } + rec(&pat, &segs) +} + +fn segment_glob(pattern: &str, segment: &str) -> bool { + fn rec(pat: &[u8], seg: &[u8]) -> bool { + if pat.is_empty() { + return seg.is_empty(); + } + if pat[0] == b'*' { + return rec(&pat[1..], seg) || (!seg.is_empty() && rec(pat, &seg[1..])); + } + if pat[0] == b'?' { + return !seg.is_empty() && rec(&pat[1..], &seg[1..]); } + !seg.is_empty() && pat[0] == seg[0] && rec(&pat[1..], &seg[1..]) } - names + rec(pattern.as_bytes(), segment.as_bytes()) } fn scan_go(src: &str) -> Scan { diff --git a/src/parse.rs.wiki.md b/src/parse.rs.wiki.md index 73ed1d5..38533ac 100644 --- a/src/parse.rs.wiki.md +++ b/src/parse.rs.wiki.md @@ -8,4 +8,4 @@ Rust `mod` names a file beside the declaring module (`lib.rs`, `main.rs`, and `m The scan does not use CPython `ast` and does not read tsconfig paths, package exports, or workspaces. The frozen fixtures, including the Cargo crate layout, are the accuracy gate. -Public scan entry points: `language`, `read_utf8`, `scan_file_warned`, `scan_file`, `resolve_project`, `resolve_python_spec`, `analyze_files`, `analyze_changed`, `edges_for_files`, `rust_public_symbols`. Public types: `RawImport`, `Kind`, `Scan`, `FactEdge`, `FileFacts`, `Analysis`. `rust_public_symbols` lists top-level `pub` items (`fn`, `struct`, `enum`, `trait`, `mod`, `type`, `const`, `static`, and the names a `pub use` exports). `pub(crate)` and items inside `impl` are left out. +Public scan entry points: `language`, `read_utf8`, `scan_file_warned`, `scan_file`, `resolve_project`, `resolve_python_spec`, `analyze_files`, `analyze_changed`, `edges_for_files`, `rust_public_symbols`. Public types: `RawImport`, `Kind`, `Scan`, `FactEdge`, `FileFacts`, `Analysis`. `rust_public_symbols` lists unrestricted `pub` items, including `pub` methods and associated items on a public type, and the items written in a trait impl for a public type. `pub(crate)`, `pub(super)`, `pub(self)`, and `pub(in ...)` stay out. A root `Cargo.toml` `[workspace] members` list, including globs, supplies each member crate name. `crate::` stays inside the member that owns the file. diff --git a/src/project_lock.rs.wiki.md b/src/project_lock.rs.wiki.md index df0d254..412b4f7 100644 --- a/src/project_lock.rs.wiki.md +++ b/src/project_lock.rs.wiki.md @@ -4,4 +4,4 @@ `ProjectLock::acquire` creates the pid file, waits 30 seconds, and steals the file when the recorded pid is dead. The same thread may re-enter. The outer guard is the one that deletes the file. Callers take this lock before the cache lock. -Public names: `ProjectLock`. +Public names: `ProjectLock`, `acquire`, `drop`. diff --git a/tests/behaviour_gaps.rs b/tests/behaviour_gaps.rs index 8ad5234..63454d6 100644 --- a/tests/behaviour_gaps.rs +++ b/tests/behaviour_gaps.rs @@ -9,7 +9,7 @@ use std::time::{Duration, Instant, SystemTime, UNIX_EPOCH}; use wikifier::cache::indexed_rows; use wikifier::jsonutil::{parse, Json}; use wikifier::mcp; -use wikifier::{load_dependencies, session_bootstrap, update_maps}; +use wikifier::{load_dependencies, mapped_edges, mark_green, read_health, session_bootstrap, update_maps}; fn scratch(name: &str) -> PathBuf { let n = SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_nanos(); @@ -218,3 +218,107 @@ fn actionable_low_conf_edges_keep_conditional_and_drop_dynamic() { assert!(counts.get("dynamic_literal").is_some(), "{counts}"); let _ = std::fs::remove_dir_all(&root); } + +#[test] +fn impl_items_are_checked_and_restricted_visibility_is_not() { + let root = scratch("impl-green"); + let source = "\ +pub struct Widget; +pub(crate) struct Hidden; + +impl Widget { + pub fn documented(&self) -> i32 { 1 } + pub const FLAG: i32 = 1; + pub(crate) fn crate_only(&self) {} + pub(super) fn super_only(&self) {} + fn private(&self) {} +} + +impl std::fmt::Display for Widget { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let _ = f; + Ok(()) + } +} + +impl Drop for Hidden { + fn drop(&mut self) {} +} + +impl Hidden { + pub fn not_exported(&self) {} +} +"; + std::fs::write(root.join("src/lib.rs"), source).unwrap(); + std::fs::write(root.join("src/lib.rs.wiki.md"), "Widget has documented, FLAG, and fmt.\n").unwrap(); + let marked = mark_green(&root, "src/lib.rs", "wiki names the methods", false); + assert_eq!(marked.get("verification").and_then(|flag| flag.as_str()), Some("verified"), "{marked}"); + let row = read_health(&root).into_iter().find(|row| row.file == "src/lib.rs").unwrap(); + for name in ["Widget", "documented", "FLAG", "fmt"] { + assert!(row.public_symbols.contains(name), "missing {name} in {}", row.public_symbols); + } + for name in ["crate_only", "super_only", "private", "drop", "not_exported", "Hidden"] { + assert!(!row.public_symbols.split(['\n', ',']).any(|have| have.trim() == name), "{name} should stay out: {}", row.public_symbols); + } + + std::fs::write(root.join("src/lib.rs.wiki.md"), "Widget has FLAG and fmt.\n").unwrap(); + let missing = mark_green(&root, "src/lib.rs", "wiki dropped documented", false); + assert_eq!(missing.get("error_type").and_then(|flag| flag.as_str()), Some("WikiVerificationError"), "{missing}"); + assert!(missing.to_string().contains("undocumented_new_symbols: documented"), "{missing}"); + + std::fs::write(root.join("src/lib.rs"), "pub struct Widget;\nimpl Widget {\n pub const FLAG: i32 = 1;\n}\n").unwrap(); + std::fs::write(root.join("src/lib.rs.wiki.md"), "Widget keeps FLAG. `documented` and `fmt` are gone.\n").unwrap(); + let removed = mark_green(&root, "src/lib.rs", "wiki still names removed methods", false); + assert_eq!(removed.get("error_type").and_then(|flag| flag.as_str()), Some("WikiVerificationError"), "{removed}"); + let text = removed.to_string(); + assert!(text.contains("wiki_mentions_removed_symbols: documented"), "{removed}"); + assert!(text.contains("wiki_mentions_removed_symbols: fmt"), "{removed}"); + let _ = std::fs::remove_dir_all(&root); +} + +#[test] +fn workspace_members_resolve_across_crates_and_keep_crate_paths_local() { + let root = scratch("workspace"); + std::fs::write(root.join("map_paths.txt"), "crates\ntools\n").unwrap(); + let files = [ + ("Cargo.toml", "[workspace]\nmembers = [\"crates/alpha\", \"crates/beta\", \"tools/*\"]\nexclude = [\"tools/skip\"]\n"), + ("crates/alpha/Cargo.toml", "[package]\nname = \"alpha-crate\"\nversion = \"0.1.0\"\n\n[lib]\nname = \"alpha\"\n"), + ("crates/alpha/src/lib.rs", "pub mod child;\npub fn from_alpha() -> i32 { 1 }\nuse helper_tool::help;\n"), + ("crates/alpha/src/child.rs", "pub fn child_fn() -> i32 { 2 }\n"), + ("crates/beta/Cargo.toml", "[package]\nname = \"beta\"\nversion = \"0.1.0\"\n"), + ("crates/beta/src/lib.rs", "use alpha::child::child_fn;\nuse alpha_crate::from_alpha;\npub fn from_beta() -> i32 { child_fn() + from_alpha() }\n"), + ("crates/beta/src/main.rs", "use alpha::from_alpha;\nfn main() { let _ = from_alpha(); }\n"), + ("crates/beta/tests/use_alpha.rs", "use alpha::from_alpha;\nuse crate::local;\n\n#[test]\nfn calls() { let _ = from_alpha(); let _ = local(); }\n"), + ("crates/beta/tests/use_alpha/local.rs", "pub fn local() -> i32 { 3 }\n"), + ("tools/helper/Cargo.toml", "[package]\nname = \"helper-tool\"\nversion = \"0.1.0\"\n\n[lib]\nname = \"helper_tool\"\n"), + ("tools/helper/src/lib.rs", "pub fn help() -> i32 { 4 }\n"), + ("tools/skip/Cargo.toml", "[package]\nname = \"skip-tool\"\nversion = \"0.1.0\"\n\n[lib]\nname = \"skip_tool\"\n"), + ("tools/skip/src/lib.rs", "mod local;\nuse alpha::from_alpha;\npub fn skipped() -> i32 { from_alpha() }\n"), + ("tools/skip/src/local.rs", "pub fn inside() -> i32 { 5 }\n"), + ]; + for (rel, text) in files { + let path = root.join(rel); + std::fs::create_dir_all(path.parent().unwrap()).unwrap(); + std::fs::write(path, text).unwrap(); + } + let mapped = update_maps(&root); + assert_eq!(mapped.get("success").and_then(|flag| flag.as_bool()), Some(true), "{mapped}"); + let edges = mapped_edges(&root); + let expect = [ + ("crates/alpha/src/lib.rs", "crates/alpha/src/child.rs"), + ("crates/alpha/src/lib.rs", "tools/helper/src/lib.rs"), + ("crates/beta/src/lib.rs", "crates/alpha/src/child.rs"), + ("crates/beta/src/lib.rs", "crates/alpha/src/lib.rs"), + ("crates/beta/src/main.rs", "crates/alpha/src/lib.rs"), + ("crates/beta/tests/use_alpha.rs", "crates/alpha/src/lib.rs"), + ("crates/beta/tests/use_alpha.rs", "crates/beta/tests/use_alpha/local.rs"), + ("tools/skip/src/lib.rs", "tools/skip/src/local.rs"), + ("tools/skip/src/lib.rs", "crates/alpha/src/lib.rs"), + ]; + for (src, dst) in expect { + assert!(edges.contains(&(src.into(), dst.into())), "missing {src} -> {dst} in {edges:?}"); + } + assert!(!edges.iter().any(|(src, dst)| src.contains("skip") && dst.contains("helper")), "excluded crate resolved as a member: {edges:?}"); + assert!(!edges.iter().any(|(src, dst)| src.starts_with("crates/beta/tests/") && dst.starts_with("crates/beta/src/")), "crate:: in a test escaped into the library: {edges:?}"); + let _ = std::fs::remove_dir_all(&root); +} diff --git a/tests/behaviour_gaps.rs.wiki.md b/tests/behaviour_gaps.rs.wiki.md index 6793122..af38a02 100644 --- a/tests/behaviour_gaps.rs.wiki.md +++ b/tests/behaviour_gaps.rs.wiki.md @@ -9,3 +9,7 @@ `sqlite_import_fills_an_empty_cache_and_reports_an_unreadable_file` always checks that a corrupt sqlite file warns `sqlite_present_unread`. When the `sqlite3` CLI is on PATH it also builds a database, checks the imported edge and the `sha256:legacy` hash, and checks that a later sqlite write does not replace a filled cache. `actionable_low_conf_edges_keep_conditional_and_drop_dynamic` maps a conditional import and a dynamic `import_module`. Only the conditional edge is actionable, at confidence 0.55 with reason `conditional_guard`. + +`impl_items_are_checked_and_restricted_visibility_is_not` marks a Rust file green when the wiki names `pub` methods, an associated const, and a trait-impl item, and refuses when one of those names is missing or a removed name stays in backticks. `pub(crate)`, `pub(super)`, private items, and impl items on a `pub(crate)` type stay out of the check. + +`workspace_members_resolve_across_crates_and_keep_crate_paths_local` builds a workspace with explicit members and a `tools/*` glob, plus an excluded package. Cross-crate `use` reaches the named member. `crate::` inside an integration test stays in that test crate. diff --git a/tests/protocol_v07.rs b/tests/protocol_v07.rs index 2b90cbd..d58186e 100644 --- a/tests/protocol_v07.rs +++ b/tests/protocol_v07.rs @@ -388,20 +388,24 @@ pub type Alias = Widget; pub const FLAG: i32 = 1; pub use child::exported; pub(crate) fn hidden() {} -impl Widget { pub fn method(&self) {} } +impl Widget { + pub fn method(&self) {} + pub(crate) fn crate_only(&self) {} +} #[cfg(test)] mod tests { pub fn inside() {} } "; fs::write(root.join("src/lib.rs"), source).unwrap(); fs::write(root.join("src/child.rs"), "pub fn exported() {}\n").unwrap(); - fs::write(root.join("src/lib.rs.wiki.md"), "Names kept, Widget, Phase, Run, child, Alias, FLAG, exported.\n").unwrap(); + fs::write(root.join("src/lib.rs.wiki.md"), "Names kept, Widget, Phase, Run, child, Alias, FLAG, exported, method.\n").unwrap(); let marked = mark_green(&root, "src/lib.rs", "wiki names the public items", false); assert_eq!(marked.get("success").and_then(|flag| flag.as_bool()), Some(true), "{marked}"); assert_eq!(marked.get("verification").and_then(|flag| flag.as_str()), Some("verified"), "{marked}"); let row = read_health(&root).into_iter().find(|row| row.file == "src/lib.rs").unwrap(); assert!(row.public_symbols.contains("kept"), "{}", row.public_symbols); + assert!(row.public_symbols.contains("method"), "inherent pub method missing: {}", row.public_symbols); assert!(!row.public_symbols.contains("hidden"), "pub(crate) counted: {}", row.public_symbols); - assert!(!row.public_symbols.contains("method"), "impl method counted: {}", row.public_symbols); + assert!(!row.public_symbols.contains("crate_only"), "restricted method counted: {}", row.public_symbols); assert!(!row.public_symbols.contains("inside"), "nested pub counted: {}", row.public_symbols); let wiki = fs::read_to_string(root.join("src/lib.rs.wiki.md")).unwrap(); diff --git a/tests/protocol_v07.rs.wiki.md b/tests/protocol_v07.rs.wiki.md index 295083f..5a06e9a 100644 --- a/tests/protocol_v07.rs.wiki.md +++ b/tests/protocol_v07.rs.wiki.md @@ -2,4 +2,4 @@ **Role:** Integration tests for the agent-facing contract that the early Rust port left thin. -Covers `library.md` and folder cards, `--directory` / `--max-files` resume, `record_deletion` plus journal de-duplication, `record_changes` globs and `--green`, dependency confidence and cycle `break_hint`, JS wiki refusal, Rust `mark_green` on top-level `pub` items, legacy JSON import, `init` keeping path lists, `init DIR` as the project root, `heal-stubs --dry-run`, `search-journal --file`, `verify-wikis` limited to one file, MCP `isError` plus resource and prompt reads, prune keeping a monitored file that sits outside the map roots, and `serve` reading `file_health.json` and `library.md` before shutdown. +Covers `library.md` and folder cards, `--directory` / `--max-files` resume, `record_deletion` plus journal de-duplication, `record_changes` globs and `--green`, dependency confidence and cycle `break_hint`, JS wiki refusal, Rust `mark_green` on `pub` items and inherent methods, legacy JSON import, `init` keeping path lists, `init DIR` as the project root, `heal-stubs --dry-run`, `search-journal --file`, `verify-wikis` limited to one file, MCP `isError` plus resource and prompt reads, prune keeping a monitored file that sits outside the map roots, and `serve` reading `file_health.json` and `library.md` before shutdown.