Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 26 additions & 0 deletions tools/code_index/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,32 @@ python -m code_index.search --corpus docs "limitation du contrat foudre"

En Python : `from code_index import search_docs ; search_docs("anti-scraping", k=6)`.

## Graphe d'appels — `code_impact` (Phase 4)

`graph.py` extrait un **graphe d'appels statique** (tree-sitter, PHP/Python/TS/JS) :
définitions (fonctions/classes/méthodes) reliées par leurs sites d'appel. Il répond à
**« qu'est-ce qui casse si je change X ? »** (`callers`, rayon d'impact) et **« de quoi
dépend X ? »** (`callees`). C'est le pendant *code* du `lineage` *data* curé — aucun
embedding, **aucun appel API** (pur AST), donc l'artefact se (re)construit n'importe où.

Résolution **par nom** (multi-langage, pragmatique) : un appel `foo()` pointe vers toute
définition nommée `foo`. Deux garde-fous data-driven contre les collisions avec des
primitives : `--max-fanout` (nom trop défini → non relié) et un seuil de fréquence d'appel
(nom appelé partout, ex. `.push()`, → non relié) ; les fichiers minifiés/bundlés sont exclus.
Limitation assumée et signalée dans la sortie (champ `ambiguous`).

```bash
# construire l'artefact (JSON ou JSON.gz — ~1,5 Mo gz pour ~37k nœuds) :
python -m code_index.graph build --out graph.json.gz
# interroger : rayon d'impact (callers) / dépendances (callees) :
python -m code_index.graph impact --graph graph.json.gz "Accumulation" --direction callers --depth 2
python -m code_index.graph impact --graph graph.json.gz "PluviometrieService" --direction callees
```

En Python : `from code_index.graph import load_graph, code_impact`. Un nœud `class` « tire »
ses méthodes comme graines (changer la classe = changer ses membres). `--meta sidecar.json`
ajoute les **URLs source** (permaliens) à chaque résultat.

## Configuration (variables d'environnement)

| Variable | Défaut | Rôle |
Expand Down
67 changes: 9 additions & 58 deletions tools/code_index/chunk_ast.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
"""
from __future__ import annotations

from . import tsutil
from .chunk import Chunk, chunk_text

# Étiquette de langage (walk.LANG_BY_EXT) → nom tree-sitter.
Expand All @@ -31,64 +32,14 @@

_COMMENT_PREFIXES = ("#", "//", "/*", "*", "*/")


def _get_parser(ts_lang: str):
"""Parser tree-sitter ou None si la lib/grammaire est absente (→ repli char-window)."""
try:
from tree_sitter_language_pack import get_parser
return get_parser(ts_lang)
except Exception: # noqa: BLE001 — lib absente, grammaire inconnue, etc.
return None


# ── Accès NŒUD/ARBRE agnostique au binding ───────────────────────────────────
# Les bindings tree-sitter divergent : py-tree-sitter expose node.type /
# node.start_point / node.named_children / tree.root_node (propriété) ; d'autres
# (ex. language-pack 1.9) exposent node.kind / node.start_position / node.named_child(i)
# / tree.root_node() (méthode). On lisse les deux pour rester portable.

def _v(obj, name):
"""Attribut `name`, appelé s'il s'agit d'une méthode (les bindings exposent tantôt des
propriétés, tantôt des méthodes pour les mêmes infos)."""
a = getattr(obj, name, None)
return a() if callable(a) else a


def _root(tree):
return _v(tree, "root_node")


def _kind(node) -> str:
return _v(node, "type") or _v(node, "kind")


def _row(pt) -> int:
"""Ligne 0-based d'un point tree-sitter : tuple (row, col) ou objet Point(.row)."""
return pt.row if hasattr(pt, "row") else pt[0]


def _start_row(node) -> int:
return _row(_v(node, "start_point") or _v(node, "start_position"))


def _end_row(node) -> int:
return _row(_v(node, "end_point") or _v(node, "end_position"))


def _named_children(node) -> list:
kids = _v(node, "named_children")
if kids is not None:
return list(kids)
cnt = _v(node, "named_child_count") or 0
return [node.named_child(i) for i in range(cnt)]


def _parse(parser, text: str):
"""parse(bytes) (py-tree-sitter) ou parse(str) (language-pack 1.9) — on tente les deux."""
try:
return parser.parse(text.encode("utf-8", errors="replace"))
except TypeError:
return parser.parse(text)
# Accès tree-sitter agnostique au binding : factorisé dans `tsutil` (partagé avec `graph`).
_get_parser = tsutil.get_parser
_root = tsutil.root
_kind = tsutil.kind
_start_row = tsutil.start_row
_end_row = tsutil.end_row
_named_children = tsutil.named_children
_parse = tsutil.parse


def _emit_window(lines: list[str], a: int, b: int, max_chars: int, out: list[Chunk]) -> None:
Expand Down
Loading
Loading