|
1 | | -# TONIGHT_SESSION_STACK.md (v2 — CORRECTED) |
| 1 | +# TONIGHT_SESSION_STACK.md (v3 — REFACTOR, NOT BRIDGE) |
2 | 2 |
|
3 | | -## ⚠ CORRECTION FROM v1 |
4 | | - |
5 | | -**v1 said delete lance_parser (P3, 5532 lines). WRONG.** |
6 | | -lance_parser IS the production Cypher parser. It's a correctly adapted |
7 | | -port of lance-graph's nom parser with proper ladybug-rs error type integration. |
8 | | -The "divergence" in semantic.rs is correct QueryError adaptation, not drift. |
9 | | - |
10 | | -**Session 1 now deletes ONLY P1 (cypher.rs, 1560 lines).** |
| 3 | +## 4 Hours. No Bridges. No Adapters. Refactor Properly. |
11 | 4 |
|
12 | 5 | --- |
13 | 6 |
|
14 | | -## ⚠ REPO FOCUS RULES |
| 7 | +## SESSION 1: Kill P1, Refactor P2 to Consume P3 AST Directly [REPO: ladybug-rs] |
15 | 8 |
|
| 9 | +**Read COMPLETELY before writing:** |
16 | 10 | ``` |
17 | | -EVERY TASK IS TAGGED WITH A REPO. |
18 | | -DO NOT touch files in a different repo than the current tag. |
19 | | -When switching repos: STOP. Read that repo's CLAUDE.md FIRST. |
20 | | -
|
21 | | -Current repo names (DO NOT RENAME ANYTHING TONIGHT): |
22 | | - ladybug-rs ← main surgery target |
23 | | - rustynum ← READ ONLY from ladybug-rs sessions |
| 11 | +src/query/lance_parser/ast.rs (532 lines — the AST types) |
| 12 | +src/cypher_bridge.rs (897 lines — the executor) |
| 13 | +src/query/cypher.rs (1560 lines — the thing being deleted) |
| 14 | +src/bin/server.rs lines 1621-1655 (the /cypher endpoint) |
24 | 15 | ``` |
25 | 16 |
|
26 | | ---- |
27 | | - |
28 | | -## SESSION 1: Delete P1 Only [REPO: ladybug-rs] |
| 17 | +**The refactor:** |
29 | 18 |
|
30 | | -**Read first:** `CLAUDE.md`, `src/query/cypher.rs` (skim — understand what's being removed) |
| 19 | +P2's `execute_cypher()` takes `&[CypherOp]`. CypherOp is a flat enum with 5 variants |
| 20 | +(MergeNode, CreateNode, CreateEdge, SetProperty, MatchReturn). |
31 | 21 |
|
32 | | -``` |
33 | | -1. Save CTE generator FIRST: |
34 | | - cp src/query/cypher.rs /tmp/cypher_backup.rs |
35 | | - Extract lines 1253-1361 → src/query/cte_builder.rs |
36 | | - (Recursive CTE for variable-length paths — UNIQUE, neither P3 nor P5 has it) |
37 | | -
|
38 | | -2. Delete src/query/cypher.rs |
39 | | -
|
40 | | -3. Update src/query/mod.rs: |
41 | | - Remove: pub mod cypher; |
42 | | - Remove: pub use cypher::{CypherParser, CypherQuery, CypherTranspiler, cypher_to_sql}; |
43 | | - |
44 | | -4. Fix broken references: |
45 | | - server.rs:1625 calls cypher_to_sql — this will break. COMMENT IT OUT for now: |
46 | | - // TODO: wire lance_parser + cypher_bridge (session 2) |
47 | | - http_error(501, "cypher_not_implemented", "being rewired", format) |
| 22 | +P3's parser produces `lance_parser::ast::CypherQuery` with rich tree types |
| 23 | +(ReadingClause, MatchClause, GraphPattern, NodePattern, PathPattern, etc.). |
48 | 24 |
|
49 | | - hybrid.rs:20 imports CypherParser — comment out or remove usage |
| 25 | +**DO NOT create an ast_bridge.rs. Rewrite cypher_bridge.rs to take P3 types directly:** |
50 | 26 |
|
51 | | -5. cargo check --no-default-features --features "simd" |
| 27 | +```rust |
| 28 | +// BEFORE (current cypher_bridge.rs): |
| 29 | +pub fn execute_cypher(bs: &mut BindSpace, ops: &[CypherOp]) -> Result<CypherResult, String> |
52 | 30 |
|
53 | | -6. Rename in src/learning/cam_ops.rs: |
54 | | - CypherOp → CypherInstruction |
55 | | - Update all references within src/learning/ |
56 | | - |
57 | | -7. cargo check again |
| 31 | +// AFTER (refactored): |
| 32 | +pub fn execute_cypher(bs: &mut BindSpace, query: &lance_parser::ast::CypherQuery) -> Result<CypherResult, String> |
58 | 33 | ``` |
59 | 34 |
|
60 | | -**-1560 lines deleted. lance_parser UNTOUCHED.** |
| 35 | +The internal `CypherOp` enum DISAPPEARS. The execute functions work directly on the AST: |
61 | 36 |
|
62 | | -**Commit:** `chore: delete P1 cypher.rs (hand-rolled transpiler) — rescue CTE builder` |
| 37 | +```rust |
| 38 | +// BEFORE: |
| 39 | +CypherOp::MergeNode { labels, properties } => execute_merge_node(bs, labels, properties, &mut result)? |
63 | 40 |
|
64 | | ---- |
| 41 | +// AFTER: |
| 42 | +for clause in &query.reading_clauses { |
| 43 | + match clause { |
| 44 | + ReadingClause::Match(m) => execute_match(bs, m, &query.where_clause, &query.return_clause, &mut result)?, |
| 45 | + ReadingClause::Unwind(u) => execute_unwind(bs, u, &mut result)?, |
| 46 | + } |
| 47 | +} |
| 48 | +// CREATE/MERGE/SET handled from query.update_clauses (or however P3 structures writes) |
| 49 | +``` |
65 | 50 |
|
66 | | -## SESSION 2: Build AST→CypherOp Bridge + Wire Server [REPO: ladybug-rs] |
| 51 | +Keep P2's execution LOGIC (find_node_by_label_and_name, evaluate_where, MERGE semantics). |
| 52 | +Replace P2's TYPES (CypherOp, NodeRef, WhereClause, CypherValue) with P3's types. |
| 53 | +Delete P2's parse_cypher() (P3's parse_cypher_query replaces it). |
| 54 | +Keep P2's execute_* functions, rewrite their signatures to take P3 AST nodes. |
67 | 55 |
|
68 | | -**Read first:** |
69 | | -- `src/query/lance_parser/ast.rs` (the AST types P3 produces) |
70 | | -- `src/cypher_bridge.rs` (the CypherOp types P2 consumes) |
| 56 | +**Steps:** |
71 | 57 |
|
72 | 58 | ``` |
73 | | -1. Create src/query/ast_bridge.rs (~100-150 lines): |
74 | | - Convert lance_parser AST → cypher_bridge CypherOp |
75 | | - |
76 | | - use crate::query::lance_parser::ast::{CypherQuery as ParsedQuery, ...}; |
77 | | - use crate::cypher_bridge::{CypherOp, NodeRef, CypherValue}; |
78 | | - |
79 | | - pub fn ast_to_ops(parsed: &ParsedQuery) -> Result<Vec<CypherOp>, String> { |
80 | | - // Walk the AST, produce CypherOp list |
81 | | - // MATCH clause → CypherOp::MatchReturn |
82 | | - // CREATE clause → CypherOp::CreateNode / CreateEdge |
83 | | - // MERGE → CypherOp::MergeNode |
84 | | - // SET → CypherOp::SetProperty |
85 | | - } |
| 59 | +1. Delete src/query/cypher.rs |
| 60 | + - Save lines 1253-1361 to src/query/cte_builder.rs first |
| 61 | + - Remove from query/mod.rs |
86 | 62 |
|
87 | | -2. Wire server.rs /cypher endpoint: |
88 | | - Replace the commented-out TODO from session 1: |
| 63 | +2. Rewrite src/cypher_bridge.rs: |
| 64 | + - Remove: CypherOp enum, NodeRef enum, WhereClause enum, CypherValue enum |
| 65 | + - Remove: parse_cypher() function |
| 66 | + - Change: execute_cypher signature to take &CypherQuery (P3 type) |
| 67 | + - Change: execute_merge_node to take &NodePattern (P3 type) |
| 68 | + - Change: execute_create_edge to take &PathPattern (P3 type) |
| 69 | + - Change: execute_match_return to take &MatchClause + &WhereClause (P3 types) |
| 70 | + - Change: evaluate_where to take &BooleanExpression (P3 type) |
| 71 | + - Keep: CypherResult (it's the output format, not input) |
| 72 | + - Keep: find_node_by_label_and_name (working scan logic) |
| 73 | + - Keep: MERGE upsert semantics |
89 | 74 | |
| 75 | + Map P3's PropertyValue → to the value types execute_* needs |
| 76 | + Map P3's BooleanExpression → to what evaluate_where checks |
| 77 | + This is INLINE refactoring, not a bridge module. |
| 78 | +
|
| 79 | +3. Wire server.rs: |
90 | 80 | use ladybug::query::lance_parser::parse_cypher_query; |
91 | | - use ladybug::query::ast_bridge::ast_to_ops; |
92 | 81 | use ladybug::cypher_bridge::execute_cypher; |
93 | 82 | |
94 | 83 | match parse_cypher_query(&query) { |
95 | | - Ok(parsed) => { |
96 | | - // Validate via semantic analyzer (P3 has this!) |
97 | | - match ast_to_ops(&parsed) { |
98 | | - Ok(ops) => { |
99 | | - let bs = db.cog_redis.bind_space_mut(); |
100 | | - match execute_cypher(bs, &ops) { |
101 | | - Ok(result) => serialize result as JSON |
102 | | - Err(e) => http_error(500, ...) |
103 | | - } |
104 | | - } |
105 | | - Err(e) => http_error(400, "ast_conversion", &e, format) |
106 | | - } |
| 84 | + Ok(ast) => match execute_cypher(&mut bs, &ast) { |
| 85 | + Ok(result) => serialize |
| 86 | + Err(e) => error |
107 | 87 | } |
108 | | - Err(e) => http_error(400, "cypher_parse", &e.to_string(), format) |
| 88 | + Err(e) => parse error |
109 | 89 | } |
110 | 90 |
|
111 | | -3. cargo check |
112 | | -4. Test: curl /cypher with MERGE → node created |
113 | | -5. Test: curl /cypher with MATCH → results returned |
114 | | -``` |
| 91 | +4. Rename cam_ops CypherOp → CypherInstruction |
115 | 92 |
|
116 | | -**~150 lines new. Full parse→validate→execute pipeline working.** |
| 93 | +5. cargo check --no-default-features --features "simd" |
| 94 | +``` |
117 | 95 |
|
118 | | -**Commit:** `feat: wire lance_parser → ast_bridge → cypher_bridge → server.rs /cypher` |
| 96 | +**Exit gate:** `/cypher` endpoint calls P3 parser → refactored P2 executor. Zero bridge types. |
119 | 97 |
|
120 | 98 | --- |
121 | 99 |
|
122 | | -## SESSION 3: Unlock spo.rs [REPO: ladybug-rs] |
| 100 | +## SESSION 2: Unlock spo.rs Properly [REPO: ladybug-rs] |
123 | 101 |
|
124 | | -**Read first:** `src/spo/spo.rs` (ENTIRE FILE, 1568 lines. No skimming.) |
| 102 | +**Read COMPLETELY:** `src/spo/spo.rs` (all 1568 lines, every function, every type) |
125 | 103 |
|
126 | 104 | ``` |
127 | | -1. src/spo/mod.rs: change `mod spo;` → `pub(crate) mod spo;` |
| 105 | +1. src/spo/mod.rs: `mod spo` → `pub(crate) mod spo` |
| 106 | +
|
| 107 | +2. In spo.rs itself: make key types pub(crate) |
| 108 | + pub(crate) struct SPOCrystal |
| 109 | + pub(crate) struct OrthogonalCodebook |
| 110 | + pub(crate) fn bundle() |
| 111 | + pub(crate) fn bundle_weighted() |
| 112 | + Keep private: internal helpers, Fingerprint (use core::Fingerprint instead) |
| 113 | +
|
| 114 | +3. Add to core/fingerprint.rs: |
| 115 | + pub fn project_out(&self, other: &Fingerprint) -> Fingerprint |
| 116 | + pub fn dot_bipolar(&self, other: &Fingerprint) -> i64 |
| 117 | + (Port from spo.rs, adapt to core::Fingerprint layout) |
| 118 | +
|
| 119 | +4. In spo.rs: replace internal Fingerprint usage with core::Fingerprint |
| 120 | + where signatures cross module boundary. |
| 121 | + Keep internal Fingerprint for private functions if easier — |
| 122 | + but crystal_api RETURNS core::Fingerprint. |
128 | 123 |
|
129 | | -2. Add to core/fingerprint.rs: |
130 | | - project_out() — Gram-Schmidt from spo.rs lines 116-140 |
131 | | - dot_bipolar() — from spo.rs lines 109-115 |
| 124 | +5. Create src/spo/crystal_api.rs — NOT a facade. Direct re-export + helpers: |
| 125 | + pub use super::spo::{SPOCrystal, OrthogonalCodebook}; |
132 | 126 | |
133 | | -3. Create src/spo/crystal_api.rs (~200 lines): |
134 | | - Public facade wrapping private SPOCrystal. |
| 127 | + Plus convenience constructors that use core types: |
| 128 | + pub fn new_crystal() -> SPOCrystal |
| 129 | + pub fn encode_and_insert(crystal: &mut SPOCrystal, s: &str, p: &str, o: &str) |
| 130 | + pub fn query_object(crystal: &SPOCrystal, s: &str, p: &str) -> Vec<QueryHit> |
135 | 131 | |
136 | | -4. Add to src/spo/mod.rs: pub mod crystal_api; |
| 132 | + These are THIN — they call spo.rs methods directly, not wrap them. |
137 | 133 |
|
138 | | -5. cargo check |
| 134 | +6. cargo check |
139 | 135 | ``` |
140 | 136 |
|
141 | | -**Commit:** `feat: unlock spo.rs — pub(crate), crystal_api facade, project_out` |
| 137 | +**Exit gate:** `use crate::spo::crystal_api::SPOCrystal` works from server.rs. |
142 | 138 |
|
143 | 139 | --- |
144 | 140 |
|
145 | | -## SESSION 4: Wire Crystal to Cypher Bridge [REPO: ladybug-rs] |
| 141 | +## SESSION 3: Wire Crystal Into Cypher Execute [REPO: ladybug-rs] |
146 | 142 |
|
147 | | -**Read:** `src/cypher_bridge.rs` lines 370-420, `src/spo/crystal_api.rs` |
| 143 | +**Read:** refactored cypher_bridge.rs (from session 1) + crystal_api.rs (from session 2) |
148 | 144 |
|
149 | 145 | ``` |
150 | | -1. Add CrystalQuery to server DatabaseState |
151 | | -2. In cypher_bridge execute_match: try crystal_api first, fall back to nodes_iter |
152 | | -3. cargo check |
153 | | -``` |
154 | | - |
155 | | -**Commit:** `feat: MATCH queries use SPO Crystal O(25) lookup with nodes_iter fallback` |
| 146 | +1. Add SPOCrystal to DatabaseState in server.rs |
156 | 147 |
|
157 | | ---- |
| 148 | +2. In cypher_bridge execute_match (the read path): |
| 149 | + REPLACE bs.nodes_iter() full scan |
| 150 | + WITH crystal.resonate_spo() for queries that have S and/or P |
| 151 | + |
| 152 | + KEEP nodes_iter as fallback for label-only queries without SPO terms |
| 153 | + |
| 154 | + This is refactoring execute_match, not adding a layer. |
158 | 155 |
|
159 | | -## SESSION 5: CI Triage [REPO: ladybug-rs, then rustynum if needed] |
| 156 | +3. In cypher_bridge execute_merge/create: |
| 157 | + AFTER writing to BindSpace, ALSO insert into SPOCrystal |
| 158 | + Both stores get the data. Crystal is the fast index. |
| 159 | + BindSpace is the source of truth. |
160 | 160 |
|
161 | | -``` |
162 | | -⚠ REPO SWITCH possible. Read each CLAUDE.md before touching. |
163 | | -Focus: get cargo check green, not cargo test (tests can wait). |
| 161 | +4. cargo check + test with curl |
164 | 162 | ``` |
165 | 163 |
|
| 164 | +**Exit gate:** MATCH with subject+predicate hits Crystal (O(25)), not scan (O(N)). |
| 165 | + |
166 | 166 | --- |
167 | 167 |
|
168 | | -## SESSION 6: Close Stale PRs [REPO: ladybug-rs] |
| 168 | +## SESSION 4: CI + Stale PRs [REPO: ladybug-rs, maybe rustynum] |
169 | 169 |
|
170 | 170 | ``` |
171 | | -Close #11-#33, #54 with "superceded by main" |
172 | | -Evaluate #168, #169 against merged #170 |
173 | | -Target: open PRs ≤ 5 |
174 | | -``` |
| 171 | +1. cargo check --all-features (or closest working feature set) |
| 172 | + Fix what breaks. Don't add features. |
175 | 173 |
|
176 | | ---- |
177 | | - |
178 | | -## SESSION 7 (STRETCH): Harvest lance-graph truth.rs [REPO: ladybug-rs] |
| 174 | +2. Close PRs #11-#33, #54 |
| 175 | + Evaluate #168, #169 against #170 |
179 | 176 |
|
180 | | -``` |
181 | | -READ lance-graph truth.rs (175 lines). |
182 | | -MERGE into ladybug-rs graph/spo/. |
183 | | -ADD walk_chain_forward from lance-graph store.rs. |
| 177 | +3. If rustynum CI blocks ladybug-rs: |
| 178 | + ⚠ SWITCH REPO — read rustynum CLAUDE.md first |
| 179 | + Fix the minimum needed for cargo check to pass |
184 | 180 | ``` |
185 | 181 |
|
186 | 182 | --- |
187 | 183 |
|
188 | | -## SESSION ORDER |
| 184 | +## TIMING |
189 | 185 |
|
190 | 186 | ``` |
191 | | -SESSION WHAT EST. TIME PRIORITY |
192 | | -1 Delete P1 only 20-30 min P0 |
193 | | -2 Bridge P3→P2 + server 45-60 min P0 |
194 | | -3 Unlock spo.rs 45-60 min P0 |
195 | | -4 Crystal → Cypher 30-45 min P1 |
196 | | -5 CI triage 30-60 min P1 |
197 | | -6 Close stale PRs 15 min P2 |
198 | | -7 Harvest truth.rs 30 min P2 (stretch) |
| 187 | +SESSION EST. PRIORITY |
| 188 | +1 90 min P0 (the big refactor) |
| 189 | +2 60 min P0 |
| 190 | +3 45 min P1 |
| 191 | +4 30 min P2 |
| 192 | +TOTAL ~4 hours |
199 | 193 | ``` |
200 | 194 |
|
201 | | -**Minimum tonight:** Sessions 1-3 (delete P1, bridge P3→P2, unlock spo.rs). |
202 | | - |
203 | 195 | --- |
204 | 196 |
|
205 | | -*"Read first. All of it. Then decide."* |
| 197 | +*"No bridges. No adapters. No facades. Refactor the types. Inline the logic."* |
0 commit comments