Skip to content

Commit a5729af

Browse files
HumanBean17claude
andcommitted
docs(plans): execution plan for init/increment perf (PR-P1..PR-P3)
Adds plans/active/PLAN-INIT-INCREMENT-PERF.md and the companion plans/AGENT-PROMPTS-INIT-INCREMENT-PERF.md implementing the approved proposal propose/active/INIT-INCREMENT-PERF-PROPOSE.md. Three PRs: - PR-P1: bulk in-memory-pyarrow COPY FROM for the full rebuild path; equivalence harness is the merge gate. - PR-P2: same primitive for the incremental path (Route-MERGE dedup retained). - PR-P3: lifespan-cached LayeredIgnore (ContextKey) + is_ignored _mega memo. No production code. Stacks behind proposal PR #338. Co-Authored-By: Claude <noreply@anthropic.com>
1 parent dae805c commit a5729af

2 files changed

Lines changed: 788 additions & 0 deletions

File tree

Lines changed: 356 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,356 @@
1+
# Agent task prompts — Faster init/increment (PR-P1 → PR-P3)
2+
3+
Status: **active**. One self-contained prompt per PR. Copy the prompt verbatim
4+
into the agent, attach the files in its `@-files` block, and let it execute.
5+
6+
**Workflow per PR:**
7+
8+
1. Create the branch named in the prompt off the stated base.
9+
2. Read the cited plan section in full **before** writing code.
10+
3. Implement step-by-step; run the listed tests after each step.
11+
4. Run the sentinel greps — every "must return zero" line must be empty.
12+
5. Paste the manual-evidence output into the PR description.
13+
6. Open a PR with the exact title in the Definition of Done.
14+
15+
**Universal rules for every prompt:**
16+
17+
- Use only `.venv/bin/python` and `.venv/bin/pip` (never system python/pip).
18+
- `server.py` is stdio — never write to stdout from anything reachable by a tool handler.
19+
- Do not add a cocoindex dependency outside `java_index_flow_lancedb.py`.
20+
- The plan is the source of truth — if this prompt and the plan disagree, the plan wins.
21+
- Do not touch any file outside the prompt's `@-files` + the test files it names. If you think an adjacent file must change, **stop and ask** — don't ship it.
22+
- Do not loosen any existing test assertion to make it pass.
23+
- Breaking changes are allowed; no compatibility shims.
24+
25+
---
26+
27+
## PR-P1 — Bulk `COPY FROM` for the full rebuild path
28+
29+
**Branch:** `perf/bulk-graph-writes-p1` off `master`.
30+
**Base:** `master`.
31+
**Plan section:** `plans/active/PLAN-INIT-INCREMENT-PERF.md` § PR-P1 (read this first).
32+
**Estimated diff size:** medium (one module + tests + a fixture).
33+
34+
**Attach (`@-files`):**
35+
36+
- `@build_ast_graph.py` (the full-rebuild write path: `_write_nodes`, `_write_edges`, `_write_routes_and_exposes`, `write_ladybug`, `_node_row`, `_SCHEMA_*`, `_callee_declaring_role_at_write`, `_populate_declares_rows`, `_populate_overrides_rows`)
37+
- `@propose/active/INIT-INCREMENT-PERF-PROPOSE.md` (design + staging invariants)
38+
- `@tests/test_ast_graph_build.py` (existing regression net + where the new tests go)
39+
- `@tests/_builders.py` (graph-build helpers: `build_ladybug_full_into`, etc.)
40+
41+
**Prompt:**
42+
43+
````
44+
You are implementing PR-P1 from `plans/active/PLAN-INIT-INCREMENT-PERF.md`.
45+
46+
Read the **PR-P1** section of the plan in full before writing any code, plus the
47+
proposal's "Staging invariants" and "Equivalence" paragraphs. The plan is the
48+
source of truth — if this prompt and the plan disagree, the plan wins.
49+
50+
## Scope
51+
52+
Implement PR-P1 exactly as specified: replace the per-row `conn.execute` writes
53+
in the **full rebuild path** (`write_ladybug`) with bulk in-memory-pyarrow
54+
`COPY FROM`. Concretely:
55+
56+
1. **Step-1 spike (first commit):** confirm the exact REL `COPY FROM` column
57+
order + `pa.Table.from_pylist` LIST typing with a throwaway 2-node + 1-edge
58+
toy. Record the working incantation in the `_bulk_copy` docstring.
59+
2. Add `_bulk_copy(conn, table_name, columns, rows)` + the `*_COLUMNS` /
60+
`_REL_*_COLUMNS` constants (column order matches `_SCHEMA_*`; REL tables list
61+
FROM/TO first).
62+
3. Convert `_write_nodes` (full path) to stage all node rows then
63+
`_bulk_copy(conn, "Symbol", NODE_COLUMNS, rows)`. Delete `_CREATE_SYMBOL`.
64+
4. Convert `_write_edges` to per-edge-type row staging (applying the SAME
65+
`seen_calls`/`seen_ucs` dedup and SAME `_callee_declaring_role_at_write`
66+
lookup, at staging time) then `_bulk_copy` each REL table. Delete the dead
67+
`_CREATE_EXT/IMPL/INJ/DECL/OVERRIDES/CALL/UNRESOLVED/UNRESOLVED_AT` strings.
68+
5. Convert `_write_routes_and_exposes` (+ Client/Producer/HTTP_CALLS/ASYNC_CALLS)
69+
to bulk; delete the dead `_CREATE_ROUTE/EXPOSES/CLIENT/DECLARES_CLIENT/
70+
PRODUCER/DECLARES_PRODUCER/HTTP_CALL/ASYNC_CALL` strings.
71+
6. Convert the `GraphMeta` write (`:3472-3473`) to a single-row `_bulk_copy`.
72+
7. Generate + commit `tests/fixtures/graph_baseline_bank_chat.json` from the
73+
**last per-row build** before you remove the per-row path.
74+
8. Add the four named tests.
75+
76+
## Out of scope (do NOT touch)
77+
78+
- The incremental path: `_write_nodes_merge`, `_MERGE_SYMBOL`, `_delete_file_scope`,
79+
`incremental_rebuild`, and the pass5/6 `MERGE (r:Route)` (`:3819-3821`) stay
80+
exactly as-is. PR-P1 is full-rebuild only.
81+
- `java_index_flow_lancedb.py`, `path_filtering.py`, `server.py`, `search_lancedb.py`.
82+
- Any schema (`_SCHEMA_*` DDL), ontology, or re-index change.
83+
- CSV or Parquet-file staging (pyarrow in-memory only).
84+
- Loosening any existing test.
85+
86+
If you find yourself wanting to touch any of the above, **stop and ask**.
87+
88+
## Deliverables
89+
90+
1. `_bulk_copy` helper + column-order constants in `build_ast_graph.py`.
91+
2. `write_ladybug` full path bulk-loads all node tables then all REL tables.
92+
3. Dead `_CREATE_*` / `_CREATE_SYMBOL` strings removed.
93+
4. `tests/fixtures/graph_baseline_bank_chat.json` committed.
94+
5. Four new tests in `tests/test_ast_graph_build.py`.
95+
96+
## Tests
97+
98+
Run, all must pass:
99+
```
100+
.venv/bin/python -m pytest tests/test_ast_graph_build.py -v
101+
.venv/bin/python -m pytest tests/test_incremental_graph.py tests/test_bank_chat_brownfield_integration.py tests/test_call_edges_e2e.py -q
102+
.venv/bin/ruff check .
103+
```
104+
105+
Sentinel greps — **must all return zero** (no output):
106+
```
107+
grep -n "_CREATE_SYMBOL\b" build_ast_graph.py
108+
grep -nE "conn\.execute\(_CREATE_(EXT|IMPL|INJ|DECL|OVERRIDES|CALL|UNRESOLVED|ROUTE|EXPOSES|CLIENT|DECLARES_CLIENT|PRODUCER|DECLARES_PRODUCER|HTTP_CALL|ASYNC_CALL)" build_ast_graph.py
109+
```
110+
111+
Sentinel greps — **must be non-zero** (guards against over-deletion):
112+
```
113+
grep -n "_MERGE_SYMBOL\b" build_ast_graph.py # incremental path, kept
114+
grep -n "MERGE (r:Route" build_ast_graph.py # pass5/6 dedup, kept
115+
grep -n "COPY .*FROM \$rows" build_ast_graph.py # bulk path present
116+
```
117+
118+
## Manual evidence (paste in PR description)
119+
120+
Build the fixture via the bulk path and inspect meta:
121+
```bash
122+
rm -rf /tmp/p1 && .venv/bin/python build_ast_graph.py \
123+
--source-root tests/bank-chat-system \
124+
--ladybug-path /tmp/p1/code_graph.lbug --verbose
125+
.venv/bin/java-codebase-rag meta --source-root tests/bank-chat-system --index-dir /tmp/p1
126+
```
127+
Expected: meta `counts_json` and node/edge counts **identical** to a pre-PR
128+
per-row build (paste both side by side). Note the graph-write phase timing from
129+
the `JCIRAG_PROGRESS` lines vs the pre-PR baseline.
130+
131+
## Definition of Done
132+
133+
- [ ] Step-1 spike result recorded in `_bulk_copy` docstring.
134+
- [ ] Full path bulk-loads nodes-then-edges; no per-row `CREATE` remains in the full path.
135+
- [ ] `_CREATE_SYMBOL` and the dead `_CREATE_*` edge/node strings deleted.
136+
- [ ] All four new tests pass; full regression suites pass unchanged.
137+
- [ ] All "must return zero" sentinel greps are empty; all "non-zero" greps hit.
138+
- [ ] `.venv/bin/ruff check .` clean.
139+
- [ ] Benchmark (before/after graph-write phase) pasted in the PR description.
140+
- [ ] PR title: `perf(graph): bulk COPY FROM for the full rebuild path (PR-P1)`.
141+
````
142+
143+
---
144+
145+
## PR-P2 — Bulk write for the incremental path
146+
147+
**Branch:** `perf/bulk-graph-writes-p2` off PR-P1's branch (or `master` if PR-P1 has merged).
148+
**Base:** PR-P1 merged.
149+
**Plan section:** `plans/active/PLAN-INIT-INCREMENT-PERF.md` § PR-P2 (read this first).
150+
**Estimated diff size:** small-medium.
151+
152+
**Attach (`@-files`):**
153+
154+
- `@build_ast_graph.py` (`_write_nodes_merge`, `_MERGE_SYMBOL`, `_delete_file_scope`, `incremental_rebuild`, `_bulk_copy` + `_REL_*_COLUMNS` from PR-P1)
155+
- `@tests/test_incremental_graph.py` (regression net)
156+
157+
**Prompt:**
158+
159+
````
160+
You are implementing PR-P2 from `plans/active/PLAN-INIT-INCREMENT-PERF.md`.
161+
162+
Read the **PR-P2** section in full. It depends on PR-P1's `_bulk_copy` primitive
163+
and `_REL_*_COLUMNS` constants, which are already merged. The plan wins if this
164+
prompt disagrees.
165+
166+
## Scope
167+
168+
Apply PR-P1's bulk primitive to the incremental path:
169+
170+
1. Convert `_write_nodes_merge` (`:817`, uses `_MERGE_SYMBOL`) to stage rows then
171+
`_bulk_copy(conn, "Symbol", NODE_COLUMNS, rows)`. The incremental path is
172+
delete-then-insert (`_delete_file_scope` already removed the old scope), so
173+
plain `COPY` insert into the cleaned scope is correct. Delete `_MERGE_SYMBOL`;
174+
remove `_write_nodes_impl` if it has no remaining caller.
175+
2. Convert the incremental edge re-emit to per-type staging + `_bulk_copy`,
176+
scoped to the re-emitted files (same pattern as PR-P1's `_write_edges`).
177+
3. **Retain the pass5/6 `MERGE (r:Route)` dedup** (`:3819-3821`) verbatim and add
178+
a one-line comment explaining it is intentionally kept (routes written during
179+
the scoped step must MERGE, not duplicate, against the global step). Do NOT
180+
bulk-convert the Route writes that this MERGE guards.
181+
4. Add the two named tests.
182+
183+
## Out of scope (do NOT touch)
184+
185+
- The full-rebuild path (already bulk in PR-P1).
186+
- Route dedup semantics — keep the `MERGE (r:Route)` exactly.
187+
- `_delete_file_scope`, `incremental_rebuild` algorithm, dependent-expansion,
188+
crash-marker logic.
189+
- Anything outside `build_ast_graph.py` + `tests/test_incremental_graph.py`.
190+
191+
If you find yourself wanting to touch any of the above, **stop and ask**.
192+
193+
## Deliverables
194+
195+
1. Incremental node re-emit via `_bulk_copy`; `_MERGE_SYMBOL` deleted.
196+
2. Incremental edge re-emit via per-type `_bulk_copy`.
197+
3. `MERGE (r:Route)` retained + commented.
198+
4. Two new tests in `tests/test_incremental_graph.py`.
199+
200+
## Tests
201+
202+
Run, all must pass:
203+
```
204+
.venv/bin/python -m pytest tests/test_incremental_graph.py -v
205+
.venv/bin/python -m pytest tests/test_ast_graph_build.py -q
206+
.venv/bin/ruff check .
207+
```
208+
209+
Sentinel greps — **must return zero**:
210+
```
211+
grep -n "_MERGE_SYMBOL\b" build_ast_graph.py
212+
```
213+
214+
Sentinel greps — **must be non-zero** (Route dedup still present; bulk still used):
215+
```
216+
grep -n "MERGE (r:Route" build_ast_graph.py
217+
grep -n "COPY .*FROM \$rows" build_ast_graph.py
218+
```
219+
220+
## Manual evidence (paste in PR description)
221+
222+
Single-file change equivalence:
223+
```bash
224+
# set up an index, touch one file, increment, then full-rebuild the same state
225+
# and diff the graphs (node count, per-type edge counts, GraphMeta counters).
226+
```
227+
Expected: incremental(bulk) == full-rebuild(bulk) for that state. Paste the
228+
side-by-side counts.
229+
230+
## Definition of Done
231+
232+
- [ ] Incremental node/edge re-emit uses `_bulk_copy`; `_MERGE_SYMBOL` deleted.
233+
- [ ] `MERGE (r:Route)` retained and commented.
234+
- [ ] `test_incremental_bulk_write_equivalent_to_full_rebuild`,
235+
`test_incremental_route_merge_dedup_preserved` pass; full
236+
`tests/test_incremental_graph.py` passes unchanged.
237+
- [ ] Sentinel greps pass (zero where required, non-zero where required).
238+
- [ ] `.venv/bin/ruff check .` clean.
239+
- [ ] PR title: `perf(graph): bulk COPY FROM for the incremental path (PR-P2)`.
240+
````
241+
242+
---
243+
244+
## PR-P3 — Cached `LayeredIgnore` + `is_ignored` memo
245+
246+
**Branch:** `perf/cached-ignore-p3` off `master`.
247+
**Base:** `master` (independent of PR-P1/P2).
248+
**Plan section:** `plans/active/PLAN-INIT-INCREMENT-PERF.md` § PR-P3 (read this first).
249+
**Estimated diff size:** small.
250+
251+
**Attach (`@-files`):**
252+
253+
- `@java_index_flow_lancedb.py` (`ContextKey` defs `:60-72`, `coco_lifespan` provide sites `~:287-306`, `process_java_file`/`process_sql_file`/`process_yaml_file` `:344`/`:416`/`:464`)
254+
- `@path_filtering.py` (`LayeredIgnore`, `_mega`, `_mega_build_for_rel`, `is_ignored`)
255+
- `@tests/test_lancedb_e2e.py` (ignore test to keep green)
256+
257+
**Prompt:**
258+
259+
````
260+
You are implementing PR-P3 from `plans/active/PLAN-INIT-INCREMENT-PERF.md`.
261+
262+
Read the **PR-P3** section in full. It is independent of PR-P1/P2. The plan wins
263+
if this prompt disagrees.
264+
265+
## Scope
266+
267+
1. Define `IGNORE = coco.ContextKey[LayeredIgnore]("java_lance_layered_ignore")`
268+
alongside `PROJECT_ROOT`/`EMBEDDER`/`LANCE_DB` (`:60-72`), reusing the SAME
269+
`_ck_params` (`detect_change` vs `tracked`) detection block.
270+
2. In `coco_lifespan`, add `builder.provide(IGNORE, LayeredIgnore(root))` next to
271+
the other `builder.provide(...)` calls — built **once** per flow run.
272+
3. In `process_java_file` (`:344`), `process_sql_file` (`:416`), `process_yaml_file`
273+
(`:464`): add `ignore = coco.use_context(IGNORE)` and replace
274+
`LayeredIgnore(project_root).is_ignored((project_root / file.file_path.path).resolve())`
275+
with `ignore.is_ignored((project_root / file.file_path.path).resolve())`.
276+
Keep `project_root` (still used for path resolution and `_parse_and_enrich_java`).
277+
4. In `path_filtering.py` `LayeredIgnore`: add `self._mega_cache` in `__init__`
278+
and memoize `_mega(rel)` keyed by `Path(rel_project).parent.as_posix()` (mega
279+
depends only on the directory — `_mega_build_for_rel` reads `dir_parts` only).
280+
`is_ignored`/`diagnose_dict` call `_mega` unchanged and benefit transparently.
281+
5. Add the three named tests.
282+
283+
## Out of scope (do NOT touch)
284+
285+
- `build_ast_graph.py` and the graph write path (PR-P1/P2 own that).
286+
- The ignore *decision* logic (`_mega_build_for_rel`, `_winning_row`, negation
287+
scanning) — only memoize, do not alter semantics.
288+
- Any schema, ontology, or re-index change.
289+
- Loosening any existing test.
290+
291+
If you find yourself wanting to touch any of the above, **stop and ask**.
292+
293+
## Deliverables
294+
295+
1. `IGNORE` ContextKey defined + provided once in `coco_lifespan`.
296+
2. The three `process_*_file` functions consume it (no per-file construction).
297+
3. `_mega` memoized by directory in `LayeredIgnore`.
298+
4. Three new tests.
299+
300+
## Tests
301+
302+
Run, all must pass:
303+
```
304+
.venv/bin/python -m pytest tests/test_lancedb_e2e.py -q
305+
.venv/bin/python -m pytest tests -q -k "ignore or path_filter or vectors_progress"
306+
.venv/bin/ruff check .
307+
```
308+
Heavy (only if you can run cocoindex e2e locally):
309+
```
310+
JAVA_CODEBASE_RAG_RUN_HEAVY=1 .venv/bin/python -m pytest tests/test_lancedb_e2e.py -q
311+
```
312+
313+
Sentinel greps — **must return zero**:
314+
```
315+
grep -nE "LayeredIgnore\(project_root\)" java_index_flow_lancedb.py
316+
```
317+
318+
Sentinel greps — **must be non-zero**:
319+
```
320+
grep -n "coco.use_context(IGNORE)" java_index_flow_lancedb.py # 3 sites
321+
grep -n "_mega_cache" path_filtering.py # memo present
322+
```
323+
324+
## Manual evidence (paste in PR description)
325+
326+
Show the ignore object is built once: with `JAVA_CODEBASE_RAG_RUN_HEAVY=1`, run
327+
the flow over a small corpus and log `id(ignore)` per file (temporary instrumentation),
328+
confirm a single object id across all files. Then confirm a micro-benchmark of
329+
`is_ignored` over N files drops with the `_mega` cache (same-directory files hit
330+
the cache).
331+
332+
## Definition of Done
333+
334+
- [ ] `IGNORE` ContextKey defined (version-detected) + provided once in `coco_lifespan`.
335+
- [ ] The three `process_*_file` consume it; no per-file `LayeredIgnore(project_root)`.
336+
- [ ] `_mega` memoized by directory; `is_ignored`/`diagnose_dict` results unchanged.
337+
- [ ] `test_is_ignored_mega_caches_by_directory`,
338+
`test_layered_ignore_memo_preserves_decisions`,
339+
`test_layered_ignore_provided_once_per_flow` pass.
340+
- [ ] Existing ignore + vectors-progress tests pass unchanged.
341+
- [ ] Sentinel greps pass.
342+
- [ ] `.venv/bin/ruff check .` clean.
343+
- [ ] PR title: `perf(vectors): lifespan-cached LayeredIgnore + is_ignored memo (PR-P3)`.
344+
````
345+
346+
---
347+
348+
## Notes for the orchestrator
349+
350+
- **Landing order:** PR-P1 → PR-P2 (P2 depends on P1's `_bulk_copy` +
351+
`_REL_*_COLUMNS`). PR-P3 is independent and can go first, last, or between.
352+
- **Review between PRs:** request code review after each PR lands (see
353+
`superpowers:requesting-code-review`) — the equivalence harness is the gate
354+
for P1/P2; the memo-parity test is the gate for P3.
355+
- **Sentinel greps are binding:** a non-empty "must return zero" grep means scope
356+
leaked; a empty "must be non-zero" grep means an over-deletion. Either blocks merge.

0 commit comments

Comments
 (0)