15 KiB
15 KiB
Tasks — Search completion
Every checkbox MUST reflect work actually done in this change. The eight capabilities closed here were silently dropped during the original genesis migration (audit dated 2026-05-02). Do not retroactively tick boxes for surface that already shipped.
0. Pre-flight (non-negotiable)
- 0.1 Confirm clean working tree (
git statusreports nothing) and no in-flight worktrees with uncommitted work (git worktree listthengit statusin each) - 0.2 If any worktrees exist under
.claude/worktrees/, account for each one: still-active agent, work-pending-merge, or stale-and-prune. Stale worktrees MUST be removed (git worktree remove) before opening a new one — code that has been lost twice on main is the failure mode this guards against. - 0.3 Confirm the package set in
MANIFEST.mdmatchesfind packages -maxdepth 3 -name package.jsoncount. Drift here means a prior change archived without regenerating the manifest.
1. Spec authoring
- 1.1 Read
../suaveplan/packages/universal/search/src/{search-client,query-builder,pipeline,hooks,adapters,types}/*.tsend-to-end - 1.2 Read
openspec/specs/search/spec.md(if it exists; create the cap dir if not) - 1.3 Read
openspec/AGENTS.mdSections 0a (parity), 13 (spec-conformance), 14 (source mapping), 14a (dependents) - 1.4 Draft
proposal.mdcovering all eight gaps (SearchClient,QueryBuilder,SearchPipeline+ 4 analyzers,searchHooks, facet/boost/scoring types,ElasticsearchAdapter,OpenSearchAdapterwith AWS sigv4,MeiliSearchAdapter) - 1.5 Fill
## Source mappinginproposal.mdwith upstream line ranges for every requirement; every "no source line" requirement justified - 1.6 Draft spec delta at
specs/search/spec.md - 1.7 Every requirement in the spec delta carries a
**Verified by:**line naming concreteit(...)titles - 1.8
openspec change validate search-completionexits with no errors
2. Implementation
- 2.1
SearchClient<TDoc>orchestrator — port from upstreamsearch-client/search-client.tsintopackages/platform/search/src/client/search-client.ts; threadAbortSignalthrough every async method - 2.2
QueryBuilderfluent DSL — port from upstreamquery-builder/query-builder.tsintopackages/platform/search/src/query-builder/query-builder.ts; addmust/should/mustNot/range/prefix/fuzzy/geo/boost/scoringFunctionclauses on top of upstream surface - 2.3
SearchPipelinecomposable analyzer chain — port from upstreampipeline/search-pipeline.tsintopackages/platform/search/src/pipeline/search-pipeline.ts; threadAbortSignalthroughprocess(query, signal?) - 2.4
Analyzerinterface + co-located built-ins:pipeline/analyzers/lowercase/lowercase.ts(verbatim from upstream)pipeline/analyzers/stemming/stemming.ts(verbatim 18-rule Porter-like table)pipeline/analyzers/stopword/stopword.ts(verbatim 51-word default + configurable set)pipeline/analyzers/synonym/synonym.ts(verbatim one-way / bidirectional)
- 2.5
searchHookstyped event bus — port from upstreamhooks.tsintopackages/platform/search/src/hooks/search-hooks.ts; rewire to@suaveplan/eventsHooksclass; add'search:indexing'and'search:result'symmetric hooks alongside upstream'search:querying' - 2.6 Co-locate
FacetDistribution,FacetValue,FacetRequest,FieldBoost,ScoringFunction<T>,SearchSuggestion,SuggestOptionsshape types next to their consumers (no sharedtypes/dump) - 2.7
BaseElasticAdapter<TDoc>shared base class — port from upstreamadapters/base-elastic-adapter.tsintopackages/platform/search/src/adapters/base-elastic/base-elastic.ts; map upstreamHttpError/RateLimitError/TimeoutError/ConfigErrorto genesisSearchErrorcodes (PROVIDER_*) - 2.8
ElasticsearchAdapter<TDoc>— port from upstreamadapters/elasticsearch-adapter.tsintopackages/platform/search/src/adapters/elasticsearch/elasticsearch.ts; ES 8 default + optional ES 7 compatibility mode - 2.9
OpenSearchAdapter<TDoc>— port from upstreamadapters/opensearch-adapter.tsintopackages/platform/search/src/adapters/opensearch/opensearch.ts; pluggableAwsSigningConfig(no AWS-SDK runtime dep) - 2.10
MeiliSearchAdapter<TDoc>— port from upstreamadapters/meilisearch-adapter.tsintopackages/platform/search/src/adapters/meilisearch/meilisearch.ts - 2.11 Extend
SearchEngine<TDoc>interface — add optionalbulkIndex(documents, options?)andensureSchema()methods; default-throw withSEARCH.NOT_IMPLEMENTEDsoMemorySearchEnginecompiles unchanged - 2.12 Add
SEARCH_*codes (PROVIDER_UNREACHABLE,PROVIDER_TIMEOUT,PROVIDER_RATE_LIMITED,PROVIDER_CONFIG_INVALID,HOOK_HANDLER_FAILED,ANALYZER_INVALID,NOT_IMPLEMENTED,AWS_SIGNING_FAILED,BULK_INDEX_PARTIAL_FAILURE) toerrors/search-error.ts - 2.13 Wire OTel counters / spans on every public entrypoint:
search.client.queries,search.client.queries_duration_ms,search.client.indexed,search.pipeline.process_duration_ms,search.hooks.fired,search.adapter.requests,search.adapter.errors,search.adapter.bulk_indexed - 2.14 Wire Zod schemas for
SearchClientConfig,ElasticBaseConfig,ElasticAuth,ElasticsearchConfig,OpenSearchConfig,AwsSigningConfig,MeiliSearchConfig,FacetRequest,FieldBoost, everyAnalyzerconfig toschemas/search.schemas.ts - 2.15 Update
index.tsbarrel — re-export every new symbol; noexport *(one explicit subpath per module) - 2.16 Update
package.jsonexportsmap — add subpaths./client/search-client,./query-builder/query-builder,./pipeline/search-pipeline,./pipeline/analyzers/lowercase/lowercase,./pipeline/analyzers/stemming/stemming,./pipeline/analyzers/stopword/stopword,./pipeline/analyzers/synonym/synonym,./hooks/search-hooks,./adapters/base-elastic/base-elastic,./adapters/elasticsearch/elasticsearch,./adapters/opensearch/opensearch,./adapters/meilisearch/meilisearch - 2.17 Update
package.jsondependencies— add@suaveplan/events(workspace:^)
3. Tests
- 3.1
packages/platform/search/src/client/search-client.test.ts—index/search/find/suggestround-trip against an in-memory engine;defaultLimitapplied;fieldBoostsandscoringFunctionre-rank results;removeManyand bulk-index path - 3.2
packages/platform/search/src/client/search-client.spec-conformance.test.ts—searchHooks.hook('search:querying')rewrites the query before the engine sees it;'search:indexing'rewrites documents;'search:result'post-processes;AbortSignalaborts mid-flight; injectedMemorySearchEngineproduces deterministic order - 3.3
packages/platform/search/src/query-builder/query-builder.test.ts—term/must/should/mustNot/range/prefix/fuzzy/geo/filter/facet/sort/paginatebuild correctly;boostandscoringFunctionround-trip;reset()clears state - 3.4
packages/platform/search/src/pipeline/search-pipeline.test.ts—LowercaseAnalyzer/StemmingAnalyzer/StopWordAnalyzer/SynonymAnalyzereach tested in isolation;SearchPipelinewalks analyzers in order; custom analyzer registers; analyzer name returned viaanalyzerNames - 3.5
packages/platform/search/src/pipeline/search-pipeline.spec-conformance.test.ts—AbortSignalmid-pipeline rejects withANALYZER_INVALIDwhen an analyzer throws; bidirectional synonyms expand correctly; stop-word fallback returns original tokens when filtering would empty the list; stem rule guards against under-3 char results - 3.6
packages/platform/search/src/hooks/search-hooks.test.ts—'search:querying'/'search:indexing'/'search:result'lifecycle bus tested with multiple handlers; handler-priority order respected;unsubscriberemoves record - 3.7
packages/platform/search/src/hooks/search-hooks.spec-conformance.test.ts— handler exception raisesHOOK_HANDLER_FAILEDwith originalcause; per-hook payload type pinned viaexpectTypeOf - 3.8
packages/platform/search/src/adapters/base-elastic/base-elastic.test.ts—index/remove/search/clear/size/suggestround-trip against afetchmock;bulkIndexbuilds NDJSON;ensureIndex/deleteIndex/refresh; auth basic / apiKey / bearer;searchFieldshonoured; facets translate to aggregations - 3.9
packages/platform/search/src/adapters/base-elastic/base-elastic.spec-conformance.test.ts— 429 rate-limit response raisesPROVIDER_RATE_LIMITEDwithretryAfterMs; 408 / 504 raisesPROVIDER_TIMEOUT; missing host raisesPROVIDER_CONFIG_INVALID; non-OK responses throwSearchErrorwrappingHttpError - 3.10
packages/platform/search/src/adapters/elasticsearch/elasticsearch.test.ts— adapter sends ES 8 headers by default;compatibilityMode: trueswitches to ES 7 headers - 3.11
packages/platform/search/src/adapters/opensearch/opensearch.test.ts— adapter withoutawsSigningbehaves likeBaseElasticAdapter; withawsSigninginvokes the supplied signer once per request and uses the returnedRequestInit; signer rejection raisesAWS_SIGNING_FAILED - 3.12
packages/platform/search/src/adapters/meilisearch/meilisearch.test.ts—index/remove/search/clear/size/suggestround-trip; filters translate to MeiliSearch filter strings;ensureIndexpatchessearchableAttributes/filterableAttributes/sortableAttributes; faceted search returnsfacetDistribution - 3.13
packages/platform/search/src/adapters/meilisearch/meilisearch.spec-conformance.test.ts— 429 raisesPROVIDER_RATE_LIMITED; 408 / 504 raisesPROVIDER_TIMEOUT; 400 raisesQUERY_INVALID; missing API key path - 3.14 Cross-package
tests/dep-graph.test.ts—@suaveplan/searchdeclares@suaveplan/eventsas a runtime dep; the package still has zero browser-only deps; subpath count matches feature module count - 3.15 Every
it(...)carries{ timeout: 5000 }(or 30000 for genuinely-async adapter integration sweeps)
4. Documentation
- 4.1 Sibling
.mdfor every new implementation.ts(≥200 words, basic + advanced examples, full API reference) —client/search-client.md,query-builder/query-builder.md,pipeline/search-pipeline.md, four analyzer.mdfiles,hooks/search-hooks.md,adapters/base-elastic/base-elastic.md,adapters/elasticsearch/elasticsearch.md,adapters/opensearch/opensearch.md,adapters/meilisearch/meilisearch.md - 4.2 README links every co-located
.mdunder## Modules - 4.3 Update
CLAUDE.mdsearchpackage listing — remove "MemorySearchEngine + BM25 + trigram + tokenizer only" wording; describe the full restored surface (orchestrator, builder, pipeline, hooks, three production adapters) - 4.4 Refresh
STATUS.md"Completed on genesis" entry forsearchto reflect parity
5. Pre-archive verification (the 5-gate triple)
- 5.1
bun run check— Biome 0 errors / 0 warnings / 0 info across the monorepo - 5.2
bunx turbo lint typecheck test --filter=@suaveplan/search— green - 5.3
bunx turbo test:coverage --filter=@suaveplan/search— 100/100/100/100 - 5.4
bun scripts/openspec-conformance.ts @suaveplan/search— exit 0, noerrorfindings - 5.5
bunx turbo build --filter=@suaveplan/search— green - 5.6 Banned-pattern scan zero output:
grep -rn "@deprecated\|TODO\|FIXME\|ts-ignore\|ts-expect-error\|\.skip\|\.only" packages/platform/search/src/ --include="*.ts" - 5.7 Missing
.mdscan zero output (per AGENTS.md §13 step 5) - 5.8 README check
test -f packages/platform/search/README.md - 5.9 Bare-version check zero output (per AGENTS.md §13 step 7)
- 5.10 Subpath count matches feature module count (per AGENTS.md §13 step 8)
6. Pre-archive 7-row checklist (AGENTS.md Section 13)
- 6.1 Generics threaded;
expectTypeOfassertions on every literal-type claim (especiallySearchClient<TDoc>,BaseElasticAdapter<TDoc>,ElasticsearchAdapter<TDoc>,OpenSearchAdapter<TDoc>,MeiliSearchAdapter<TDoc>) - 6.2 Zod schemas wired at every declared boundary (
SearchClientConfig,ElasticBaseConfig,ElasticAuth,ElasticsearchConfig,OpenSearchConfig,AwsSigningConfig,MeiliSearchConfig,FacetRequest,FieldBoost, everyAnalyzerconfig) - 6.3 Every error code added in §2.12 thrown by at least one production code path; no orphans
- 6.4 Strict-mode behaviors tested both on and off (e.g.
compatibilityMode: truevsfalse;awsSigningpresent vs absent; bulk-index partial failure vs success) - 6.5 OTel counters / histograms emitted from production paths and asserted with a real in-memory
MeterProvider - 6.6 Lifecycle dedup tested where claimed (
SearchPipelineanalyzer registry idempotent on identical analyzer;searchHooks.hookreturns a one-shot unsubscriber) - 6.7 No retroactive tick — every box above reflects work done in this change
7. Archive
- 7.1 Fold spec delta into
openspec/specs/search/spec.md(creating the cap file if missing) - 7.2 Move
openspec/changes/search-completion/→openspec/archive/<yyyy-mm-dd>-search-completion/ - 7.3 Refresh
MANIFEST.md(dep set changed:@suaveplan/searchnow depends on@suaveplan/events) - 7.4 Update
STATUS.mdto mark the eight dropped capabilities as restored
Audit Note (2026-05-02)
Audit verdict: DEFERRED — 0/65 tasks evidenced in code.
Evidence reviewed:
packages/platform/search/src/contains onlyengine/memory-engine/,text/{tokenizer,scorer,trigram}/,errors/,schemas/,types/,constants/. Noclient/, noquery-builder/, nopipeline/, nohooks/, noadapters/.package.jsondoes not depend on@suaveplan/events.- The eight upstream capabilities (SearchClient, QueryBuilder, SearchPipeline + 4 analyzers, searchHooks, facet/boost/scoring types, ElasticsearchAdapter, OpenSearchAdapter with sigv4, MeiliSearchAdapter, SearchEngine.bulkIndex/ensureSchema) are absent.
Missing pieces:
client/search-client.tsorchestrator +query-builder/query-builder.tsfluent DSL with the upstream clauses.pipeline/search-pipeline.tsanalyzer chain withlowercase,stemming,stopword,synonymanalyzers ported verbatim from upstream.- Three production adapters (
adapters/elasticsearch,adapters/opensearchwith pluggableAwsSigningConfig,adapters/meilisearch) on top of a sharedBaseElasticAdapter, plus the 9 newSEARCH_*error codes.
Decision: leave change open. This is the largest of the deferred set (8 dropped capabilities, full dep graph rewire to @suaveplan/events, nine new error codes, parity audit).