Skip to content
Open
1 change: 1 addition & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,4 @@
worked/**/*.html linguist-vendored=true
graphify-out/**/*.html linguist-vendored=true
*.html linguist-detectable=false
graphify-out/graph.json merge=graphify
37 changes: 37 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -109,11 +109,47 @@ What you get out of the box:
| **Cross-file links** | `calls` / `imports` / `inherits` / `mixes_in` resolved across ~40 languages via tree-sitter AST |
| **Query, path, explain** | Ask a question, trace the path between two things, or explain one concept, all against `graph.json` |
| **Rationale + doc refs** | `# NOTE:` / `# WHY:` comments and ADR/RFC citations become first-class nodes linked to the code |
| **Validated knowledge** | `lat.md/` sections, summaries, wiki links, source links, and `@lat` implementation references join the code graph deterministically |
| **Beyond code** | Docs, PDFs, images, and video/audio all map into the same graph |
| **Local-first** | Code is parsed locally with tree-sitter (no LLM, nothing leaves your machine); only the semantic pass over docs/media calls a backend, and only if you configure one |

---

## Validated knowledge with lat.md

Graphify automatically recognizes Markdown files inside a project-level
`lat.md/` directory. Each heading becomes a stable `knowledge_section` node,
the first paragraph becomes its searchable summary, and wiki links connect the
curated knowledge to other sections or source files. Explicit implementation
comments connect knowledge back to code:

```python
# @lat: [[security#Security#Tenant isolation]]
def load_orders(tenant_id):
...
```

Run the normal update command to include the lattice in `graph.json`. Projects
with a `lat.md/` directory are validated automatically after every successful
update; invalid knowledge makes the command exit nonzero. The standalone command
is useful for CI checks and structured JSON diagnostics:

```bash
graphify update .
graphify check-knowledge .
graphify check-knowledge . --json
graphify query "tenant isolation constraints"
```

`check-knowledge` reports broken or ambiguous wiki links, missing source files,
stale or ambiguous `@lat` code mentions, and leaf sections marked
`require-code-mention: true` that have no matching `@lat` reference. The
supported format is compatible with the public lat.md
heading, wiki-link, source-reference, and code-mention conventions while
remaining fully local and deterministic.

---

## Benchmarks

| Benchmark | Metric | graphify | Field |
Expand Down Expand Up @@ -213,6 +249,7 @@ for example `graphify claude install --project` or `graphify codex install --pro
| Claude Code (Windows) | `graphify install` (auto-detected) or `graphify install --platform windows` |
| CodeBuddy | `graphify install --platform codebuddy` |
| Codex | `graphify install --platform codex` |
| Jcode | `graphify jcode install` |
| OpenCode | `graphify install --platform opencode` |
| Kilo Code | `graphify install --platform kilo` |
| GitHub Copilot CLI | `graphify install --platform copilot` |
Expand Down
89 changes: 89 additions & 0 deletions docs/testing/validated-knowledge-ingestion.tdd.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
# Validated knowledge ingestion TDD evidence

## Source

The user approved implementation of the recommended Graphify and Jcode validated
knowledge plan. This document covers the first Graphify milestone: native
`lat.md` ingestion, integrity checking, implementation linkage, and retrieval.

## User journeys

1. As an agent, I can query curated design constraints together with source-code
symbols so that I do not miss project invariants before editing code.
2. As a maintainer, I can validate wiki links, source links, and required
implementation mentions so that project knowledge does not silently drift.
3. As a project adopting lat.md, I can use Graphify's normal update workflow
without running a second retrieval service.

## Requirement-to-check evidence

| # | Guarantee | Test or command | Type | Result |
|---|---|---|---|---|
| 1 | Headings become stable knowledge sections with first-paragraph summaries | `test_lattice_markdown_emits_stable_sections_summaries_and_wiki_edges` | Unit | PASS |
| 2 | Inline and fenced examples are ignored while source links become `documents` edges | `test_lattice_ignores_example_links_and_emits_source_documentation_edges` | Integration | PASS |
| 3 | `@lat` comments connect a knowledge section to its implementation file | `test_full_extract_links_at_lat_comment_to_knowledge_section` | Integration | PASS |
| 4 | Cross-file shorthand wiki links resolve to full stable section IDs | `test_full_extract_resolves_cross_file_short_wiki_reference` | Integration | PASS |
| 5 | Broken, ambiguous, and missing implementation references are diagnosed | `test_validate_lattice_reports_broken_ambiguous_and_unimplemented_required_sections` | Unit | PASS |
| 6 | Invalid lattices return structured JSON and exit code 1 | `test_check_knowledge_cli_returns_json_and_nonzero_for_invalid_lattice` | CLI acceptance | PASS |
| 7 | Query scoring searches summaries and returns the summary in bounded output | `test_query_retrieves_lattice_summary_after_normal_extraction` | Public retrieval | PASS |
| 8 | A real valid lattice is accepted through the public CLI | `uv run python -m graphify check-knowledge /home/sergey/.jcode/scratch/graphify-lattice-valid --json` | CLI acceptance | PASS, 2 sections, 0 errors |
| 9 | The implementation is compatible with the official lat.md repository | `validate_lattice(Path('/home/sergey/.jcode/scratch/lat.md-official'))` | Real integration | PASS, 24 files, 192 sections, 0 errors |
| 10 | Dotted lattice filenames remain knowledge references rather than source paths | `test_dotted_lattice_file_reference_is_not_misclassified_as_source` | Integration | PASS |
| 11 | Incremental lattice updates rediscover mentions in unchanged source files | `test_lattice_change_rescans_unchanged_source_mentions` | Incremental integration | PASS |
| 12 | Source links cannot escape the project root | `test_source_reference_cannot_escape_project_root` | Security | PASS |
| 13 | Code-mention validation honors Graphify ignore rules | `test_validation_respects_graphifyignore_when_scanning_code_mentions` | Integration | PASS |
| 14 | Adjacent extraction, CLI, query, and security behavior remains intact | focused regression command below | Regression | PASS, 460 tests |
| 15 | Removed or ambiguous knowledge targets in source comments are diagnosed | `test_validation_reports_stale_and_ambiguous_code_mentions` | Integrity | PASS |
| 16 | `graphify update` returns exit code 1 after rebuilding an invalid lattice | `test_update_automatically_fails_after_rebuild_when_lattice_is_invalid` plus public scratch workflow | CLI acceptance | PASS, both wiki and code diagnostics emitted |
| 17 | Projects without `lat.md/` keep their existing update behavior | `test_update_skips_knowledge_validation_for_projects_without_lattice` | Compatibility | PASS |
| 18 | Valid knowledge passes automatically through the public update workflow | `python -m graphify update .../graphify-knowledge-update-valid-94 --no-cluster` | CLI acceptance | PASS, 2 sections across 1 file |

## RED evidence

1. `uv run pytest -q tests/test_lattice_ingest.py` failed during collection with
`ModuleNotFoundError: graphify.lattice_ingest` before production code existed.
2. After the first minimal implementation, the strengthened tests failed because
cross-file shorthand links were pruned and summary-only queries returned
`No matching nodes found.`
3. The source-link compatibility test failed because no `documents` edge was
emitted before Markdown-aware parsing was implemented.
4. Independent review added four regressions which initially failed: incremental
`@lat` rediscovery, dotted lattice filenames, source-root containment, and
ignore-aware validation scanning.
5. The workflow milestone began with two expected failures: stale `@lat` comments
were silently ignored, and `graphify update` returned success for an invalid
knowledge lattice.

Checkpoint commits:

- `79fe28b` specifies the initial missing behavior.
- `cee64f3` specifies cross-file resolution and public retrieval behavior.

## GREEN evidence

- `uv run pytest -q tests/test_lattice_ingest.py`: 11 passed.
- `uv run pytest -q tests/test_lattice_ingest.py tests/test_manifest_ingest.py tests/test_languages.py tests/test_cli_export.py tests/test_query_cli.py tests/test_security.py`: 460 passed.
- `env -u DEEPSEEK_API_KEY -u DEEPSEEK_BASE_URL uv run pytest -q --ignore=tests/test_falkordb_integration.py`: 4,157 passed, 1 skipped. The excluded integration requires a FalkorDB server with the `GRAPH.QUERY` module; the available localhost service was plain Redis.
- Workflow milestone focused suite: 14 passed.
- Workflow milestone adjacent suite including update/watch behavior: 577 passed.
- Workflow milestone deterministic full suite: 4,160 passed, 1 skipped.
- `uv run ruff check graphify/lattice_ingest.py graphify/extract.py graphify/serve.py graphify/cli.py graphify/__main__.py tests/test_lattice_ingest.py`: passed.
- GREEN implementation checkpoint: `5dcee5a`.

## Coverage

`uv run pytest -q tests/test_lattice_ingest.py --cov=graphify.lattice_ingest --cov-report=term-missing --cov-fail-under=80`

The initial seven-test milestone reached 89% statement coverage. The final
eleven-test suite adds independent-review coverage for incremental, security,
ignore, and dotted-filename edge cases.

## Known boundaries

- Graphify validates that referenced source files exist. Symbol-level source-link
validation is deferred to the next milestone, where links will resolve against
Graphify's language-aware symbol index.
- This milestone does not add a separate lat.md semantic index or service.
Curated summaries participate directly in Graphify's existing query scorer.
- Jcode changes are intentionally deferred until the Graphify public graph and
CLI contract is committed and stable.
6 changes: 5 additions & 1 deletion graphify/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@
_install_codex_hook,
_install_gemini_hook,
_install_kilo_plugin,
_install_jcode_hook,
_install_opencode_plugin,
_install_skill_references,
_kilo_config_path,
Expand Down Expand Up @@ -85,6 +86,7 @@
_uninstall_codex_hook,
_uninstall_gemini_hook,
_uninstall_kilo_plugin,
_uninstall_jcode_hook,
_uninstall_opencode_plugin,
claude_install,
claude_uninstall,
Expand Down Expand Up @@ -507,7 +509,7 @@ def _run_cli() -> None:
print("Usage: graphify <command>")
print()
print("Commands:")
print(" install [--platform P] copy skill to platform config dir (claude|windows|codebuddy|codex|opencode|aider|amp|agents|claw|droid|trae|trae-cn|gemini|cursor|antigravity|hermes|kiro|pi|devin)")
print(" install [--platform P] copy skill to platform config dir (claude|windows|codebuddy|codex|jcode|opencode|aider|amp|agents|claw|droid|trae|trae-cn|gemini|cursor|antigravity|hermes|kiro|pi|devin)")
print(" uninstall remove graphify from all detected platforms in one shot")
print(" --purge also delete graphify-out/ directory")
print(" path \"A\" \"B\" shortest path between two nodes in graph.json")
Expand Down Expand Up @@ -539,6 +541,8 @@ def _run_cli() -> None:
print(" --force overwrite graph.json even if the rebuild has fewer nodes")
print(" (also: GRAPHIFY_FORCE=1 env var; use after refactors that delete code)")
print(" --no-cluster skip clustering, write raw extraction only")
print(" check-knowledge <path> validate lat.md wiki links and @lat code references")
print(" --json emit machine-readable validation results")
print(" cluster-only <path> rerun clustering on an existing graph.json and regenerate report")
print(" --no-viz skip graph.html generation (useful for >5000 node graphs / CI)")
print(" --graph <path> path to graph.json (default <path>/graphify-out/graph.json)")
Expand Down
97 changes: 97 additions & 0 deletions graphify/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -710,6 +710,52 @@ def _run_hook_guard(kind: str, strict: bool = False) -> None:
pass


def _run_jcode_hook_guard() -> bool:
"""Redirect Jcode's first raw code lookup to Graphify.

Jcode sends the tool input JSON on stdin, exports metadata through
``JCODE_HOOK_*``, and treats exit 2 plus stderr as a blocked tool call.
Return True only for the first matching raw lookup in a session; malformed
input and unsupported tools fail open.
"""
from graphify.paths import out_path

try:
if not out_path("graph.json").is_file() or _query_stamp_fresh():
return False
payload = json.loads(sys.stdin.buffer.read().decode("utf-8", "replace"))
if not isinstance(payload, dict):
return False

tool_name = os.environ.get("JCODE_HOOK_TOOL_NAME", "").strip().lower()
command = str(payload.get("command") or "")
command_lower = command.lower()
if "graphify query" in command_lower:
return False

is_raw_lookup = tool_name in {"agentgrep", "grep", "read"}
if tool_name == "bash":
is_raw_lookup = any(
token in command_lower
for token in ("grep", "ripgrep", "rg ", "find ", "fd ", "ack ", "ag ")
)
if not is_raw_lookup:
return False

session_id = os.environ.get("JCODE_HOOK_SESSION_ID", "").strip()
if not session_id or not _mark_session_denied(f"jcode-{session_id}"):
return False

sys.stderr.write(
"Graphify knowledge graph is available for this project. "
"Run `graphify query \"<your codebase question>\"` before raw "
"search/read, then retry this tool if more detail is needed.\n"
)
return True
except Exception:
return False


def _target_is_indexed(file_path: str, root: "Path") -> bool:
"""Guard the strict deny: only block a read of a file the graph actually indexes.
Reads manifest.json (cheap, capped); on any doubt (missing/corrupt/oversized
Expand Down Expand Up @@ -802,6 +848,25 @@ def _reenter_main() -> None:
main()


def _print_knowledge_validation(result: dict) -> None:
"""Print one deterministic human-readable knowledge validation report."""
if result["valid"]:
print(
f"Knowledge lattice valid: {result['sections']} sections "
f"across {result['files']} files."
)
return
for error in result["errors"]:
print(
f"{error['file']}:{error['line']}: "
f"{error['code']}: {error['message']}"
)
print(
f"Knowledge lattice invalid: {len(result['errors'])} error(s).",
file=sys.stderr,
)


def dispatch_command(cmd: str) -> None:

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Health regressiondispatch_command()

fans out to 120 callees (efferent coupling).

Grounded coupling-delta finding (deterministic), not an LLM guess.

if cmd == "provider":
from graphify.llm import _custom_providers_path, BACKENDS
Expand Down Expand Up @@ -1658,6 +1723,26 @@ def dispatch_command(cmd: str) -> None:
else:
print(format_diagnostic_report(summary))

elif cmd == "check-knowledge":
import json as _json
from graphify.lattice_ingest import validate_lattice

check_path = Path(".")
for arg in sys.argv[2:]:
if not arg.startswith("--"):
check_path = Path(arg)
break
if not check_path.exists() or not check_path.is_dir():
print(f"error: path not found or not a directory: {check_path}", file=sys.stderr)
sys.exit(1)
result = validate_lattice(check_path)
if "--json" in sys.argv:
print(_json.dumps(result, indent=2, ensure_ascii=False))
else:
_print_knowledge_validation(result)
if not result["valid"]:
sys.exit(1)

elif cmd == "add":
if len(sys.argv) < 3:
print(
Expand Down Expand Up @@ -2115,6 +2200,14 @@ def dispatch_command(cmd: str) -> None:
ok = _rebuild_code(watch_path, force=force, no_cluster=no_cluster, block_on_lock=True)
if ok:
print("Code graph updated. For doc/paper/image changes run /graphify --update in your AI assistant.")
lattice_dir = watch_path.resolve() / "lat.md"
if lattice_dir.is_dir():
from graphify.lattice_ingest import validate_lattice

knowledge = validate_lattice(watch_path)
_print_knowledge_validation(knowledge)
if not knowledge["valid"]:
sys.exit(1)
if not (
os.environ.get("GEMINI_API_KEY")
or os.environ.get("GOOGLE_API_KEY")
Expand Down Expand Up @@ -2147,6 +2240,10 @@ def dispatch_command(cmd: str) -> None:
strict="--strict" in sys.argv[3:],
)
sys.exit(0)
elif cmd == "jcode-hook":
# Jcode pre_tool gate: exit 2 blocks once and exposes stderr to the
# model; every unsupported/error path fails open with exit 0.
sys.exit(2 if _run_jcode_hook_guard() else 0)
elif cmd == "check-update":
if len(sys.argv) < 3:
print("Usage: graphify check-update <path>", file=sys.stderr)
Expand Down
Loading