wikifier 4.5.1__tar.gz → 4.5.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.
Files changed (47) hide show
  1. wikifier-4.5.2/PKG-INFO +154 -0
  2. wikifier-4.5.2/README.md +122 -0
  3. {wikifier-4.5.1 → wikifier-4.5.2}/skills/run.md +4 -2
  4. {wikifier-4.5.1 → wikifier-4.5.2}/wikifier/__init__.py +1 -1
  5. {wikifier-4.5.1 → wikifier-4.5.2}/wikifier/cli.py +42 -2
  6. {wikifier-4.5.1 → wikifier-4.5.2}/wikifier/mcp/server.py +58 -30
  7. {wikifier-4.5.1 → wikifier-4.5.2}/wikifier/parsers/javascript.py +15 -1
  8. {wikifier-4.5.1 → wikifier-4.5.2}/wikifier/scripts/wikifier.sh +42 -9
  9. wikifier-4.5.2/wikifier.egg-info/PKG-INFO +154 -0
  10. wikifier-4.5.1/PKG-INFO +0 -169
  11. wikifier-4.5.1/README.md +0 -137
  12. wikifier-4.5.1/wikifier.egg-info/PKG-INFO +0 -169
  13. {wikifier-4.5.1 → wikifier-4.5.2}/CONTRIBUTING.md +0 -0
  14. {wikifier-4.5.1 → wikifier-4.5.2}/LICENSE +0 -0
  15. {wikifier-4.5.1 → wikifier-4.5.2}/MANIFEST.in +0 -0
  16. {wikifier-4.5.1 → wikifier-4.5.2}/diagnostics.html +0 -0
  17. {wikifier-4.5.1 → wikifier-4.5.2}/docs/spec.md +0 -0
  18. {wikifier-4.5.1 → wikifier-4.5.2}/index.html +0 -0
  19. {wikifier-4.5.1 → wikifier-4.5.2}/pyproject.toml +0 -0
  20. {wikifier-4.5.1 → wikifier-4.5.2}/setup.cfg +0 -0
  21. {wikifier-4.5.1 → wikifier-4.5.2}/tests/test_barrel_invalidation.py +0 -0
  22. {wikifier-4.5.1 → wikifier-4.5.2}/tests/test_health.py +0 -0
  23. {wikifier-4.5.1 → wikifier-4.5.2}/tests/test_import_cache.py +0 -0
  24. {wikifier-4.5.1 → wikifier-4.5.2}/tests/test_parsers.py +0 -0
  25. {wikifier-4.5.1 → wikifier-4.5.2}/wikifier/__main__.py +0 -0
  26. {wikifier-4.5.1 → wikifier-4.5.2}/wikifier/contracts.py +0 -0
  27. {wikifier-4.5.1 → wikifier-4.5.2}/wikifier/daemon.py +0 -0
  28. {wikifier-4.5.1 → wikifier-4.5.2}/wikifier/diagnostics.py +0 -0
  29. {wikifier-4.5.1 → wikifier-4.5.2}/wikifier/health.py +0 -0
  30. {wikifier-4.5.1 → wikifier-4.5.2}/wikifier/import_cache.py +0 -0
  31. {wikifier-4.5.1 → wikifier-4.5.2}/wikifier/index.html +0 -0
  32. {wikifier-4.5.1 → wikifier-4.5.2}/wikifier/library.py +0 -0
  33. {wikifier-4.5.1 → wikifier-4.5.2}/wikifier/locking.py +0 -0
  34. {wikifier-4.5.1 → wikifier-4.5.2}/wikifier/mcp/__init__.py +0 -0
  35. {wikifier-4.5.1 → wikifier-4.5.2}/wikifier/parsers/__init__.py +0 -0
  36. {wikifier-4.5.1 → wikifier-4.5.2}/wikifier/parsers/bree.py +0 -0
  37. {wikifier-4.5.1 → wikifier-4.5.2}/wikifier/parsers/cdia.py +0 -0
  38. {wikifier-4.5.1 → wikifier-4.5.2}/wikifier/parsers/python.py +0 -0
  39. {wikifier-4.5.1 → wikifier-4.5.2}/wikifier/resolution.py +0 -0
  40. {wikifier-4.5.1 → wikifier-4.5.2}/wikifier/scripts/wikifier.bat +0 -0
  41. {wikifier-4.5.1 → wikifier-4.5.2}/wikifier/scripts/wikifier.ps1 +0 -0
  42. {wikifier-4.5.1 → wikifier-4.5.2}/wikifier/serve.py +0 -0
  43. {wikifier-4.5.1 → wikifier-4.5.2}/wikifier.egg-info/SOURCES.txt +0 -0
  44. {wikifier-4.5.1 → wikifier-4.5.2}/wikifier.egg-info/dependency_links.txt +0 -0
  45. {wikifier-4.5.1 → wikifier-4.5.2}/wikifier.egg-info/entry_points.txt +0 -0
  46. {wikifier-4.5.1 → wikifier-4.5.2}/wikifier.egg-info/requires.txt +0 -0
  47. {wikifier-4.5.1 → wikifier-4.5.2}/wikifier.egg-info/top_level.txt +0 -0
@@ -0,0 +1,154 @@
1
+ Metadata-Version: 2.4
2
+ Name: wikifier
3
+ Version: 4.5.2
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
+ Author-email: Aron Amos <aron@example.com>
6
+ Maintainer: Aron Amos
7
+ License: MIT
8
+ Project-URL: Homepage, https://github.com/IronAdamant/wikifier
9
+ Project-URL: Repository, https://github.com/IronAdamant/wikifier
10
+ Project-URL: Documentation, https://github.com/IronAdamant/wikifier#readme
11
+ Project-URL: Bug Tracker, https://github.com/IronAdamant/wikifier/issues
12
+ Keywords: wiki,documentation,llm,agent,mcp,codebase,health-matrix,zero-dependency,shell,token-efficient,autonomous,record-change,mark-green,agent-wiki,llm-tools,dependency-graph,monorepo
13
+ Classifier: Development Status :: 4 - Beta
14
+ Classifier: Intended Audience :: Developers
15
+ Classifier: License :: OSI Approved :: MIT License
16
+ Classifier: Programming Language :: Python :: 3
17
+ Classifier: Programming Language :: Python :: 3.8
18
+ Classifier: Programming Language :: Python :: 3.9
19
+ Classifier: Programming Language :: Python :: 3.10
20
+ Classifier: Programming Language :: Python :: 3.11
21
+ Classifier: Programming Language :: Python :: 3.12
22
+ Classifier: Operating System :: OS Independent
23
+ Classifier: Topic :: Software Development :: Documentation
24
+ Classifier: Topic :: Software Development :: Version Control
25
+ Classifier: Topic :: Text Processing :: Markup :: Markdown
26
+ Requires-Python: >=3.8
27
+ Description-Content-Type: text/markdown
28
+ License-File: LICENSE
29
+ Provides-Extra: mcp
30
+ Requires-Dist: mcp>=1.0.0; extra == "mcp"
31
+ Dynamic: license-file
32
+
33
+ # Wikifier
34
+
35
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
36
+ [![PyPI version](https://img.shields.io/pypi/v/wikifier.svg)](https://pypi.org/project/wikifier/)
37
+ [![GitHub Stars](https://img.shields.io/github/stars/IronAdamant/wikifier?style=social)](https://github.com/IronAdamant/wikifier/stargazers)
38
+
39
+ **A zero-dependency codebase wiki for AI agents** — token-efficient maps so LLMs look things up instead of re-reading full sources.
40
+
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
+
43
+ Works from small scripts to large monorepos (Python + JavaScript/TypeScript imports, barrels, cycles, incremental updates).
44
+
45
+ ## Why
46
+
47
+ Context windows are finite. Re-reading a large file to answer “what is this and who depends on it?” wastes tokens.
48
+
49
+ Wikifier keeps a small set of artifacts agents can query:
50
+
51
+ | Artifact | Role |
52
+ |----------|------|
53
+ | `file_health.md` | 🟢 / 🟡 / 🔴 matrix — what to trust, what to fix first |
54
+ | `library.md` | File tree, Mermaid dependency map, import tables, cycles + confidence |
55
+ | `*.wiki.md` | Short per-file “what this is for” notes (agent-maintained) |
56
+ | `journal/` | Semantic *why* trail from `record-change` |
57
+
58
+ Lookup beats re-ingest. That’s the whole idea.
59
+
60
+ ## Quick start
61
+
62
+ ```bash
63
+ pip install wikifier # pure Python stdlib core — no runtime deps
64
+ pip install wikifier[mcp] # optional Model Context Protocol (MCP) server
65
+
66
+ cd /path/to/your/project
67
+ wikifier init
68
+ wikifier update-maps # dependency graph + library.md
69
+ wikifier check-changes
70
+ wikifier health --summary
71
+ ```
72
+
73
+ Always set an explicit root for external trees:
74
+
75
+ ```bash
76
+ WIKIFIER_PROJECT_ROOT=/abs/path/to/project wikifier check-changes
77
+ ```
78
+
79
+ ## Agent loop
80
+
81
+ Full protocol: [`skills/run.md`](skills/run.md).
82
+
83
+ ```bash
84
+ wikifier check-changes
85
+ # prioritize 🔴 then 🟡 in file_health.md / pending_updates.md
86
+ # ... edit source ...
87
+ wikifier record-change "path/file.py" "why this changed" # required
88
+ # ... refresh that file’s wiki summary ...
89
+ wikifier mark-green "path/file.py"
90
+ wikifier update-maps # if imports/structure changed
91
+ ```
92
+
93
+ `record-change` logs intent a git diff can’t reconstruct for the next agent (or human).
94
+
95
+ ## What you get
96
+
97
+ - **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)
98
+ - **Incremental pipeline** — pure-Python `update-maps`: dirty parse → import cache → reverse deps → cycles → `library.md`
99
+ - **Scale** — reverse index + barrel invalidation so one edit doesn’t re-scan the monorepo
100
+ - **MCP tools** — optional server for Claude, Cursor, Cline, and other MCP clients (`get_project_status`, `get_dependencies`, `get_file_wiki`, `record_change`, …)
101
+ - **Zero core dependencies** — stdlib only; forks can add their own stack on top
102
+
103
+ ## Performance (measured)
104
+
105
+ | Project | Scale | Full `update-maps` |
106
+ |---------|-------|--------------------|
107
+ | llama_index | ~3.8k Python files | ~8.5s |
108
+ | Babylon.js | ~3.9k TS files, barrel-heavy | ~4.5 min (scoped re-runs ~80s) |
109
+ | Large trees (e.g. LLVM-scale) | tens of thousands of files | candidate scan in seconds |
110
+
111
+ Tests: `python -m unittest discover tests` (stdlib only).
112
+
113
+ ## Commands
114
+
115
+ | Command | Purpose |
116
+ |---------|---------|
117
+ | `wikifier init [--target DIR]` | Bootstrap project + human `index.html` |
118
+ | `wikifier check-changes` | Incremental scan → health / pending |
119
+ | `wikifier record-change <file> "reason"` | Log *why* (required after edits) |
120
+ | `wikifier mark-green <file>` | Mark wiki current |
121
+ | `wikifier update-maps [--directory=src/] [--max-files=N]` | Rebuild graph + `library.md` |
122
+ | `wikifier health [--summary\|--json]` | Health matrix (machine-friendly flags) |
123
+ | `wikifier cycles` | Circular deps + break hints |
124
+ | `wikifier monitor` / `daemon` | Background maintenance |
125
+ | `wikifier serve` | Localhost dashboard with Run/Stop |
126
+
127
+ Library: `from wikifier import check_changes, record_change, mark_green, health, update_maps`.
128
+
129
+ ## MCP
130
+
131
+ ```bash
132
+ WIKIFIER_PROJECT_ROOT=/abs/path/to/project wikifier-mcp
133
+ # or: python3 -m wikifier.mcp.server
134
+ ```
135
+
136
+ Setup and tool list: [`wikifier/mcp/README.md`](wikifier/mcp/README.md).
137
+
138
+ ## Human dashboard (secondary)
139
+
140
+ ![Wikifier dashboard — file tree, health pills, local Run/Stop](https://raw.githubusercontent.com/IronAdamant/wikifier/main/screenshot/front_page_review.png)
141
+
142
+ `wikifier init` drops a single `index.html`. Prefer **`wikifier serve`** (e.g. http://localhost:8787/index.html) — `file://` can’t load project files. The markdown artifacts and CLI/MCP tools stay the source of truth; the UI is a read-only window.
143
+
144
+ ## Scope
145
+
146
+ **In:** agent-maintained codebase wiki, dependency intelligence, token-saving lookup for LLMs and coding agents.
147
+ **Out:** general human documentation systems, IDE plugins, “docs for everyone” product growth.
148
+
149
+ ## Links
150
+
151
+ - [PyPI](https://pypi.org/project/wikifier/) · [GitHub](https://github.com/IronAdamant/wikifier)
152
+ - Agent protocol: [`skills/run.md`](skills/run.md)
153
+ - Changelog: [`CHANGELOG.md`](CHANGELOG.md)
154
+ - Dogfood notes: `Findings/`
@@ -0,0 +1,122 @@
1
+ # Wikifier
2
+
3
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
4
+ [![PyPI version](https://img.shields.io/pypi/v/wikifier.svg)](https://pypi.org/project/wikifier/)
5
+ [![GitHub Stars](https://img.shields.io/github/stars/IronAdamant/wikifier?style=social)](https://github.com/IronAdamant/wikifier/stargazers)
6
+
7
+ **A zero-dependency codebase wiki for AI agents** — token-efficient maps so LLMs look things up instead of re-reading full sources.
8
+
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
+
11
+ Works from small scripts to large monorepos (Python + JavaScript/TypeScript imports, barrels, cycles, incremental updates).
12
+
13
+ ## Why
14
+
15
+ Context windows are finite. Re-reading a large file to answer “what is this and who depends on it?” wastes tokens.
16
+
17
+ Wikifier keeps a small set of artifacts agents can query:
18
+
19
+ | Artifact | Role |
20
+ |----------|------|
21
+ | `file_health.md` | 🟢 / 🟡 / 🔴 matrix — what to trust, what to fix first |
22
+ | `library.md` | File tree, Mermaid dependency map, import tables, cycles + confidence |
23
+ | `*.wiki.md` | Short per-file “what this is for” notes (agent-maintained) |
24
+ | `journal/` | Semantic *why* trail from `record-change` |
25
+
26
+ Lookup beats re-ingest. That’s the whole idea.
27
+
28
+ ## Quick start
29
+
30
+ ```bash
31
+ pip install wikifier # pure Python stdlib core — no runtime deps
32
+ pip install wikifier[mcp] # optional Model Context Protocol (MCP) server
33
+
34
+ cd /path/to/your/project
35
+ wikifier init
36
+ wikifier update-maps # dependency graph + library.md
37
+ wikifier check-changes
38
+ wikifier health --summary
39
+ ```
40
+
41
+ Always set an explicit root for external trees:
42
+
43
+ ```bash
44
+ WIKIFIER_PROJECT_ROOT=/abs/path/to/project wikifier check-changes
45
+ ```
46
+
47
+ ## Agent loop
48
+
49
+ Full protocol: [`skills/run.md`](skills/run.md).
50
+
51
+ ```bash
52
+ wikifier check-changes
53
+ # prioritize 🔴 then 🟡 in file_health.md / pending_updates.md
54
+ # ... edit source ...
55
+ wikifier record-change "path/file.py" "why this changed" # required
56
+ # ... refresh that file’s wiki summary ...
57
+ wikifier mark-green "path/file.py"
58
+ wikifier update-maps # if imports/structure changed
59
+ ```
60
+
61
+ `record-change` logs intent a git diff can’t reconstruct for the next agent (or human).
62
+
63
+ ## What you get
64
+
65
+ - **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)
66
+ - **Incremental pipeline** — pure-Python `update-maps`: dirty parse → import cache → reverse deps → cycles → `library.md`
67
+ - **Scale** — reverse index + barrel invalidation so one edit doesn’t re-scan the monorepo
68
+ - **MCP tools** — optional server for Claude, Cursor, Cline, and other MCP clients (`get_project_status`, `get_dependencies`, `get_file_wiki`, `record_change`, …)
69
+ - **Zero core dependencies** — stdlib only; forks can add their own stack on top
70
+
71
+ ## Performance (measured)
72
+
73
+ | Project | Scale | Full `update-maps` |
74
+ |---------|-------|--------------------|
75
+ | llama_index | ~3.8k Python files | ~8.5s |
76
+ | Babylon.js | ~3.9k TS files, barrel-heavy | ~4.5 min (scoped re-runs ~80s) |
77
+ | Large trees (e.g. LLVM-scale) | tens of thousands of files | candidate scan in seconds |
78
+
79
+ Tests: `python -m unittest discover tests` (stdlib only).
80
+
81
+ ## Commands
82
+
83
+ | Command | Purpose |
84
+ |---------|---------|
85
+ | `wikifier init [--target DIR]` | Bootstrap project + human `index.html` |
86
+ | `wikifier check-changes` | Incremental scan → health / pending |
87
+ | `wikifier record-change <file> "reason"` | Log *why* (required after edits) |
88
+ | `wikifier mark-green <file>` | Mark wiki current |
89
+ | `wikifier update-maps [--directory=src/] [--max-files=N]` | Rebuild graph + `library.md` |
90
+ | `wikifier health [--summary\|--json]` | Health matrix (machine-friendly flags) |
91
+ | `wikifier cycles` | Circular deps + break hints |
92
+ | `wikifier monitor` / `daemon` | Background maintenance |
93
+ | `wikifier serve` | Localhost dashboard with Run/Stop |
94
+
95
+ Library: `from wikifier import check_changes, record_change, mark_green, health, update_maps`.
96
+
97
+ ## MCP
98
+
99
+ ```bash
100
+ WIKIFIER_PROJECT_ROOT=/abs/path/to/project wikifier-mcp
101
+ # or: python3 -m wikifier.mcp.server
102
+ ```
103
+
104
+ Setup and tool list: [`wikifier/mcp/README.md`](wikifier/mcp/README.md).
105
+
106
+ ## Human dashboard (secondary)
107
+
108
+ ![Wikifier dashboard — file tree, health pills, local Run/Stop](https://raw.githubusercontent.com/IronAdamant/wikifier/main/screenshot/front_page_review.png)
109
+
110
+ `wikifier init` drops a single `index.html`. Prefer **`wikifier serve`** (e.g. http://localhost:8787/index.html) — `file://` can’t load project files. The markdown artifacts and CLI/MCP tools stay the source of truth; the UI is a read-only window.
111
+
112
+ ## Scope
113
+
114
+ **In:** agent-maintained codebase wiki, dependency intelligence, token-saving lookup for LLMs and coding agents.
115
+ **Out:** general human documentation systems, IDE plugins, “docs for everyone” product growth.
116
+
117
+ ## Links
118
+
119
+ - [PyPI](https://pypi.org/project/wikifier/) · [GitHub](https://github.com/IronAdamant/wikifier)
120
+ - Agent protocol: [`skills/run.md`](skills/run.md)
121
+ - Changelog: [`CHANGELOG.md`](CHANGELOG.md)
122
+ - Dogfood notes: `Findings/`
@@ -2,11 +2,13 @@
2
2
 
3
3
  **Formerly "Wikifier Skills & Commands". This is the authoritative, versioned specification for agent behavior when using Wikifier.**
4
4
 
5
- **Version**: v0.6 (v4.2.0 real-pipeline + reliability update)
6
- **Date**: 2026-06-10
5
+ **Version**: v0.6 (v4.2.0 real-pipeline + reliability update; package current **4.5.x**)
6
+ **Date**: 2026-06-10 (package notes refreshed 2026-07-09)
7
7
  **Status**: Active. Supersedes v0.5.
8
8
  **See also**: `README.md` (Intended Use section: strictly agent-to-agent wiki for token saving), M5-Dogfood-Assessment-Report.md, Findings/2026-06-10-Dogfood-Refactor-Validation.md, and the library in wikifier/.
9
9
 
10
+ **Package 4.5.x notes (additive; protocol still v0.6):** File Tree as primary structure view in `library.md` + dashboard pan/zoom graph; `wikifier serve` with real Run/Stop controls; Python parser no longer leaks `#`-comment text into edges; MCP `get_project_status` / `get_files_needing_attention` use library+emoji health statuses (not legacy `[GREEN]` tags). Full history: `CHANGELOG.md`.
11
+
10
12
  **v0.6 migration notes (packages v4.2.0–v4.3.0)** — changes are additive or strictly-better defaults; v0.5 agent behavior keeps working:
11
13
  - `update-maps` (CLI) now runs the **pure-Python full pipeline**: every dirty file is parsed in-process, the canonical per-file cache is persisted, reverse deps + cycles + ACS are computed, and `library.md` is regenerated atomically. `--python-primary` is accepted but redundant; the in-shell first-pass was retired entirely (wikifier.sh's update-maps delegates to this pipeline; `--sh`/`--legacy-sh` are deprecated no-ops). Scoping is explicit via `--directory=`/`--max-files=` and reported in the result (`files_skipped`) — there are no silent caps.
12
14
  - `run_full_update`/`update_maps` returns gained additive fields: `files_parsed` (actual), `parseable_files`, `edges_persisted`, `files_skipped`, `cycles`, `library`. Tolerate additive fields as always.
@@ -61,4 +61,4 @@ from .contracts import (
61
61
  compute_acs_confidence,
62
62
  )
63
63
 
64
- __version__ = "4.5.1"
64
+ __version__ = "4.5.2"
@@ -683,8 +683,17 @@ def main():
683
683
  filtered_argv.append(arg)
684
684
  i += 1
685
685
 
686
- # Micro-step 2 (A2 CLI wiring): detect streaming UX flags after parsing
687
- a2_flag_markers = ("--stream", "--stream=", "--resume", "--resume_token", "--max-time", "--max_time", "--progress", "--summary", "--partial", "--dir=", "--directory=", "--max-files", "--max_files", "--format", "--format=summary", "--format=stream")
686
+ # Micro-step 2 (A2 CLI wiring): detect streaming UX flags after parsing.
687
+ # --max-files / --directory are normal run_full_update scoping options and
688
+ # must NOT force the stream facade (agents expect batch JSON + files_skipped).
689
+ a2_flag_markers = (
690
+ "--stream", "--stream=",
691
+ "--resume", "--resume_token",
692
+ "--max-time", "--max_time",
693
+ "--progress",
694
+ "--partial",
695
+ "--format=stream",
696
+ )
688
697
  has_a2_ux_flags = any(any(a == m or a.startswith(m) for m in a2_flag_markers) for a in filtered_argv)
689
698
 
690
699
  if project_root:
@@ -710,6 +719,37 @@ def main():
710
719
  print(f"[wikifier] health --{fmt} failed: {e}", file=sys.stderr)
711
720
  return 1
712
721
 
722
+ # Mandatory workflow commands: pure-Python primary (updates file_health.json + md).
723
+ # Shell upsert_health only patches the .md and on macOS `realpath --relative-to`
724
+ # is unavailable, so it used to store absolute paths as health keys.
725
+ if filtered_argv:
726
+ _cmd0 = filtered_argv[0].replace("_", "-")
727
+ _args = filtered_argv[1:]
728
+ try:
729
+ if _cmd0 == "check-changes":
730
+ res = check_changes(project_root=project_root)
731
+ n = int(res.get("changes_detected") or 0)
732
+ print("[wikifier] Running incremental change detection...")
733
+ if n:
734
+ print(f"[wikifier] Detected {n} changed file(s). See pending_updates.md and file_health.md.")
735
+ else:
736
+ print("[wikifier] No new changes detected.")
737
+ if res.get("message"):
738
+ print(res["message"])
739
+ return 0 if res.get("success", True) else 1
740
+ if _cmd0 == "record-change" and len(_args) >= 2:
741
+ res = record_change(_args[0], " ".join(_args[1:]), project_root=project_root)
742
+ print(res.get("message") or res)
743
+ return 0 if res.get("success") else 1
744
+ if _cmd0 == "mark-green" and len(_args) >= 1:
745
+ reason = " ".join(_args[1:]) if len(_args) > 1 else ""
746
+ res = mark_green(_args[0], reason, project_root=project_root)
747
+ print(res.get("message") or res)
748
+ return 0 if res.get("success") else 1
749
+ except Exception as e:
750
+ print(f"[wikifier] Python-primary {_cmd0} failed: {e}", file=sys.stderr)
751
+ return 1
752
+
713
753
  # Human sub-project: ensure dashboards are in the target (for MCP + human investigation)
714
754
  # index.html = clean human view (chart + files + descriptions + copies); diagnostics.html for technical depth. Works alongside agent MCP/CLI use. No effect on agent SSOT or tools.
715
755
  if project_root:
@@ -643,7 +643,8 @@ def health(
643
643
  root = _get_effective_root(project_root)
644
644
 
645
645
  try:
646
- import wikifier.health as health_module
646
+ import importlib
647
+ health_module = importlib.import_module("wikifier.health")
647
648
 
648
649
  if format == "summary":
649
650
  summary = health_module.get_summary(root, directory)
@@ -744,7 +745,8 @@ def list_healable_stubs(
744
745
  """
745
746
  root = _get_effective_root(project_root)
746
747
  try:
747
- import wikifier.health as health_module
748
+ import importlib
749
+ health_module = importlib.import_module("wikifier.health")
748
750
  candidates = health_module.get_healable_stubs(
749
751
  root, min_wiki_length=min_wiki_length, directory=directory
750
752
  )
@@ -793,7 +795,8 @@ def heal_stubs(
793
795
  """
794
796
  root = _get_effective_root(project_root)
795
797
  try:
796
- import wikifier.health as health_module
798
+ import importlib
799
+ health_module = importlib.import_module("wikifier.health")
797
800
  count = health_module.heal_outdated_stubs(
798
801
  root, min_wiki_length=min_wiki_length, dry_run=dry_run
799
802
  )
@@ -1571,13 +1574,15 @@ def get_files_needing_attention(
1571
1574
  root = _get_effective_root(project_root)
1572
1575
 
1573
1576
  try:
1574
- import wikifier.health as health_module
1577
+ import importlib
1578
+ health_module = importlib.import_module("wikifier.health")
1575
1579
 
1580
+ # Health matrix stores emoji statuses (🟢/🟡/🔴), not [RED]/[YELLOW] tags.
1576
1581
  status_filter = None
1577
1582
  if status == "red":
1578
- status_filter = "[RED]"
1583
+ status_filter = "🔴"
1579
1584
  elif status == "yellow":
1580
- status_filter = "[YELLOW]"
1585
+ status_filter = "🟡"
1581
1586
 
1582
1587
  files = health_module.get_files_needing_attention(root, status_filter, directory)
1583
1588
 
@@ -1604,25 +1609,30 @@ def get_files_needing_attention(
1604
1609
  if not files:
1605
1610
  return "No files currently need attention."
1606
1611
 
1607
- red = [f for f in files if "[RED]" in f] # This won't work well since we only have paths
1608
- # Better: we don't have status here easily. Let's just list them.
1609
1612
  return "Files needing attention:\n" + "\n".join(f"- {f}" for f in files)
1610
1613
 
1611
1614
  except Exception:
1612
- # Fallback
1615
+ # Library fallback — avoid shell text parsing ([RED] tags vs emoji).
1613
1616
  root = _get_effective_root(project_root)
1614
- output = _run_wikifier_command("health", root=root)
1615
- lines = []
1616
- for line in output.splitlines():
1617
- if "[RED]" in line or "[YELLOW]" in line:
1618
- if directory and not line.strip().startswith(f"| {directory}"):
1619
- continue
1620
- if status == "red" and "[RED]" not in line:
1621
- continue
1622
- if status == "yellow" and "[YELLOW]" not in line:
1623
- continue
1624
- lines.append(line.strip())
1625
- return "\n".join(lines) if lines else "No files currently need attention."
1617
+ try:
1618
+ import importlib
1619
+ health_module = importlib.import_module("wikifier.health")
1620
+ status_filter = "🔴" if status == "red" else ("🟡" if status == "yellow" else None)
1621
+ files = health_module.get_files_needing_attention(root, status_filter, directory)
1622
+ if format == "json":
1623
+ return {
1624
+ "project_root": str(root),
1625
+ "directory": directory or ".",
1626
+ "status_filter": status,
1627
+ "files": files,
1628
+ "count": len(files),
1629
+ "acs_low_conf_context": None,
1630
+ }
1631
+ if not files:
1632
+ return "No files currently need attention."
1633
+ return "Files needing attention:\n" + "\n".join(f"- {f}" for f in files)
1634
+ except Exception:
1635
+ return "No files currently need attention."
1626
1636
 
1627
1637
 
1628
1638
  @mcp.tool()
@@ -1638,7 +1648,8 @@ def get_project_status(
1638
1648
  root = _get_effective_root(project_root)
1639
1649
 
1640
1650
  try:
1641
- import wikifier.health as health_module
1651
+ import importlib
1652
+ health_module = importlib.import_module("wikifier.health")
1642
1653
  summary = health_module.get_summary(root, directory)
1643
1654
  pending = _read_file_safe("pending_updates.md", root=root)
1644
1655
 
@@ -1763,19 +1774,35 @@ Pending updates: {pending_count}
1763
1774
  Use get_files_needing_attention() for the actual list. Use get_cycles(analysis=True) + get_dependencies(format="json") for ACS confidence_explanation Recommendations."""
1764
1775
 
1765
1776
  except Exception:
1766
- # Fallback to shell + text parsing
1777
+ # Prefer library summary over shell text parsing. Live health text uses
1778
+ # emoji (🟢/🟡/🔴); counting legacy [GREEN]/[YELLOW]/[RED] tags always
1779
+ # yielded zeros and lied to agents.
1767
1780
  root = _get_effective_root(project_root)
1768
- health = _run_wikifier_command("health", root=root)
1769
1781
  pending = _read_file_safe("pending_updates.md", root=root)
1770
-
1771
- red = health.count("[RED]")
1772
- yellow = health.count("[YELLOW]")
1773
- green = health.count("[GREEN]")
1774
1782
  pending_count = len([l for l in pending.splitlines() if l.strip() and not l.startswith("#")])
1783
+ green = yellow = red = total = 0
1784
+ try:
1785
+ import importlib
1786
+ health_module = importlib.import_module("wikifier.health")
1787
+ summary = health_module.get_summary(root, directory)
1788
+ total = int(summary.get("total", 0) or 0)
1789
+ green = int(summary.get("green", 0) or 0)
1790
+ yellow = int(summary.get("yellow", 0) or 0)
1791
+ red = int(summary.get("red", 0) or 0)
1792
+ except Exception:
1793
+ # Last resort: emoji-aware (+ legacy tag) counts from health text.
1794
+ try:
1795
+ health_text = _run_wikifier_command("health", root=root)
1796
+ green = health_text.count("🟢") + health_text.count("[GREEN]")
1797
+ yellow = health_text.count("🟡") + health_text.count("[YELLOW]")
1798
+ red = health_text.count("🔴") + health_text.count("[RED]")
1799
+ total = green + yellow + red
1800
+ except Exception:
1801
+ pass
1775
1802
 
1776
1803
  if format == "json":
1777
1804
  return ProjectHealthSummary(
1778
- total_files=green + yellow + red,
1805
+ total_files=total or (green + yellow + red),
1779
1806
  green=green,
1780
1807
  yellow=yellow,
1781
1808
  red=red,
@@ -1884,7 +1911,8 @@ def suggest_next_actions(
1884
1911
  root = _get_effective_root(project_root)
1885
1912
 
1886
1913
  try:
1887
- import wikifier.health as health_module
1914
+ import importlib
1915
+ health_module = importlib.import_module("wikifier.health")
1888
1916
  summary = health_module.get_summary(root, directory)
1889
1917
  red = summary["red"]
1890
1918
  yellow = summary["yellow"]
@@ -938,7 +938,21 @@ def _follow_reexports(
938
938
  if target_module.startswith('.'):
939
939
  level = len(re.match(r'\.+', target_module).group())
940
940
  resolved = _resolve_relative_import(current_file, target_module, level)
941
- resolved_path = _try_resolve_relative_path(current_file, target_module)
941
+ # Prefer central_resolve directly (R4); avoid the deprecated shim on the
942
+ # hot path so tests/production do not emit DeprecationWarning.
943
+ resolved_path = None
944
+ try:
945
+ if central_resolve is not None:
946
+ proj_root = _get_project_root_fallback(current_file.parent)
947
+ r = central_resolve(target_module, str(current_file), proj_root)
948
+ resolved_path = r.resolved_file
949
+ except Exception:
950
+ resolved_path = None
951
+ if not resolved_path:
952
+ # Silent legacy FS fallthrough (same body as deprecated shim tail).
953
+ with warnings.catch_warnings():
954
+ warnings.simplefilter("ignore", DeprecationWarning)
955
+ resolved_path = _try_resolve_relative_path(current_file, target_module)
942
956
  if not resolved_path:
943
957
  return [{
944
958
  "module": resolved,
@@ -149,6 +149,29 @@ build_exclude_expr() {
149
149
  fi
150
150
  }
151
151
 
152
+ # Build find(1) exclude arguments from exclude_patterns.txt as a bash array.
153
+ # The string form from build_exclude_expr embeds literal quote characters and
154
+ # cannot be expanded safely into a find command without eval; this array form
155
+ # is what scan commands (check-changes, validate) must use.
156
+ EXCLUDE_FIND_ARGS=()
157
+ build_exclude_find_args() {
158
+ EXCLUDE_FIND_ARGS=()
159
+ [[ -f "$EXCLUDE_PATTERNS_FILE" ]] || return 0
160
+ local pat
161
+ local parts=()
162
+ while IFS= read -r pat; do
163
+ [[ -z "$pat" || "$pat" =~ ^[[:space:]]*# ]] && continue
164
+ pat=$(echo "$pat" | xargs)
165
+ [[ -z "$pat" ]] && continue
166
+ if ((${#parts[@]})); then parts+=(-o); fi
167
+ # dir-name patterns prune contents; -name catches file globs (*.pyc) and exact basenames
168
+ parts+=(-path "*/$pat/*" -o -path "*/$pat" -o -name "$pat")
169
+ done < "$EXCLUDE_PATTERNS_FILE"
170
+ if ((${#parts[@]})); then
171
+ EXCLUDE_FIND_ARGS=('!' '(' "${parts[@]}" ')')
172
+ fi
173
+ }
174
+
152
175
  # Upsert a row in the Markdown health table (very simple but effective)
153
176
  # We treat file_health.md as the single source of truth.
154
177
  upsert_health() {
@@ -298,8 +321,7 @@ cmd_check_changes() {
298
321
  echo "$last_ts" > "$LAST_CHECK_FILE"
299
322
  fi
300
323
 
301
- local exclude
302
- exclude=$(build_exclude_expr)
324
+ build_exclude_find_args
303
325
 
304
326
  local changed=0
305
327
  local changed_files_list=""
@@ -309,7 +331,6 @@ cmd_check_changes() {
309
331
  [[ -z "$root" ]] && continue
310
332
  [[ ! -e "$root" ]] && { log "Warning: monitored path does not exist: $root"; continue; }
311
333
 
312
- # shellcheck disable=SC2086
313
334
  while IFS= read -r -d '' file; do
314
335
  # Skip the wikifier tool's own internal files
315
336
  if [[ "$file" == *"/.wikifier_staging/"* || "$file" == *"/journal/"* || \
@@ -317,8 +338,16 @@ cmd_check_changes() {
317
338
  continue
318
339
  fi
319
340
 
341
+ # Portable project-relative path. GNU realpath --relative-to is not on
342
+ # macOS BSD realpath; falling back to the absolute path polluted health
343
+ # keys (and shell upsert only patched .md, not file_health.json).
320
344
  local rel_file
321
- rel_file=$(realpath --relative-to="$PROJECT_ROOT" "$file" 2>/dev/null || echo "$file")
345
+ case "$file" in
346
+ "$PROJECT_ROOT"/*) rel_file="${file#"$PROJECT_ROOT"/}" ;;
347
+ *)
348
+ rel_file=$(python3 -c "from pathlib import Path; import sys; print(Path(sys.argv[1]).resolve().relative_to(Path(sys.argv[2]).resolve()))" "$file" "$PROJECT_ROOT" 2>/dev/null || echo "$file")
349
+ ;;
350
+ esac
322
351
 
323
352
  upsert_health "$rel_file" "🟡 Yellow" "mtime changed since last check-changes (auto-detected)"
324
353
  # Collect for delta barrel reports (Wave continuation: pass changed list to BRC get_reports for O(changed) + rich auto-Yellow only on relevant)
@@ -327,7 +356,7 @@ cmd_check_changes() {
327
356
  write_journal "auto-detected" "$rel_file" "File mtime changed (check-changes)"
328
357
 
329
358
  changed=$((changed + 1)) # set -e safe (((changed++)) returns 1 at 0)
330
- done < <(find "$root" -type f -newermt "$last_ts" -print0 2>/dev/null || true)
359
+ done < <(find "$root" -type f ${EXCLUDE_FIND_ARGS[@]+"${EXCLUDE_FIND_ARGS[@]}"} -newermt "$last_ts" -print0 2>/dev/null || true)
331
360
  done < <(get_monitored_paths)
332
361
 
333
362
  date '+%Y-%m-%d %H:%M:%S' > "$LAST_CHECK_FILE"
@@ -550,14 +579,18 @@ cmd_validate() {
550
579
  log "Validating that every monitored file has a health entry..."
551
580
 
552
581
  local missing=0
553
- local exclude
554
- exclude=$(build_exclude_expr)
582
+ build_exclude_find_args
555
583
 
556
584
  while IFS= read -r root; do
557
585
  [[ -z "$root" ]] && continue
558
- find "$root" -type f ! -path "*/.git/*" ! -path "*/.wikifier_staging/*" 2>/dev/null | while read -r f; do
586
+ find "$root" -type f ! -path "*/.git/*" ! -path "*/.wikifier_staging/*" ${EXCLUDE_FIND_ARGS[@]+"${EXCLUDE_FIND_ARGS[@]}"} 2>/dev/null | while read -r f; do
559
587
  local rel
560
- rel=$(realpath --relative-to="$PROJECT_ROOT" "$f" 2>/dev/null || echo "$f")
588
+ case "$f" in
589
+ "$PROJECT_ROOT"/*) rel="${f#"$PROJECT_ROOT"/}" ;;
590
+ *)
591
+ rel=$(python3 -c "from pathlib import Path; import sys; print(Path(sys.argv[1]).resolve().relative_to(Path(sys.argv[2]).resolve()))" "$f" "$PROJECT_ROOT" 2>/dev/null || echo "$f")
592
+ ;;
593
+ esac
561
594
  if ! grep -qF "| $rel |" "$FILE_HEALTH" 2>/dev/null; then
562
595
  echo "🔴 MISSING WIKI ENTRY: $rel"
563
596
  ((missing++)) || true