diff --git a/CLAUDE.md b/CLAUDE.md index 4ccb3d8c..b6de10bc 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -45,12 +45,13 @@ make coverage-html # same, writes htmlcov/ The pipeline runs in three phases: **Extract → Resolve → Store**. ``` -IngestionPipeline (pipeline.py) +IngestionPipeline (pipeline/__init__.py) ├── cgis.languages (registry) ← lazy: a pack is imported when its first file is met │ ├── languages/python/ ← PythonExtractor + PACK │ └── languages/typescript/ ← TypeScriptExtractor (.ts/.tsx) + PACK ├── ResolverEngine (resolver/engine.py) ← raw_call: targets → resolved FQN edges - └── SQLiteStore (storage/sqlite_store.py) ← persists graph; WAL mode + ├── SQLiteStore (storage/sqlite_store.py) ← persists graph; WAL mode + └── post-write passes (pipeline/) ← refresh_dispatch, SemanticUpliftEngine (pipeline/uplift.py) └── QueryEngine (query/engine.py) ← BFS traversals (impact / flow) ``` diff --git a/docs/architecture/ONTOLOGY.md b/docs/architecture/ONTOLOGY.md index bcebeaca..0f4692f6 100644 --- a/docs/architecture/ONTOLOGY.md +++ b/docs/architecture/ONTOLOGY.md @@ -129,4 +129,4 @@ domains: - "*storage.*" ``` -**Ontology compliance rule:** Any PR that adds a new `NodeType`, `EdgeType`, or modifies `domains.yaml` must update this document and the `_ONTOLOGY_CLASS` mapping in `src/cgis/resolver/uplift.py`. +**Ontology compliance rule:** Any PR that adds a new `NodeType`, `EdgeType`, or modifies `domains.yaml` must update this document and the `_ONTOLOGY_CLASS` mapping in `src/cgis/pipeline/uplift.py`. diff --git a/docs/ontology/domains.yaml b/docs/ontology/domains.yaml index 3d803f81..050edad7 100644 --- a/docs/ontology/domains.yaml +++ b/docs/ontology/domains.yaml @@ -21,7 +21,7 @@ domains: - "*storage.*" SemanticEngine: - description: "Symbol resolution, linkage, and semantic uplift" + description: "Symbol resolution and linkage" heuristics: file_path_patterns: - "*resolver/*" @@ -29,8 +29,6 @@ domains: - "*.Resolver*" - "*.ResolverEngine*" - "*resolver.*" - - "*.uplift*" - - "*.SemanticUplift*" QueryLayer: description: "Graph traversal and query operations" @@ -53,12 +51,13 @@ domains: - "*.EdgeType*" Pipeline: - description: "Ingestion pipeline orchestration" + description: "Ingestion pipeline orchestration and its post-write passes (semantic uplift, dispatch)" heuristics: file_path_patterns: - - "*pipeline.py" + - "*pipeline/*" fqn_patterns: - "*.IngestionPipeline*" + - "*pipeline" - "*pipeline.*" APILayer: diff --git a/docs/ontology/patterns.yaml b/docs/ontology/patterns.yaml index 822cca72..2f08eb8e 100644 --- a/docs/ontology/patterns.yaml +++ b/docs/ontology/patterns.yaml @@ -139,7 +139,12 @@ project_domains: profile: python drift_tolerance: 0.40 # v2 measured ≈0.25 post-#115 split (tv_imp 0.00 — real 021C chain engine→symbols→indices; residual is tv_calls) hygiene_baseline: - unresolved_ratio: 0.45 # measured 0.4451 when the gate came alive (#149) + # Re-baselined 0.45 -> 0.46 in language-packs step 5 (user decision): moving + # resolver/uplift.py into cgis/pipeline/ took 70 calls, 28 of them unresolved + # (0.40), out of a domain at 0.44 — 136/309 became 108/239 = 0.452 with no + # unresolved edge added and the moved code unchanged. A denominator effect, + # not a resolution regression; 0.46 keeps the ratchet's usual small headroom. + unresolved_ratio: 0.46 # measured 0.4519 after step 5 (was 0.4451 at #149) - name: "pipeline" fqn_prefix: "cgis.pipeline" diff --git a/docs/plans/2026-10-14-language-packs-step5.md b/docs/plans/2026-10-14-language-packs-step5.md new file mode 100644 index 00000000..dda06723 --- /dev/null +++ b/docs/plans/2026-10-14-language-packs-step5.md @@ -0,0 +1,187 @@ +# Language Packs — Step 5: Build-Plane Passes — Implementation Plan + +> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. + +**Goal:** Move the post-write passes into a `cgis/pipeline/` package so the resolver no longer touches storage, with no stored graph changing. + +**Architecture:** `src/cgis/pipeline.py` becomes the package `src/cgis/pipeline/` and keeps its contents in `__init__.py`, so the module FQN `cgis.pipeline` and all 32 `from cgis.pipeline import IngestionPipeline` imports stay as they are. There is no shim: the module simply becomes a package. `src/cgis/resolver/uplift.py` moves to `src/cgis/pipeline/uplift.py`. It is the only resolver module that reads or writes the store, so the `resolver-storage` allowlist (8 edges today) becomes empty. `refresh_dispatch` already lives in the pipeline. Its pure half, `dispatch_edges`, takes methods and an inheritance tree with no store, so it stays in the resolver. + +**Tech Stack:** Python 3.12, tree-sitter, SQLite, pytest, ruff, mypy strict, interrogate. + +**Spec:** `docs/specs/2026-10-14-language-packs-design.md` (rev 3): "Architecture → Build plane" and Migration row 5. Outline: `docs/plans/2026-10-14-language-packs.md`, "Step 5". Steps 1–4: #597, #599, #600 and #601, plus the spec's implementation notes. + +## Global Constraints + +- **Full gate before every commit:** `make format && make lint && make type-check && make pytest && make doc-coverage` passes. Chain the commands with `&&`; never use `;`, a bare newline after a heredoc, or a pipe on the gating command. +- **Typing and quality floors:** mypy strict on `src/`; docstring coverage ≥ 90%; Sonar cognitive complexity ≤ 15 per function. +- **Behaviour unchanged:** `tests/fixtures/graph_identity/snapshot.json` is not modified, and a main-vs-branch `graph_identity diff` reads `identical` for cgis `src` @715cd9a, owner-api @8de2245 and `ui/`. +- **Spec acceptance:** the `resolver-storage` allowlist is empty, and the `resolution` domain is re-measured. +- **Drift:** + - `pipeline` (orchestrator) stands at drift 0.072 against a lock of 0.25, and unresolved 0.402 against 0.43. It gains the uplift module. + - `resolution` stands at 0.180 / 0.440 against locks of 0.40 / 0.45. It loses the uplift module. + - Measure both after the move. If either exceeds its bounds, **stop and report**. Never raise a lock. + - Remember step 4: one stray import moved a domain's drift by 0.2. If a bound breaks, look for an accidental intra-domain edge before anything else. +- **Disk:** the root disk is ~98% full. Keep scratch under the scratchpad (`/tmp`) and delete databases after each measurement. +- **Test-count floor:** raise `.github/test-count-baseline` (currently 3095) if the suite passes 3245. +- **Attribution:** commits end with `Co-Authored-By: Claude Opus 5.5 `; the PR body ends with `🤖 Generated with [Claude Code](https://claude.com/claude-code)`. +- **Workspace:** worktree `/home/zaebee/projects/ownima/cgb-step5`, branch `feat/language-packs-step5`. + +## Rulings (where this plan departs from the spec's or outline's text) + +- **P1 — `pipeline.py` becomes `pipeline/__init__.py`, not `pipeline/ingest.py` plus a re-export.** + - The orchestrator stays at `cgis.pipeline.IngestionPipeline`, so no importer changes and no compatibility re-export is needed (the step-1 rule was "no shims"). + - The package gains the uplift module beside it. + - *Cost if wrong:* one more move if `__init__` later grows past one responsibility. +- **P2 — `refresh_dispatch` stays where it is.** The spec says it "moves here from `resolver/`", but it is already `cgis.pipeline.refresh_dispatch`. What stays in `resolver/symbols.py` is the pure `dispatch_edges(methods, inheritance)`. The engine calls it on what it extracted, and it never touches a store. +- **P3 — the language residue in the resolver's text is out of scope.** Ruling C4 of step 4 kept generic `resolve_layout` and the JS-builtins root in the generic classify tail; this step leaves them, along with `_is_dunder` and the language-named project-state keys threaded through `IndexBuilder.build`. Removing them changes behaviour, either for a source no pack reads or for the dunder filter of DISPATCHES. That is not what migration row 5 is for. The PR lists them, and the recommendation is a follow-up issue. + +## Review Focus + +- **`cgis ingest` with `--domains`.** Semantic uplift still runs after resolution, with the same domain tags and `ontology_class`. The existing `test_pipeline.py::...domains...` tests and `test_uplift.py` cover this, and the identity corpus records `domains` per node. +- **An incremental run.** `refresh_dispatch` and uplift run in the same order as before, after `_persist_incremental` and `record`. This is pinned by Task 1, `test_post_write_passes_run_in_order`. +- **Imports of the old module path.** None remain: Task 1 greps for them, and the test suite imports the new path. +- **The forbidden-edge gate.** `resolver-storage` is empty, and nothing in `cgis.resolver` imports `cgis.storage`, even under `TYPE_CHECKING`. The gate counts `IMPORTS` edges whatever their `type_only` flag says. +- **Self-graph FQNs.** `pipeline` keeps its id (`pipeline/__init__.py` strips `/__init__`); `resolver.uplift.*` becomes `pipeline.uplift.*` in god-object, calibration and receiver fixtures, and Task 1 greps for leftovers. + +--- + +### Task 1: The pipeline package; uplift moves into it + +**Files:** +- Move: `src/cgis/pipeline.py` → `src/cgis/pipeline/__init__.py` +- Move: `src/cgis/resolver/uplift.py` → `src/cgis/pipeline/uplift.py` +- Modify: + - `src/cgis/pipeline/__init__.py:22` (the import) + - `tests/unit/test_uplift.py:16` + - `tests/self_parsing/forbidden_edges_allowlist.json` (`resolver-storage` → `{}`) + - any self-graph test naming `resolver.uplift` + - the docstring in `src/cgis/query/context/audit.py:17`, only if it names the module path +- Test: `tests/unit/test_pipeline.py` (append) + +**Interfaces:** +- Produces: `cgis.pipeline.uplift.SemanticUpliftEngine` (unchanged API). `cgis.pipeline` keeps `IngestionPipeline`, `refresh_dispatch` and everything else it had. + +- [ ] **Step 1: Write the failing gate expectation and the order test.** + 1. In `tests/self_parsing/forbidden_edges_allowlist.json`, set `"resolver-storage": {}`. Spec acceptance is the test. + 2. Append to `tests/unit/test_pipeline.py`: + +```python +def test_post_write_passes_run_in_order(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None: + """Dispatch is refreshed over the stored graph, then uplift runs — after the write (#545).""" + (tmp_path / "a.py").write_text("class A:\n def f(self):\n pass\n", encoding="utf-8") + order: list[str] = [] + real_refresh = pipeline_module.refresh_dispatch + monkeypatch.setattr( + pipeline_module, "refresh_dispatch", lambda store: (order.append("dispatch"), real_refresh(store)) + ) + monkeypatch.setattr( + pipeline_module.SemanticUpliftEngine, "execute_uplift", lambda self: order.append("uplift") + ) + with SQLiteStore(str(tmp_path / "g.db")) as store: + IngestionPipeline({".py": PythonExtractor()}).run(str(tmp_path), store=store) + assert order == ["dispatch", "uplift"] +``` + + Add `import cgis.pipeline as pipeline_module` (and `pytest`, `SQLiteStore` and `PythonExtractor` if they are missing) to the file's imports. + +- [ ] **Step 2: Run them.** + - Run: `uv run pytest tests/self_parsing/test_architecture.py -q -k allowlist && uv run pytest tests/unit/test_pipeline.py -q -k post_write` + - Expected: + - the allowlist test FAILS, printing `"resolver-storage": {"cgis.resolver.uplift -> cgis.storage.sqlite_store": 8}`; + - the order test PASSES. It is a guard for the move, mutation-checked in Step 5. + +- [ ] **Step 3: Move with git, then fix the imports.** + +```bash +cd /home/zaebee/projects/ownima/cgb-step5 && mkdir -p src/cgis/pipeline && git mv src/cgis/pipeline.py src/cgis/pipeline/__init__.py && git mv src/cgis/resolver/uplift.py src/cgis/pipeline/uplift.py && sed -i 's/^from cgis\.resolver\.uplift import SemanticUpliftEngine$/from cgis.pipeline.uplift import SemanticUpliftEngine/' src/cgis/pipeline/__init__.py && sed -i 's/^from cgis\.resolver\.uplift import/from cgis.pipeline.uplift import/' tests/unit/test_uplift.py && git grep -n "resolver\.uplift\|resolver/uplift" -- src tests scripts docs/ontology +``` + + - Expected: the final grep lists only prose (docstrings and comments). Re-point those that describe where the code lives now; leave historical mentions alone. + - `pipeline/__init__.py` importing its own submodule `cgis.pipeline.uplift` is fine. Check with `uv run python -c "import cgis.pipeline, cgis.pipeline.uplift"`. + - If the self-graph tests name `resolver.uplift.*` FQNs (`git grep -n "resolver.uplift" -- tests/self_parsing`), re-point them to `pipeline.uplift.*`. + +- [ ] **Step 4: Run the tests.** + - Run: `uv run pytest tests/self_parsing tests/unit/test_pipeline.py tests/unit/test_uplift.py tests/unit/test_graph_identity.py -q` + - Expected: all pass, including the allowlist with `resolver-storage` empty. + +- [ ] **Step 5: Mutation-check the order test.** + 1. Swap the two lines in `IngestionPipeline.run` (`refresh_dispatch(store)` and the `SemanticUpliftEngine(...)` call) for one run. + 2. `test_post_write_passes_run_in_order` must fail. + 3. Restore the file and confirm `git diff --stat` shows only the intended files. + +- [ ] **Step 6: Measure the two domains.** + +```bash +cd /home/zaebee/projects/ownima/cgb-step5 && S=/tmp/claude-1001/-home-zaebee-projects-ownima-codegraph-brain/97c475b5-6bc7-4231-a643-ff81f3f651ec/scratchpad/step5 && mkdir -p $S && uv run cgis ingest src --output $S/self.db > /dev/null 2>&1 && uv run cgis drift --db $S/self.db --profile python --format json > $S/drift.json 2>/dev/null +python3 -c "import json; d=json.load(open('$S/drift.json')); rs=d if isinstance(d,list) else d.get('reports') or d.get('domains'); [print(r['domain'], round(r['drift_score'],4), round(r['actual']['unresolved_ratio'],4), r['actual']['node_count'], r['status']) for r in rs if r['domain'] in ('pipeline','resolution')]" +/bin/rm -f $S/self.db +``` + + - Expected: + - `pipeline`: drift ≤ 0.25 and unresolved ≤ 0.43; + - `resolution`: drift ≤ 0.40 and unresolved ≤ 0.45. + - If any value is over its bound, stop. First list the intra-domain import edges on both sides: + +``` +sqlite3 "select source, target from edges where type='IMPORTS' and source like 'cgis.pipeline%' and target like 'cgis.pipeline%'" +``` + + A cross-import triangle was the cause in step 4. Then report. + +- [ ] **Step 7: Inventory.** + - Regenerate `tests/unit/review_path_inventory.txt` as in step 2's plan (Task 2, Step 3). + - Expected: `gone` holds at most `src/cgis/resolver/uplift.py` or `src/cgis/pipeline.py` if they were in the closure, and `new` holds their new paths. Anything else gone means the review walk lost a module: stop. + +- [ ] **Step 8: Gate and commit.** + - Message: `refactor(pipeline): pipeline.py becomes a package; semantic uplift moves into it`. + - The body records: + - the measured drift for both domains; + - that `resolver-storage` is now empty; + - ruling P1 (no importer changes). + +--- + +### Task 2: Documentation + +**Files:** +- `CLAUDE.md` (Architecture block and the pipeline line) +- `docs/specs/2026-10-14-language-packs-design.md` (implementation note under "Build plane") + +- [ ] **Step 1: CLAUDE.md.** + - The Architecture block reads `IngestionPipeline (pipeline.py)`. Change it to `IngestionPipeline (pipeline/__init__.py)`. + - Add a line under the store: `├── post-write passes (pipeline/) ← refresh_dispatch, SemanticUpliftEngine (pipeline/uplift.py)`. Match the block's existing indentation and arrow style. + - Correct any other line that names `resolver/uplift.py`. + +- [ ] **Step 2: Spec note.** Under "Build plane", add `> **Implementation note (step 5):** …`. It covers: + - P1: the package keeps the orchestrator in `__init__.py`, so imports are unchanged; + - P2: `refresh_dispatch` was already in the pipeline, and `dispatch_edges` stays pure in the resolver; + - the empty `resolver-storage` allowlist; + - the measured drift; + - P3: the language residue left in the resolver's text, listed as follow-up work for a measured change. + +- [ ] **Step 3: Gate and commit.** + - Message: `docs: step 5 implementation notes`. + +--- + +### Task 3: Identity report, review, PR + +- [ ] **Step 1: Identity.** + - Run the same commands as step 4's plan, Task 8 Step 1, using `step5` paths and `BR=/home/zaebee/projects/ownima/cgb-step5`. + - Expected: `identical` three times. + - Delete the databases. +- [ ] **Step 2: Independent review.** + - Dispatch a reviewer with execution tools on the most capable model. + - Give it: the branch diff, this plan, the spec, the ledger, the Review Focus and the disk warning. + - Ask it to confirm, with a `--domains` ingest on old and new, that uplift output (`domains`, `ontology_class`, domain-dependency edges) is identical. + - Fix Critical/Important findings test-first. +- [ ] **Step 3: PR.** + - Push, then open `refactor(pipeline): step 5 — build-plane passes`. + - The body carries: + - the module map; + - rulings P1–P3; + - the identity results; + - the drift numbers; + - the emptied `resolver-storage` allowlist; + - the residue list with the follow-up recommendation. + - Check the Gemini and Guardian comments and Sonar, and reply in every thread. diff --git a/docs/specs/2026-10-14-language-packs-design.md b/docs/specs/2026-10-14-language-packs-design.md index 760f8f0d..ceae86a5 100644 --- a/docs/specs/2026-10-14-language-packs-design.md +++ b/docs/specs/2026-10-14-language-packs-design.md @@ -97,6 +97,20 @@ Two planes over a shared core: the result, then runs the post-write passes: semantic uplift and `refresh_dispatch`. Both move here from `resolver/` (step 5). They read and write the store, which is what the resolver must not do. + + > **Implementation note (step 5):** `pipeline.py` became `pipeline/__init__.py`, + > so `cgis.pipeline.IngestionPipeline` and every importer are unchanged (no + > re-export); `resolver/uplift.py` became `pipeline/uplift.py`, and with it the + > `resolver-storage` allowlist is empty. `refresh_dispatch` was already the + > pipeline's; its pure half, `dispatch_edges(methods, inheritance)`, stays in the + > resolver — it never touches a store. Measured: `pipeline` drift 0.106, + > `resolution` 0.179; `resolution`'s hygiene baseline went 0.45 → 0.46 because + > uplift left with a lower unresolved share (0.40) than the domain's (0.44) — no + > unresolved edge was added. Left for a measured follow-up, since removing them + > changes behaviour: the Python `__init__` root rule in `strip_layout_prefixes`, + > the JS-builtins root in `classify_fqn`'s generic tail, `_is_dunder` in + > DISPATCHES, and the language-named project state threaded through + > `IndexBuilder.build`. - **Read plane.** `storage → query → surfaces`. - **Core.** Model types and FQN utilities. It imports nothing else from cgis. diff --git a/src/cgis/pipeline.py b/src/cgis/pipeline/__init__.py similarity index 99% rename from src/cgis/pipeline.py rename to src/cgis/pipeline/__init__.py index 2064be45..b721edbc 100644 --- a/src/cgis/pipeline.py +++ b/src/cgis/pipeline/__init__.py @@ -17,9 +17,9 @@ from cgis.core.paths import EXCLUDED_DIRS from cgis.import_names import ImportNameCollector from cgis.languages import cross_file_metadata, skips +from cgis.pipeline.uplift import SemanticUpliftEngine from cgis.resolver.engine import ResolverEngine from cgis.resolver.symbols import dispatch_edges -from cgis.resolver.uplift import SemanticUpliftEngine if TYPE_CHECKING: from cgis.storage.sqlite_store import SQLiteStore diff --git a/src/cgis/resolver/uplift.py b/src/cgis/pipeline/uplift.py similarity index 100% rename from src/cgis/resolver/uplift.py rename to src/cgis/pipeline/uplift.py diff --git a/tests/self_parsing/forbidden_edges_allowlist.json b/tests/self_parsing/forbidden_edges_allowlist.json index 4fff686f..e86b4752 100644 --- a/tests/self_parsing/forbidden_edges_allowlist.json +++ b/tests/self_parsing/forbidden_edges_allowlist.json @@ -3,9 +3,7 @@ "cross-pack": {}, "engine-packs": {}, "pack-isolation": {}, - "resolver-storage": { - "cgis.resolver.uplift -> cgis.storage.sqlite_store": 8 - }, + "resolver-storage": {}, "surface-packs": {}, "surface-storage": { "cgis.api.auto_refresh -> cgis.storage.sqlite_store": 7, diff --git a/tests/self_parsing/test_domains_ontology.py b/tests/self_parsing/test_domains_ontology.py new file mode 100644 index 00000000..e384040b --- /dev/null +++ b/tests/self_parsing/test_domains_ontology.py @@ -0,0 +1,43 @@ +"""The repository's own domains.yaml tags its own layout (language-packs step 5). + +`cgis ingest ./src --domains docs/ontology/domains.yaml` is the documented +self-ingest. When a module moves, the heuristics must move with it: no CI gate +consumes these tags, so a stale pattern only shows up as an untagged +orchestrator or a dual-tagged module and a domain edge that is not there. +""" + +from pathlib import Path + +import pytest + +from cgis.languages.python.extractor import PythonExtractor +from cgis.pipeline import IngestionPipeline +from cgis.storage.sqlite_store import SQLiteStore + +_REPO = Path(__file__).resolve().parents[2] + + +@pytest.fixture(scope="module") +def tagged(tmp_path_factory: pytest.TempPathFactory) -> dict[str, list[str]]: + db = tmp_path_factory.mktemp("domains") / "graph.db" + pipeline = IngestionPipeline( + {".py": PythonExtractor()}, domains_config=str(_REPO / "docs/ontology/domains.yaml") + ) + with SQLiteStore(str(db)) as store: + pipeline.run(str(_REPO / "src"), store=store) + return {n.id: list(n.domains) for n in store.get_all_nodes()} + + +def test_the_pipeline_package_is_the_pipeline_domain(tagged: dict[str, list[str]]) -> None: + assert tagged["cgis.pipeline"] == ["Pipeline"] + assert tagged["cgis.pipeline.IngestionPipeline"] == ["Pipeline"] + + +def test_uplift_is_a_pipeline_pass_not_the_semantic_engine(tagged: dict[str, list[str]]) -> None: + """Step 5 moved uplift into the build plane; one domain, not two.""" + assert tagged["cgis.pipeline.uplift"] == ["Pipeline"] + assert tagged["cgis.pipeline.uplift.SemanticUpliftEngine"] == ["Pipeline"] + + +def test_the_resolver_is_still_the_semantic_engine(tagged: dict[str, list[str]]) -> None: + assert tagged["cgis.resolver.engine.ResolverEngine"] == ["SemanticEngine"] diff --git a/tests/unit/test_pipeline.py b/tests/unit/test_pipeline.py index 3d8c20e7..e7436d70 100644 --- a/tests/unit/test_pipeline.py +++ b/tests/unit/test_pipeline.py @@ -5,6 +5,7 @@ import pytest +import cgis.pipeline as pipeline_module from cgis.core.models import EdgeType, NodeType from cgis.languages.python.extractor import PythonExtractor from cgis.pipeline import IngestionPipeline @@ -286,3 +287,22 @@ def test_pipeline_stamps_generated_nodes(pipeline: IngestionPipeline, tmp_path: by_file = {n.id: n.is_generated for n in nodes if n.type == NodeType.CLASS} assert by_file["stub.Vehicle"] is True assert by_file["service.Service"] is False + + +def test_post_write_passes_run_in_order(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None: + """Dispatch is refreshed over the stored graph, then uplift runs — after the write (#545).""" + (tmp_path / "a.py").write_text("class A:\n def f(self):\n pass\n", encoding="utf-8") + order: list[str] = [] + real_refresh = pipeline_module.refresh_dispatch + + def refresh(store: SQLiteStore) -> None: + order.append("dispatch") + real_refresh(store) + + monkeypatch.setattr(pipeline_module, "refresh_dispatch", refresh) + monkeypatch.setattr( + pipeline_module.SemanticUpliftEngine, "execute_uplift", lambda _self: order.append("uplift") + ) + with SQLiteStore(str(tmp_path / "g.db")) as store: + IngestionPipeline({".py": PythonExtractor()}).run(str(tmp_path), store=store) + assert order == ["dispatch", "uplift"] diff --git a/tests/unit/test_uplift.py b/tests/unit/test_uplift.py index fd0c0021..80775c66 100644 --- a/tests/unit/test_uplift.py +++ b/tests/unit/test_uplift.py @@ -1,4 +1,4 @@ -"""Unit tests for the SemanticUpliftEngine (src/cgis/resolver/uplift.py).""" +"""Unit tests for the SemanticUpliftEngine (src/cgis/pipeline/uplift.py).""" from collections.abc import Generator from pathlib import Path @@ -13,7 +13,7 @@ NodeNamespace, NodeType, ) -from cgis.resolver.uplift import ( +from cgis.pipeline.uplift import ( SemanticUpliftEngine, _phase1_map_ontology_classes, _phase2_apply_heuristic_tagging,