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.
Files changed (54) hide show
  1. {wikifier-4.5.3/wikifier.egg-info → wikifier-4.5.4}/PKG-INFO +15 -3
  2. {wikifier-4.5.3 → wikifier-4.5.4}/README.md +14 -2
  3. {wikifier-4.5.3 → wikifier-4.5.4}/skills/run.md +2 -1
  4. wikifier-4.5.4/tests/test_multi_lang_parsers.py +104 -0
  5. {wikifier-4.5.3 → wikifier-4.5.4}/wikifier/__init__.py +1 -1
  6. {wikifier-4.5.3 → wikifier-4.5.4}/wikifier/cli.py +101 -13
  7. wikifier-4.5.4/wikifier/parsers/__init__.py +23 -0
  8. wikifier-4.5.4/wikifier/parsers/_edge.py +71 -0
  9. wikifier-4.5.4/wikifier/parsers/c_cpp.py +83 -0
  10. wikifier-4.5.4/wikifier/parsers/csharp.py +64 -0
  11. wikifier-4.5.4/wikifier/parsers/go_lang.py +87 -0
  12. wikifier-4.5.4/wikifier/parsers/java.py +64 -0
  13. wikifier-4.5.4/wikifier/parsers/rust.py +133 -0
  14. {wikifier-4.5.3 → wikifier-4.5.4/wikifier.egg-info}/PKG-INFO +15 -3
  15. {wikifier-4.5.3 → wikifier-4.5.4}/wikifier.egg-info/SOURCES.txt +13 -0
  16. wikifier-4.5.3/wikifier/parsers/__init__.py +0 -0
  17. {wikifier-4.5.3 → wikifier-4.5.4}/CONTRIBUTING.md +0 -0
  18. {wikifier-4.5.3 → wikifier-4.5.4}/LICENSE +0 -0
  19. {wikifier-4.5.3 → wikifier-4.5.4}/MANIFEST.in +0 -0
  20. {wikifier-4.5.3 → wikifier-4.5.4}/diagnostics.html +0 -0
  21. {wikifier-4.5.3 → wikifier-4.5.4}/docs/spec.md +0 -0
  22. {wikifier-4.5.3 → wikifier-4.5.4}/index.html +0 -0
  23. {wikifier-4.5.3 → wikifier-4.5.4}/pyproject.toml +0 -0
  24. {wikifier-4.5.3 → wikifier-4.5.4}/setup.cfg +0 -0
  25. {wikifier-4.5.3 → wikifier-4.5.4}/tests/test_barrel_invalidation.py +0 -0
  26. {wikifier-4.5.3 → wikifier-4.5.4}/tests/test_gap_closure.py +0 -0
  27. {wikifier-4.5.3 → wikifier-4.5.4}/tests/test_health.py +0 -0
  28. {wikifier-4.5.3 → wikifier-4.5.4}/tests/test_import_cache.py +0 -0
  29. {wikifier-4.5.3 → wikifier-4.5.4}/tests/test_parsers.py +0 -0
  30. {wikifier-4.5.3 → wikifier-4.5.4}/tests/test_selftest_wrappers.py +0 -0
  31. {wikifier-4.5.3 → wikifier-4.5.4}/wikifier/__main__.py +0 -0
  32. {wikifier-4.5.3 → wikifier-4.5.4}/wikifier/contracts.py +0 -0
  33. {wikifier-4.5.3 → wikifier-4.5.4}/wikifier/daemon.py +0 -0
  34. {wikifier-4.5.3 → wikifier-4.5.4}/wikifier/diagnostics.py +0 -0
  35. {wikifier-4.5.3 → wikifier-4.5.4}/wikifier/health.py +0 -0
  36. {wikifier-4.5.3 → wikifier-4.5.4}/wikifier/import_cache.py +0 -0
  37. {wikifier-4.5.3 → wikifier-4.5.4}/wikifier/index.html +0 -0
  38. {wikifier-4.5.3 → wikifier-4.5.4}/wikifier/library.py +0 -0
  39. {wikifier-4.5.3 → wikifier-4.5.4}/wikifier/locking.py +0 -0
  40. {wikifier-4.5.3 → wikifier-4.5.4}/wikifier/mcp/__init__.py +0 -0
  41. {wikifier-4.5.3 → wikifier-4.5.4}/wikifier/mcp/server.py +0 -0
  42. {wikifier-4.5.3 → wikifier-4.5.4}/wikifier/parsers/bree.py +0 -0
  43. {wikifier-4.5.3 → wikifier-4.5.4}/wikifier/parsers/cdia.py +0 -0
  44. {wikifier-4.5.3 → wikifier-4.5.4}/wikifier/parsers/javascript.py +0 -0
  45. {wikifier-4.5.3 → wikifier-4.5.4}/wikifier/parsers/python.py +0 -0
  46. {wikifier-4.5.3 → wikifier-4.5.4}/wikifier/resolution.py +0 -0
  47. {wikifier-4.5.3 → wikifier-4.5.4}/wikifier/scripts/wikifier.bat +0 -0
  48. {wikifier-4.5.3 → wikifier-4.5.4}/wikifier/scripts/wikifier.ps1 +0 -0
  49. {wikifier-4.5.3 → wikifier-4.5.4}/wikifier/scripts/wikifier.sh +0 -0
  50. {wikifier-4.5.3 → wikifier-4.5.4}/wikifier/serve.py +0 -0
  51. {wikifier-4.5.3 → wikifier-4.5.4}/wikifier.egg-info/dependency_links.txt +0 -0
  52. {wikifier-4.5.3 → wikifier-4.5.4}/wikifier.egg-info/entry_points.txt +0 -0
  53. {wikifier-4.5.3 → wikifier-4.5.4}/wikifier.egg-info/requires.txt +0 -0
  54. {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
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** cover **Python + JavaScript/TypeScript** (barrels, cycles, incremental updates). Other languages can still use health/journal/watch; they won’t get full import graphs until a parser exists.
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 + JS/TS (ESM, CommonJS, dynamic imports, path aliases, package exports); per-edge confidence; name-routed barrel expansion (precise leaves, not edge explosion)
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** cover **Python + JavaScript/TypeScript** (barrels, cycles, incremental updates). Other languages can still use health/journal/watch; they won’t get full import graphs until a parser exists.
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 + JS/TS (ESM, CommonJS, dynamic imports, path aliases, package exports); per-edge confidence; name-routed barrel expansion (precise leaves, not edge explosion)
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 + JS/TS. journal/pending = audit queue, not Jira.
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()
@@ -71,4 +71,4 @@ from .contracts import (
71
71
  compute_acs_confidence,
72
72
  )
73
73
 
74
- __version__ = "4.5.3"
74
+ __version__ = "4.5.4"
@@ -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
- exts = (".py", ".js", ".ts", ".jsx", ".tsx")
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
- if low.endswith((".js", ".ts", ".jsx", ".tsx")):
540
- edges = js_parser.parse_javascript_imports(fstr) or []
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 (dirty or [])[:200]: # bounded for skeleton safety at scale
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
- for g in (_health_mod.find_ghost_entries(root) or [])[:50]:
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
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** cover **Python + JavaScript/TypeScript** (barrels, cycles, incremental updates). Other languages can still use health/journal/watch; they won’t get full import graphs until a parser exists.
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 + JS/TS (ESM, CommonJS, dynamic imports, path aliases, package exports); per-edge confidence; name-routed barrel expansion (precise leaves, not edge explosion)
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