wikifier 4.5.5__tar.gz → 4.5.8__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.5.5/wikifier.egg-info → wikifier-4.5.8}/PKG-INFO +1 -1
- {wikifier-4.5.5 → wikifier-4.5.8}/skills/run.md +15 -8
- {wikifier-4.5.5 → wikifier-4.5.8}/tests/test_gap_closure.py +158 -0
- {wikifier-4.5.5 → wikifier-4.5.8}/wikifier/__init__.py +1 -1
- {wikifier-4.5.5 → wikifier-4.5.8}/wikifier/cli.py +131 -34
- {wikifier-4.5.5 → wikifier-4.5.8}/wikifier/daemon.py +113 -21
- {wikifier-4.5.5 → wikifier-4.5.8}/wikifier/health.py +771 -46
- {wikifier-4.5.5 → wikifier-4.5.8}/wikifier/mcp/server.py +38 -8
- {wikifier-4.5.5 → wikifier-4.5.8/wikifier.egg-info}/PKG-INFO +1 -1
- {wikifier-4.5.5 → wikifier-4.5.8}/CONTRIBUTING.md +0 -0
- {wikifier-4.5.5 → wikifier-4.5.8}/LICENSE +0 -0
- {wikifier-4.5.5 → wikifier-4.5.8}/MANIFEST.in +0 -0
- {wikifier-4.5.5 → wikifier-4.5.8}/README.md +0 -0
- {wikifier-4.5.5 → wikifier-4.5.8}/diagnostics.html +0 -0
- {wikifier-4.5.5 → wikifier-4.5.8}/docs/spec.md +0 -0
- {wikifier-4.5.5 → wikifier-4.5.8}/index.html +0 -0
- {wikifier-4.5.5 → wikifier-4.5.8}/pyproject.toml +0 -0
- {wikifier-4.5.5 → wikifier-4.5.8}/setup.cfg +0 -0
- {wikifier-4.5.5 → wikifier-4.5.8}/tests/test_barrel_invalidation.py +0 -0
- {wikifier-4.5.5 → wikifier-4.5.8}/tests/test_health.py +0 -0
- {wikifier-4.5.5 → wikifier-4.5.8}/tests/test_import_cache.py +0 -0
- {wikifier-4.5.5 → wikifier-4.5.8}/tests/test_multi_lang_parsers.py +0 -0
- {wikifier-4.5.5 → wikifier-4.5.8}/tests/test_parsers.py +0 -0
- {wikifier-4.5.5 → wikifier-4.5.8}/tests/test_selftest_wrappers.py +0 -0
- {wikifier-4.5.5 → wikifier-4.5.8}/wikifier/__main__.py +0 -0
- {wikifier-4.5.5 → wikifier-4.5.8}/wikifier/contracts.py +0 -0
- {wikifier-4.5.5 → wikifier-4.5.8}/wikifier/diagnostics.py +0 -0
- {wikifier-4.5.5 → wikifier-4.5.8}/wikifier/import_cache.py +0 -0
- {wikifier-4.5.5 → wikifier-4.5.8}/wikifier/index.html +0 -0
- {wikifier-4.5.5 → wikifier-4.5.8}/wikifier/library.py +0 -0
- {wikifier-4.5.5 → wikifier-4.5.8}/wikifier/locking.py +0 -0
- {wikifier-4.5.5 → wikifier-4.5.8}/wikifier/mcp/__init__.py +0 -0
- {wikifier-4.5.5 → wikifier-4.5.8}/wikifier/parsers/__init__.py +0 -0
- {wikifier-4.5.5 → wikifier-4.5.8}/wikifier/parsers/_edge.py +0 -0
- {wikifier-4.5.5 → wikifier-4.5.8}/wikifier/parsers/bree.py +0 -0
- {wikifier-4.5.5 → wikifier-4.5.8}/wikifier/parsers/c_cpp.py +0 -0
- {wikifier-4.5.5 → wikifier-4.5.8}/wikifier/parsers/cdia.py +0 -0
- {wikifier-4.5.5 → wikifier-4.5.8}/wikifier/parsers/csharp.py +0 -0
- {wikifier-4.5.5 → wikifier-4.5.8}/wikifier/parsers/go_lang.py +0 -0
- {wikifier-4.5.5 → wikifier-4.5.8}/wikifier/parsers/java.py +0 -0
- {wikifier-4.5.5 → wikifier-4.5.8}/wikifier/parsers/javascript.py +0 -0
- {wikifier-4.5.5 → wikifier-4.5.8}/wikifier/parsers/python.py +0 -0
- {wikifier-4.5.5 → wikifier-4.5.8}/wikifier/parsers/rust.py +0 -0
- {wikifier-4.5.5 → wikifier-4.5.8}/wikifier/resolution.py +0 -0
- {wikifier-4.5.5 → wikifier-4.5.8}/wikifier/scripts/wikifier.bat +0 -0
- {wikifier-4.5.5 → wikifier-4.5.8}/wikifier/scripts/wikifier.ps1 +0 -0
- {wikifier-4.5.5 → wikifier-4.5.8}/wikifier/scripts/wikifier.sh +0 -0
- {wikifier-4.5.5 → wikifier-4.5.8}/wikifier/serve.py +0 -0
- {wikifier-4.5.5 → wikifier-4.5.8}/wikifier.egg-info/SOURCES.txt +0 -0
- {wikifier-4.5.5 → wikifier-4.5.8}/wikifier.egg-info/dependency_links.txt +0 -0
- {wikifier-4.5.5 → wikifier-4.5.8}/wikifier.egg-info/entry_points.txt +0 -0
- {wikifier-4.5.5 → wikifier-4.5.8}/wikifier.egg-info/requires.txt +0 -0
- {wikifier-4.5.5 → wikifier-4.5.8}/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.5.
|
|
3
|
+
Version: 4.5.8
|
|
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
|
|
@@ -10,12 +10,15 @@
|
|
|
10
10
|
**Package 4.5.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
12
|
- **First-run:** `init` → `update-maps` → `health --summary` → `suggest_next_actions`. Map is automatic; wiki *prose* is agent-filled over time.
|
|
13
|
-
- **
|
|
14
|
-
- **
|
|
15
|
-
- **
|
|
16
|
-
- **
|
|
17
|
-
- **
|
|
18
|
-
-
|
|
13
|
+
- **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 re-wiki the whole stub set). ACS uses `actionable_low_conf_edges`.
|
|
15
|
+
- **Dual scope:** `monitored_paths` = check-changes/health scope (keep lean). `update-maps --directory=` / `--max-files=` = map walk. Never set `project_root` to a multi-repo parent (e.g. `cloned_sample_projects`).
|
|
16
|
+
- **CLI target:** `wikifier --target /abs/project health --summary` (flags stripped before the command). Or `WIKIFIER_PROJECT_ROOT=…`.
|
|
17
|
+
- **Long-horizon:** `wikifier autonomous-status` (or `readiness`) before unattended runs; daemon writes `.wikifier_staging/daemon_heartbeat.json`. Soak ≥72h is still evidence work, not automatic.
|
|
18
|
+
- **Deletions:** `record-deletion` + ghost detection. **Languages:** py/js/ts/rust/go/c/c#/java (regex). journal/pending = audit queue, not Jira.
|
|
19
|
+
- **Huge monorepos:** lean `monitored_paths.txt` (not bare `.`). `WIKIFIER_CHECK_CHANGES_MAX` default 2000. Hygiene: `seed-health`, `prune-pending`, `prune-health`.
|
|
20
|
+
- **Monitor/daemon:** mtime heartbeat; agents still own wiki/mark-green. `WIKIFIER_DAEMON_MAPS_INTERVAL` default 600s; `WIKIFIER_DAEMON_MAPS=0` for check-only.
|
|
21
|
+
- Health module: `importlib.import_module("wikifier.health")` or `wikifier.health_module` — `from wikifier import health` is the *function*.
|
|
19
22
|
|
|
20
23
|
**v0.6 migration notes (packages v4.2.0–v4.3.0)** — changes are additive or strictly-better defaults; v0.5 agent behavior keeps working:
|
|
21
24
|
- `update-maps` (CLI) now runs the **pure-Python full pipeline**: every dirty file is parsed in-process, the canonical per-file cache is persisted, reverse deps + cycles + ACS are computed, and `library.md` is regenerated atomically. `--python-primary` is accepted but redundant; the in-shell first-pass was retired entirely (wikifier.sh's update-maps delegates to this pipeline; `--sh`/`--legacy-sh` are deprecated no-ops). Scoping is explicit via `--directory=`/`--max-files=` and reported in the result (`files_skipped`) — there are no silent caps.
|
|
@@ -39,11 +42,15 @@ Do **not** open megamodules (`javascript.py`, `import_cache.py`, `bree.py`) to d
|
|
|
39
42
|
| Need | Use |
|
|
40
43
|
|------|-----|
|
|
41
44
|
| First-run map | `init` → `update-maps` → `health --summary` → `suggest-next` |
|
|
42
|
-
| Steady-state | `check-changes` → edit
|
|
45
|
+
| Steady-state | `check-changes` → edit 🔴/actionable 🟡 only → `record-change` → wiki → `mark-green` |
|
|
43
46
|
| Lookup | MCP Core 6: status, check_changes, needing_attention, get_file_wiki, suggest, record/mark |
|
|
44
47
|
| Deps / cycles | `get_dependencies`, `get_dependents`, `get_cycles` (json) |
|
|
45
48
|
| Deletion | `record-deletion` |
|
|
46
|
-
|
|
|
49
|
+
| Long-horizon prep | `autonomous-status` / `readiness`; then `daemon start` with lean monitor |
|
|
50
|
+
| Hygiene | `seed-health`, `prune-pending`, `prune-health`, `validate` |
|
|
51
|
+
| Code entry | `cli.py` dispatch + `health.py` + parsers — self-tests in `tests/` + `tests/selftest/` |
|
|
52
|
+
|
|
53
|
+
**Do not open megamodules** (`javascript.py` ~2.6k, `import_cache.py` ~2.3k, `bree.py` ~2k) for workflow decisions — use tools + this table.
|
|
47
54
|
|
|
48
55
|
**CLI pure-Python vs shell:** mutators and maps prefer `python -m wikifier …`. Shell still owns `init`, `monitor`, `daemon`, `serve`, `journal`.
|
|
49
56
|
**Scope:** `monitored_paths` = change detection; `exclude_patterns` + optional `--directory` = map walk.
|
|
@@ -190,5 +190,163 @@ class TestPendingAndPollutionHygiene(TempProjectTestCase):
|
|
|
190
190
|
self.assertIn("gone_real.py", entries2)
|
|
191
191
|
|
|
192
192
|
|
|
193
|
+
class TestMapFirstTaxonomyAndReadiness(TempProjectTestCase):
|
|
194
|
+
def test_stub_yellow_is_map_ready_not_needs_attention(self):
|
|
195
|
+
health_mod = importlib.import_module("wikifier.health")
|
|
196
|
+
health_mod.upsert_entry(
|
|
197
|
+
self.root, "a.py", "🟡 Yellow",
|
|
198
|
+
"Initial stub — present in dependency map; agent should wiki + mark-green when editing",
|
|
199
|
+
)
|
|
200
|
+
health_mod.upsert_entry(
|
|
201
|
+
self.root, "b.py", "🟡 Yellow",
|
|
202
|
+
"Initial stub — parseable source under monitored_paths; agent should wiki + mark-green when editing",
|
|
203
|
+
)
|
|
204
|
+
s = health_mod.get_summary(self.root)
|
|
205
|
+
self.assertEqual(s.get("stub_yellow"), 2)
|
|
206
|
+
self.assertEqual(s.get("actionable_yellow"), 0)
|
|
207
|
+
self.assertEqual(s.get("health_score"), "Map Ready")
|
|
208
|
+
|
|
209
|
+
def test_actionable_yellow_needs_attention(self):
|
|
210
|
+
health_mod = importlib.import_module("wikifier.health")
|
|
211
|
+
health_mod.upsert_entry(
|
|
212
|
+
self.root, "a.py", "🟡 Yellow",
|
|
213
|
+
"mtime changed since last check-changes (Python primary auto-detected)",
|
|
214
|
+
)
|
|
215
|
+
s = health_mod.get_summary(self.root)
|
|
216
|
+
self.assertEqual(s.get("actionable_yellow"), 1)
|
|
217
|
+
self.assertEqual(s.get("health_score"), "Needs Attention")
|
|
218
|
+
|
|
219
|
+
def test_suggest_does_not_bulk_wiki_stubs(self):
|
|
220
|
+
health_mod = importlib.import_module("wikifier.health")
|
|
221
|
+
health_mod.upsert_entry(self.root, "a.py", "🟡 Yellow", "Initial stub — present in dependency map")
|
|
222
|
+
text = cli.suggest_next_actions(project_root=self.root, format="text")
|
|
223
|
+
self.assertIn("Map-first", text)
|
|
224
|
+
self.assertNotIn("Review the 1 🟡 Yellow file(s) only", text)
|
|
225
|
+
|
|
226
|
+
def test_detect_multi_project_container(self):
|
|
227
|
+
health_mod = importlib.import_module("wikifier.health")
|
|
228
|
+
for name in ("proj_a", "proj_b", "proj_c"):
|
|
229
|
+
d = self.root / name
|
|
230
|
+
d.mkdir()
|
|
231
|
+
(d / ".git").mkdir()
|
|
232
|
+
scope = health_mod.detect_scope_risks(self.root)
|
|
233
|
+
self.assertGreaterEqual(scope.get("child_project_count"), 3)
|
|
234
|
+
self.assertFalse(scope.get("ok"))
|
|
235
|
+
self.assertTrue(any("multi-project" in w.lower() for w in scope.get("warnings") or []))
|
|
236
|
+
|
|
237
|
+
def test_autonomous_readiness_shape(self):
|
|
238
|
+
health_mod = importlib.import_module("wikifier.health")
|
|
239
|
+
(self.root / "monitored_paths.txt").write_text("src\n", encoding="utf-8")
|
|
240
|
+
self.write("src/app.py", "x=1\n")
|
|
241
|
+
health_mod.upsert_entry(self.root, "src/app.py", "🟡 Yellow", "Initial stub — present in dependency map")
|
|
242
|
+
r = health_mod.assess_autonomous_readiness(self.root)
|
|
243
|
+
self.assertTrue(r.get("success"))
|
|
244
|
+
self.assertIn(r.get("readiness"), (
|
|
245
|
+
"blocked", "map_ok_scope_risk", "ready_for_daemon",
|
|
246
|
+
"ready_with_agent_wiki_work", "not_ready",
|
|
247
|
+
))
|
|
248
|
+
self.assertIn("long_horizon_note", r)
|
|
249
|
+
self.assertIn("metrics", r)
|
|
250
|
+
|
|
251
|
+
def test_write_metrics_snapshot_history(self):
|
|
252
|
+
health_mod = importlib.import_module("wikifier.health")
|
|
253
|
+
(self.root / "monitored_paths.txt").write_text("src\n", encoding="utf-8")
|
|
254
|
+
self.write("src/app.py", "x=1\n")
|
|
255
|
+
health_mod.upsert_entry(self.root, "src/app.py", "🟢 Green", "ok")
|
|
256
|
+
s1 = health_mod.write_metrics_snapshot(self.root, source="test1")
|
|
257
|
+
self.assertTrue(s1.get("success"))
|
|
258
|
+
latest = self.root / ".wikifier_staging" / "metrics_latest.json"
|
|
259
|
+
hist = self.root / ".wikifier_staging" / "metrics_history.jsonl"
|
|
260
|
+
self.assertTrue(latest.exists())
|
|
261
|
+
self.assertTrue(hist.exists())
|
|
262
|
+
s2 = health_mod.write_metrics_snapshot(self.root, source="test2")
|
|
263
|
+
self.assertTrue(s2.get("success"))
|
|
264
|
+
rows = health_mod.read_metrics_history(self.root, limit=10)
|
|
265
|
+
self.assertGreaterEqual(len(rows), 2)
|
|
266
|
+
self.assertEqual(rows[-1].get("source"), "test2")
|
|
267
|
+
self.assertIn("staging_bytes", rows[-1])
|
|
268
|
+
self.assertIn("health_score", rows[-1])
|
|
269
|
+
|
|
270
|
+
|
|
271
|
+
class TestMapFirstHealthAndValidate(TempProjectTestCase):
|
|
272
|
+
def test_deep_relative_keys_are_under_root(self):
|
|
273
|
+
"""Regression: deep monorepo rel paths must not be treated as abs pollution."""
|
|
274
|
+
health_mod = importlib.import_module("wikifier.health")
|
|
275
|
+
deep = "airflow-core/src/airflow/api/client/local_client.py"
|
|
276
|
+
self.assertTrue(health_mod._entry_is_under_root(self.root, deep))
|
|
277
|
+
self.assertTrue(health_mod._entry_is_under_root(self.root, "pkg/a.py"))
|
|
278
|
+
|
|
279
|
+
def test_seed_health_from_map_creates_stubs(self):
|
|
280
|
+
health_mod = importlib.import_module("wikifier.health")
|
|
281
|
+
from wikifier import import_cache as ic
|
|
282
|
+
|
|
283
|
+
self.write("pkg/a.py", "import b\n")
|
|
284
|
+
self.write("pkg/b.py", "x=1\n")
|
|
285
|
+
self.write("README.md", "# hi\n")
|
|
286
|
+
cache = {}
|
|
287
|
+
ic.update_file_data(cache, "pkg/a.py", mtime=1, imports=["b"], resolved_pairs=[])
|
|
288
|
+
ic.update_file_data(cache, "pkg/b.py", mtime=1, imports=[], resolved_pairs=[])
|
|
289
|
+
ic.save_cache(self.root, cache)
|
|
290
|
+
|
|
291
|
+
# No health file yet
|
|
292
|
+
self.assertFalse((self.root / "file_health.json").exists())
|
|
293
|
+
res = health_mod.seed_health_from_map(self.root)
|
|
294
|
+
self.assertTrue(res.get("success"))
|
|
295
|
+
self.assertGreaterEqual(res.get("seeded"), 2)
|
|
296
|
+
entries = health_mod.load_health(self.root)["entries"]
|
|
297
|
+
self.assertIn("pkg/a.py", entries)
|
|
298
|
+
self.assertIn("pkg/b.py", entries)
|
|
299
|
+
self.assertTrue(entries["pkg/a.py"]["status"].startswith("🟡"))
|
|
300
|
+
|
|
301
|
+
def test_validate_ignores_non_source_and_reports_mapped_gaps(self):
|
|
302
|
+
health_mod = importlib.import_module("wikifier.health")
|
|
303
|
+
from wikifier import import_cache as ic
|
|
304
|
+
|
|
305
|
+
self.write("src/app.py", "print(1)\n")
|
|
306
|
+
self.write("src/notes.md", "docs\n")
|
|
307
|
+
self.write("src/logo.png", "x")
|
|
308
|
+
(self.root / "monitored_paths.txt").write_text("src\n", encoding="utf-8")
|
|
309
|
+
cache = {}
|
|
310
|
+
ic.update_file_data(cache, "src/app.py", mtime=1, imports=[], resolved_pairs=[])
|
|
311
|
+
ic.update_file_data(cache, "src/other.py", mtime=1, imports=[], resolved_pairs=[])
|
|
312
|
+
ic.save_cache(self.root, cache)
|
|
313
|
+
|
|
314
|
+
# No health yet — in-scope mapped + monitored parseable gaps
|
|
315
|
+
v = health_mod.validate_health(self.root)
|
|
316
|
+
self.assertTrue(v.get("map_first"))
|
|
317
|
+
self.assertGreaterEqual(v.get("mapped_in_scope_without_health_count", 0), 1)
|
|
318
|
+
self.assertGreaterEqual(v.get("missing_count", 0), 1)
|
|
319
|
+
# notes.md / logo.png must not appear as monitored-source gaps
|
|
320
|
+
mon_missing = v.get("missing_monitored_source") or []
|
|
321
|
+
self.assertTrue(all(not m.endswith(".md") and not m.endswith(".png") for m in mon_missing))
|
|
322
|
+
|
|
323
|
+
health_mod.seed_health_from_map(self.root) # only_monitored default
|
|
324
|
+
# Seed in-scope map keys; app.py on disk + mapped in-scope covered
|
|
325
|
+
v2 = health_mod.validate_health(self.root)
|
|
326
|
+
self.assertEqual(v2.get("mapped_in_scope_without_health_count"), 0)
|
|
327
|
+
# missing_count may still include on-disk sources not in map (ok if 0 after seed of map keys only)
|
|
328
|
+
self.assertEqual(v2.get("mapped_in_scope_without_health_count"), 0)
|
|
329
|
+
|
|
330
|
+
def test_prune_pending_and_health_to_monitored(self):
|
|
331
|
+
health_mod = importlib.import_module("wikifier.health")
|
|
332
|
+
(self.root / "monitored_paths.txt").write_text("src\n", encoding="utf-8")
|
|
333
|
+
self.write("src/keep.py", "k=1\n")
|
|
334
|
+
self.write("other/out.py", "o=1\n")
|
|
335
|
+
health_mod.upsert_entry(self.root, "src/keep.py", "🟡 Yellow", "in scope")
|
|
336
|
+
health_mod.upsert_entry(self.root, "other/out.py", "🟡 Yellow", "out of scope")
|
|
337
|
+
health_mod.add_to_pending(self.root, "src/keep.py", "need wiki")
|
|
338
|
+
health_mod.add_to_pending(self.root, "other/out.py", "noise")
|
|
339
|
+
|
|
340
|
+
pr = health_mod.prune_pending_to_monitored(self.root)
|
|
341
|
+
self.assertEqual(pr.get("removed"), 1)
|
|
342
|
+
self.assertEqual(health_mod.count_pending(self.root), 1)
|
|
343
|
+
|
|
344
|
+
hr = health_mod.prune_health_outside_monitored(self.root)
|
|
345
|
+
self.assertEqual(hr.get("removed"), 1)
|
|
346
|
+
entries = health_mod.load_health(self.root)["entries"]
|
|
347
|
+
self.assertIn("src/keep.py", entries)
|
|
348
|
+
self.assertNotIn("other/out.py", entries)
|
|
349
|
+
|
|
350
|
+
|
|
193
351
|
if __name__ == "__main__":
|
|
194
352
|
unittest.main()
|
|
@@ -616,23 +616,6 @@ def run_full_update(
|
|
|
616
616
|
result["parse_errors"] = parse_errors[:10]
|
|
617
617
|
result["parse_error_count"] = len(parse_errors)
|
|
618
618
|
|
|
619
|
-
# Seed health stubs for newly parsed files (map-first wiki: agents fill prose later)
|
|
620
|
-
health_seeded = 0
|
|
621
|
-
if _health_mod is not None and new_entries:
|
|
622
|
-
try:
|
|
623
|
-
existing = (_health_mod.load_health(root) or {}).get("entries") or {}
|
|
624
|
-
for rel in new_entries:
|
|
625
|
-
if rel in existing:
|
|
626
|
-
continue
|
|
627
|
-
_health_mod.upsert_entry(
|
|
628
|
-
root, rel, "🟡 Yellow",
|
|
629
|
-
"Initial stub — parsed into map; agent should wiki + mark-green when editing"
|
|
630
|
-
)
|
|
631
|
-
health_seeded += 1
|
|
632
|
-
except Exception:
|
|
633
|
-
health_seeded = 0
|
|
634
|
-
result["health_stubs_seeded"] = health_seeded
|
|
635
|
-
|
|
636
619
|
# === 4. Persist (single save; reload first to pick up the barrel flush) ===
|
|
637
620
|
cache = ic.load_cache(root) or {}
|
|
638
621
|
cache.update(new_entries)
|
|
@@ -669,6 +652,27 @@ def run_full_update(
|
|
|
669
652
|
except Exception:
|
|
670
653
|
pass
|
|
671
654
|
|
|
655
|
+
# === 5b. Map-first health stubs (always backfill; warm cache safe) ===
|
|
656
|
+
# 0-dirty incremental runs never used to create file_health.json — fixed here.
|
|
657
|
+
health_seeded = 0
|
|
658
|
+
if _health_mod is not None and hasattr(_health_mod, "seed_health_from_map"):
|
|
659
|
+
try:
|
|
660
|
+
max_seed = int(os.environ.get("WIKIFIER_HEALTH_SEED_MAX", "20000") or "20000")
|
|
661
|
+
map_keys = [k for k in cache if isinstance(k, str) and k and not k.startswith("_")]
|
|
662
|
+
seed_res = _health_mod.seed_health_from_map(
|
|
663
|
+
root, map_keys=map_keys, max_new=max_seed,
|
|
664
|
+
)
|
|
665
|
+
health_seeded = int(seed_res.get("seeded") or 0)
|
|
666
|
+
if hasattr(_health_mod, "seed_health_for_monitored_sources"):
|
|
667
|
+
disk_res = _health_mod.seed_health_for_monitored_sources(
|
|
668
|
+
root, max_new=max_seed,
|
|
669
|
+
)
|
|
670
|
+
health_seeded += int(disk_res.get("seeded") or 0)
|
|
671
|
+
except Exception as se:
|
|
672
|
+
result["health_seed_error"] = str(se)
|
|
673
|
+
health_seeded = 0
|
|
674
|
+
result["health_stubs_seeded"] = health_seeded
|
|
675
|
+
|
|
672
676
|
# === 6. library.md (atomic; pure Python) ===
|
|
673
677
|
try:
|
|
674
678
|
from .library import write_library_md
|
|
@@ -724,18 +728,20 @@ def main():
|
|
|
724
728
|
i = 0
|
|
725
729
|
while i < len(argv):
|
|
726
730
|
arg = argv[i]
|
|
731
|
+
# Consume --target / --project-root so the *command* remains filtered_argv[0]
|
|
732
|
+
# (previously left --target as argv[0] → "Unknown command: --target").
|
|
727
733
|
if arg in ("--target", "--project-root") and i + 1 < len(argv):
|
|
728
734
|
project_root = argv[i + 1]
|
|
729
|
-
filtered_argv.append(arg)
|
|
730
|
-
filtered_argv.append(argv[i + 1])
|
|
731
735
|
i += 2
|
|
732
736
|
continue
|
|
733
737
|
elif arg.startswith("--target="):
|
|
734
738
|
project_root = arg.split("=", 1)[1]
|
|
735
|
-
|
|
739
|
+
i += 1
|
|
740
|
+
continue
|
|
736
741
|
elif arg.startswith("--project-root="):
|
|
737
742
|
project_root = arg.split("=", 1)[1]
|
|
738
|
-
|
|
743
|
+
i += 1
|
|
744
|
+
continue
|
|
739
745
|
elif arg in ("--use-canonical", "--use_canonical"):
|
|
740
746
|
use_canonical = True
|
|
741
747
|
filtered_argv.append(arg)
|
|
@@ -857,13 +863,64 @@ def main():
|
|
|
857
863
|
if _health_mod is not None:
|
|
858
864
|
root = _get_effective_root(project_root)
|
|
859
865
|
res = _health_mod.validate_health(root)
|
|
860
|
-
ghosts = []
|
|
861
|
-
if hasattr(_health_mod, "find_ghost_entries"):
|
|
862
|
-
ghosts = _health_mod.find_ghost_entries(root)
|
|
863
|
-
res["ghost_count"] = len(ghosts)
|
|
864
|
-
res["ghosts"] = ghosts[:50]
|
|
865
866
|
print(_json.dumps(res, indent=2, default=str))
|
|
867
|
+
# Map-first: exit 0 when map is covered (or no map + no monitored source gaps)
|
|
866
868
|
return 0 if res.get("missing_count", 0) == 0 else 1
|
|
869
|
+
if _cmd0 in ("seed-health", "seed-health-from-map"):
|
|
870
|
+
import json as _json
|
|
871
|
+
if _health_mod is None:
|
|
872
|
+
print("[wikifier] health module unavailable", file=sys.stderr)
|
|
873
|
+
return 1
|
|
874
|
+
root = _get_effective_root(project_root)
|
|
875
|
+
res = _health_mod.seed_health_from_map(root)
|
|
876
|
+
if hasattr(_health_mod, "seed_health_for_monitored_sources"):
|
|
877
|
+
disk = _health_mod.seed_health_for_monitored_sources(root)
|
|
878
|
+
res["disk_seeded"] = disk.get("seeded")
|
|
879
|
+
res["seeded_total"] = int(res.get("seeded") or 0) + int(disk.get("seeded") or 0)
|
|
880
|
+
print(_json.dumps(res, indent=2, default=str))
|
|
881
|
+
return 0 if res.get("success") else 1
|
|
882
|
+
if _cmd0 in ("prune-pending", "prune-pending-monitored"):
|
|
883
|
+
import json as _json
|
|
884
|
+
if _health_mod is None:
|
|
885
|
+
print("[wikifier] health module unavailable", file=sys.stderr)
|
|
886
|
+
return 1
|
|
887
|
+
root = _get_effective_root(project_root)
|
|
888
|
+
res = _health_mod.prune_pending_to_monitored(root)
|
|
889
|
+
print(_json.dumps(res, indent=2, default=str))
|
|
890
|
+
return 0 if res.get("success") else 1
|
|
891
|
+
if _cmd0 in ("prune-health-monitored", "prune-health"):
|
|
892
|
+
import json as _json
|
|
893
|
+
if _health_mod is None:
|
|
894
|
+
print("[wikifier] health module unavailable", file=sys.stderr)
|
|
895
|
+
return 1
|
|
896
|
+
root = _get_effective_root(project_root)
|
|
897
|
+
res = _health_mod.prune_health_outside_monitored(root)
|
|
898
|
+
print(_json.dumps(res, indent=2, default=str))
|
|
899
|
+
return 0 if res.get("success") else 1
|
|
900
|
+
if _cmd0 in (
|
|
901
|
+
"autonomous-status",
|
|
902
|
+
"autonomous_status",
|
|
903
|
+
"readiness",
|
|
904
|
+
"long-horizon",
|
|
905
|
+
):
|
|
906
|
+
import json as _json
|
|
907
|
+
if _health_mod is None or not hasattr(_health_mod, "assess_autonomous_readiness"):
|
|
908
|
+
print("[wikifier] health.assess_autonomous_readiness unavailable", file=sys.stderr)
|
|
909
|
+
return 1
|
|
910
|
+
root = _get_effective_root(project_root)
|
|
911
|
+
res = _health_mod.assess_autonomous_readiness(root)
|
|
912
|
+
print(_json.dumps(res, indent=2, default=str))
|
|
913
|
+
# exit 0 only when not blocked
|
|
914
|
+
return 0 if res.get("readiness") != "blocked" else 2
|
|
915
|
+
if _cmd0 in ("metrics-snapshot", "metrics_snapshot", "metrics"):
|
|
916
|
+
import json as _json
|
|
917
|
+
if _health_mod is None or not hasattr(_health_mod, "write_metrics_snapshot"):
|
|
918
|
+
print("[wikifier] write_metrics_snapshot unavailable", file=sys.stderr)
|
|
919
|
+
return 1
|
|
920
|
+
root = _get_effective_root(project_root)
|
|
921
|
+
res = _health_mod.write_metrics_snapshot(root, source="cli")
|
|
922
|
+
print(_json.dumps(res, indent=2, default=str))
|
|
923
|
+
return 0 if res.get("success") else 1
|
|
867
924
|
except Exception as e:
|
|
868
925
|
print(f"[wikifier] Python-primary {_cmd0} failed: {e}", file=sys.stderr)
|
|
869
926
|
return 1
|
|
@@ -1197,6 +1254,8 @@ def check_changes(project_root: Optional[Union[str, Path]] = None) -> Dict[str,
|
|
|
1197
1254
|
ghosts_marked = 0
|
|
1198
1255
|
if _health_mod is not None:
|
|
1199
1256
|
root_res = root.resolve()
|
|
1257
|
+
# Prefer unlocked helpers while we already hold project lock
|
|
1258
|
+
_upsert = getattr(_health_mod, "_do_upsert_entry", None) or _health_mod.upsert_entry
|
|
1200
1259
|
for p in dirty_batch:
|
|
1201
1260
|
try:
|
|
1202
1261
|
pr = Path(p).resolve()
|
|
@@ -1205,13 +1264,20 @@ def check_changes(project_root: Optional[Union[str, Path]] = None) -> Dict[str,
|
|
|
1205
1264
|
rel = str(pr.relative_to(root_res))
|
|
1206
1265
|
except Exception:
|
|
1207
1266
|
continue
|
|
1208
|
-
|
|
1267
|
+
_upsert(
|
|
1209
1268
|
root, rel, "🟡 Yellow",
|
|
1210
1269
|
"mtime changed since last check-changes (Python primary auto-detected)"
|
|
1211
1270
|
)
|
|
1212
1271
|
_add_to_pending(root, rel, "Auto-detected modification — review and run mark-green after wiki update")
|
|
1213
1272
|
_ensure_journal_entry(root, "auto-detected", rel, "File mtime changed (check_changes Python primary)")
|
|
1214
1273
|
changed_count += 1
|
|
1274
|
+
# Keep pending queue aligned with lean monitored_paths (no flood outside scope)
|
|
1275
|
+
if hasattr(_health_mod, "prune_pending_to_monitored"):
|
|
1276
|
+
try:
|
|
1277
|
+
pr = _health_mod.prune_pending_to_monitored(root)
|
|
1278
|
+
result["pending_pruned"] = pr.get("removed", 0)
|
|
1279
|
+
except Exception:
|
|
1280
|
+
pass
|
|
1215
1281
|
|
|
1216
1282
|
# G7: surface ghost health entries (tracked path missing on disk, not already DELETED)
|
|
1217
1283
|
try:
|
|
@@ -1379,12 +1445,14 @@ def suggest_next_actions(
|
|
|
1379
1445
|
"""
|
|
1380
1446
|
root = _get_effective_root(project_root)
|
|
1381
1447
|
try:
|
|
1382
|
-
red = yellow = 0
|
|
1448
|
+
red = yellow = stub_y = action_y = 0
|
|
1383
1449
|
health_sum: Dict[str, Any] = {}
|
|
1384
1450
|
if _health_mod is not None:
|
|
1385
1451
|
health_sum = _health_mod.get_summary(root, directory) or {}
|
|
1386
|
-
red = health_sum.get("red", 0)
|
|
1387
|
-
yellow = health_sum.get("yellow", 0)
|
|
1452
|
+
red = int(health_sum.get("red", 0) or 0)
|
|
1453
|
+
yellow = int(health_sum.get("yellow", 0) or 0)
|
|
1454
|
+
stub_y = int(health_sum.get("stub_yellow", 0) or 0)
|
|
1455
|
+
action_y = int(health_sum.get("actionable_yellow", yellow - stub_y) or 0)
|
|
1388
1456
|
|
|
1389
1457
|
suggestions: List[str] = []
|
|
1390
1458
|
n = 1
|
|
@@ -1394,10 +1462,23 @@ def suggest_next_actions(
|
|
|
1394
1462
|
"Do not re-wiki 🟢 Green files."
|
|
1395
1463
|
)
|
|
1396
1464
|
n += 1
|
|
1397
|
-
if
|
|
1465
|
+
if action_y > 0:
|
|
1398
1466
|
suggestions.append(
|
|
1399
|
-
f"{n}. Review
|
|
1400
|
-
"
|
|
1467
|
+
f"{n}. Review {action_y} *actionable* 🟡 Yellow file(s) "
|
|
1468
|
+
"(mtime/record-change/barrel — not Initial stubs). "
|
|
1469
|
+
"record-change → wiki that file → mark-green. Skip green."
|
|
1470
|
+
)
|
|
1471
|
+
n += 1
|
|
1472
|
+
if stub_y > 0 and action_y == 0 and red == 0:
|
|
1473
|
+
suggestions.append(
|
|
1474
|
+
f"{n}. Map-first OK: {stub_y} 🟡 Initial stubs mean \"on the map\", "
|
|
1475
|
+
"NOT \"wiki this tree now\". Lookup via get_file_wiki/deps; "
|
|
1476
|
+
"write prose only when you edit a file, then mark-green."
|
|
1477
|
+
)
|
|
1478
|
+
n += 1
|
|
1479
|
+
elif stub_y > 0 and action_y > 0:
|
|
1480
|
+
suggestions.append(
|
|
1481
|
+
f"{n}. Ignore {stub_y} map-first stubs for bulk work; only actionable yellows need wiki."
|
|
1401
1482
|
)
|
|
1402
1483
|
n += 1
|
|
1403
1484
|
if red == 0 and yellow == 0:
|
|
@@ -1405,6 +1486,15 @@ def suggest_next_actions(
|
|
|
1405
1486
|
f"{n}. Health is clean (no red/yellow). Do not re-summarize the tree; use the map for lookup only."
|
|
1406
1487
|
)
|
|
1407
1488
|
n += 1
|
|
1489
|
+
# Scope hygiene
|
|
1490
|
+
if _health_mod is not None and hasattr(_health_mod, "detect_scope_risks"):
|
|
1491
|
+
try:
|
|
1492
|
+
scope = _health_mod.detect_scope_risks(root) or {}
|
|
1493
|
+
for w in (scope.get("warnings") or [])[:2]:
|
|
1494
|
+
suggestions.append(f"{n}. SCOPE: {w}")
|
|
1495
|
+
n += 1
|
|
1496
|
+
except Exception:
|
|
1497
|
+
pass
|
|
1408
1498
|
suggestions.append(
|
|
1409
1499
|
f"{n}. Run `update_maps(directory=...)` only if imports/structure changed (not for wiki-only edits)."
|
|
1410
1500
|
)
|
|
@@ -1413,7 +1503,10 @@ def suggest_next_actions(
|
|
|
1413
1503
|
f"{n}. On yellow/red hotspots, query dependents (get_dependents) before editing callers."
|
|
1414
1504
|
)
|
|
1415
1505
|
n += 1
|
|
1416
|
-
suggestions.append(
|
|
1506
|
+
suggestions.append(
|
|
1507
|
+
f"{n}. Long-horizon: `wikifier autonomous-status` before unattended daemon; "
|
|
1508
|
+
"lean monitored_paths; never parent multi-repo folders as project_root."
|
|
1509
|
+
)
|
|
1417
1510
|
|
|
1418
1511
|
acs_note = ""
|
|
1419
1512
|
if _ic_mod is not None:
|
|
@@ -1448,10 +1541,14 @@ def suggest_next_actions(
|
|
|
1448
1541
|
"project_root": str(root),
|
|
1449
1542
|
"red": red,
|
|
1450
1543
|
"yellow": yellow,
|
|
1544
|
+
"stub_yellow": stub_y,
|
|
1545
|
+
"actionable_yellow": action_y,
|
|
1546
|
+
"health_score": health_sum.get("health_score"),
|
|
1451
1547
|
"suggestions": suggestions,
|
|
1452
1548
|
"health_summary": health_sum,
|
|
1453
1549
|
"acs_note": acs_note,
|
|
1454
1550
|
"selective_work": True,
|
|
1551
|
+
"map_first": True,
|
|
1455
1552
|
}
|
|
1456
1553
|
return "\n".join(suggestions) + (acs_note or "")
|
|
1457
1554
|
except Exception as e:
|