wikifier 4.6.1__tar.gz → 4.6.2__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.1/wikifier.egg-info → wikifier-4.6.2}/PKG-INFO +1 -1
- {wikifier-4.6.1 → wikifier-4.6.2}/skills/run.md +3 -1
- {wikifier-4.6.1 → wikifier-4.6.2}/tests/test_agent_loop.py +102 -0
- {wikifier-4.6.1 → wikifier-4.6.2}/wikifier/__init__.py +3 -1
- {wikifier-4.6.1 → wikifier-4.6.2}/wikifier/agent_loop.py +115 -25
- {wikifier-4.6.1 → wikifier-4.6.2}/wikifier/cli.py +37 -0
- {wikifier-4.6.1 → wikifier-4.6.2}/wikifier/health.py +83 -0
- {wikifier-4.6.1 → wikifier-4.6.2}/wikifier/mcp/server.py +26 -0
- {wikifier-4.6.1 → wikifier-4.6.2/wikifier.egg-info}/PKG-INFO +1 -1
- {wikifier-4.6.1 → wikifier-4.6.2}/CONTRIBUTING.md +0 -0
- {wikifier-4.6.1 → wikifier-4.6.2}/LICENSE +0 -0
- {wikifier-4.6.1 → wikifier-4.6.2}/MANIFEST.in +0 -0
- {wikifier-4.6.1 → wikifier-4.6.2}/README.md +0 -0
- {wikifier-4.6.1 → wikifier-4.6.2}/diagnostics.html +0 -0
- {wikifier-4.6.1 → wikifier-4.6.2}/docs/spec.md +0 -0
- {wikifier-4.6.1 → wikifier-4.6.2}/index.html +0 -0
- {wikifier-4.6.1 → wikifier-4.6.2}/pyproject.toml +0 -0
- {wikifier-4.6.1 → wikifier-4.6.2}/setup.cfg +0 -0
- {wikifier-4.6.1 → wikifier-4.6.2}/tests/test_barrel_invalidation.py +0 -0
- {wikifier-4.6.1 → wikifier-4.6.2}/tests/test_gap_closure.py +0 -0
- {wikifier-4.6.1 → wikifier-4.6.2}/tests/test_health.py +0 -0
- {wikifier-4.6.1 → wikifier-4.6.2}/tests/test_import_cache.py +0 -0
- {wikifier-4.6.1 → wikifier-4.6.2}/tests/test_multi_lang_parsers.py +0 -0
- {wikifier-4.6.1 → wikifier-4.6.2}/tests/test_parsers.py +0 -0
- {wikifier-4.6.1 → wikifier-4.6.2}/tests/test_selftest_wrappers.py +0 -0
- {wikifier-4.6.1 → wikifier-4.6.2}/wikifier/__main__.py +0 -0
- {wikifier-4.6.1 → wikifier-4.6.2}/wikifier/contracts.py +0 -0
- {wikifier-4.6.1 → wikifier-4.6.2}/wikifier/daemon.py +0 -0
- {wikifier-4.6.1 → wikifier-4.6.2}/wikifier/diagnostics.py +0 -0
- {wikifier-4.6.1 → wikifier-4.6.2}/wikifier/import_cache.py +0 -0
- {wikifier-4.6.1 → wikifier-4.6.2}/wikifier/index.html +0 -0
- {wikifier-4.6.1 → wikifier-4.6.2}/wikifier/library.py +0 -0
- {wikifier-4.6.1 → wikifier-4.6.2}/wikifier/locking.py +0 -0
- {wikifier-4.6.1 → wikifier-4.6.2}/wikifier/mcp/__init__.py +0 -0
- {wikifier-4.6.1 → wikifier-4.6.2}/wikifier/parsers/__init__.py +0 -0
- {wikifier-4.6.1 → wikifier-4.6.2}/wikifier/parsers/_edge.py +0 -0
- {wikifier-4.6.1 → wikifier-4.6.2}/wikifier/parsers/bree.py +0 -0
- {wikifier-4.6.1 → wikifier-4.6.2}/wikifier/parsers/c_cpp.py +0 -0
- {wikifier-4.6.1 → wikifier-4.6.2}/wikifier/parsers/cdia.py +0 -0
- {wikifier-4.6.1 → wikifier-4.6.2}/wikifier/parsers/csharp.py +0 -0
- {wikifier-4.6.1 → wikifier-4.6.2}/wikifier/parsers/go_lang.py +0 -0
- {wikifier-4.6.1 → wikifier-4.6.2}/wikifier/parsers/java.py +0 -0
- {wikifier-4.6.1 → wikifier-4.6.2}/wikifier/parsers/javascript.py +0 -0
- {wikifier-4.6.1 → wikifier-4.6.2}/wikifier/parsers/python.py +0 -0
- {wikifier-4.6.1 → wikifier-4.6.2}/wikifier/parsers/rust.py +0 -0
- {wikifier-4.6.1 → wikifier-4.6.2}/wikifier/project_root.py +0 -0
- {wikifier-4.6.1 → wikifier-4.6.2}/wikifier/resolution.py +0 -0
- {wikifier-4.6.1 → wikifier-4.6.2}/wikifier/scripts/wikifier.bat +0 -0
- {wikifier-4.6.1 → wikifier-4.6.2}/wikifier/scripts/wikifier.ps1 +0 -0
- {wikifier-4.6.1 → wikifier-4.6.2}/wikifier/scripts/wikifier.sh +0 -0
- {wikifier-4.6.1 → wikifier-4.6.2}/wikifier/serve.py +0 -0
- {wikifier-4.6.1 → wikifier-4.6.2}/wikifier.egg-info/SOURCES.txt +0 -0
- {wikifier-4.6.1 → wikifier-4.6.2}/wikifier.egg-info/dependency_links.txt +0 -0
- {wikifier-4.6.1 → wikifier-4.6.2}/wikifier.egg-info/entry_points.txt +0 -0
- {wikifier-4.6.1 → wikifier-4.6.2}/wikifier.egg-info/requires.txt +0 -0
- {wikifier-4.6.1 → wikifier-4.6.2}/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.2
|
|
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
|
|
@@ -43,8 +43,10 @@ Do **not** open megamodules (`javascript.py`, `import_cache.py`, `bree.py`) to d
|
|
|
43
43
|
|------|-----|
|
|
44
44
|
| First-run map | `init` → `update-maps` → `health --summary` → `suggest-next` |
|
|
45
45
|
| **Session start (4.6+)** | `session_bootstrap` (or CLI `session-bootstrap`) — one shot: root, health, attention, `actions[]` |
|
|
46
|
+
| **Core tool list** | `list_core_tools` / bootstrap `core_surface` — prefer Core 6; advanced is non-core |
|
|
46
47
|
| Steady-state | `check-changes` (content-honest) → edit 🔴/actionable 🟡 only → `record-change` → wiki → `mark-green` |
|
|
47
|
-
| **
|
|
48
|
+
| **Hash migration** | `seed_source_content_hashes` / CLI `seed-source-hashes` — baseline Greens without mass Yellow |
|
|
49
|
+
| **Preflight edit** | `prepare_edit` / CLI `prepare-edit <file>` — wiki + status + deps + dependents (multi-shape reverse index) |
|
|
48
50
|
| **Why / journal** | `why_file` / `search_journal` — semantic trail, not mtime theater |
|
|
49
51
|
| Lookup Core | `session_bootstrap`, `check_changes`, `prepare_edit`, `suggest_next_actions` (json `actions[]`), `record_change`, `mark_green` |
|
|
50
52
|
| Advanced intel | `get_dependencies`, `get_dependents`, `get_cycles`, barrel/diagnostics — not daily Core |
|
|
@@ -222,5 +222,107 @@ class TestCliBootstrapEntry(TempProjectTestCase):
|
|
|
222
222
|
self.assertIn("actions", res)
|
|
223
223
|
|
|
224
224
|
|
|
225
|
+
class TestSeedSourceHashes(TempProjectTestCase):
|
|
226
|
+
def test_seed_then_touch_ok_rewrite_yellows(self):
|
|
227
|
+
p = self.write("seeded.py", "print(0)\n")
|
|
228
|
+
health_mod.upsert_entry(self.root, "seeded.py", "🟢 Green", "legacy no hash")
|
|
229
|
+
data = health_mod.load_health(self.root)
|
|
230
|
+
self.assertNotIn("source_content_hash", data["entries"]["seeded.py"] or {})
|
|
231
|
+
res = health_mod.seed_source_content_hashes(self.root, only_green=True)
|
|
232
|
+
self.assertTrue(res.get("success"), res)
|
|
233
|
+
self.assertGreaterEqual(int(res.get("seeded") or 0), 1)
|
|
234
|
+
data2 = health_mod.load_health(self.root)
|
|
235
|
+
ent = data2["entries"]["seeded.py"]
|
|
236
|
+
self.assertIn("Green", ent["status"])
|
|
237
|
+
self.assertTrue(ent.get("source_content_hash"))
|
|
238
|
+
baseline = ent["source_content_hash"]
|
|
239
|
+
from wikifier import import_cache as ic
|
|
240
|
+
cache = {}
|
|
241
|
+
ic.update_file_data(cache, "seeded.py", mtime=1, imports=[], resolved_pairs=[])
|
|
242
|
+
ic.save_cache(self.root, cache)
|
|
243
|
+
os.utime(p, None)
|
|
244
|
+
cache = ic.load_cache(self.root)
|
|
245
|
+
cache["seeded.py"]["mtime"] = 1
|
|
246
|
+
ic.save_cache(self.root, cache)
|
|
247
|
+
r = cli.check_changes(project_root=self.root)
|
|
248
|
+
self.assertTrue(r.get("success"), r)
|
|
249
|
+
data3 = health_mod.load_health(self.root)
|
|
250
|
+
self.assertIn("Green", data3["entries"]["seeded.py"]["status"])
|
|
251
|
+
self.assertEqual(data3["entries"]["seeded.py"]["source_content_hash"], baseline)
|
|
252
|
+
p.write_text("print(99)\n", encoding="utf-8")
|
|
253
|
+
cache = ic.load_cache(self.root)
|
|
254
|
+
cache["seeded.py"]["mtime"] = 1
|
|
255
|
+
ic.save_cache(self.root, cache)
|
|
256
|
+
r2 = cli.check_changes(project_root=self.root)
|
|
257
|
+
self.assertTrue(r2.get("success"), r2)
|
|
258
|
+
self.assertGreaterEqual(int(r2.get("changes_detected") or 0), 1)
|
|
259
|
+
data4 = health_mod.load_health(self.root)
|
|
260
|
+
self.assertIn("Yellow", data4["entries"]["seeded.py"]["status"])
|
|
261
|
+
|
|
262
|
+
|
|
263
|
+
class TestCoreSurfaceListing(unittest.TestCase):
|
|
264
|
+
def test_list_core_tools_has_six(self):
|
|
265
|
+
from wikifier.agent_loop import list_core_tools, CORE_DAILY_NAMES
|
|
266
|
+
res = list_core_tools()
|
|
267
|
+
self.assertTrue(res.get("success"))
|
|
268
|
+
self.assertEqual(res.get("core_count"), 6)
|
|
269
|
+
names = res.get("core_names") or []
|
|
270
|
+
for n in (
|
|
271
|
+
"session_bootstrap", "check_changes", "prepare_edit",
|
|
272
|
+
"suggest_next_actions", "record_change", "mark_green",
|
|
273
|
+
):
|
|
274
|
+
self.assertIn(n, names)
|
|
275
|
+
self.assertEqual(names, CORE_DAILY_NAMES)
|
|
276
|
+
adv = res.get("advanced_intel") or []
|
|
277
|
+
self.assertTrue(adv)
|
|
278
|
+
self.assertNotIn("session_bootstrap", adv) # core not listed as advanced-only
|
|
279
|
+
|
|
280
|
+
|
|
281
|
+
class TestPrepareEditReverseShapes(TempProjectTestCase):
|
|
282
|
+
def test_flat_and_nested_reverse_index(self):
|
|
283
|
+
self.write("lib/t.py", "x=1\n")
|
|
284
|
+
health_mod.upsert_entry(self.root, "lib/t.py", "🟢 Green", "ok")
|
|
285
|
+
from wikifier import import_cache as ic
|
|
286
|
+
from wikifier.agent_loop import prepare_edit, resolve_dependents_from_cache
|
|
287
|
+
|
|
288
|
+
# Flat shape
|
|
289
|
+
cache_flat = {
|
|
290
|
+
"lib/t.py": {"mtime": 1, "resolved_pairs": []},
|
|
291
|
+
"lib/a.py": {"mtime": 1, "resolved_pairs": [{"resolved": "lib/t.py", "resolved_path": "lib/t.py"}]},
|
|
292
|
+
"_reverse_dependencies": {"lib/t.py": ["lib/a.py", "lib/b.py"]},
|
|
293
|
+
}
|
|
294
|
+
deps = resolve_dependents_from_cache(cache_flat, "lib/t.py")
|
|
295
|
+
self.assertIn("lib/a.py", deps)
|
|
296
|
+
self.assertIn("lib/b.py", deps)
|
|
297
|
+
ic.save_cache(self.root, cache_flat)
|
|
298
|
+
pe = prepare_edit("lib/t.py", project_root=self.root)
|
|
299
|
+
self.assertTrue(pe.get("success"))
|
|
300
|
+
self.assertIn("lib/a.py", pe.get("dependents") or [])
|
|
301
|
+
|
|
302
|
+
# Nested index shape
|
|
303
|
+
cache_nest = {
|
|
304
|
+
"lib/t.py": {"mtime": 1, "resolved_pairs": []},
|
|
305
|
+
"_reverse_dependencies": {
|
|
306
|
+
"index": {"lib/t.py": ["lib/nested1.py", "lib/nested2.py"]},
|
|
307
|
+
"version": 1,
|
|
308
|
+
},
|
|
309
|
+
}
|
|
310
|
+
deps2 = resolve_dependents_from_cache(cache_nest, "lib/t.py")
|
|
311
|
+
self.assertEqual(set(deps2), {"lib/nested1.py", "lib/nested2.py"})
|
|
312
|
+
ic.save_cache(self.root, cache_nest)
|
|
313
|
+
pe2 = prepare_edit("lib/t.py", project_root=self.root)
|
|
314
|
+
self.assertTrue(pe2.get("success"))
|
|
315
|
+
self.assertIn("lib/nested1.py", pe2.get("dependents") or [])
|
|
316
|
+
|
|
317
|
+
# Dict value shape
|
|
318
|
+
cache_dict = {
|
|
319
|
+
"_reverse_dependencies": {
|
|
320
|
+
"lib/t.py": {"importers": ["lib/from_dict.py"]},
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
deps3 = resolve_dependents_from_cache(cache_dict, "lib/t.py")
|
|
324
|
+
self.assertIn("lib/from_dict.py", deps3)
|
|
325
|
+
|
|
326
|
+
|
|
225
327
|
if __name__ == "__main__":
|
|
226
328
|
unittest.main()
|
|
@@ -46,6 +46,8 @@ from .cli import (
|
|
|
46
46
|
prepare_edit,
|
|
47
47
|
search_journal,
|
|
48
48
|
why_file,
|
|
49
|
+
seed_source_content_hashes,
|
|
50
|
+
list_core_tools,
|
|
49
51
|
)
|
|
50
52
|
from . import agent_loop
|
|
51
53
|
|
|
@@ -76,4 +78,4 @@ from .contracts import (
|
|
|
76
78
|
compute_acs_confidence,
|
|
77
79
|
)
|
|
78
80
|
|
|
79
|
-
__version__ = "4.6.
|
|
81
|
+
__version__ = "4.6.2"
|
|
@@ -21,12 +21,122 @@ import importlib
|
|
|
21
21
|
|
|
22
22
|
from .project_root import discover_project_root
|
|
23
23
|
|
|
24
|
+
# Core daily agent surface (4.6+) — prefer these every session.
|
|
25
|
+
CORE_DAILY_TOOLS: List[Dict[str, str]] = [
|
|
26
|
+
{"name": "session_bootstrap", "role": "one-shot root + health + attention + actions[]"},
|
|
27
|
+
{"name": "check_changes", "role": "content-honest dirty / ghosts"},
|
|
28
|
+
{"name": "prepare_edit", "role": "single-file preflight wiki/status/deps/dependents"},
|
|
29
|
+
{"name": "suggest_next_actions", "role": "selective work + dispatchable actions[]"},
|
|
30
|
+
{"name": "record_change", "role": "semantic why after edits"},
|
|
31
|
+
{"name": "mark_green", "role": "trust + source content-hash baseline"},
|
|
32
|
+
]
|
|
33
|
+
CORE_DAILY_NAMES: List[str] = [t["name"] for t in CORE_DAILY_TOOLS]
|
|
34
|
+
ADVANCED_INTEL_TOOLS: List[str] = [
|
|
35
|
+
"get_dependencies",
|
|
36
|
+
"get_dependents",
|
|
37
|
+
"get_cycles",
|
|
38
|
+
"get_barrel_reports",
|
|
39
|
+
"get_resolution_diagnostics",
|
|
40
|
+
"health",
|
|
41
|
+
"get_project_status",
|
|
42
|
+
"get_files_needing_attention",
|
|
43
|
+
"search_journal",
|
|
44
|
+
"why_file",
|
|
45
|
+
"update_maps",
|
|
46
|
+
"validate",
|
|
47
|
+
]
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def list_core_tools() -> Dict[str, Any]:
|
|
51
|
+
"""Return Core daily vs advanced intel tool metadata (agents prefer Core)."""
|
|
52
|
+
return {
|
|
53
|
+
"success": True,
|
|
54
|
+
"core_daily": list(CORE_DAILY_TOOLS),
|
|
55
|
+
"core_names": list(CORE_DAILY_NAMES),
|
|
56
|
+
"advanced_intel": list(ADVANCED_INTEL_TOOLS),
|
|
57
|
+
"core_count": len(CORE_DAILY_NAMES),
|
|
58
|
+
"note": "Use Core every session; advanced remains available but non-core.",
|
|
59
|
+
}
|
|
60
|
+
|
|
24
61
|
|
|
25
62
|
def _health_module():
|
|
26
63
|
"""Real health submodule (not the shadowed package attribute function)."""
|
|
27
64
|
return importlib.import_module("wikifier.health")
|
|
28
65
|
|
|
29
66
|
|
|
67
|
+
def resolve_dependents_from_cache(cache: Dict[str, Any], rel: str) -> List[str]:
|
|
68
|
+
"""Extract reverse dependents for *rel* from multiple reverse-index shapes.
|
|
69
|
+
|
|
70
|
+
Supported:
|
|
71
|
+
- flat: ``_reverse_dependencies[rel] -> [importers]``
|
|
72
|
+
- nested index: ``_reverse_dependencies["index"][rel]``
|
|
73
|
+
- dependents key: ``_reverse_dependencies["dependents"][rel]``
|
|
74
|
+
- map value dict: ``{rel: {"importers": [...]}}`` or ``{"sources": [...]}``
|
|
75
|
+
- import_cache.get_reverse_dependencies when available
|
|
76
|
+
Fallback: scan resolved_pairs for edges pointing at rel.
|
|
77
|
+
"""
|
|
78
|
+
if not rel or not isinstance(cache, dict):
|
|
79
|
+
return []
|
|
80
|
+
|
|
81
|
+
def _as_list(val: Any) -> List[str]:
|
|
82
|
+
if val is None:
|
|
83
|
+
return []
|
|
84
|
+
if isinstance(val, list):
|
|
85
|
+
return [str(x) for x in val if x]
|
|
86
|
+
if isinstance(val, dict):
|
|
87
|
+
for k in ("importers", "sources", "dependents", "files", "from"):
|
|
88
|
+
if isinstance(val.get(k), list):
|
|
89
|
+
return [str(x) for x in val[k] if x]
|
|
90
|
+
# keys-as-importers
|
|
91
|
+
return [str(k) for k in val.keys() if k and not str(k).startswith("_")]
|
|
92
|
+
if isinstance(val, str) and val:
|
|
93
|
+
return [val]
|
|
94
|
+
return []
|
|
95
|
+
|
|
96
|
+
# Prefer library helper when present (canonical flat map)
|
|
97
|
+
try:
|
|
98
|
+
from . import import_cache as ic
|
|
99
|
+
rev_map = ic.get_reverse_dependencies(cache) or {}
|
|
100
|
+
if isinstance(rev_map, dict) and rel in rev_map:
|
|
101
|
+
got = _as_list(rev_map.get(rel))
|
|
102
|
+
if got:
|
|
103
|
+
return got[:80]
|
|
104
|
+
except Exception:
|
|
105
|
+
pass
|
|
106
|
+
|
|
107
|
+
rev = cache.get("_reverse_dependencies") or {}
|
|
108
|
+
if not isinstance(rev, dict):
|
|
109
|
+
return []
|
|
110
|
+
|
|
111
|
+
# Direct flat
|
|
112
|
+
if rel in rev:
|
|
113
|
+
got = _as_list(rev.get(rel))
|
|
114
|
+
if got:
|
|
115
|
+
return got[:80]
|
|
116
|
+
|
|
117
|
+
# Nested common containers
|
|
118
|
+
for nest_key in ("index", "dependents", "map", "by_target", "targets"):
|
|
119
|
+
nested = rev.get(nest_key)
|
|
120
|
+
if isinstance(nested, dict) and rel in nested:
|
|
121
|
+
got = _as_list(nested.get(rel))
|
|
122
|
+
if got:
|
|
123
|
+
return got[:80]
|
|
124
|
+
|
|
125
|
+
# Fallback: O(E) scan of resolved_pairs
|
|
126
|
+
found: List[str] = []
|
|
127
|
+
for src, data in cache.items():
|
|
128
|
+
if not isinstance(src, str) or src.startswith("_") or not isinstance(data, dict):
|
|
129
|
+
continue
|
|
130
|
+
for p in data.get("resolved_pairs") or []:
|
|
131
|
+
if not isinstance(p, dict):
|
|
132
|
+
continue
|
|
133
|
+
tgt = p.get("resolved_path") or p.get("resolved") or ""
|
|
134
|
+
if tgt == rel or str(tgt).endswith("/" + rel) or str(tgt).endswith(rel):
|
|
135
|
+
if src not in found:
|
|
136
|
+
found.append(src)
|
|
137
|
+
return found[:80]
|
|
138
|
+
|
|
139
|
+
|
|
30
140
|
def _root(project_root: Optional[Union[str, Path]] = None) -> Path:
|
|
31
141
|
if project_root:
|
|
32
142
|
try:
|
|
@@ -327,17 +437,7 @@ def prepare_edit(
|
|
|
327
437
|
})
|
|
328
438
|
out["dependencies"] = deps[:80]
|
|
329
439
|
out["low_conf_edges"] = low
|
|
330
|
-
|
|
331
|
-
rev = cache.get("_reverse_dependencies") or {}
|
|
332
|
-
if isinstance(rev, dict):
|
|
333
|
-
# may be {file: [importers]} or nested
|
|
334
|
-
importers = rev.get(rel)
|
|
335
|
-
if importers is None and isinstance(rev.get("index"), dict):
|
|
336
|
-
importers = rev["index"].get(rel)
|
|
337
|
-
if isinstance(importers, list):
|
|
338
|
-
out["dependents"] = importers[:80]
|
|
339
|
-
elif isinstance(importers, dict):
|
|
340
|
-
out["dependents"] = list(importers.keys())[:80]
|
|
440
|
+
out["dependents"] = resolve_dependents_from_cache(cache, rel)
|
|
341
441
|
cycles = cache.get("_cycles") or {}
|
|
342
442
|
all_cyc = set(cycles.get("all_cycle_files") or [])
|
|
343
443
|
out["in_cycle"] = rel in all_cyc
|
|
@@ -363,20 +463,10 @@ def session_bootstrap(
|
|
|
363
463
|
out: Dict[str, Any] = {
|
|
364
464
|
"success": True,
|
|
365
465
|
"project_root": str(root),
|
|
366
|
-
"core_surface":
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
"suggest_next_actions",
|
|
371
|
-
"record_change",
|
|
372
|
-
"mark_green",
|
|
373
|
-
],
|
|
374
|
-
"advanced_intel": [
|
|
375
|
-
"get_cycles",
|
|
376
|
-
"get_barrel_reports",
|
|
377
|
-
"get_resolution_diagnostics",
|
|
378
|
-
"health(format=json)",
|
|
379
|
-
],
|
|
466
|
+
"core_surface": list(CORE_DAILY_NAMES),
|
|
467
|
+
"core_daily": list(CORE_DAILY_TOOLS),
|
|
468
|
+
"core_count": len(CORE_DAILY_NAMES),
|
|
469
|
+
"advanced_intel": list(ADVANCED_INTEL_TOOLS),
|
|
380
470
|
}
|
|
381
471
|
health_sum: Dict[str, Any] = {}
|
|
382
472
|
red_files: List[str] = []
|
|
@@ -772,6 +772,22 @@ def main():
|
|
|
772
772
|
res = why_file(_args[0], project_root=project_root)
|
|
773
773
|
print(_json.dumps(res, indent=2, default=str))
|
|
774
774
|
return 0 if res.get("success") else 1
|
|
775
|
+
if _cmd0 in (
|
|
776
|
+
"seed-source-hashes",
|
|
777
|
+
"seed-source-content-hashes",
|
|
778
|
+
"seed_source_content_hashes",
|
|
779
|
+
"seed-hashes",
|
|
780
|
+
):
|
|
781
|
+
import json as _json
|
|
782
|
+
force = any(a in ("--force", "-f") for a in _args)
|
|
783
|
+
res = seed_source_content_hashes(project_root=project_root, force=force)
|
|
784
|
+
print(_json.dumps(res, indent=2, default=str))
|
|
785
|
+
return 0 if res.get("success") else 1
|
|
786
|
+
if _cmd0 in ("list-core-tools", "list_core_tools", "core-tools"):
|
|
787
|
+
import json as _json
|
|
788
|
+
res = list_core_tools()
|
|
789
|
+
print(_json.dumps(res, indent=2, default=str))
|
|
790
|
+
return 0 if res.get("success") else 1
|
|
775
791
|
if _cmd0 == "validate":
|
|
776
792
|
import json as _json
|
|
777
793
|
if _health_mod is not None:
|
|
@@ -1633,6 +1649,27 @@ def why_file(
|
|
|
1633
1649
|
return _wf(file, project_root=project_root, max_results=max_results)
|
|
1634
1650
|
|
|
1635
1651
|
|
|
1652
|
+
def seed_source_content_hashes(
|
|
1653
|
+
project_root: Optional[Union[str, Path]] = None,
|
|
1654
|
+
only_green: bool = True,
|
|
1655
|
+
force: bool = False,
|
|
1656
|
+
directory: Optional[str] = None,
|
|
1657
|
+
) -> Dict[str, Any]:
|
|
1658
|
+
"""Seed source_content_hash baselines without mass Yellow (migration helper)."""
|
|
1659
|
+
root = _get_effective_root(project_root)
|
|
1660
|
+
if _health_mod is None or not hasattr(_health_mod, "seed_source_content_hashes"):
|
|
1661
|
+
return {"success": False, "project_root": str(root), "error": "health.seed_source_content_hashes unavailable"}
|
|
1662
|
+
return _health_mod.seed_source_content_hashes(
|
|
1663
|
+
root, only_green=only_green, force=force, directory=directory
|
|
1664
|
+
)
|
|
1665
|
+
|
|
1666
|
+
|
|
1667
|
+
def list_core_tools() -> Dict[str, Any]:
|
|
1668
|
+
"""Core daily agent tool listing (prefer over full MCP catalog)."""
|
|
1669
|
+
from .agent_loop import list_core_tools as _lct
|
|
1670
|
+
return _lct()
|
|
1671
|
+
|
|
1672
|
+
|
|
1636
1673
|
def update_maps(
|
|
1637
1674
|
project_root: Optional[Union[str, Path]] = None,
|
|
1638
1675
|
full: bool = False,
|
|
@@ -300,6 +300,89 @@ def classify_content_dirty(
|
|
|
300
300
|
}
|
|
301
301
|
|
|
302
302
|
|
|
303
|
+
def seed_source_content_hashes(
|
|
304
|
+
root: "str | Path",
|
|
305
|
+
*,
|
|
306
|
+
only_green: bool = True,
|
|
307
|
+
force: bool = False,
|
|
308
|
+
directory: Optional[str] = None,
|
|
309
|
+
max_files: int = 50000,
|
|
310
|
+
) -> Dict[str, Any]:
|
|
311
|
+
"""Seed trusted ``source_content_hash`` for existing health entries without Yellow thrash.
|
|
312
|
+
|
|
313
|
+
Migration helper for post-4.6.x content-honest dirty: legacy 🟢 entries without a
|
|
314
|
+
baseline get the *current on-disk* hash recorded so subsequent mtime-only touches
|
|
315
|
+
do not Yellow. Status/reason are left unchanged (no mass auto-Yellow).
|
|
316
|
+
|
|
317
|
+
Args:
|
|
318
|
+
only_green: if True, only seed entries whose status is Green.
|
|
319
|
+
force: if True, re-hash even when a baseline already exists.
|
|
320
|
+
directory: optional path prefix filter.
|
|
321
|
+
max_files: safety cap.
|
|
322
|
+
|
|
323
|
+
Returns structured stats (seeded, skipped_*, missing_on_disk, errors sample).
|
|
324
|
+
"""
|
|
325
|
+
root = Path(root).resolve()
|
|
326
|
+
result: Dict[str, Any] = {
|
|
327
|
+
"success": True,
|
|
328
|
+
"project_root": str(root),
|
|
329
|
+
"seeded": 0,
|
|
330
|
+
"skipped_has_hash": 0,
|
|
331
|
+
"skipped_not_green": 0,
|
|
332
|
+
"skipped_missing": 0,
|
|
333
|
+
"skipped_filter": 0,
|
|
334
|
+
"errors": [],
|
|
335
|
+
"sample_seeded": [],
|
|
336
|
+
}
|
|
337
|
+
try:
|
|
338
|
+
data = load_health(root)
|
|
339
|
+
entries = data.setdefault("entries", {})
|
|
340
|
+
dirty = False
|
|
341
|
+
dir_pref = (directory or "").rstrip("/")
|
|
342
|
+
for rel, ent in list(entries.items()):
|
|
343
|
+
if not isinstance(ent, dict):
|
|
344
|
+
continue
|
|
345
|
+
if dir_pref and not (str(rel) == dir_pref or str(rel).startswith(dir_pref + "/")):
|
|
346
|
+
result["skipped_filter"] += 1
|
|
347
|
+
continue
|
|
348
|
+
st = str(ent.get("status") or "")
|
|
349
|
+
if only_green and "Green" not in st and "🟢" not in st:
|
|
350
|
+
result["skipped_not_green"] += 1
|
|
351
|
+
continue
|
|
352
|
+
if ent.get("source_content_hash") and not force:
|
|
353
|
+
result["skipped_has_hash"] += 1
|
|
354
|
+
continue
|
|
355
|
+
src = root / rel
|
|
356
|
+
if not src.is_file():
|
|
357
|
+
result["skipped_missing"] += 1
|
|
358
|
+
continue
|
|
359
|
+
h = compute_source_content_hash(src)
|
|
360
|
+
if not h:
|
|
361
|
+
result["skipped_missing"] += 1
|
|
362
|
+
continue
|
|
363
|
+
ent["source_content_hash"] = h
|
|
364
|
+
# Do not change status/reason — migration only
|
|
365
|
+
entries[rel] = ent
|
|
366
|
+
dirty = True
|
|
367
|
+
result["seeded"] += 1
|
|
368
|
+
if len(result["sample_seeded"]) < 8:
|
|
369
|
+
result["sample_seeded"].append(rel)
|
|
370
|
+
if result["seeded"] >= max_files:
|
|
371
|
+
break
|
|
372
|
+
if dirty:
|
|
373
|
+
save_health(root, data)
|
|
374
|
+
result["message"] = (
|
|
375
|
+
f"Seeded {result['seeded']} source_content_hash baseline(s) "
|
|
376
|
+
f"(skipped has_hash={result['skipped_has_hash']}, "
|
|
377
|
+
f"not_green={result['skipped_not_green']}, missing={result['skipped_missing']})."
|
|
378
|
+
)
|
|
379
|
+
except Exception as e:
|
|
380
|
+
result["success"] = False
|
|
381
|
+
result["error"] = str(e)
|
|
382
|
+
result["errors"].append(str(e))
|
|
383
|
+
return result
|
|
384
|
+
|
|
385
|
+
|
|
303
386
|
def _is_stale_wiki(root: Path, file: str, entry: Dict[str, Any]) -> Optional[Dict[str, Any]]:
|
|
304
387
|
"""
|
|
305
388
|
Core durable stale wiki detector (B3).
|
|
@@ -526,6 +526,32 @@ def why_file(
|
|
|
526
526
|
return {"success": False, "file": file, "project_root": str(root), "error": str(e)}
|
|
527
527
|
|
|
528
528
|
|
|
529
|
+
@mcp.tool()
|
|
530
|
+
def seed_source_content_hashes(
|
|
531
|
+
project_root: Optional[str] = None,
|
|
532
|
+
only_green: bool = True,
|
|
533
|
+
force: bool = False,
|
|
534
|
+
directory: Optional[str] = None,
|
|
535
|
+
) -> dict:
|
|
536
|
+
"""Seed source_content_hash for Green entries without mass Yellow thrash (migration)."""
|
|
537
|
+
root = _get_effective_root(project_root)
|
|
538
|
+
try:
|
|
539
|
+
from wikifier.cli import seed_source_content_hashes as _lib_seed
|
|
540
|
+
return _lib_seed(project_root=root, only_green=only_green, force=force, directory=directory)
|
|
541
|
+
except Exception as e:
|
|
542
|
+
return {"success": False, "project_root": str(root), "error": str(e)}
|
|
543
|
+
|
|
544
|
+
|
|
545
|
+
@mcp.tool()
|
|
546
|
+
def list_core_tools() -> dict:
|
|
547
|
+
"""List Core daily agent tools vs advanced intel (prefer Core every session)."""
|
|
548
|
+
try:
|
|
549
|
+
from wikifier.cli import list_core_tools as _lib_lct
|
|
550
|
+
return _lib_lct()
|
|
551
|
+
except Exception as e:
|
|
552
|
+
return {"success": False, "error": str(e)}
|
|
553
|
+
|
|
554
|
+
|
|
529
555
|
@mcp.tool()
|
|
530
556
|
def update_maps(
|
|
531
557
|
project_root: Optional[str] = None,
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: wikifier
|
|
3
|
-
Version: 4.6.
|
|
3
|
+
Version: 4.6.2
|
|
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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|