wikifier 4.5.3__tar.gz → 4.5.4__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.3/wikifier.egg-info → wikifier-4.5.4}/PKG-INFO +15 -3
- {wikifier-4.5.3 → wikifier-4.5.4}/README.md +14 -2
- {wikifier-4.5.3 → wikifier-4.5.4}/skills/run.md +2 -1
- wikifier-4.5.4/tests/test_multi_lang_parsers.py +104 -0
- {wikifier-4.5.3 → wikifier-4.5.4}/wikifier/__init__.py +1 -1
- {wikifier-4.5.3 → wikifier-4.5.4}/wikifier/cli.py +101 -13
- wikifier-4.5.4/wikifier/parsers/__init__.py +23 -0
- wikifier-4.5.4/wikifier/parsers/_edge.py +71 -0
- wikifier-4.5.4/wikifier/parsers/c_cpp.py +83 -0
- wikifier-4.5.4/wikifier/parsers/csharp.py +64 -0
- wikifier-4.5.4/wikifier/parsers/go_lang.py +87 -0
- wikifier-4.5.4/wikifier/parsers/java.py +64 -0
- wikifier-4.5.4/wikifier/parsers/rust.py +133 -0
- {wikifier-4.5.3 → wikifier-4.5.4/wikifier.egg-info}/PKG-INFO +15 -3
- {wikifier-4.5.3 → wikifier-4.5.4}/wikifier.egg-info/SOURCES.txt +13 -0
- wikifier-4.5.3/wikifier/parsers/__init__.py +0 -0
- {wikifier-4.5.3 → wikifier-4.5.4}/CONTRIBUTING.md +0 -0
- {wikifier-4.5.3 → wikifier-4.5.4}/LICENSE +0 -0
- {wikifier-4.5.3 → wikifier-4.5.4}/MANIFEST.in +0 -0
- {wikifier-4.5.3 → wikifier-4.5.4}/diagnostics.html +0 -0
- {wikifier-4.5.3 → wikifier-4.5.4}/docs/spec.md +0 -0
- {wikifier-4.5.3 → wikifier-4.5.4}/index.html +0 -0
- {wikifier-4.5.3 → wikifier-4.5.4}/pyproject.toml +0 -0
- {wikifier-4.5.3 → wikifier-4.5.4}/setup.cfg +0 -0
- {wikifier-4.5.3 → wikifier-4.5.4}/tests/test_barrel_invalidation.py +0 -0
- {wikifier-4.5.3 → wikifier-4.5.4}/tests/test_gap_closure.py +0 -0
- {wikifier-4.5.3 → wikifier-4.5.4}/tests/test_health.py +0 -0
- {wikifier-4.5.3 → wikifier-4.5.4}/tests/test_import_cache.py +0 -0
- {wikifier-4.5.3 → wikifier-4.5.4}/tests/test_parsers.py +0 -0
- {wikifier-4.5.3 → wikifier-4.5.4}/tests/test_selftest_wrappers.py +0 -0
- {wikifier-4.5.3 → wikifier-4.5.4}/wikifier/__main__.py +0 -0
- {wikifier-4.5.3 → wikifier-4.5.4}/wikifier/contracts.py +0 -0
- {wikifier-4.5.3 → wikifier-4.5.4}/wikifier/daemon.py +0 -0
- {wikifier-4.5.3 → wikifier-4.5.4}/wikifier/diagnostics.py +0 -0
- {wikifier-4.5.3 → wikifier-4.5.4}/wikifier/health.py +0 -0
- {wikifier-4.5.3 → wikifier-4.5.4}/wikifier/import_cache.py +0 -0
- {wikifier-4.5.3 → wikifier-4.5.4}/wikifier/index.html +0 -0
- {wikifier-4.5.3 → wikifier-4.5.4}/wikifier/library.py +0 -0
- {wikifier-4.5.3 → wikifier-4.5.4}/wikifier/locking.py +0 -0
- {wikifier-4.5.3 → wikifier-4.5.4}/wikifier/mcp/__init__.py +0 -0
- {wikifier-4.5.3 → wikifier-4.5.4}/wikifier/mcp/server.py +0 -0
- {wikifier-4.5.3 → wikifier-4.5.4}/wikifier/parsers/bree.py +0 -0
- {wikifier-4.5.3 → wikifier-4.5.4}/wikifier/parsers/cdia.py +0 -0
- {wikifier-4.5.3 → wikifier-4.5.4}/wikifier/parsers/javascript.py +0 -0
- {wikifier-4.5.3 → wikifier-4.5.4}/wikifier/parsers/python.py +0 -0
- {wikifier-4.5.3 → wikifier-4.5.4}/wikifier/resolution.py +0 -0
- {wikifier-4.5.3 → wikifier-4.5.4}/wikifier/scripts/wikifier.bat +0 -0
- {wikifier-4.5.3 → wikifier-4.5.4}/wikifier/scripts/wikifier.ps1 +0 -0
- {wikifier-4.5.3 → wikifier-4.5.4}/wikifier/scripts/wikifier.sh +0 -0
- {wikifier-4.5.3 → wikifier-4.5.4}/wikifier/serve.py +0 -0
- {wikifier-4.5.3 → wikifier-4.5.4}/wikifier.egg-info/dependency_links.txt +0 -0
- {wikifier-4.5.3 → wikifier-4.5.4}/wikifier.egg-info/entry_points.txt +0 -0
- {wikifier-4.5.3 → wikifier-4.5.4}/wikifier.egg-info/requires.txt +0 -0
- {wikifier-4.5.3 → wikifier-4.5.4}/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.4
|
|
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
|
|
@@ -40,7 +40,19 @@ Dynamic: license-file
|
|
|
40
40
|
|
|
41
41
|
Wikifier is an **agent-to-agent** tool: it builds a living map of a project (health matrix, dependency graph, short file summaries) and agents keep that map current as they work. Humans can peek via a small dashboard; the product is the agent loop, not a general docs site or IDE.
|
|
42
42
|
|
|
43
|
-
Works from small scripts to large monorepos. **Deep import maps**
|
|
43
|
+
Works from small scripts to large monorepos. **Deep import/include maps** (zero-dep regex parsers):
|
|
44
|
+
|
|
45
|
+
| Language | Extensions | Notes |
|
|
46
|
+
|----------|------------|--------|
|
|
47
|
+
| Python | `.py` | full ACS/CDIA path |
|
|
48
|
+
| JavaScript / TypeScript | `.js` `.ts` `.jsx` `.tsx` | barrels (BREE), dynamic/CDIA |
|
|
49
|
+
| Rust | `.rs` | `use` / `mod` / `extern crate` |
|
|
50
|
+
| Go | `.go` | `import` / import blocks |
|
|
51
|
+
| C / C++ | `.c` `.h` `.cpp` `.cc` `.cxx` `.hpp` `.hh` | `#include` (local + system) |
|
|
52
|
+
| C# | `.cs` | `using` namespaces |
|
|
53
|
+
| Java | `.java` | `import` / `import static` |
|
|
54
|
+
|
|
55
|
+
Health/journal still work for any monitored path. Parsers are **pragmatic regex** (not full cargo/`go.mod`/classpath/`-I` resolution). Prefer **lean `monitored_paths.txt`** on huge monorepos; raise dirty cap with `WIKIFIER_CHECK_CHANGES_MAX` (default 2000) only when needed.
|
|
44
56
|
|
|
45
57
|
## Why
|
|
46
58
|
|
|
@@ -90,7 +102,7 @@ MCP **Core 6** (start every session): `get_project_status`, `check_changes`, `ge
|
|
|
90
102
|
|
|
91
103
|
## What you get
|
|
92
104
|
|
|
93
|
-
- **Import analysis** — Python
|
|
105
|
+
- **Import analysis** — Python, JS/TS (ESM/CJS, barrels), Rust, Go, C/C++ includes, C# usings; per-edge confidence; name-routed barrel expansion for TS/JS
|
|
94
106
|
- **Incremental pipeline** — pure-Python `update-maps`: dirty parse → import cache → reverse deps → cycles → `library.md`
|
|
95
107
|
- **Selective agent work** — health + suggest bias to 🔴/🟡 only; ACS *actionable* low-conf excludes stdlib/external noise
|
|
96
108
|
- **Scale** — reverse index + barrel invalidation so one edit doesn’t re-scan the monorepo
|
|
@@ -8,7 +8,19 @@
|
|
|
8
8
|
|
|
9
9
|
Wikifier is an **agent-to-agent** tool: it builds a living map of a project (health matrix, dependency graph, short file summaries) and agents keep that map current as they work. Humans can peek via a small dashboard; the product is the agent loop, not a general docs site or IDE.
|
|
10
10
|
|
|
11
|
-
Works from small scripts to large monorepos. **Deep import maps**
|
|
11
|
+
Works from small scripts to large monorepos. **Deep import/include maps** (zero-dep regex parsers):
|
|
12
|
+
|
|
13
|
+
| Language | Extensions | Notes |
|
|
14
|
+
|----------|------------|--------|
|
|
15
|
+
| Python | `.py` | full ACS/CDIA path |
|
|
16
|
+
| JavaScript / TypeScript | `.js` `.ts` `.jsx` `.tsx` | barrels (BREE), dynamic/CDIA |
|
|
17
|
+
| Rust | `.rs` | `use` / `mod` / `extern crate` |
|
|
18
|
+
| Go | `.go` | `import` / import blocks |
|
|
19
|
+
| C / C++ | `.c` `.h` `.cpp` `.cc` `.cxx` `.hpp` `.hh` | `#include` (local + system) |
|
|
20
|
+
| C# | `.cs` | `using` namespaces |
|
|
21
|
+
| Java | `.java` | `import` / `import static` |
|
|
22
|
+
|
|
23
|
+
Health/journal still work for any monitored path. Parsers are **pragmatic regex** (not full cargo/`go.mod`/classpath/`-I` resolution). Prefer **lean `monitored_paths.txt`** on huge monorepos; raise dirty cap with `WIKIFIER_CHECK_CHANGES_MAX` (default 2000) only when needed.
|
|
12
24
|
|
|
13
25
|
## Why
|
|
14
26
|
|
|
@@ -58,7 +70,7 @@ MCP **Core 6** (start every session): `get_project_status`, `check_changes`, `ge
|
|
|
58
70
|
|
|
59
71
|
## What you get
|
|
60
72
|
|
|
61
|
-
- **Import analysis** — Python
|
|
73
|
+
- **Import analysis** — Python, JS/TS (ESM/CJS, barrels), Rust, Go, C/C++ includes, C# usings; per-edge confidence; name-routed barrel expansion for TS/JS
|
|
62
74
|
- **Incremental pipeline** — pure-Python `update-maps`: dirty parse → import cache → reverse deps → cycles → `library.md`
|
|
63
75
|
- **Selective agent work** — health + suggest bias to 🔴/🟡 only; ACS *actionable* low-conf excludes stdlib/external noise
|
|
64
76
|
- **Scale** — reverse index + barrel invalidation so one edit doesn’t re-scan the monorepo
|
|
@@ -12,7 +12,8 @@
|
|
|
12
12
|
- **First-run:** `init` → `update-maps` → `health --summary` → `suggest_next_actions`. Map is automatic; wiki *prose* is agent-filled over time.
|
|
13
13
|
- **Steady-state selective work:** only 🔴/🟡 (never re-wiki 🟢 unless required). ACS suggestions use `actionable_low_conf_edges` (stdlib/external bare demoted).
|
|
14
14
|
- **Deletions:** `record-deletion` + check-changes ghost detection (missing paths → Red DELETED).
|
|
15
|
-
- **Languages:** deep import maps = Python
|
|
15
|
+
- **Languages:** deep import maps = Python, JS/TS, Rust, Go, C/C++, C#, Java (regex; not full toolchain resolution). journal/pending = audit queue, not Jira.
|
|
16
|
+
- **Huge monorepos:** lean `monitored_paths.txt` (package roots, not bare `.`). Optional `WIKIFIER_CHECK_CHANGES_MAX` (default 2000). `update-maps` seeds Yellow health stubs for newly parsed files (map-first).
|
|
16
17
|
- **Monitor:** optional background `monitor`/`daemon` for mtime heartbeat; agents still own wiki/mark-green.
|
|
17
18
|
- Health module: use `importlib.import_module("wikifier.health")` or `wikifier.health_module` — `from wikifier import health` is the *function*.
|
|
18
19
|
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
"""Multi-language parser smoke tests (Rust, Go, C/C++, C#)."""
|
|
2
|
+
|
|
3
|
+
import unittest
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
|
|
6
|
+
from tests._base import TempProjectTestCase
|
|
7
|
+
|
|
8
|
+
from wikifier.parsers import rust, go_lang, c_cpp, csharp, java
|
|
9
|
+
from wikifier.cli import run_full_update, check_changes
|
|
10
|
+
import importlib
|
|
11
|
+
import os
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class TestRustParser(TempProjectTestCase):
|
|
15
|
+
def test_use_and_mod(self):
|
|
16
|
+
self.write("lib.rs", "mod foo;\nuse crate::foo::Bar;\nuse std::io;\n")
|
|
17
|
+
self.write("foo.rs", "pub struct Bar;\n")
|
|
18
|
+
edges = rust.parse_rust_imports(str(self.root / "lib.rs"))
|
|
19
|
+
kinds = {e.get("statement_type") for e in edges}
|
|
20
|
+
self.assertIn("mod", kinds)
|
|
21
|
+
self.assertIn("use", kinds)
|
|
22
|
+
mod_e = next(e for e in edges if e.get("statement_type") == "mod")
|
|
23
|
+
self.assertTrue(mod_e.get("resolved_path") and mod_e["resolved_path"].endswith("foo.rs"))
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class TestGoParser(TempProjectTestCase):
|
|
27
|
+
def test_import_block(self):
|
|
28
|
+
self.write("main.go", 'package main\nimport (\n\t"fmt"\n\t"os"\n)\n')
|
|
29
|
+
edges = go_lang.parse_go_imports(str(self.root / "main.go"))
|
|
30
|
+
mods = {e["raw_module"] for e in edges}
|
|
31
|
+
self.assertIn("fmt", mods)
|
|
32
|
+
self.assertIn("os", mods)
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
class TestCParser(TempProjectTestCase):
|
|
36
|
+
def test_local_and_system_include(self):
|
|
37
|
+
self.write("a.c", '#include "b.h"\n#include <stdio.h>\n')
|
|
38
|
+
self.write("b.h", "int x;\n")
|
|
39
|
+
edges = c_cpp.parse_c_cpp_imports(str(self.root / "a.c"))
|
|
40
|
+
self.assertGreaterEqual(len(edges), 2)
|
|
41
|
+
local = next(e for e in edges if e["raw_module"] == "b.h")
|
|
42
|
+
self.assertTrue(local.get("resolved_path"))
|
|
43
|
+
sys_e = next(e for e in edges if e["raw_module"] == "stdio.h")
|
|
44
|
+
self.assertEqual((sys_e.get("diagnostic") or {}).get("category"), "external_or_bare")
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
class TestCsharpParser(TempProjectTestCase):
|
|
48
|
+
def test_using(self):
|
|
49
|
+
self.write("A.cs", "using System;\nusing System.IO;\nusing MyApp.Core;\n")
|
|
50
|
+
edges = csharp.parse_csharp_imports(str(self.root / "A.cs"))
|
|
51
|
+
mods = {e["raw_module"] for e in edges}
|
|
52
|
+
self.assertIn("System", mods)
|
|
53
|
+
self.assertIn("MyApp.Core", mods)
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
class TestJavaParser(TempProjectTestCase):
|
|
57
|
+
def test_import(self):
|
|
58
|
+
self.write("A.java", "import java.util.List;\nimport com.foo.Bar;\n")
|
|
59
|
+
edges = java.parse_java_imports(str(self.root / "A.java"))
|
|
60
|
+
mods = {e["raw_module"] for e in edges}
|
|
61
|
+
self.assertIn("java.util.List", mods)
|
|
62
|
+
self.assertIn("com.foo.Bar", mods)
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
class TestPipelineMultiLang(TempProjectTestCase):
|
|
66
|
+
def test_run_full_update_parses_all_langs(self):
|
|
67
|
+
self.write("a.py", "import os\n")
|
|
68
|
+
self.write("b.rs", "mod x;\n")
|
|
69
|
+
self.write("x.rs", "pub fn f() {}\n")
|
|
70
|
+
self.write("c.go", 'package p\nimport "fmt"\n')
|
|
71
|
+
self.write("d.c", '#include "e.h"\n')
|
|
72
|
+
self.write("e.h", "int y;\n")
|
|
73
|
+
self.write("f.cs", "using System;\n")
|
|
74
|
+
self.write("G.java", "import java.io.File;\n")
|
|
75
|
+
res = run_full_update(root=self.root, force_full=True, verbose=False)
|
|
76
|
+
self.assertTrue(res.get("success"))
|
|
77
|
+
self.assertGreaterEqual(res.get("files_parsed", 0), 5)
|
|
78
|
+
self.assertIn("languages_parsed", res)
|
|
79
|
+
self.assertGreaterEqual(res.get("health_stubs_seeded", 0), 1)
|
|
80
|
+
health_mod = importlib.import_module("wikifier.health")
|
|
81
|
+
entries = health_mod.load_health(self.root).get("entries") or {}
|
|
82
|
+
self.assertTrue(any(k.endswith((".py", ".rs", ".java", ".cs", ".go", ".c")) for k in entries))
|
|
83
|
+
|
|
84
|
+
def test_check_changes_cap_env(self):
|
|
85
|
+
self.write("monitored_paths.txt", ".\n")
|
|
86
|
+
for i in range(5):
|
|
87
|
+
self.write(f"f{i}.py", f"x={i}\n")
|
|
88
|
+
run_full_update(root=self.root, force_full=True, verbose=False)
|
|
89
|
+
import time
|
|
90
|
+
time.sleep(0.05)
|
|
91
|
+
for i in range(5):
|
|
92
|
+
(self.root / f"f{i}.py").write_text(f"x={i + 1}\n")
|
|
93
|
+
os.environ["WIKIFIER_CHECK_CHANGES_MAX"] = "2"
|
|
94
|
+
try:
|
|
95
|
+
r = check_changes(project_root=self.root)
|
|
96
|
+
self.assertTrue(r.get("success"))
|
|
97
|
+
self.assertLessEqual(r.get("changes_detected", 0), 2)
|
|
98
|
+
self.assertTrue(r.get("dirty_truncated") or (r.get("dirty_total") or 0) <= 2)
|
|
99
|
+
finally:
|
|
100
|
+
os.environ.pop("WIKIFIER_CHECK_CHANGES_MAX", None)
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
if __name__ == "__main__":
|
|
104
|
+
unittest.main()
|
|
@@ -174,7 +174,13 @@ def _collect_candidate_source_files(root: Path) -> List[Path]:
|
|
|
174
174
|
".pnpm", ".yarn", ".store", "store", "virtual-store", ".pnp",
|
|
175
175
|
".pnp.cjs", "node_modules/.pnpm", "node_modules/.yarn"
|
|
176
176
|
}
|
|
177
|
-
|
|
177
|
+
# Deep-map languages (agent multi-lang dogfood)
|
|
178
|
+
exts = (
|
|
179
|
+
".py", ".js", ".ts", ".jsx", ".tsx",
|
|
180
|
+
".rs", ".go",
|
|
181
|
+
".c", ".h", ".cpp", ".cc", ".cxx", ".hpp", ".hh",
|
|
182
|
+
".cs", ".java",
|
|
183
|
+
)
|
|
178
184
|
try:
|
|
179
185
|
root = Path(root).resolve()
|
|
180
186
|
except Exception:
|
|
@@ -499,6 +505,26 @@ def run_full_update(
|
|
|
499
505
|
# === 3. Parse every dirty file (in-process, BREE batched) ===
|
|
500
506
|
from .parsers import javascript as js_parser
|
|
501
507
|
from .parsers import python as py_parser
|
|
508
|
+
try:
|
|
509
|
+
from .parsers import rust as rust_parser
|
|
510
|
+
except Exception:
|
|
511
|
+
rust_parser = None
|
|
512
|
+
try:
|
|
513
|
+
from .parsers import go_lang as go_parser
|
|
514
|
+
except Exception:
|
|
515
|
+
go_parser = None
|
|
516
|
+
try:
|
|
517
|
+
from .parsers import c_cpp as c_cpp_parser
|
|
518
|
+
except Exception:
|
|
519
|
+
c_cpp_parser = None
|
|
520
|
+
try:
|
|
521
|
+
from .parsers import csharp as csharp_parser
|
|
522
|
+
except Exception:
|
|
523
|
+
csharp_parser = None
|
|
524
|
+
try:
|
|
525
|
+
from .parsers import java as java_parser
|
|
526
|
+
except Exception:
|
|
527
|
+
java_parser = None
|
|
502
528
|
try:
|
|
503
529
|
from .parsers import bree as bree_mod
|
|
504
530
|
except Exception:
|
|
@@ -521,10 +547,28 @@ def run_full_update(
|
|
|
521
547
|
except Exception:
|
|
522
548
|
return None
|
|
523
549
|
|
|
550
|
+
def _parse_file(fstr: str, low: str):
|
|
551
|
+
if low.endswith((".js", ".ts", ".jsx", ".tsx")):
|
|
552
|
+
return js_parser.parse_javascript_imports(fstr) or []
|
|
553
|
+
if low.endswith(".py"):
|
|
554
|
+
return py_parser.parse_python_imports(fstr) or []
|
|
555
|
+
if low.endswith(".rs") and rust_parser is not None:
|
|
556
|
+
return rust_parser.parse_rust_imports(fstr) or []
|
|
557
|
+
if low.endswith(".go") and go_parser is not None:
|
|
558
|
+
return go_parser.parse_go_imports(fstr) or []
|
|
559
|
+
if low.endswith((".c", ".h", ".cpp", ".cc", ".cxx", ".hpp", ".hh")) and c_cpp_parser is not None:
|
|
560
|
+
return c_cpp_parser.parse_c_cpp_imports(fstr) or []
|
|
561
|
+
if low.endswith(".cs") and csharp_parser is not None:
|
|
562
|
+
return csharp_parser.parse_csharp_imports(fstr) or []
|
|
563
|
+
if low.endswith(".java") and java_parser is not None:
|
|
564
|
+
return java_parser.parse_java_imports(fstr) or []
|
|
565
|
+
return None
|
|
566
|
+
|
|
524
567
|
new_entries: Dict[str, Dict[str, Any]] = {}
|
|
525
568
|
edges_total = 0
|
|
526
569
|
parsed_count = 0
|
|
527
570
|
parse_errors: List[Dict[str, str]] = []
|
|
571
|
+
lang_counts: Dict[str, int] = {}
|
|
528
572
|
|
|
529
573
|
if bree_mod is not None:
|
|
530
574
|
try:
|
|
@@ -536,11 +580,8 @@ def run_full_update(
|
|
|
536
580
|
fstr = str(f)
|
|
537
581
|
low = fstr.lower()
|
|
538
582
|
try:
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
elif low.endswith(".py"):
|
|
542
|
-
edges = py_parser.parse_python_imports(fstr) or []
|
|
543
|
-
else:
|
|
583
|
+
edges = _parse_file(fstr, low)
|
|
584
|
+
if edges is None:
|
|
544
585
|
continue
|
|
545
586
|
except Exception as pe:
|
|
546
587
|
parse_errors.append({"file": fstr, "error": f"{type(pe).__name__}: {pe}"})
|
|
@@ -557,6 +598,8 @@ def run_full_update(
|
|
|
557
598
|
}
|
|
558
599
|
parsed_count += 1
|
|
559
600
|
edges_total += len(pairs)
|
|
601
|
+
ext = Path(low).suffix.lower() or "unknown"
|
|
602
|
+
lang_counts[ext] = lang_counts.get(ext, 0) + 1
|
|
560
603
|
if verbose and parsed_count % 200 == 0:
|
|
561
604
|
print(f"[run_full_update] parsed {parsed_count}/{len(dirty)}")
|
|
562
605
|
finally:
|
|
@@ -568,10 +611,28 @@ def run_full_update(
|
|
|
568
611
|
|
|
569
612
|
result["files_parsed"] = parsed_count
|
|
570
613
|
result["edges_persisted"] = edges_total
|
|
614
|
+
result["languages_parsed"] = lang_counts
|
|
571
615
|
if parse_errors:
|
|
572
616
|
result["parse_errors"] = parse_errors[:10]
|
|
573
617
|
result["parse_error_count"] = len(parse_errors)
|
|
574
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
|
+
|
|
575
636
|
# === 4. Persist (single save; reload first to pick up the barrel flush) ===
|
|
576
637
|
cache = ic.load_cache(root) or {}
|
|
577
638
|
cache.update(new_entries)
|
|
@@ -1079,11 +1140,28 @@ def check_changes(project_root: Optional[Union[str, Path]] = None) -> Dict[str,
|
|
|
1079
1140
|
except Exception:
|
|
1080
1141
|
pass
|
|
1081
1142
|
|
|
1143
|
+
# Cap is configurable: WIKIFIER_CHECK_CHANGES_MAX (default 2000; was hard 200).
|
|
1144
|
+
# Huge monorepos with monitored_paths=. can still thrash — prefer lean monitored paths.
|
|
1145
|
+
try:
|
|
1146
|
+
max_dirty = int(os.environ.get("WIKIFIER_CHECK_CHANGES_MAX", "2000") or "2000")
|
|
1147
|
+
except ValueError:
|
|
1148
|
+
max_dirty = 2000
|
|
1149
|
+
max_dirty = max(1, min(max_dirty, 50000))
|
|
1150
|
+
try:
|
|
1151
|
+
max_ghosts = int(os.environ.get("WIKIFIER_CHECK_CHANGES_GHOST_MAX", "200") or "200")
|
|
1152
|
+
except ValueError:
|
|
1153
|
+
max_ghosts = 200
|
|
1154
|
+
max_ghosts = max(1, min(max_ghosts, 10000))
|
|
1155
|
+
|
|
1156
|
+
dirty_list = list(dirty or [])
|
|
1157
|
+
dirty_truncated = len(dirty_list) > max_dirty
|
|
1158
|
+
dirty_batch = dirty_list[:max_dirty]
|
|
1159
|
+
|
|
1082
1160
|
changed_count = 0
|
|
1083
1161
|
ghosts_marked = 0
|
|
1084
1162
|
if _health_mod is not None:
|
|
1085
1163
|
root_res = root.resolve()
|
|
1086
|
-
for p in
|
|
1164
|
+
for p in dirty_batch:
|
|
1087
1165
|
try:
|
|
1088
1166
|
pr = Path(p).resolve()
|
|
1089
1167
|
if not str(pr).startswith(str(root_res)):
|
|
@@ -1102,7 +1180,8 @@ def check_changes(project_root: Optional[Union[str, Path]] = None) -> Dict[str,
|
|
|
1102
1180
|
# G7: surface ghost health entries (tracked path missing on disk, not already DELETED)
|
|
1103
1181
|
try:
|
|
1104
1182
|
if hasattr(_health_mod, "find_ghost_entries"):
|
|
1105
|
-
|
|
1183
|
+
ghosts_all = _health_mod.find_ghost_entries(root) or []
|
|
1184
|
+
for g in ghosts_all[:max_ghosts]:
|
|
1106
1185
|
_health_mod.upsert_entry(
|
|
1107
1186
|
root, g, "🔴 Red",
|
|
1108
1187
|
"DELETED — path missing on disk (check_changes ghost detection)"
|
|
@@ -1115,15 +1194,24 @@ def check_changes(project_root: Optional[Union[str, Path]] = None) -> Dict[str,
|
|
|
1115
1194
|
except Exception:
|
|
1116
1195
|
pass
|
|
1117
1196
|
|
|
1197
|
+
msg = (
|
|
1198
|
+
f"Python-primary check_changes complete: {changed_count} files marked/updated"
|
|
1199
|
+
+ (f", {ghosts_marked} ghost(s) marked Red" if ghosts_marked else "")
|
|
1200
|
+
+ ". Health + pending + journal touched."
|
|
1201
|
+
)
|
|
1202
|
+
if dirty_truncated:
|
|
1203
|
+
msg += (
|
|
1204
|
+
f" Note: dirty set truncated to {max_dirty} of {len(dirty_list)} "
|
|
1205
|
+
f"(set WIKIFIER_CHECK_CHANGES_MAX or lean monitored_paths.txt)."
|
|
1206
|
+
)
|
|
1118
1207
|
result.update({
|
|
1119
1208
|
"success": True,
|
|
1120
1209
|
"changes_detected": changed_count,
|
|
1210
|
+
"dirty_total": len(dirty_list),
|
|
1211
|
+
"dirty_truncated": dirty_truncated,
|
|
1212
|
+
"max_dirty": max_dirty,
|
|
1121
1213
|
"ghosts_marked": ghosts_marked,
|
|
1122
|
-
"message":
|
|
1123
|
-
f"Python-primary check_changes complete: {changed_count} files marked/updated"
|
|
1124
|
-
+ (f", {ghosts_marked} ghost(s) marked Red" if ghosts_marked else "")
|
|
1125
|
-
+ ". Health + pending + journal touched."
|
|
1126
|
-
),
|
|
1214
|
+
"message": msg,
|
|
1127
1215
|
})
|
|
1128
1216
|
except Exception as e:
|
|
1129
1217
|
result["error"] = str(e)
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
"""Language parsers — shared edge contract for update-maps.
|
|
2
|
+
|
|
3
|
+
Supported (deep import/include graphs):
|
|
4
|
+
python, javascript/typescript, rust, go, c/c++, csharp, java
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from . import python
|
|
8
|
+
from . import javascript
|
|
9
|
+
from . import rust
|
|
10
|
+
from . import go_lang
|
|
11
|
+
from . import c_cpp
|
|
12
|
+
from . import csharp
|
|
13
|
+
from . import java
|
|
14
|
+
|
|
15
|
+
__all__ = [
|
|
16
|
+
"python",
|
|
17
|
+
"javascript",
|
|
18
|
+
"rust",
|
|
19
|
+
"go_lang",
|
|
20
|
+
"c_cpp",
|
|
21
|
+
"csharp",
|
|
22
|
+
"java",
|
|
23
|
+
]
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
"""Shared edge helpers for language parsers (zero-dep, agent-first)."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import Any, Dict, List, Optional
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def make_edge(
|
|
9
|
+
*,
|
|
10
|
+
module: str,
|
|
11
|
+
raw_module: str,
|
|
12
|
+
is_relative: bool = False,
|
|
13
|
+
level: int = 0,
|
|
14
|
+
resolved_path: Optional[str] = None,
|
|
15
|
+
resolution_confidence: str = "medium",
|
|
16
|
+
confidence_score: Optional[float] = None,
|
|
17
|
+
confidence_reasons: Optional[List[str]] = None,
|
|
18
|
+
is_dynamic: bool = False,
|
|
19
|
+
statement_type: str = "import",
|
|
20
|
+
original_statement: str = "",
|
|
21
|
+
diagnostic: Optional[Dict[str, Any]] = None,
|
|
22
|
+
strategy: str = "lang-static",
|
|
23
|
+
imported_names: Optional[List[str]] = None,
|
|
24
|
+
) -> Dict[str, Any]:
|
|
25
|
+
"""Build a parser edge dict matching the shared py/js contract (additive fields OK)."""
|
|
26
|
+
conf = resolution_confidence
|
|
27
|
+
if confidence_score is None:
|
|
28
|
+
confidence_score = {"high": 0.9, "medium": 0.6, "low": 0.4, "unresolved": 0.2}.get(conf, 0.5)
|
|
29
|
+
reasons = list(confidence_reasons or [f"base:{conf}"])
|
|
30
|
+
if not resolved_path and "no_resolved_path" not in reasons:
|
|
31
|
+
reasons.append("no_resolved_path")
|
|
32
|
+
expl = f"Base {conf} ({confidence_score:.2f})."
|
|
33
|
+
if not resolved_path:
|
|
34
|
+
expl += " unresolved/partial target."
|
|
35
|
+
if diagnostic and diagnostic.get("category") == "external_or_bare":
|
|
36
|
+
expl += " External/stdlib-style include."
|
|
37
|
+
expl += " Recommendation: Use project-relative edges for automation trust; externals are expected noise."
|
|
38
|
+
|
|
39
|
+
edge: Dict[str, Any] = {
|
|
40
|
+
"module": module,
|
|
41
|
+
"raw_module": raw_module,
|
|
42
|
+
"is_relative": is_relative,
|
|
43
|
+
"level": level,
|
|
44
|
+
"alias": None,
|
|
45
|
+
"imported_names": imported_names or [],
|
|
46
|
+
"original_statement": original_statement or raw_module,
|
|
47
|
+
"statement_type": statement_type,
|
|
48
|
+
"resolved_path": resolved_path,
|
|
49
|
+
"resolved": resolved_path or module,
|
|
50
|
+
"resolution_confidence": conf,
|
|
51
|
+
"confidence": conf,
|
|
52
|
+
"confidence_score": float(confidence_score),
|
|
53
|
+
"confidence_reasons": reasons,
|
|
54
|
+
"confidence_explanation": expl,
|
|
55
|
+
"is_dynamic": is_dynamic,
|
|
56
|
+
"dynamic_type": "static" if not is_dynamic else "expression",
|
|
57
|
+
"is_conditional": False,
|
|
58
|
+
"via_barrel": False,
|
|
59
|
+
"barrel_depth": 0,
|
|
60
|
+
"resolution_metadata": {
|
|
61
|
+
"strategy": strategy,
|
|
62
|
+
"package_hierarchy_walk": False,
|
|
63
|
+
"target_on_disk": bool(resolved_path),
|
|
64
|
+
"relative_level": level,
|
|
65
|
+
},
|
|
66
|
+
"parser": strategy.split("-")[0] if strategy else "unknown",
|
|
67
|
+
"strategy": strategy,
|
|
68
|
+
}
|
|
69
|
+
if diagnostic:
|
|
70
|
+
edge["diagnostic"] = diagnostic
|
|
71
|
+
return edge
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
"""C/C++ #include parser (zero-dep, agent-first).
|
|
2
|
+
|
|
3
|
+
AGENT MAP:
|
|
4
|
+
parse_c_cpp_imports(filepath) → edge list for .c/.h/.cpp/.hpp/.cc/.cxx
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
import re
|
|
10
|
+
from pathlib import Path
|
|
11
|
+
from typing import Dict, List, Optional
|
|
12
|
+
|
|
13
|
+
from ._edge import make_edge
|
|
14
|
+
|
|
15
|
+
_INCLUDE_RE = re.compile(
|
|
16
|
+
r'^\s*#\s*include\s*([<"])([^>"]+)([>"])',
|
|
17
|
+
re.MULTILINE,
|
|
18
|
+
)
|
|
19
|
+
_COMMENT_BLOCK = re.compile(r"/\*.*?\*/", re.DOTALL)
|
|
20
|
+
_COMMENT_LINE = re.compile(r"//.*?$", re.MULTILINE)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def _strip(src: str) -> str:
|
|
24
|
+
return _COMMENT_LINE.sub(" ", _COMMENT_BLOCK.sub(" ", src))
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def _resolve_local(current: Path, inc: str) -> Optional[str]:
|
|
28
|
+
parent = current.parent
|
|
29
|
+
cand = (parent / inc).resolve()
|
|
30
|
+
if cand.is_file():
|
|
31
|
+
return str(cand)
|
|
32
|
+
# walk up a few parents for monorepo headers (bounded)
|
|
33
|
+
for _ in range(4):
|
|
34
|
+
parent = parent.parent
|
|
35
|
+
cand = (parent / inc).resolve()
|
|
36
|
+
if cand.is_file():
|
|
37
|
+
return str(cand)
|
|
38
|
+
return None
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def parse_c_cpp_imports(filepath: str) -> List[Dict]:
|
|
42
|
+
path = Path(filepath)
|
|
43
|
+
try:
|
|
44
|
+
text = path.read_text(encoding="utf-8", errors="ignore")
|
|
45
|
+
except Exception:
|
|
46
|
+
return []
|
|
47
|
+
text = _strip(text)
|
|
48
|
+
edges: List[Dict] = []
|
|
49
|
+
|
|
50
|
+
for m in _INCLUDE_RE.finditer(text):
|
|
51
|
+
quote, inc, _ = m.group(1), m.group(2), m.group(3)
|
|
52
|
+
is_system = quote == "<"
|
|
53
|
+
is_rel = not is_system
|
|
54
|
+
rp = _resolve_local(path, inc) if is_rel else None
|
|
55
|
+
diag = None
|
|
56
|
+
if is_system or not rp:
|
|
57
|
+
diag = {
|
|
58
|
+
"category": "external_or_bare" if is_system else "no_fs_match",
|
|
59
|
+
"reason": f"#include {'<' if is_system else '\"'}{inc}{' >' if is_system else '\"'}",
|
|
60
|
+
"severity": "info",
|
|
61
|
+
"alternatives": [],
|
|
62
|
+
"suggestion_for_agent": (
|
|
63
|
+
"System includes are expected external."
|
|
64
|
+
if is_system
|
|
65
|
+
else "Could not resolve local include on disk (include paths not fully modeled)."
|
|
66
|
+
),
|
|
67
|
+
"details": {},
|
|
68
|
+
}
|
|
69
|
+
edges.append(
|
|
70
|
+
make_edge(
|
|
71
|
+
module=inc,
|
|
72
|
+
raw_module=inc,
|
|
73
|
+
is_relative=is_rel,
|
|
74
|
+
level=1 if is_rel else 0,
|
|
75
|
+
resolved_path=rp,
|
|
76
|
+
resolution_confidence="high" if rp else ("medium" if is_system else "low"),
|
|
77
|
+
statement_type="include",
|
|
78
|
+
original_statement=m.group(0).strip(),
|
|
79
|
+
diagnostic=diag,
|
|
80
|
+
strategy="c-include" if is_system else "c-include-local",
|
|
81
|
+
)
|
|
82
|
+
)
|
|
83
|
+
return edges
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
"""C# using-directive parser (zero-dep, agent-first).
|
|
2
|
+
|
|
3
|
+
AGENT MAP:
|
|
4
|
+
parse_csharp_imports(filepath) → edge list for .cs using directives
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
import re
|
|
10
|
+
from pathlib import Path
|
|
11
|
+
from typing import Dict, List
|
|
12
|
+
|
|
13
|
+
from ._edge import make_edge
|
|
14
|
+
|
|
15
|
+
_USING_RE = re.compile(
|
|
16
|
+
r"^\s*using\s+(?:static\s+)?(?:[\w.]+\s*=\s*)?([\w.]+)\s*;",
|
|
17
|
+
re.MULTILINE,
|
|
18
|
+
)
|
|
19
|
+
_COMMENT_BLOCK = re.compile(r"/\*.*?\*/", re.DOTALL)
|
|
20
|
+
_COMMENT_LINE = re.compile(r"//.*?$", re.MULTILINE)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def _strip(src: str) -> str:
|
|
24
|
+
return _COMMENT_LINE.sub(" ", _COMMENT_BLOCK.sub(" ", src))
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def parse_csharp_imports(filepath: str) -> List[Dict]:
|
|
28
|
+
path = Path(filepath)
|
|
29
|
+
try:
|
|
30
|
+
text = path.read_text(encoding="utf-8", errors="ignore")
|
|
31
|
+
except Exception:
|
|
32
|
+
return []
|
|
33
|
+
text = _strip(text)
|
|
34
|
+
edges: List[Dict] = []
|
|
35
|
+
seen = set()
|
|
36
|
+
|
|
37
|
+
for m in _USING_RE.finditer(text):
|
|
38
|
+
ns = m.group(1).strip()
|
|
39
|
+
if not ns or ns in seen:
|
|
40
|
+
continue
|
|
41
|
+
seen.add(ns)
|
|
42
|
+
# Project namespaces often PascalCase multi-part; System.* is framework
|
|
43
|
+
is_framework = ns == "System" or ns.startswith("System.") or ns.startswith("Microsoft.")
|
|
44
|
+
diag = {
|
|
45
|
+
"category": "external_or_bare",
|
|
46
|
+
"reason": f"C# using '{ns}' (namespace; not a filesystem path).",
|
|
47
|
+
"severity": "info",
|
|
48
|
+
"alternatives": [],
|
|
49
|
+
"suggestion_for_agent": "Namespace graph is symbolic; pair with project structure for deeper maps.",
|
|
50
|
+
"details": {"framework": is_framework},
|
|
51
|
+
}
|
|
52
|
+
edges.append(
|
|
53
|
+
make_edge(
|
|
54
|
+
module=ns,
|
|
55
|
+
raw_module=ns,
|
|
56
|
+
is_relative=False,
|
|
57
|
+
resolution_confidence="medium",
|
|
58
|
+
statement_type="using",
|
|
59
|
+
original_statement=m.group(0).strip(),
|
|
60
|
+
diagnostic=diag,
|
|
61
|
+
strategy="csharp-using",
|
|
62
|
+
)
|
|
63
|
+
)
|
|
64
|
+
return edges
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
"""Go import parser (zero-dep, agent-first).
|
|
2
|
+
|
|
3
|
+
AGENT MAP:
|
|
4
|
+
parse_go_imports(filepath) → edge list (import / import ())
|
|
5
|
+
Module name: go_lang (avoid stdlib `go` clash if any).
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
import re
|
|
11
|
+
from pathlib import Path
|
|
12
|
+
from typing import Dict, List, Optional
|
|
13
|
+
|
|
14
|
+
from ._edge import make_edge
|
|
15
|
+
|
|
16
|
+
_IMPORT_SINGLE = re.compile(r'^\s*import\s+(?:(\w+)\s+)?["\']([^"\']+)["\']', re.MULTILINE)
|
|
17
|
+
_IMPORT_BLOCK = re.compile(r"^\s*import\s*\((.*?)\)", re.MULTILINE | re.DOTALL)
|
|
18
|
+
_BLOCK_LINE = re.compile(r'^\s*(?:(\w+)\s+)?["\']([^"\']+)["\']', re.MULTILINE)
|
|
19
|
+
_COMMENT_BLOCK = re.compile(r"/\*.*?\*/", re.DOTALL)
|
|
20
|
+
_COMMENT_LINE = re.compile(r"//.*?$", re.MULTILINE)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def _strip(src: str) -> str:
|
|
24
|
+
return _COMMENT_LINE.sub(" ", _COMMENT_BLOCK.sub(" ", src))
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def _is_stdlib(path: str) -> bool:
|
|
28
|
+
# Rough: no dot in first path segment → often stdlib (fmt, os, net/http)
|
|
29
|
+
first = path.split("/")[0]
|
|
30
|
+
return "." not in first and not path.startswith(".")
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def _edge_for(path: str, alias: Optional[str], stmt: str) -> Dict:
|
|
34
|
+
is_rel = path.startswith("./") or path.startswith("../")
|
|
35
|
+
external = (not is_rel) and (_is_stdlib(path) or "." in path.split("/")[0] or True)
|
|
36
|
+
# Mark non-relative as external_or_bare for ACS noise filter (module paths are rarely on-disk relative)
|
|
37
|
+
diag = None
|
|
38
|
+
if not is_rel:
|
|
39
|
+
diag = {
|
|
40
|
+
"category": "external_or_bare",
|
|
41
|
+
"reason": f"Go import path '{path}' (module path; not relative filesystem).",
|
|
42
|
+
"severity": "info",
|
|
43
|
+
"alternatives": [],
|
|
44
|
+
"suggestion_for_agent": "Relative imports are rare in Go; module paths are expected.",
|
|
45
|
+
"details": {"stdlib_heuristic": _is_stdlib(path)},
|
|
46
|
+
}
|
|
47
|
+
return make_edge(
|
|
48
|
+
module=path,
|
|
49
|
+
raw_module=path,
|
|
50
|
+
is_relative=is_rel,
|
|
51
|
+
level=1 if is_rel else 0,
|
|
52
|
+
resolution_confidence="medium",
|
|
53
|
+
statement_type="import",
|
|
54
|
+
original_statement=stmt.strip(),
|
|
55
|
+
diagnostic=diag,
|
|
56
|
+
strategy="go-import",
|
|
57
|
+
imported_names=[alias] if alias else [],
|
|
58
|
+
)
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def parse_go_imports(filepath: str) -> List[Dict]:
|
|
62
|
+
path = Path(filepath)
|
|
63
|
+
try:
|
|
64
|
+
text = path.read_text(encoding="utf-8", errors="ignore")
|
|
65
|
+
except Exception:
|
|
66
|
+
return []
|
|
67
|
+
text = _strip(text)
|
|
68
|
+
edges: List[Dict] = []
|
|
69
|
+
seen = set()
|
|
70
|
+
|
|
71
|
+
for m in _IMPORT_BLOCK.finditer(text):
|
|
72
|
+
block = m.group(1)
|
|
73
|
+
for lm in _BLOCK_LINE.finditer(block):
|
|
74
|
+
alias, imp = lm.group(1), lm.group(2)
|
|
75
|
+
if imp in seen:
|
|
76
|
+
continue
|
|
77
|
+
seen.add(imp)
|
|
78
|
+
edges.append(_edge_for(imp, alias, lm.group(0)))
|
|
79
|
+
|
|
80
|
+
for m in _IMPORT_SINGLE.finditer(text):
|
|
81
|
+
alias, imp = m.group(1), m.group(2)
|
|
82
|
+
if imp in seen:
|
|
83
|
+
continue
|
|
84
|
+
seen.add(imp)
|
|
85
|
+
edges.append(_edge_for(imp, alias, m.group(0)))
|
|
86
|
+
|
|
87
|
+
return edges
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
"""Java import parser (zero-dep, agent-first).
|
|
2
|
+
|
|
3
|
+
AGENT MAP:
|
|
4
|
+
parse_java_imports(filepath) → edge list for .java import statements
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
import re
|
|
10
|
+
from pathlib import Path
|
|
11
|
+
from typing import Dict, List
|
|
12
|
+
|
|
13
|
+
from ._edge import make_edge
|
|
14
|
+
|
|
15
|
+
_IMPORT_RE = re.compile(
|
|
16
|
+
r"^\s*import\s+(static\s+)?([\w.]+)(?:\.\*)?\s*;",
|
|
17
|
+
re.MULTILINE,
|
|
18
|
+
)
|
|
19
|
+
_COMMENT_BLOCK = re.compile(r"/\*.*?\*/", re.DOTALL)
|
|
20
|
+
_COMMENT_LINE = re.compile(r"//.*?$", re.MULTILINE)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def _strip(src: str) -> str:
|
|
24
|
+
return _COMMENT_LINE.sub(" ", _COMMENT_BLOCK.sub(" ", src))
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def parse_java_imports(filepath: str) -> List[Dict]:
|
|
28
|
+
path = Path(filepath)
|
|
29
|
+
try:
|
|
30
|
+
text = path.read_text(encoding="utf-8", errors="ignore")
|
|
31
|
+
except Exception:
|
|
32
|
+
return []
|
|
33
|
+
text = _strip(text)
|
|
34
|
+
edges: List[Dict] = []
|
|
35
|
+
seen = set()
|
|
36
|
+
|
|
37
|
+
for m in _IMPORT_RE.finditer(text):
|
|
38
|
+
is_static = bool(m.group(1))
|
|
39
|
+
name = m.group(2).strip()
|
|
40
|
+
if not name or name in seen:
|
|
41
|
+
continue
|
|
42
|
+
seen.add(name)
|
|
43
|
+
is_jdk = name.startswith("java.") or name.startswith("javax.") or name.startswith("jdk.")
|
|
44
|
+
diag = {
|
|
45
|
+
"category": "external_or_bare",
|
|
46
|
+
"reason": f"Java import '{name}'" + (" (static)" if is_static else ""),
|
|
47
|
+
"severity": "info",
|
|
48
|
+
"alternatives": [],
|
|
49
|
+
"suggestion_for_agent": "Package names are symbolic without classpath resolution.",
|
|
50
|
+
"details": {"jdk": is_jdk, "static": is_static},
|
|
51
|
+
}
|
|
52
|
+
edges.append(
|
|
53
|
+
make_edge(
|
|
54
|
+
module=name,
|
|
55
|
+
raw_module=name,
|
|
56
|
+
is_relative=False,
|
|
57
|
+
resolution_confidence="medium",
|
|
58
|
+
statement_type="import_static" if is_static else "import",
|
|
59
|
+
original_statement=m.group(0).strip(),
|
|
60
|
+
diagnostic=diag,
|
|
61
|
+
strategy="java-import",
|
|
62
|
+
)
|
|
63
|
+
)
|
|
64
|
+
return edges
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
"""Rust import/use parser (zero-dep, agent-first).
|
|
2
|
+
|
|
3
|
+
AGENT MAP:
|
|
4
|
+
parse_rust_imports(filepath) → edge list (use / mod / extern crate)
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
import re
|
|
10
|
+
from pathlib import Path
|
|
11
|
+
from typing import Dict, List, Optional
|
|
12
|
+
|
|
13
|
+
from ._edge import make_edge
|
|
14
|
+
|
|
15
|
+
_USE_RE = re.compile(
|
|
16
|
+
r"^\s*(?:pub\s+)?use\s+((?:crate|super|self|::)?(?:::)?[\w:{}*,\s]+);",
|
|
17
|
+
re.MULTILINE,
|
|
18
|
+
)
|
|
19
|
+
_MOD_RE = re.compile(r"^\s*(?:pub\s+)?mod\s+(\w+)\s*;", re.MULTILINE)
|
|
20
|
+
_EXTERN_RE = re.compile(r"^\s*extern\s+crate\s+(\w+)\s*;", re.MULTILINE)
|
|
21
|
+
_COMMENT_BLOCK = re.compile(r"/\*.*?\*/", re.DOTALL)
|
|
22
|
+
_COMMENT_LINE = re.compile(r"//.*?$", re.MULTILINE)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def _strip_comments(src: str) -> str:
|
|
26
|
+
src = _COMMENT_BLOCK.sub(" ", src)
|
|
27
|
+
src = _COMMENT_LINE.sub(" ", src)
|
|
28
|
+
return src
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def _resolve_mod(current: Path, name: str) -> Optional[str]:
|
|
32
|
+
parent = current.parent
|
|
33
|
+
for cand in (
|
|
34
|
+
parent / f"{name}.rs",
|
|
35
|
+
parent / name / "mod.rs",
|
|
36
|
+
parent / name / "lib.rs",
|
|
37
|
+
parent / name / "main.rs",
|
|
38
|
+
):
|
|
39
|
+
if cand.is_file():
|
|
40
|
+
return str(cand.resolve())
|
|
41
|
+
return None
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def parse_rust_imports(filepath: str) -> List[Dict]:
|
|
45
|
+
path = Path(filepath)
|
|
46
|
+
try:
|
|
47
|
+
text = path.read_text(encoding="utf-8", errors="ignore")
|
|
48
|
+
except Exception:
|
|
49
|
+
return []
|
|
50
|
+
text = _strip_comments(text)
|
|
51
|
+
edges: List[Dict] = []
|
|
52
|
+
|
|
53
|
+
for m in _USE_RE.finditer(text):
|
|
54
|
+
raw = m.group(1).strip()
|
|
55
|
+
# Collapse use foo::{a, b} to foo
|
|
56
|
+
base = raw.split("::")[0].replace("{", "").replace("}", "").strip().split(",")[0].strip()
|
|
57
|
+
is_rel = base in ("crate", "super", "self") or raw.startswith("crate::") or raw.startswith("super::")
|
|
58
|
+
conf = "medium" if is_rel else "medium"
|
|
59
|
+
diag = None
|
|
60
|
+
if not is_rel and base not in ("std", "core", "alloc"):
|
|
61
|
+
# external crate name
|
|
62
|
+
diag = {
|
|
63
|
+
"category": "external_or_bare",
|
|
64
|
+
"reason": f"Rust crate/path use '{raw}' (not crate/super/self-local).",
|
|
65
|
+
"severity": "info",
|
|
66
|
+
"alternatives": [],
|
|
67
|
+
"suggestion_for_agent": "Local crate::/super::/mod edges are preferred for graph trust.",
|
|
68
|
+
"details": {},
|
|
69
|
+
}
|
|
70
|
+
if base in ("std", "core", "alloc") or (not is_rel and "::" not in raw and base.isidentifier()):
|
|
71
|
+
if base in ("std", "core", "alloc") or (diag and not is_rel):
|
|
72
|
+
diag = diag or {
|
|
73
|
+
"category": "external_or_bare",
|
|
74
|
+
"reason": f"Rust std/external use '{raw}'.",
|
|
75
|
+
"severity": "info",
|
|
76
|
+
"alternatives": [],
|
|
77
|
+
"suggestion_for_agent": "Expected for std/external crates.",
|
|
78
|
+
"details": {},
|
|
79
|
+
}
|
|
80
|
+
edges.append(
|
|
81
|
+
make_edge(
|
|
82
|
+
module=raw.replace(" ", ""),
|
|
83
|
+
raw_module=raw,
|
|
84
|
+
is_relative=is_rel,
|
|
85
|
+
level=1 if is_rel else 0,
|
|
86
|
+
resolution_confidence=conf,
|
|
87
|
+
statement_type="use",
|
|
88
|
+
original_statement=m.group(0).strip(),
|
|
89
|
+
diagnostic=diag,
|
|
90
|
+
strategy="rust-use",
|
|
91
|
+
)
|
|
92
|
+
)
|
|
93
|
+
|
|
94
|
+
for m in _MOD_RE.finditer(text):
|
|
95
|
+
name = m.group(1)
|
|
96
|
+
rp = _resolve_mod(path, name)
|
|
97
|
+
edges.append(
|
|
98
|
+
make_edge(
|
|
99
|
+
module=name,
|
|
100
|
+
raw_module=name,
|
|
101
|
+
is_relative=True,
|
|
102
|
+
level=1,
|
|
103
|
+
resolved_path=rp,
|
|
104
|
+
resolution_confidence="high" if rp else "medium",
|
|
105
|
+
statement_type="mod",
|
|
106
|
+
original_statement=m.group(0).strip(),
|
|
107
|
+
strategy="rust-mod",
|
|
108
|
+
)
|
|
109
|
+
)
|
|
110
|
+
|
|
111
|
+
for m in _EXTERN_RE.finditer(text):
|
|
112
|
+
name = m.group(1)
|
|
113
|
+
edges.append(
|
|
114
|
+
make_edge(
|
|
115
|
+
module=name,
|
|
116
|
+
raw_module=name,
|
|
117
|
+
is_relative=False,
|
|
118
|
+
resolution_confidence="medium",
|
|
119
|
+
statement_type="extern_crate",
|
|
120
|
+
original_statement=m.group(0).strip(),
|
|
121
|
+
diagnostic={
|
|
122
|
+
"category": "external_or_bare",
|
|
123
|
+
"reason": f"extern crate {name}",
|
|
124
|
+
"severity": "info",
|
|
125
|
+
"alternatives": [],
|
|
126
|
+
"suggestion_for_agent": "External crate.",
|
|
127
|
+
"details": {},
|
|
128
|
+
},
|
|
129
|
+
strategy="rust-extern",
|
|
130
|
+
)
|
|
131
|
+
)
|
|
132
|
+
|
|
133
|
+
return edges
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: wikifier
|
|
3
|
-
Version: 4.5.
|
|
3
|
+
Version: 4.5.4
|
|
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
|
|
@@ -40,7 +40,19 @@ Dynamic: license-file
|
|
|
40
40
|
|
|
41
41
|
Wikifier is an **agent-to-agent** tool: it builds a living map of a project (health matrix, dependency graph, short file summaries) and agents keep that map current as they work. Humans can peek via a small dashboard; the product is the agent loop, not a general docs site or IDE.
|
|
42
42
|
|
|
43
|
-
Works from small scripts to large monorepos. **Deep import maps**
|
|
43
|
+
Works from small scripts to large monorepos. **Deep import/include maps** (zero-dep regex parsers):
|
|
44
|
+
|
|
45
|
+
| Language | Extensions | Notes |
|
|
46
|
+
|----------|------------|--------|
|
|
47
|
+
| Python | `.py` | full ACS/CDIA path |
|
|
48
|
+
| JavaScript / TypeScript | `.js` `.ts` `.jsx` `.tsx` | barrels (BREE), dynamic/CDIA |
|
|
49
|
+
| Rust | `.rs` | `use` / `mod` / `extern crate` |
|
|
50
|
+
| Go | `.go` | `import` / import blocks |
|
|
51
|
+
| C / C++ | `.c` `.h` `.cpp` `.cc` `.cxx` `.hpp` `.hh` | `#include` (local + system) |
|
|
52
|
+
| C# | `.cs` | `using` namespaces |
|
|
53
|
+
| Java | `.java` | `import` / `import static` |
|
|
54
|
+
|
|
55
|
+
Health/journal still work for any monitored path. Parsers are **pragmatic regex** (not full cargo/`go.mod`/classpath/`-I` resolution). Prefer **lean `monitored_paths.txt`** on huge monorepos; raise dirty cap with `WIKIFIER_CHECK_CHANGES_MAX` (default 2000) only when needed.
|
|
44
56
|
|
|
45
57
|
## Why
|
|
46
58
|
|
|
@@ -90,7 +102,7 @@ MCP **Core 6** (start every session): `get_project_status`, `check_changes`, `ge
|
|
|
90
102
|
|
|
91
103
|
## What you get
|
|
92
104
|
|
|
93
|
-
- **Import analysis** — Python
|
|
105
|
+
- **Import analysis** — Python, JS/TS (ESM/CJS, barrels), Rust, Go, C/C++ includes, C# usings; per-edge confidence; name-routed barrel expansion for TS/JS
|
|
94
106
|
- **Incremental pipeline** — pure-Python `update-maps`: dirty parse → import cache → reverse deps → cycles → `library.md`
|
|
95
107
|
- **Selective agent work** — health + suggest bias to 🔴/🟡 only; ACS *actionable* low-conf excludes stdlib/external noise
|
|
96
108
|
- **Scale** — reverse index + barrel invalidation so one edit doesn’t re-scan the monorepo
|
|
@@ -21,10 +21,16 @@ pyproject.toml
|
|
|
21
21
|
./wikifier/mcp/__init__.py
|
|
22
22
|
./wikifier/mcp/server.py
|
|
23
23
|
./wikifier/parsers/__init__.py
|
|
24
|
+
./wikifier/parsers/_edge.py
|
|
24
25
|
./wikifier/parsers/bree.py
|
|
26
|
+
./wikifier/parsers/c_cpp.py
|
|
25
27
|
./wikifier/parsers/cdia.py
|
|
28
|
+
./wikifier/parsers/csharp.py
|
|
29
|
+
./wikifier/parsers/go_lang.py
|
|
30
|
+
./wikifier/parsers/java.py
|
|
26
31
|
./wikifier/parsers/javascript.py
|
|
27
32
|
./wikifier/parsers/python.py
|
|
33
|
+
./wikifier/parsers/rust.py
|
|
28
34
|
./wikifier/scripts/wikifier.bat
|
|
29
35
|
./wikifier/scripts/wikifier.ps1
|
|
30
36
|
./wikifier/scripts/wikifier.sh
|
|
@@ -34,6 +40,7 @@ tests/test_barrel_invalidation.py
|
|
|
34
40
|
tests/test_gap_closure.py
|
|
35
41
|
tests/test_health.py
|
|
36
42
|
tests/test_import_cache.py
|
|
43
|
+
tests/test_multi_lang_parsers.py
|
|
37
44
|
tests/test_parsers.py
|
|
38
45
|
tests/test_selftest_wrappers.py
|
|
39
46
|
wikifier/__init__.py
|
|
@@ -58,10 +65,16 @@ wikifier.egg-info/top_level.txt
|
|
|
58
65
|
wikifier/mcp/__init__.py
|
|
59
66
|
wikifier/mcp/server.py
|
|
60
67
|
wikifier/parsers/__init__.py
|
|
68
|
+
wikifier/parsers/_edge.py
|
|
61
69
|
wikifier/parsers/bree.py
|
|
70
|
+
wikifier/parsers/c_cpp.py
|
|
62
71
|
wikifier/parsers/cdia.py
|
|
72
|
+
wikifier/parsers/csharp.py
|
|
73
|
+
wikifier/parsers/go_lang.py
|
|
74
|
+
wikifier/parsers/java.py
|
|
63
75
|
wikifier/parsers/javascript.py
|
|
64
76
|
wikifier/parsers/python.py
|
|
77
|
+
wikifier/parsers/rust.py
|
|
65
78
|
wikifier/scripts/wikifier.bat
|
|
66
79
|
wikifier/scripts/wikifier.ps1
|
|
67
80
|
wikifier/scripts/wikifier.sh
|
|
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
|