wikifier 4.6.7__tar.gz → 4.6.9__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {wikifier-4.6.7/wikifier.egg-info → wikifier-4.6.9}/PKG-INFO +31 -17
- {wikifier-4.6.7 → wikifier-4.6.9}/README.md +30 -16
- {wikifier-4.6.7 → wikifier-4.6.9}/skills/run.md +84 -13
- wikifier-4.6.9/tests/test_gap_amendment_2026_08.py +238 -0
- wikifier-4.6.9/tests/test_init_seed.py +123 -0
- {wikifier-4.6.7 → wikifier-4.6.9}/wikifier/__init__.py +1 -1
- {wikifier-4.6.7 → wikifier-4.6.9}/wikifier/agent_loop.py +101 -22
- {wikifier-4.6.7 → wikifier-4.6.9}/wikifier/cli.py +9 -0
- {wikifier-4.6.7 → wikifier-4.6.9}/wikifier/library.py +38 -6
- {wikifier-4.6.7 → wikifier-4.6.9}/wikifier/locking.py +63 -18
- {wikifier-4.6.7 → wikifier-4.6.9}/wikifier/scripts/wikifier.sh +44 -6
- {wikifier-4.6.7 → wikifier-4.6.9/wikifier.egg-info}/PKG-INFO +31 -17
- {wikifier-4.6.7 → wikifier-4.6.9}/wikifier.egg-info/SOURCES.txt +2 -0
- {wikifier-4.6.7 → wikifier-4.6.9}/CONTRIBUTING.md +0 -0
- {wikifier-4.6.7 → wikifier-4.6.9}/LICENSE +0 -0
- {wikifier-4.6.7 → wikifier-4.6.9}/MANIFEST.in +0 -0
- {wikifier-4.6.7 → wikifier-4.6.9}/diagnostics.html +0 -0
- {wikifier-4.6.7 → wikifier-4.6.9}/docs/spec.md +0 -0
- {wikifier-4.6.7 → wikifier-4.6.9}/index.html +0 -0
- {wikifier-4.6.7 → wikifier-4.6.9}/pyproject.toml +0 -0
- {wikifier-4.6.7 → wikifier-4.6.9}/setup.cfg +0 -0
- {wikifier-4.6.7 → wikifier-4.6.9}/tests/test_agent_loop.py +0 -0
- {wikifier-4.6.7 → wikifier-4.6.9}/tests/test_agent_scale.py +0 -0
- {wikifier-4.6.7 → wikifier-4.6.9}/tests/test_barrel_invalidation.py +0 -0
- {wikifier-4.6.7 → wikifier-4.6.9}/tests/test_cache_store.py +0 -0
- {wikifier-4.6.7 → wikifier-4.6.9}/tests/test_gap_closure.py +0 -0
- {wikifier-4.6.7 → wikifier-4.6.9}/tests/test_health.py +0 -0
- {wikifier-4.6.7 → wikifier-4.6.9}/tests/test_import_cache.py +0 -0
- {wikifier-4.6.7 → wikifier-4.6.9}/tests/test_index_map_paths.py +0 -0
- {wikifier-4.6.7 → wikifier-4.6.9}/tests/test_multi_lang_parsers.py +0 -0
- {wikifier-4.6.7 → wikifier-4.6.9}/tests/test_parsers.py +0 -0
- {wikifier-4.6.7 → wikifier-4.6.9}/tests/test_selftest_wrappers.py +0 -0
- {wikifier-4.6.7 → wikifier-4.6.9}/tests/test_walk_coverage_resolvers.py +0 -0
- {wikifier-4.6.7 → wikifier-4.6.9}/wikifier/__main__.py +0 -0
- {wikifier-4.6.7 → wikifier-4.6.9}/wikifier/cache_store.py +0 -0
- {wikifier-4.6.7 → wikifier-4.6.9}/wikifier/candidates.py +0 -0
- {wikifier-4.6.7 → wikifier-4.6.9}/wikifier/contracts.py +0 -0
- {wikifier-4.6.7 → wikifier-4.6.9}/wikifier/daemon.py +0 -0
- {wikifier-4.6.7 → wikifier-4.6.9}/wikifier/diagnostics.py +0 -0
- {wikifier-4.6.7 → wikifier-4.6.9}/wikifier/health.py +0 -0
- {wikifier-4.6.7 → wikifier-4.6.9}/wikifier/import_cache.py +0 -0
- {wikifier-4.6.7 → wikifier-4.6.9}/wikifier/index.html +0 -0
- {wikifier-4.6.7 → wikifier-4.6.9}/wikifier/mcp/__init__.py +0 -0
- {wikifier-4.6.7 → wikifier-4.6.9}/wikifier/mcp/server.py +0 -0
- {wikifier-4.6.7 → wikifier-4.6.9}/wikifier/parsers/__init__.py +0 -0
- {wikifier-4.6.7 → wikifier-4.6.9}/wikifier/parsers/_edge.py +0 -0
- {wikifier-4.6.7 → wikifier-4.6.9}/wikifier/parsers/bree.py +0 -0
- {wikifier-4.6.7 → wikifier-4.6.9}/wikifier/parsers/c_cpp.py +0 -0
- {wikifier-4.6.7 → wikifier-4.6.9}/wikifier/parsers/cdia.py +0 -0
- {wikifier-4.6.7 → wikifier-4.6.9}/wikifier/parsers/csharp.py +0 -0
- {wikifier-4.6.7 → wikifier-4.6.9}/wikifier/parsers/go_lang.py +0 -0
- {wikifier-4.6.7 → wikifier-4.6.9}/wikifier/parsers/java.py +0 -0
- {wikifier-4.6.7 → wikifier-4.6.9}/wikifier/parsers/javascript.py +0 -0
- {wikifier-4.6.7 → wikifier-4.6.9}/wikifier/parsers/python.py +0 -0
- {wikifier-4.6.7 → wikifier-4.6.9}/wikifier/parsers/rust.py +0 -0
- {wikifier-4.6.7 → wikifier-4.6.9}/wikifier/project_root.py +0 -0
- {wikifier-4.6.7 → wikifier-4.6.9}/wikifier/resolution.py +0 -0
- {wikifier-4.6.7 → wikifier-4.6.9}/wikifier/scripts/wikifier.bat +0 -0
- {wikifier-4.6.7 → wikifier-4.6.9}/wikifier/scripts/wikifier.ps1 +0 -0
- {wikifier-4.6.7 → wikifier-4.6.9}/wikifier/serve.py +0 -0
- {wikifier-4.6.7 → wikifier-4.6.9}/wikifier.egg-info/dependency_links.txt +0 -0
- {wikifier-4.6.7 → wikifier-4.6.9}/wikifier.egg-info/entry_points.txt +0 -0
- {wikifier-4.6.7 → wikifier-4.6.9}/wikifier.egg-info/requires.txt +0 -0
- {wikifier-4.6.7 → wikifier-4.6.9}/wikifier.egg-info/top_level.txt +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: wikifier
|
|
3
|
-
Version: 4.6.
|
|
3
|
+
Version: 4.6.9
|
|
4
4
|
Summary: Zero-dependency agent-to-agent codebase wiki for LLMs and AI agents. Autonomously maintained via record-change and mark-green for token-efficient lookup of files, dependencies, health, and summaries — across tiny scripts to 50k+ monorepos. Optional MCP server with rich tools for agents.
|
|
5
5
|
Author-email: Aron Amos <aron@example.com>
|
|
6
6
|
Maintainer: Aron Amos
|
|
@@ -52,7 +52,14 @@ Works from small scripts to large monorepos. **Deep import/include maps** (zero-
|
|
|
52
52
|
| C# | `.cs` | `using` namespaces |
|
|
53
53
|
| Java | `.java` | `import` / `import static` |
|
|
54
54
|
|
|
55
|
-
Health/journal still work for any monitored path. Parsers are **pragmatic regex** (not full cargo/`go.mod`/classpath/`-I` resolution).
|
|
55
|
+
Health/journal still work for any monitored path. Parsers are **pragmatic regex** (not full cargo/`go.mod`/classpath/`-I` resolution). On huge monorepos, split scope deliberately:
|
|
56
|
+
|
|
57
|
+
| File | Surface |
|
|
58
|
+
|------|---------|
|
|
59
|
+
| **`map_paths.txt`** | Package roots for **import maps** (`update-maps` walk). Prefer package dirs (`src/`, `packages/foo/`) — not a wiki-only file list. |
|
|
60
|
+
| **`monitored_paths.txt`** | **Wiki / health** watch list (can be individual `.md` files). Does **not** define the map. |
|
|
61
|
+
|
|
62
|
+
Or pass `--directory=pkg/` / `--max-files=N` per run. Raise dirty cap with `WIKIFIER_CHECK_CHANGES_MAX` (default 2000) only when needed. Never set `project_root` to a multi-repo parent of clones.
|
|
56
63
|
|
|
57
64
|
## Why
|
|
58
65
|
|
|
@@ -74,7 +81,8 @@ pip install wikifier # pure Python stdlib core — no runtime deps
|
|
|
74
81
|
pip install wikifier[mcp] # optional Model Context Protocol (MCP) server
|
|
75
82
|
|
|
76
83
|
cd /path/to/your/project
|
|
77
|
-
wikifier init # seeds +
|
|
84
|
+
wikifier init # seeds index.html + lean path-list templates
|
|
85
|
+
# Edit monitored_paths.txt + map_paths.txt to package roots (not bare ".") on real trees
|
|
78
86
|
wikifier update-maps # full structural map → library.md + import cache
|
|
79
87
|
wikifier health --summary # matrix counts
|
|
80
88
|
wikifier suggest-next # or MCP suggest_next_actions — 🔴/🟡 only
|
|
@@ -82,6 +90,8 @@ wikifier suggest-next # or MCP suggest_next_actions — 🔴/🟡 only
|
|
|
82
90
|
|
|
83
91
|
Always set an explicit root for external trees: `WIKIFIER_PROJECT_ROOT=/abs/path wikifier …`
|
|
84
92
|
|
|
93
|
+
**MCP `session_bootstrap` → `readiness: blocked`?** That means lean scope and/or the map are missing (often bare `.` monitor + never ran `update-maps`) — not a broken install. Fix: write lean `monitored_paths.txt` / `map_paths.txt`, then `update-maps`. Agent contract: **`skills/run.md`** § *Readiness blocked*; dogfood: `Findings/readiness-blocked-bare-monitor-2026-07.md`.
|
|
94
|
+
|
|
85
95
|
## Steady state (only touch what needs it)
|
|
86
96
|
|
|
87
97
|
Full protocol: [`skills/run.md`](skills/run.md) (Agent Protocol v0.6 — package **4.6.x**).
|
|
@@ -109,11 +119,11 @@ Advanced intel as needed: `get_dependencies`, `get_dependents`, `get_cycles`, ba
|
|
|
109
119
|
|
|
110
120
|
- **Import analysis** — Python, JS/TS (ESM/CJS, barrels), Rust (`use`/`mod` + best-effort `crate::` paths), Go, C/C++ includes, C# usings; per-edge confidence; barrel expansion for TS/JS
|
|
111
121
|
- **Incremental pipeline** — pure-Python `update-maps`: dirty parse → import cache → reverse deps → cycles → `library.md`
|
|
112
|
-
- **Warm agent maps (4.6.3–4.6.
|
|
113
|
-
- **Two path lists** — `map_paths.txt` = map package roots; `monitored_paths.txt` = wiki/health watch (independent)
|
|
122
|
+
- **Warm agent maps (4.6.3–4.6.7)** — zero-dirty + **index-first** candidates (re-list only when fingerprint / map-scoped index / live count disagree); **MapScope** keeps collect, live count, index filter, and prune aligned; **stdlib SQLite**; content-hash dirty
|
|
123
|
+
- **Two path lists** — `map_paths.txt` = map package roots; `monitored_paths.txt` = wiki/health watch (**independent** — wiki file lists never collapse the map)
|
|
114
124
|
- **Partial-map honesty** — `map_coverage` on `update_maps` / bootstrap / **`suggest_next`**; `update_maps_until_complete` when incomplete
|
|
115
|
-
- **Cache ops** — `wikifier cache-status`; JSON dual-write **
|
|
116
|
-
- **Selective agent work** — health + suggest bias to 🔴/actionable 🟡 only; **ACS v1.3** `reason_code` / `agent_signal`; prefer `actionable_low_conf_edges` + reason codes — **never** raw `low_conf_edges` alone
|
|
125
|
+
- **Cache ops** — `wikifier cache-status`; JSON dual-write **deprecated default-off** (`WIKIFIER_CACHE_JSON=1` opt-in); dual-read for migrate
|
|
126
|
+
- **Selective agent work** — health + suggest bias to 🔴/actionable 🟡 only; **ACS v1.3** `reason_code` / `agent_signal`; prefer `actionable_low_conf_edges` + reason codes — **never** raw `low_conf_edges` averages alone
|
|
117
127
|
- **Scale** — reverse index + barrel invalidation so one edit doesn’t re-scan the monorepo
|
|
118
128
|
- **MCP tools** — optional server for Claude, Cursor, Cline, and other MCP clients
|
|
119
129
|
- **Zero core dependencies** — stdlib only; forks can add their own stack on top
|
|
@@ -127,18 +137,21 @@ Full / heavy runs (historical order-of-magnitude):
|
|
|
127
137
|
|---------|-------|----------------------------|
|
|
128
138
|
| llama_index | ~3.8k Python files | ~8.5s class full |
|
|
129
139
|
| Babylon.js | ~3.9k TS files, barrel-heavy | minutes full; scoped re-runs tens of seconds |
|
|
130
|
-
| Large trees (e.g. LLVM-scale) | tens of thousands of files |
|
|
140
|
+
| Large trees (e.g. LLVM-scale) | tens of thousands of files | `map_paths` / `--directory` / `--max-files` — never unscoped one-shot |
|
|
141
|
+
|
|
142
|
+
Warm **0-dirty** re-runs after **4.6.7** (same machine class; scoped; candidates **reused** — agent session path):
|
|
131
143
|
|
|
132
|
-
|
|
144
|
+
| Project | Scope | Warm `update-maps` | n |
|
|
145
|
+
|---------|-------|--------------------|---|
|
|
146
|
+
| Wikifier (self) | `map_paths`: `wikifier/` + `tests/` | **~30 ms** | 50 |
|
|
147
|
+
| llama_index | `llama-index-core` | **~76 ms** | 724 |
|
|
148
|
+
| rust | `library/std` | **~79 ms** | 719 |
|
|
149
|
+
| airflow | `airflow-core` | **~180 ms** | 1920 |
|
|
150
|
+
| Babylon.js | `packages` | **~400 ms** | 3895 |
|
|
133
151
|
|
|
134
|
-
|
|
135
|
-
|---------|-------|---------------------|
|
|
136
|
-
| Wikifier (self) | incremental full | ~43 ms |
|
|
137
|
-
| redox | `src` | ~17 ms |
|
|
138
|
-
| llama_index | `llama-index-core` | ~0.6 s |
|
|
139
|
-
| rust | `library/std/src` (budgeted) | ~0.7 s (vs multi-second full-tree walk before scoped collect) |
|
|
152
|
+
Residual floor on large scopes is mtime/stat + live count under MapScope (not full JSON re-walk). Sub-100ms is not a hard SLA on every 1k+ tree.
|
|
140
153
|
|
|
141
|
-
Tests: `python -m unittest discover tests` (stdlib only;
|
|
154
|
+
Tests: `python -m unittest discover tests` (stdlib only; **125** cases including MapScope / index-first / dual-write).
|
|
142
155
|
|
|
143
156
|
## Commands
|
|
144
157
|
|
|
@@ -152,7 +165,8 @@ Tests: `python -m unittest discover tests` (stdlib only; 86+ cases including age
|
|
|
152
165
|
| `wikifier mark-green <file>` | Mark wiki current + source content-hash baseline |
|
|
153
166
|
| `wikifier record-deletion <file> "reason"` | Mark removed paths 🔴 + prune barrel refs |
|
|
154
167
|
| `wikifier suggest-next` | Next actions (🔴/actionable 🟡 only; `--json` for `actions[]`) |
|
|
155
|
-
| `wikifier update-maps [--directory=src/] [--max-files=N]` | Rebuild graph + `library.md` (warm 0-dirty is fast) |
|
|
168
|
+
| `wikifier update-maps [--directory=src/] [--max-files=N]` | Rebuild graph + `library.md` (warm 0-dirty is fast; honors `map_paths.txt`) |
|
|
169
|
+
| `wikifier cache-status` | SQLite/JSON backend, dual-write policy, coverage snapshot (no full pair load) |
|
|
156
170
|
| `wikifier health [--summary\|--json]` | Health matrix (machine-friendly flags) |
|
|
157
171
|
| `wikifier validate` | Missing wiki rows + ghost paths |
|
|
158
172
|
| `wikifier cycles` | Circular deps + break hints |
|
|
@@ -20,7 +20,14 @@ Works from small scripts to large monorepos. **Deep import/include maps** (zero-
|
|
|
20
20
|
| C# | `.cs` | `using` namespaces |
|
|
21
21
|
| Java | `.java` | `import` / `import static` |
|
|
22
22
|
|
|
23
|
-
Health/journal still work for any monitored path. Parsers are **pragmatic regex** (not full cargo/`go.mod`/classpath/`-I` resolution).
|
|
23
|
+
Health/journal still work for any monitored path. Parsers are **pragmatic regex** (not full cargo/`go.mod`/classpath/`-I` resolution). On huge monorepos, split scope deliberately:
|
|
24
|
+
|
|
25
|
+
| File | Surface |
|
|
26
|
+
|------|---------|
|
|
27
|
+
| **`map_paths.txt`** | Package roots for **import maps** (`update-maps` walk). Prefer package dirs (`src/`, `packages/foo/`) — not a wiki-only file list. |
|
|
28
|
+
| **`monitored_paths.txt`** | **Wiki / health** watch list (can be individual `.md` files). Does **not** define the map. |
|
|
29
|
+
|
|
30
|
+
Or pass `--directory=pkg/` / `--max-files=N` per run. Raise dirty cap with `WIKIFIER_CHECK_CHANGES_MAX` (default 2000) only when needed. Never set `project_root` to a multi-repo parent of clones.
|
|
24
31
|
|
|
25
32
|
## Why
|
|
26
33
|
|
|
@@ -42,7 +49,8 @@ pip install wikifier # pure Python stdlib core — no runtime deps
|
|
|
42
49
|
pip install wikifier[mcp] # optional Model Context Protocol (MCP) server
|
|
43
50
|
|
|
44
51
|
cd /path/to/your/project
|
|
45
|
-
wikifier init # seeds +
|
|
52
|
+
wikifier init # seeds index.html + lean path-list templates
|
|
53
|
+
# Edit monitored_paths.txt + map_paths.txt to package roots (not bare ".") on real trees
|
|
46
54
|
wikifier update-maps # full structural map → library.md + import cache
|
|
47
55
|
wikifier health --summary # matrix counts
|
|
48
56
|
wikifier suggest-next # or MCP suggest_next_actions — 🔴/🟡 only
|
|
@@ -50,6 +58,8 @@ wikifier suggest-next # or MCP suggest_next_actions — 🔴/🟡 only
|
|
|
50
58
|
|
|
51
59
|
Always set an explicit root for external trees: `WIKIFIER_PROJECT_ROOT=/abs/path wikifier …`
|
|
52
60
|
|
|
61
|
+
**MCP `session_bootstrap` → `readiness: blocked`?** That means lean scope and/or the map are missing (often bare `.` monitor + never ran `update-maps`) — not a broken install. Fix: write lean `monitored_paths.txt` / `map_paths.txt`, then `update-maps`. Agent contract: **`skills/run.md`** § *Readiness blocked*; dogfood: `Findings/readiness-blocked-bare-monitor-2026-07.md`.
|
|
62
|
+
|
|
53
63
|
## Steady state (only touch what needs it)
|
|
54
64
|
|
|
55
65
|
Full protocol: [`skills/run.md`](skills/run.md) (Agent Protocol v0.6 — package **4.6.x**).
|
|
@@ -77,11 +87,11 @@ Advanced intel as needed: `get_dependencies`, `get_dependents`, `get_cycles`, ba
|
|
|
77
87
|
|
|
78
88
|
- **Import analysis** — Python, JS/TS (ESM/CJS, barrels), Rust (`use`/`mod` + best-effort `crate::` paths), Go, C/C++ includes, C# usings; per-edge confidence; barrel expansion for TS/JS
|
|
79
89
|
- **Incremental pipeline** — pure-Python `update-maps`: dirty parse → import cache → reverse deps → cycles → `library.md`
|
|
80
|
-
- **Warm agent maps (4.6.3–4.6.
|
|
81
|
-
- **Two path lists** — `map_paths.txt` = map package roots; `monitored_paths.txt` = wiki/health watch (independent)
|
|
90
|
+
- **Warm agent maps (4.6.3–4.6.7)** — zero-dirty + **index-first** candidates (re-list only when fingerprint / map-scoped index / live count disagree); **MapScope** keeps collect, live count, index filter, and prune aligned; **stdlib SQLite**; content-hash dirty
|
|
91
|
+
- **Two path lists** — `map_paths.txt` = map package roots; `monitored_paths.txt` = wiki/health watch (**independent** — wiki file lists never collapse the map)
|
|
82
92
|
- **Partial-map honesty** — `map_coverage` on `update_maps` / bootstrap / **`suggest_next`**; `update_maps_until_complete` when incomplete
|
|
83
|
-
- **Cache ops** — `wikifier cache-status`; JSON dual-write **
|
|
84
|
-
- **Selective agent work** — health + suggest bias to 🔴/actionable 🟡 only; **ACS v1.3** `reason_code` / `agent_signal`; prefer `actionable_low_conf_edges` + reason codes — **never** raw `low_conf_edges` alone
|
|
93
|
+
- **Cache ops** — `wikifier cache-status`; JSON dual-write **deprecated default-off** (`WIKIFIER_CACHE_JSON=1` opt-in); dual-read for migrate
|
|
94
|
+
- **Selective agent work** — health + suggest bias to 🔴/actionable 🟡 only; **ACS v1.3** `reason_code` / `agent_signal`; prefer `actionable_low_conf_edges` + reason codes — **never** raw `low_conf_edges` averages alone
|
|
85
95
|
- **Scale** — reverse index + barrel invalidation so one edit doesn’t re-scan the monorepo
|
|
86
96
|
- **MCP tools** — optional server for Claude, Cursor, Cline, and other MCP clients
|
|
87
97
|
- **Zero core dependencies** — stdlib only; forks can add their own stack on top
|
|
@@ -95,18 +105,21 @@ Full / heavy runs (historical order-of-magnitude):
|
|
|
95
105
|
|---------|-------|----------------------------|
|
|
96
106
|
| llama_index | ~3.8k Python files | ~8.5s class full |
|
|
97
107
|
| Babylon.js | ~3.9k TS files, barrel-heavy | minutes full; scoped re-runs tens of seconds |
|
|
98
|
-
| Large trees (e.g. LLVM-scale) | tens of thousands of files |
|
|
108
|
+
| Large trees (e.g. LLVM-scale) | tens of thousands of files | `map_paths` / `--directory` / `--max-files` — never unscoped one-shot |
|
|
109
|
+
|
|
110
|
+
Warm **0-dirty** re-runs after **4.6.7** (same machine class; scoped; candidates **reused** — agent session path):
|
|
99
111
|
|
|
100
|
-
|
|
112
|
+
| Project | Scope | Warm `update-maps` | n |
|
|
113
|
+
|---------|-------|--------------------|---|
|
|
114
|
+
| Wikifier (self) | `map_paths`: `wikifier/` + `tests/` | **~30 ms** | 50 |
|
|
115
|
+
| llama_index | `llama-index-core` | **~76 ms** | 724 |
|
|
116
|
+
| rust | `library/std` | **~79 ms** | 719 |
|
|
117
|
+
| airflow | `airflow-core` | **~180 ms** | 1920 |
|
|
118
|
+
| Babylon.js | `packages` | **~400 ms** | 3895 |
|
|
101
119
|
|
|
102
|
-
|
|
103
|
-
|---------|-------|---------------------|
|
|
104
|
-
| Wikifier (self) | incremental full | ~43 ms |
|
|
105
|
-
| redox | `src` | ~17 ms |
|
|
106
|
-
| llama_index | `llama-index-core` | ~0.6 s |
|
|
107
|
-
| rust | `library/std/src` (budgeted) | ~0.7 s (vs multi-second full-tree walk before scoped collect) |
|
|
120
|
+
Residual floor on large scopes is mtime/stat + live count under MapScope (not full JSON re-walk). Sub-100ms is not a hard SLA on every 1k+ tree.
|
|
108
121
|
|
|
109
|
-
Tests: `python -m unittest discover tests` (stdlib only;
|
|
122
|
+
Tests: `python -m unittest discover tests` (stdlib only; **125** cases including MapScope / index-first / dual-write).
|
|
110
123
|
|
|
111
124
|
## Commands
|
|
112
125
|
|
|
@@ -120,7 +133,8 @@ Tests: `python -m unittest discover tests` (stdlib only; 86+ cases including age
|
|
|
120
133
|
| `wikifier mark-green <file>` | Mark wiki current + source content-hash baseline |
|
|
121
134
|
| `wikifier record-deletion <file> "reason"` | Mark removed paths 🔴 + prune barrel refs |
|
|
122
135
|
| `wikifier suggest-next` | Next actions (🔴/actionable 🟡 only; `--json` for `actions[]`) |
|
|
123
|
-
| `wikifier update-maps [--directory=src/] [--max-files=N]` | Rebuild graph + `library.md` (warm 0-dirty is fast) |
|
|
136
|
+
| `wikifier update-maps [--directory=src/] [--max-files=N]` | Rebuild graph + `library.md` (warm 0-dirty is fast; honors `map_paths.txt`) |
|
|
137
|
+
| `wikifier cache-status` | SQLite/JSON backend, dual-write policy, coverage snapshot (no full pair load) |
|
|
124
138
|
| `wikifier health [--summary\|--json]` | Health matrix (machine-friendly flags) |
|
|
125
139
|
| `wikifier validate` | Missing wiki rows + ghost paths |
|
|
126
140
|
| `wikifier cycles` | Circular deps + break hints |
|
|
@@ -2,16 +2,17 @@
|
|
|
2
2
|
|
|
3
3
|
**Formerly "Wikifier Skills & Commands". This is the authoritative, versioned specification for agent behavior when using Wikifier.**
|
|
4
4
|
|
|
5
|
-
**Version**: v0.6 (v4.2.0 real-pipeline + reliability update; package current **4.
|
|
6
|
-
**Date**: 2026-06-10 (package notes refreshed 2026-
|
|
5
|
+
**Version**: v0.6 (v4.2.0 real-pipeline + reliability update; package current **4.6.x**, live `__version__` in `wikifier/__init__.py`)
|
|
6
|
+
**Date**: 2026-06-10 (package notes refreshed 2026-08-01; gap-closure contract 2026-07-09)
|
|
7
7
|
**Status**: Active. Supersedes v0.5.
|
|
8
|
-
**See also**: `README.md`, `Findings/gap-closure-report.md`, and the library in wikifier/.
|
|
8
|
+
**See also**: `README.md`, `Findings/gap-closure-report.md`, `Findings/gap-amendment-plan-2026-08-01.md`, and the library in wikifier/.
|
|
9
9
|
|
|
10
|
-
**Package 4.
|
|
10
|
+
**Package 4.6.x + gap-closure notes (additive; protocol still v0.6):**
|
|
11
11
|
- File Tree + `wikifier serve`; MCP status/attention use library + emoji (not `[GREEN]` tags).
|
|
12
|
-
- **First-run:** `init` → `update-maps` → `health --summary` → `suggest_next_actions`. Map is automatic; wiki *prose* is agent-filled over time.
|
|
12
|
+
- **First-run:** `init` → lean `monitored_paths.txt` + `map_paths.txt` (default active root `src/`, not bare `.`) → `update-maps` → `health --summary` → `suggest_next_actions`. Map is automatic; wiki *prose* is agent-filled over time.
|
|
13
|
+
- **`session_bootstrap` readiness:** message embeds the readiness tier. `blocked` is not a crash — see **§ Readiness blocked**. Hard blockers = missing map/health (or severe ghosts). Bare `.` monitor alone → `map_ok_scope_risk` when map+health exist (scope warnings / fix_scope), not the same tier as missing map. Unattended ops require **`readiness=ready_for_daemon`**, not `health_score=Map Ready` alone.
|
|
13
14
|
- **Map-first ≠ wiki-done:** `health_score` **Map Ready** + many 🟡 *Initial stubs* means structural coverage only. Do **not** bulk-wiki stubs. Wiki a file when you edit it, then `mark-green`. Prefer `actionable_yellow` / 🔴 over raw yellow counts.
|
|
14
|
-
- **Steady-state selective work:** only 🔴 and *actionable* 🟡 (never re-wiki 🟢; never
|
|
15
|
+
- **Steady-state selective work:** only 🔴 and *actionable* 🟡 (never re-wiki 🟢; never bulk-wiki Initial/map stubs). **ACS v1.3:** use `actionable_low_conf_edges` + `reason_code_counts` / `agent_signal` (`skip`|`investigate`). Do **not** thrash on raw `low_conf_edges` (includes external/bare scores ~0.48). Prefer `map_coverage.complete` / `map_ready` / `files_remaining_dirty` after budgeted `update-maps` — `success: true` ≠ map complete.
|
|
15
16
|
- **Warm maps:** SQLite primary; zero-dirty uses mtime index + meta. **Index-first:** re-list candidates only when fingerprint / map-scoped index / live count disagree (`index_first_dirty` / `candidates_reused` on `update_maps`). Prefer `--directory=` or `map_paths.txt` package roots (not bare `.`). Collect, live count, index filter, and prune share **MapScope** so full-tree→`map_paths` narrow does not thrash.
|
|
16
17
|
- **map_paths vs monitored_paths:** `map_paths.txt` = import-map package roots; `monitored_paths.txt` = wiki/health thrash bound. Independent. Wiki-only monitored lists do **not** define the map.
|
|
17
18
|
- **Cache ops:** `wikifier cache-status`. Dual-write JSON **deprecated default-off** (`WIKIFIER_CACHE_JSON=1` to opt in). Dual-read legacy JSON for migrate remains.
|
|
@@ -45,8 +46,9 @@ Do **not** open megamodules (`javascript.py`, `import_cache.py`, `bree.py`) to d
|
|
|
45
46
|
|
|
46
47
|
| Need | Use |
|
|
47
48
|
|------|-----|
|
|
48
|
-
| First-run map | `init` → `update-maps` → `health --summary` → `suggest-next` |
|
|
49
|
-
| **Session start (4.6+)** | `session_bootstrap` (or CLI `session-bootstrap`) — one shot: root, health, attention, `actions[]` |
|
|
49
|
+
| First-run map | `init` → lean paths (not bare `.`) → `update-maps` → `health --summary` → `suggest-next` |
|
|
50
|
+
| **Session start (4.6+)** | `session_bootstrap` (or CLI `session-bootstrap`) — one shot: root, health, attention, `actions[]`. If `readiness` is `blocked`, fix `actions[]` / `blockers[]` first (usually scope + maps). |
|
|
51
|
+
| **Unblock readiness** | Write lean `monitored_paths.txt` + `map_paths.txt` → `update_maps` (full once) → re-`session_bootstrap` until **`readiness=ready_for_daemon`** (not Map Ready alone) |
|
|
50
52
|
| **Core tool list** | `list_core_tools` / bootstrap `core_surface` — prefer Core 6; advanced is non-core |
|
|
51
53
|
| Steady-state | `check-changes` (content-honest) → edit 🔴/actionable 🟡 only → `record-change` → wiki → `mark-green` |
|
|
52
54
|
| **Hash migration** | `seed_source_content_hashes` / CLI `seed-source-hashes` — baseline Greens without mass Yellow |
|
|
@@ -65,6 +67,74 @@ Do **not** open megamodules (`javascript.py`, `import_cache.py`, `bree.py`) to d
|
|
|
65
67
|
**Scope:** `monitored_paths` = change detection; `exclude_patterns` + optional `--directory` = map walk.
|
|
66
68
|
**Human HTML** is secondary observation only.
|
|
67
69
|
|
|
70
|
+
## Readiness blocked (`session_bootstrap` / external projects)
|
|
71
|
+
|
|
72
|
+
When MCP `session_bootstrap` (or CLI `session-bootstrap`) returns:
|
|
73
|
+
|
|
74
|
+
```text
|
|
75
|
+
readiness: "blocked"
|
|
76
|
+
scope.ok: false
|
|
77
|
+
blockers: [ ... ]
|
|
78
|
+
actions: [ { "action": "fix_scope", ... }, ... ]
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
**this is expected on a project that has never been scoped/mapped**, not a Wikifier install failure.
|
|
82
|
+
|
|
83
|
+
### Readiness tiers (code truth — `assess_autonomous_readiness`)
|
|
84
|
+
|
|
85
|
+
| Tier | When |
|
|
86
|
+
|------|------|
|
|
87
|
+
| `blocked` | Hard **blockers[]**: no import map and/or no file_health (or severe ghost flood). |
|
|
88
|
+
| `map_ok_scope_risk` | Map+health present but **scope warnings** (often bare `.` monitor or multi-repo parent) and clean red/actionable yellow. |
|
|
89
|
+
| `ready_for_daemon` | Map Ready/Good **and** not bare_dot_monitor — only safe unattended tier. |
|
|
90
|
+
| `ready_with_agent_wiki_work` | Needs Attention, red=0 — daemon OK but agents still have wiki work. |
|
|
91
|
+
| `not_ready` | Other (e.g. reds). |
|
|
92
|
+
|
|
93
|
+
Bare `.` is a **scope warning** (`scope.warnings` / `fix_scope`), not always a hard blocker. Missing map/health **are** hard blockers and emit priority-1 `update_maps` / `seed_health` in `actions[]`.
|
|
94
|
+
|
|
95
|
+
### Why it happens (Grok-Bevy 2026-07 dogfood)
|
|
96
|
+
|
|
97
|
+
On **Grok-Bevy** (Rust workspace), first bootstrap reported:
|
|
98
|
+
|
|
99
|
+
| Signal | Field / tier | Meaning |
|
|
100
|
+
|--------|--------------|---------|
|
|
101
|
+
| `No import map (run update-maps first)` | `blockers[]` → `blocked` | No dependency/import map yet. Action: **`update_maps`**. |
|
|
102
|
+
| `No file_health` | `blockers[]` → `blocked` | Health matrix not seeded. Action: **`seed_health` / update-maps**. |
|
|
103
|
+
| `monitored_paths is bare '.'` | `scope.warnings` (often `map_ok_scope_risk` once map exists) | check-changes thrash risk. Action: **`fix_scope`**. |
|
|
104
|
+
|
|
105
|
+
Also: `bare_dot_monitor: true` and `scope.ok: false` until `monitored_paths.txt` lists **lean package roots**, not bare `.`.
|
|
106
|
+
|
|
107
|
+
### Fix once per project (agents must do this, not ignore blockers)
|
|
108
|
+
|
|
109
|
+
1. **Scope (wiki/health thrash bound)** — create/edit **`monitored_paths.txt`** at project root with *specific* dirs/files, e.g.:
|
|
110
|
+
```text
|
|
111
|
+
crates/foo/src/
|
|
112
|
+
crates/bar/src/
|
|
113
|
+
README.md
|
|
114
|
+
docs/
|
|
115
|
+
```
|
|
116
|
+
Never leave only `.` on non-tiny trees.
|
|
117
|
+
2. **Map roots** — create/edit **`map_paths.txt`** (import-map package roots; independent of monitored):
|
|
118
|
+
```text
|
|
119
|
+
crates/foo/src/
|
|
120
|
+
crates/bar/src/
|
|
121
|
+
```
|
|
122
|
+
3. **Build map + health** — from the target project (or with `project_root=` / `WIKIFIER_PROJECT_ROOT=`):
|
|
123
|
+
```bash
|
|
124
|
+
wikifier update-maps --full # or MCP update_maps full=true
|
|
125
|
+
# re-run session_bootstrap until readiness == ready_for_daemon
|
|
126
|
+
```
|
|
127
|
+
4. **Do not** bulk-re-wiki 🟡 *Initial stubs* after maps land — stubs = map coverage only.
|
|
128
|
+
|
|
129
|
+
After a successful fix, bootstrap looks like: `scope.ok: true`, `blockers: []`, `health_score: Map Ready` (stubs OK), **`readiness: ready_for_daemon`**. Map Ready without `ready_for_daemon` is **not** unattended-ready.
|
|
130
|
+
|
|
131
|
+
### Product note for Wikifier maintainers
|
|
132
|
+
|
|
133
|
+
- Default bare `.` is a footgun for real repos; agents must treat **`fix_scope` as P0** when scope warns bare-dot, and **update_maps/seed_health as P0** when readiness is `blocked`.
|
|
134
|
+
- **`wikifier init` (4.6.8+ / 4.6.9 templates)** seeds comment-guided lean templates with active default **`src/`** (bare `.` is commented opt-in for tiny toys only). Multi-crate / monorepo agents must replace with package roots before map-ready work.
|
|
135
|
+
|
|
136
|
+
See also: Findings note `Findings/readiness-blocked-bare-monitor-2026-07.md`.
|
|
137
|
+
|
|
68
138
|
## Mandatory New-Session Rule
|
|
69
139
|
|
|
70
140
|
**Copy this exact block into the system prompt or the very first instruction of every new LLM session that uses Wikifier:**
|
|
@@ -74,7 +144,7 @@ You are now operating inside a Wikifier-managed codebase (Agent Protocol v0.6
|
|
|
74
144
|
|
|
75
145
|
This is strictly an agent-to-agent wiki for token saving: map lookup (health + file wikis + deps) instead of full sources; selective wiki updates; not a human docs product or Jira.
|
|
76
146
|
|
|
77
|
-
SELECTIVE WORK (mandatory): Only update/remove/re-wiki 🔴 Red and 🟡 Yellow files. Do not re-summarize 🟢 Green files
|
|
147
|
+
SELECTIVE WORK (mandatory): Only update/remove/re-wiki 🔴 Red and *actionable* 🟡 Yellow files. Do **not** re-summarize 🟢 Green files, and do **not** bulk-wiki 🟡 Initial/map stubs (map coverage only — wiki a stub only when you edit that file). First-run builds the structural map; wiki depth is filled as you touch files. check-changes is content-honest (mtime-only thrash does not re-Yellow when source hash matches mark-green baseline).
|
|
78
148
|
|
|
79
149
|
FIRST ACTIONS:
|
|
80
150
|
1. Prefer MCP Core: session_bootstrap, check_changes, prepare_edit, suggest_next_actions (use actions[]), record_change, mark_green. Always pass project_root= for external projects.
|
|
@@ -131,10 +201,11 @@ However, if you are doing low-level direct writes to Wikifier state files (e.g.
|
|
|
131
201
|
### Limitations (Final M2-Rem-07 Assessment)
|
|
132
202
|
- Locks are **advisory** — a broken or malicious process can ignore them.
|
|
133
203
|
- Currently **project-level** (one lock for the entire project). This is the right tradeoff for current needs (including heavy multi-agent + monitor dogfooding) and keeps the implementation simple and fast.
|
|
134
|
-
-
|
|
204
|
+
- **Finite timeout** is supported: `file_lock(root, timeout=seconds)` raises `LockTimeoutError` if the lock is not free in time (`timeout=None` still blocks). `is_project_locked()` remains a diagnostic non-blocking probe.
|
|
205
|
+
- Per-file / sharded locking is a permanent non-goal unless extreme concurrency pressure appears.
|
|
135
206
|
- Best-effort portability on non-Unix systems.
|
|
136
207
|
|
|
137
|
-
The locking system (Python `file_lock` + shell `with_project_lock`)
|
|
208
|
+
The locking system (Python `file_lock` + shell `with_project_lock`) is production-ready for the M2 scope.
|
|
138
209
|
|
|
139
210
|
## Protocol v0.4 Additions: I/O Contracts, Error Handling, Structured Output, Versioning
|
|
140
211
|
|
|
@@ -168,7 +239,7 @@ Pending/journal/health side effects are observable via the returned messages + d
|
|
|
168
239
|
### Error Handling Expectations
|
|
169
240
|
- **Operational failures** (e.g., partial scan on huge tree, lock edge): return `{"success": False, "error": "...", "project_root": "...", ...partial_data }`. Agent must handle gracefully and log; continue where safe.
|
|
170
241
|
- **Programming / contract errors** (bad types, missing required): raise (standard Python exceptions) — these indicate agent bug.
|
|
171
|
-
- **Locking**: Mutators block on project lock
|
|
242
|
+
- **Locking**: Mutators block on project lock by default; optional finite `timeout=` raises on contention.
|
|
172
243
|
- Never assume text output parsability. Always use structured returns + explicit format="json".
|
|
173
244
|
- On external/packaged installs: discovery is robust; pass explicit project_root if cwd is ambiguous.
|
|
174
245
|
|
|
@@ -219,7 +290,7 @@ This project exposes a first-class MCP server (wikifier-mcp or python -m wikifie
|
|
|
219
290
|
|
|
220
291
|
**M5.1+ reality (from dogfood on external 5k-79k+ projects)**: MCP tools are preferred when available (get_project_status, get_file_wiki, get_barrel_reports for deep BRC, check_changes, record_change, mark_green, suggest_next_actions, health equivalents, etc.). **Always pass project_root= (or use WIKIFIER_PROJECT_ROOT env)** for external/user projects (RecipeLab_alt BRC stress, ConsistencyHub, llvm subs, etc.).
|
|
221
292
|
|
|
222
|
-
|
|
293
|
+
**CLI-at-scale policy (G15):** On barrel-heavy or large monorepos (BRC stress, multi-k source trees), treat **CLI/library as the primary reliability path** for `update_maps`, barrel reports, and long health/suggest loops. MCP subprocess tools use a ~60s cap and may still time out; that is **not** a product failure — **immediately fall back** to `python -m wikifier …` / library calls. Do not claim DoD2 (<30s MCP on alt/Consistency/llama) closed without a fresh measured pack; default operator policy is CLI for scale.
|
|
223
294
|
|
|
224
295
|
The server implements hardened external discovery (delegates to cli.py discover_project_root / _get_effective_root), 60s timeouts, actionable errors, and parity with library.
|
|
225
296
|
|
|
@@ -0,0 +1,238 @@
|
|
|
1
|
+
"""Real-path tests for gap amendment plan G1–G13/G15 closed-when bars.
|
|
2
|
+
|
|
3
|
+
Drives shipped library functions (session_bootstrap, build_structured_actions,
|
|
4
|
+
file_lock, build_map_coverage / run_full_update, library ACS section, protocol
|
|
5
|
+
strings). No theater: asserts against live code outputs.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
import importlib
|
|
11
|
+
import os
|
|
12
|
+
import unittest
|
|
13
|
+
|
|
14
|
+
from tests._base import REPO_ROOT, TempProjectTestCase
|
|
15
|
+
|
|
16
|
+
from wikifier import __version__
|
|
17
|
+
from wikifier.agent_loop import build_structured_actions, session_bootstrap
|
|
18
|
+
from wikifier import cli
|
|
19
|
+
from wikifier import locking
|
|
20
|
+
from wikifier.library import _generate_acs_section
|
|
21
|
+
|
|
22
|
+
health_mod = importlib.import_module("wikifier.health")
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class TestProtocolVersionPointers(unittest.TestCase):
|
|
26
|
+
"""G1: operator package pointers match live major.minor."""
|
|
27
|
+
|
|
28
|
+
def test_version_is_4_6(self):
|
|
29
|
+
self.assertTrue(
|
|
30
|
+
__version__.startswith("4.6."),
|
|
31
|
+
f"expected 4.6.x package, got {__version__}",
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
def test_skills_run_header_not_4_5(self):
|
|
35
|
+
text = (REPO_ROOT / "skills" / "run.md").read_text(encoding="utf-8")
|
|
36
|
+
# Header / package-notes must not claim current 4.5.x
|
|
37
|
+
first_30 = "\n".join(text.splitlines()[:30])
|
|
38
|
+
self.assertNotIn("package current **4.5.x**", first_30)
|
|
39
|
+
self.assertIn("4.6.x", first_30)
|
|
40
|
+
# SELECTIVE WORK excludes Initial stubs (G3)
|
|
41
|
+
self.assertIn("actionable", text)
|
|
42
|
+
self.assertIn("Initial", text)
|
|
43
|
+
self.assertRegex(
|
|
44
|
+
text,
|
|
45
|
+
r"SELECTIVE WORK.*actionable.*Initial",
|
|
46
|
+
)
|
|
47
|
+
# G15 CLI-at-scale policy present
|
|
48
|
+
self.assertIn("CLI-at-scale policy", text)
|
|
49
|
+
# G8 ready_for_daemon not Map Ready alone
|
|
50
|
+
self.assertIn("ready_for_daemon", text)
|
|
51
|
+
self.assertIn("not Map Ready alone", text)
|
|
52
|
+
|
|
53
|
+
def test_claude_md_not_4_5(self):
|
|
54
|
+
for name in ("Claude.md", "CLAUDE.md"):
|
|
55
|
+
p = REPO_ROOT / name
|
|
56
|
+
if not p.is_file():
|
|
57
|
+
continue
|
|
58
|
+
t = p.read_text(encoding="utf-8")
|
|
59
|
+
self.assertNotIn("currently 4.5.x", t)
|
|
60
|
+
self.assertIn("4.6.x", t)
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
class TestBootstrapMessageAndActions(TempProjectTestCase):
|
|
64
|
+
"""G2/G5/G6: bootstrap message + blocker actions."""
|
|
65
|
+
|
|
66
|
+
def test_bootstrap_message_blocked_never_says_ready(self):
|
|
67
|
+
# Fresh temp root: no cache, no health → blocked
|
|
68
|
+
boot = session_bootstrap(project_root=self.root)
|
|
69
|
+
self.assertTrue(boot.get("success"))
|
|
70
|
+
readiness = boot.get("readiness")
|
|
71
|
+
msg = str(boot.get("message") or "")
|
|
72
|
+
if readiness == "blocked":
|
|
73
|
+
self.assertIn("readiness=blocked", msg)
|
|
74
|
+
self.assertNotRegex(msg, r"^session_bootstrap ready")
|
|
75
|
+
self.assertNotIn("session_bootstrap ready —", msg)
|
|
76
|
+
# Always embeds readiness=
|
|
77
|
+
self.assertIn("readiness=", msg)
|
|
78
|
+
|
|
79
|
+
def test_blockers_produce_priority_leq_2_map_or_health(self):
|
|
80
|
+
acts = build_structured_actions(
|
|
81
|
+
blockers=[
|
|
82
|
+
"No import map (run update-maps first).",
|
|
83
|
+
"No file_health — run seed-health or update-maps.",
|
|
84
|
+
],
|
|
85
|
+
scope_warnings=["monitored_paths is bare '.' — thrash risk"],
|
|
86
|
+
red=0,
|
|
87
|
+
actionable_yellow=0,
|
|
88
|
+
clean=True,
|
|
89
|
+
)
|
|
90
|
+
kinds = {a["action"] for a in acts}
|
|
91
|
+
self.assertIn("update_maps", kinds)
|
|
92
|
+
self.assertIn("seed_health", kinds)
|
|
93
|
+
self.assertIn("fix_scope", kinds)
|
|
94
|
+
for a in acts:
|
|
95
|
+
if a["action"] in ("update_maps", "seed_health"):
|
|
96
|
+
self.assertLessEqual(int(a["priority"]), 2, a)
|
|
97
|
+
if a["action"] == "fix_scope":
|
|
98
|
+
self.assertLessEqual(int(a["priority"]), 2, a)
|
|
99
|
+
|
|
100
|
+
def test_map_incomplete_action_priority(self):
|
|
101
|
+
acts = build_structured_actions(
|
|
102
|
+
map_coverage={
|
|
103
|
+
"complete": False,
|
|
104
|
+
"files_remaining_dirty": 12,
|
|
105
|
+
"files_skipped": 12,
|
|
106
|
+
"budget_max_files": 5,
|
|
107
|
+
},
|
|
108
|
+
red=0,
|
|
109
|
+
actionable_yellow=0,
|
|
110
|
+
)
|
|
111
|
+
incomplete = [a for a in acts if a["action"] == "update_maps_until_complete"]
|
|
112
|
+
self.assertTrue(incomplete)
|
|
113
|
+
self.assertLessEqual(int(incomplete[0]["priority"]), 2)
|
|
114
|
+
self.assertIn("success alone", incomplete[0]["reason"].lower())
|
|
115
|
+
|
|
116
|
+
def test_stub_only_map_first_ok_no_wiki_refresh(self):
|
|
117
|
+
acts = build_structured_actions(
|
|
118
|
+
stub_yellow=100,
|
|
119
|
+
actionable_yellow=0,
|
|
120
|
+
red=0,
|
|
121
|
+
actionable_yellow_files=[],
|
|
122
|
+
clean=False,
|
|
123
|
+
)
|
|
124
|
+
kinds = {a["action"] for a in acts}
|
|
125
|
+
self.assertIn("map_first_ok", kinds)
|
|
126
|
+
self.assertNotIn("wiki_refresh", kinds)
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
class TestMapCoverageHonesty(TempProjectTestCase):
|
|
130
|
+
"""G5: update_maps surfaces map_complete / map_ready."""
|
|
131
|
+
|
|
132
|
+
def test_run_full_update_exposes_map_ready_flags(self):
|
|
133
|
+
self.write("pkg/a.py", "import os\n")
|
|
134
|
+
(self.root / "monitored_paths.txt").write_text("pkg/\n", encoding="utf-8")
|
|
135
|
+
(self.root / "map_paths.txt").write_text("pkg/\n", encoding="utf-8")
|
|
136
|
+
res = cli.run_full_update(root=self.root, directory="pkg/")
|
|
137
|
+
self.assertTrue(res.get("success"), res)
|
|
138
|
+
self.assertIn("map_coverage", res)
|
|
139
|
+
self.assertIn("map_complete", res)
|
|
140
|
+
self.assertIn("map_ready", res)
|
|
141
|
+
# map_ready must track complete + remaining dirty
|
|
142
|
+
mc = res["map_coverage"]
|
|
143
|
+
expected_ready = bool(mc.get("complete")) and int(
|
|
144
|
+
mc.get("files_remaining_dirty") or 0
|
|
145
|
+
) == 0
|
|
146
|
+
self.assertEqual(res["map_ready"], expected_ready)
|
|
147
|
+
self.assertEqual(res["map_complete"], bool(mc.get("complete")))
|
|
148
|
+
|
|
149
|
+
def test_budgeted_max_files_incomplete_flags(self):
|
|
150
|
+
for i in range(6):
|
|
151
|
+
self.write(f"src/m{i}.py", f"x={i}\n")
|
|
152
|
+
(self.root / "monitored_paths.txt").write_text("src/\n", encoding="utf-8")
|
|
153
|
+
(self.root / "map_paths.txt").write_text("src/\n", encoding="utf-8")
|
|
154
|
+
res = cli.run_full_update(
|
|
155
|
+
root=self.root, directory="src/", max_files=2
|
|
156
|
+
)
|
|
157
|
+
self.assertTrue(res.get("success"), res)
|
|
158
|
+
self.assertIn("map_coverage", res)
|
|
159
|
+
# With budget, remaining dirty may be > 0
|
|
160
|
+
rem = int((res.get("map_coverage") or {}).get("files_remaining_dirty") or 0)
|
|
161
|
+
if rem > 0 or (res.get("map_coverage") or {}).get("complete") is False:
|
|
162
|
+
self.assertFalse(res.get("map_ready"))
|
|
163
|
+
acts = build_structured_actions(map_coverage=res["map_coverage"])
|
|
164
|
+
self.assertTrue(
|
|
165
|
+
any(a["action"] == "update_maps_until_complete" for a in acts)
|
|
166
|
+
)
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
class TestLockTimeout(TempProjectTestCase):
|
|
170
|
+
"""G13: file_lock honors finite timeout."""
|
|
171
|
+
|
|
172
|
+
def test_timeout_raises_when_held(self):
|
|
173
|
+
"""Hold exclusive lock on a second fd; finite timeout must raise (G13)."""
|
|
174
|
+
if locking.fcntl is None:
|
|
175
|
+
self.skipTest("fcntl not available")
|
|
176
|
+
lock_path = self.root / locking.LOCK_FILE_NAME
|
|
177
|
+
lock_path.parent.mkdir(parents=True, exist_ok=True)
|
|
178
|
+
holder_fd = os.open(str(lock_path), os.O_CREAT | os.O_RDWR, 0o644)
|
|
179
|
+
try:
|
|
180
|
+
locking.fcntl.flock(holder_fd, locking.fcntl.LOCK_EX)
|
|
181
|
+
contender_fd = os.open(str(lock_path), os.O_CREAT | os.O_RDWR, 0o644)
|
|
182
|
+
try:
|
|
183
|
+
with self.assertRaises(locking.LockTimeoutError):
|
|
184
|
+
locking._acquire_exclusive(contender_fd, 0.25)
|
|
185
|
+
finally:
|
|
186
|
+
os.close(contender_fd)
|
|
187
|
+
finally:
|
|
188
|
+
locking.fcntl.flock(holder_fd, locking.fcntl.LOCK_UN)
|
|
189
|
+
os.close(holder_fd)
|
|
190
|
+
|
|
191
|
+
def test_reentrant_still_works(self):
|
|
192
|
+
with locking.file_lock(self.root):
|
|
193
|
+
with locking.file_lock(self.root):
|
|
194
|
+
pass # nested re-entrant
|
|
195
|
+
|
|
196
|
+
|
|
197
|
+
class TestAcsLibrarySection(unittest.TestCase):
|
|
198
|
+
"""G9: library ACS prefers actionable."""
|
|
199
|
+
|
|
200
|
+
def test_acs_section_prefers_actionable(self):
|
|
201
|
+
cache = {
|
|
202
|
+
"_acs_summary": {
|
|
203
|
+
"total_scored_edges": 100,
|
|
204
|
+
"avg_confidence": 0.7,
|
|
205
|
+
"low_conf_edges": 40,
|
|
206
|
+
"actionable_low_conf_edges": 5,
|
|
207
|
+
"low_conf_threshold": 0.65,
|
|
208
|
+
"reason_code_counts": {
|
|
209
|
+
"external_or_bare": 30,
|
|
210
|
+
"unresolved_project": 5,
|
|
211
|
+
},
|
|
212
|
+
"sample_low_conf_explanations": ["raw sample"],
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
lines = _generate_acs_section(cache)
|
|
216
|
+
text = "\n".join(lines)
|
|
217
|
+
self.assertIn("actionable_low_conf", text)
|
|
218
|
+
self.assertIn("reason_code_counts", text)
|
|
219
|
+
self.assertIn("thrash", text.lower())
|
|
220
|
+
self.assertIn("actionable_low_conf_edges", text)
|
|
221
|
+
self.assertIn("actionable", text.lower())
|
|
222
|
+
|
|
223
|
+
|
|
224
|
+
class TestReadinessTiersDocShape(TempProjectTestCase):
|
|
225
|
+
"""G4: readiness tiers match code for missing map/health."""
|
|
226
|
+
|
|
227
|
+
def test_no_map_is_blocked(self):
|
|
228
|
+
ready = health_mod.assess_autonomous_readiness(self.root)
|
|
229
|
+
self.assertEqual(ready.get("readiness"), "blocked")
|
|
230
|
+
blockers = " ".join(ready.get("blockers") or [])
|
|
231
|
+
self.assertTrue(
|
|
232
|
+
"import map" in blockers.lower() or "file_health" in blockers.lower(),
|
|
233
|
+
blockers,
|
|
234
|
+
)
|
|
235
|
+
|
|
236
|
+
|
|
237
|
+
if __name__ == "__main__":
|
|
238
|
+
unittest.main()
|