|
| 1 | +# LLP 0265: `hyp query grep` implementation plan |
| 2 | + |
| 3 | +**Type:** Plan |
| 4 | +**Status:** Active |
| 5 | +**Systems:** Query, Cache, CLI, MCP |
| 6 | +**Author:** Brendan / Claude |
| 7 | +**Date:** 2026-08-18 |
| 8 | +**Related:** LLP 0264 (the decision this executes), LLP 0105 (the visibility wrapper T4 reuses), LLP 0222 (the hyparquet floor T1 must hold), LLP 0034 (the verb surface T5 lands on), LLP 0209 (the compaction pass T6 hooks); hypaware-server LLP 0178 / PR #364 (the server half, already open and inert) |
| 9 | + |
| 10 | +> Turns [LLP 0264](./0264-grep-search-mirrors-the-server.decision.md) into |
| 11 | +> seven tasks. The decision is Accepted; the server-side displacement fix |
| 12 | +> (hypaware-server #364) is open and mergeable independently. |
| 13 | +
|
| 14 | +## Sequencing principle {#sequencing} |
| 15 | + |
| 16 | +**Every wave leaves a shippable tree, and search works before any index |
| 17 | +exists.** The scan tier alone is a complete, correct `hyp query grep` |
| 18 | +(slower on deep history, never wrong), and `--remote` reaches the server's |
| 19 | +archive-backed search from the moment the verb lands, because the server's |
| 20 | +`grep_search` tool exists today. So the verb (T5) ships on the scan tier, |
| 21 | +and the sidecar build (T6) lands behind it as pure acceleration, exactly |
| 22 | +the property server LLP 0130 designed for ("index presence is purely a |
| 23 | +performance property"). |
| 24 | + |
| 25 | +**Cross-repo order, resolving LLP 0264 #open.** hypaware-server #364 |
| 26 | +(displace the kernel twin) is inert against today's kernel and merges |
| 27 | +first, any time. This repo then implements T1-T7 and releases. |
| 28 | +hypaware-server bumps its `hypaware` dependency **last**, after #364 is in, |
| 29 | +and follows with its import-swap PR onto the T3 exports. The only forbidden |
| 30 | +order is a server dependency bump before #364. |
| 31 | + |
| 32 | +## What was verified against the tree {#verified} |
| 33 | + |
| 34 | +Checked 2026-08-18, each load-bearing for a task below: |
| 35 | + |
| 36 | +- **The server's `grep_search` contract is live** (hypaware-server |
| 37 | + `src/daemon.js`): params `query`, `regex`, `session_id`, `chain_id`, |
| 38 | + `from`, `to`, `limit`; hits carry `date`, `sessionId`, `agentId`, |
| 39 | + `conversationId`, `partId`, `messageId`, `messageCreatedAt`, |
| 40 | + `matches: [{column, snippet}]`. T5's schema must be wire-compatible: |
| 41 | + `--remote` sends the verb's params to the remote tool verbatim. |
| 42 | +- **Neither kernel registry supports removal.** `VerbRegistry` is |
| 43 | + register/get/getByTool/list (`src/core/registry/verbs.js`), and the |
| 44 | + command registry has no removal either; `registerVerb` also projects a |
| 45 | + CLI command immediately. hypaware-server #364 calls |
| 46 | + `verbs.unregister(name)` behind a feature guard, so T2 owes exactly that |
| 47 | + shape, and it must retract the projected command too. |
| 48 | +- **The exports map has `./core/query` but nothing search-shaped**, so T3 |
| 49 | + adds a `./core/search` entry (types via the `types/` tree like its |
| 50 | + siblings). |
| 51 | +- **`withLocalOnlyVisibility` wraps any `AsyncDataSource`** |
| 52 | + (`src/core/query/visibility.js`), and `storage.dataSourceForTable` |
| 53 | + returns the icebird source with position deletes applied (LLP 0104 |
| 54 | + annotation in `ai-gateway/src/dataset.js`), so T4 gets purge-correct, |
| 55 | + visibility-correct rows without reimplementing either. |
| 56 | +- **Compaction finalizes immutable files in `compactGeneration`** |
| 57 | + (`src/core/cache/maintenance.js`), and the orphan sweep and retention |
| 58 | + delete recursively, so a `data/`-adjacent sidecar dies with its file. |
| 59 | + T6 builds there and pins the GC with a test rather than writing any. |
| 60 | +- **hypgrep 0.5.1 pins hyparquet 1.27.1**, below the exact 1.28.2 floor |
| 61 | + ([LLP 0222 #hyparquet-floor](./0222-one-pushdown-converter.decision.md#hyparquet-floor)), |
| 62 | + and needs `hyparquet-writer` only for `createIndex`; the root already |
| 63 | + carries the writer as an optionalDependency on the cache write path. |
| 64 | +- **The server's build hardening is directly portable**: worker-thread |
| 65 | + handle (`hypaware-server/src/search/index-worker.js`, 149 lines), |
| 66 | + three-attempt poison bound (server LLP 0158), sidecar existence as the |
| 67 | + idempotency marker (server LLP 0128). |
| 68 | + |
| 69 | +Not verified, and therefore not assumed: whether the verb codec's argv |
| 70 | +flag spelling for underscore param names (`--session_id`) is acceptable |
| 71 | +CLI ergonomics or T5 needs a codec alias; and the walk cost of a 90-day |
| 72 | +cache under the narrow projection (T4 measures it, T6 is sized by it). |
| 73 | + |
| 74 | +## The task graph {#tasks} |
| 75 | + |
| 76 | +### Wave 1 (deps `[]`), three-wide |
| 77 | + |
| 78 | +- **T1, hypgrep dependency + overrides.** `hypgrep` into `dependencies`; |
| 79 | + `overrides` pinning its `hyparquet` to 1.28.2 and `hyparquet-writer` to |
| 80 | + 0.16.6, beside the icebird override. Verify one deduped hyparquet copy |
| 81 | + (`npm ls hyparquet`) and that `npm pack --dry-run` stays sane. |
| 82 | + Complexity 1. |
| 83 | +- **T2, `unregister(name)` on the verb registry.** Removes the verb from |
| 84 | + both maps and retracts the projected CLI command, which needs a matching |
| 85 | + removal on the command registry (including alias cleanup). This is the |
| 86 | + affordance hypaware-server #364 already guards on; its contract is |
| 87 | + by-name, idempotent, and safe on an unknown name. Complexity 2. |
| 88 | +- **T3, hoist the shared search modules.** `src/core/search/`: the |
| 89 | + searchable-column allowlist and brute-scan projection (server |
| 90 | + `searchable-columns.js`, imported semantics unchanged), the matcher |
| 91 | + (literal/regex compile, `test`/`locate`/`rowTest`) and snippet-window |
| 92 | + constants, and the hit shapes as a `.d.ts`. New `./core/search` export. |
| 93 | + The server's import swap is server-repo work after this publishes, not |
| 94 | + part of this plan. Complexity 2. |
| 95 | + |
| 96 | +### Wave 2 (deps `[T1, T3]`) |
| 97 | + |
| 98 | +- **T4, the local grep service.** `src/core/search/grep_service.js`: flush |
| 99 | + the spool first (the query seam's freshness move), walk cache partitions |
| 100 | + newest-day-first, per-partition source from `storage.dataSourceForTable` |
| 101 | + wrapped in `withLocalOnlyVisibility` with the caller's `cwd`, files |
| 102 | + processed sequentially with the narrow projection for unindexed files |
| 103 | + and `parquetFind` through the sidecar for indexed ones, stop at the |
| 104 | + limit, report `truncated`/`exhausted` like the server. The indexed path |
| 105 | + is dormant until T6 but is exercised in tests with a hand-built sidecar. |
| 106 | + Complexity 3. |
| 107 | + |
| 108 | +### Wave 3 (deps `[T2, T4]`) |
| 109 | + |
| 110 | +- **T5, the verb.** `src/core/search/grep_verb.js` registered in |
| 111 | + `CORE_VERBS`: `name: 'query grep'`, `tool: 'grep_search'`, read-class, |
| 112 | + wire-compatible schema (T5 verifies the argv spelling question above), |
| 113 | + the server's coverage clause in the summary, `--include-local-only` |
| 114 | + with LLP 0105 #override semantics, rg-style snippet render honoring |
| 115 | + LLP 0225 (table escapes, `--format json` does not). After this task |
| 116 | + `hyp query grep` works locally (scan tier) and `--remote <target>` |
| 117 | + works against any current server. Complexity 2. |
| 118 | + |
| 119 | +### Wave 4 (deps `[T1, T3]`, parallel to T4/T5) |
| 120 | + |
| 121 | +- **T6, sidecar build at maintenance.** Port the server's worker handle |
| 122 | + and poison bound; `compactGeneration` queues an index build for each |
| 123 | + finalized data file over the allowlist columns; sidecar existence is |
| 124 | + the marker, no ledger; a test pins that orphan sweep and retention |
| 125 | + delete sidecars with their files, and that an unindexed or quarantined |
| 126 | + file is served by the scan tier. Complexity 3. |
| 127 | + |
| 128 | +### Wave 5 (deps `[T5, T6]`) |
| 129 | + |
| 130 | +- **T7, surfaces and proof.** `hyp query status` gains an index-coverage |
| 131 | + line; `hypaware-query` SKILL.md adds grep (fixing "these are the only |
| 132 | + subcommands"), the sub-`ngramLength` literal cliff, and the coverage |
| 133 | + caveat; a hermetic smoke (`query_grep_roundtrip`) asserts a scan-tier |
| 134 | + hit, a sidecar-tier hit, a purged row absent, and a `local-only` row |
| 135 | + withheld from a `full`-class caller and present with the override. |
| 136 | + Complexity 2. |
| 137 | + |
| 138 | +## Out of scope {#out-of-scope} |
| 139 | + |
| 140 | +- The server's import swap and dependency bump (hypaware-server repo, |
| 141 | + after T3 publishes and #364 merges). |
| 142 | +- Any config knob for indexed columns or index cadence: the allowlist is |
| 143 | + the shared constant by decision (LLP 0264 #shared), and cadence is |
| 144 | + compaction's. |
| 145 | +- Remote-side behavior changes: `--remote` intentionally hits the |
| 146 | + server's existing service unchanged. |
0 commit comments