db-mcp 1.1.0 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (84) hide show
  1. package/.gitleaks.toml +9 -0
  2. package/.trivyignore +8 -0
  3. package/README.md +193 -120
  4. package/dist/{chunk-DWJXQEFA.js → chunk-5Y42NPBP.js} +4168 -2411
  5. package/dist/{chunk-AOUL5SHS.js → chunk-645ZEFLA.js} +70 -20
  6. package/dist/chunk-OKOVZ5QE.js +28 -0
  7. package/dist/chunk-SFJQCNG7.js +131 -0
  8. package/dist/{chunk-S5IDDSSB.js → chunk-VIDSICEL.js} +12 -1
  9. package/dist/chunk-WBER5YY4.js +2053 -0
  10. package/dist/{chunk-4IA3DB5C.js → chunk-X3MUUOWM.js} +19 -2
  11. package/dist/{chunk-Z2GFQU3G.js → chunk-Z7C2TM4L.js} +114 -21
  12. package/dist/cli.js +79 -5
  13. package/dist/{http-VSB7DBJR.js → http-6KF4ULDI.js} +199 -77
  14. package/dist/index.d.ts +231 -8
  15. package/dist/index.js +6 -5
  16. package/dist/{sqlite-VHBA4ABV.js → sqlite-U5KSYQXK.js} +63 -140
  17. package/dist/{sqlite-native-D2LH5ZT7.js → sqlite-native-JXMCFQBA.js} +576 -114
  18. package/dist/worker-script.js +34 -10
  19. package/logs/.gitkeep +1 -0
  20. package/mcp-config-example.json +83 -0
  21. package/package.json +9 -8
  22. package/playwright.config.ts +1 -1
  23. package/scripts/update-badges.ts +99 -0
  24. package/server.json +7 -5
  25. package/test-server/README.md +20 -23
  26. package/test-server/code-map.md +45 -33
  27. package/test-server/reset-database.ps1 +59 -17
  28. package/test-server/scripts/README.md +27 -0
  29. package/test-server/{test-help-resources.mjs → scripts/test-help-resources.mjs} +12 -5
  30. package/test-server/scripts/test-prompts.mjs +251 -0
  31. package/test-server/{test-tool-annotations.mjs → scripts/test-tool-annotations.mjs} +9 -4
  32. package/test-server/test-advanced/README.md +70 -0
  33. package/test-server/test-advanced/test-codemode-advanced-admin.md +174 -0
  34. package/test-server/test-advanced/test-codemode-advanced-core.md +193 -0
  35. package/test-server/test-advanced/test-codemode-advanced-geo.md +157 -0
  36. package/test-server/test-advanced/test-codemode-advanced-introspection.md +171 -0
  37. package/test-server/test-advanced/test-codemode-advanced-json.md +169 -0
  38. package/test-server/test-advanced/test-codemode-advanced-migration.md +160 -0
  39. package/test-server/test-advanced/test-codemode-advanced-stats.md +185 -0
  40. package/test-server/test-advanced/test-codemode-advanced-text.md +183 -0
  41. package/test-server/test-advanced/test-codemode-advanced-transactions.md +136 -0
  42. package/test-server/test-advanced/test-codemode-advanced-vector.md +141 -0
  43. package/test-server/test-codemode/README.md +121 -0
  44. package/test-server/test-codemode/test-codemode-admin.md +223 -0
  45. package/test-server/test-codemode/test-codemode-core.md +286 -0
  46. package/test-server/test-codemode/test-codemode-geo.md +177 -0
  47. package/test-server/test-codemode/test-codemode-introspection.md +190 -0
  48. package/test-server/test-codemode/test-codemode-json.md +237 -0
  49. package/test-server/test-codemode/test-codemode-migration.md +278 -0
  50. package/test-server/test-codemode/test-codemode-sandbox.md +413 -0
  51. package/test-server/test-codemode/test-codemode-stats.md +232 -0
  52. package/test-server/test-codemode/test-codemode-text.md +237 -0
  53. package/test-server/test-codemode/test-codemode-transactions.md +236 -0
  54. package/test-server/test-codemode/test-codemode-vector.md +244 -0
  55. package/test-server/test-codemode/test-codemode-wasm-degradation.md +394 -0
  56. package/test-server/test-database.sql +37 -1
  57. package/test-server/test-resources.md +43 -16
  58. package/test-server/test-tool-groups/README.md +100 -0
  59. package/test-server/test-tool-groups/test-admin-core.md +165 -0
  60. package/test-server/test-tool-groups/test-admin-extensions.md +133 -0
  61. package/test-server/{test-tools.md → test-tool-groups/test-core-data.md} +103 -17
  62. package/test-server/test-tool-groups/test-core-schema.md +240 -0
  63. package/test-server/test-tool-groups/test-geo-haversine.md +130 -0
  64. package/test-server/test-tool-groups/test-geo-spatialite.md +110 -0
  65. package/test-server/test-tool-groups/test-introspection-diagnostics.md +123 -0
  66. package/test-server/test-tool-groups/test-introspection-schema.md +133 -0
  67. package/test-server/test-tool-groups/test-json-read.md +219 -0
  68. package/test-server/test-tool-groups/test-json-write.md +157 -0
  69. package/test-server/test-tool-groups/test-migration.md +193 -0
  70. package/test-server/test-tool-groups/test-stats-advanced.md +118 -0
  71. package/test-server/test-tool-groups/test-stats-basic.md +156 -0
  72. package/test-server/test-tool-groups/test-text-advanced.md +169 -0
  73. package/test-server/test-tool-groups/test-text-basic.md +177 -0
  74. package/test-server/test-tool-groups/test-transactions.md +179 -0
  75. package/test-server/test-tool-groups/test-vector-read.md +130 -0
  76. package/test-server/test-tool-groups/test-vector-write.md +115 -0
  77. package/test-server/tool-reference.md +79 -58
  78. package/tsconfig.build.json +6 -0
  79. package/dist/chunk-DZQLDEQS.js +0 -879
  80. package/test-server/test-agent-experience.md +0 -243
  81. package/test-server/test-group-tools.md +0 -861
  82. package/test-server/test-tools-advanced-1.md +0 -517
  83. package/test-server/test-tools-advanced-2.md +0 -487
  84. package/test-server/test-tools-codemode.md +0 -629
@@ -0,0 +1,232 @@
1
+ # db-mcp Code Mode Testing: [stats]
2
+
3
+ > [!IMPORTANT]
4
+ > **Do not track progress in this file.** Track your test progress, coverage matrix, and findings in your internal task tracking system (artifact). However, you SHOULD edit this file to fix any factual errors, broken code, or incorrect assertions in the test prompts.
5
+ > If there is nothing to fix, don't update UNRELEASED.md.
6
+ > We're currently testing Native mode.
7
+
8
+ **Step 1:** Read `C:\Users\chris\Desktop\db-mcp\src\constants\server-instructions\gotchas.md` using `view_file`.
9
+
10
+ **Step 2:** Conduct an exhaustive test of the **stats** tool group using ONLY `sqlite_execute_code`. Do not use direct tool calls or terminal.
11
+
12
+ **Step 3:** The agent should update C:\Users\chris\Desktop\db-mcp\UNRELEASED.md with any/all changes/fixes.
13
+
14
+ ## WASM Mode
15
+
16
+ > When testing against a **WASM backend** (`--sqlite` / sql.js), apply these adjustments:
17
+
18
+ - **Skip Phase 3** entirely — all 6 window function tools (items 18-23) are `[NATIVE ONLY]`.
19
+ - **Phase 4**: Skip item 28 (`windowRowNumber`) — `[NATIVE ONLY]`.
20
+ - **Phase 5**: Skip items 45-50 (all `window*` tools) — `[NATIVE ONLY]`.
21
+ - **Phase 6**: Skip item 52 (`windowMovingAvg`) — `[NATIVE ONLY]`.
22
+ - All other phases (1, 2, 7) are fully WASM-compatible.
23
+
24
+ ## Reporting Format
25
+
26
+ - ❌ Fail: Tool errors or produces incorrect results
27
+ - ⚠️ Issue: Unexpected behavior or improvement opportunity
28
+ - 📦 Payload: Unnecessarily large response — monitor `metrics.tokenEstimate`.
29
+
30
+ ## Test Database Schema
31
+
32
+ | Table | Rows | Key Columns |
33
+ | ----------------- | ---- | ------------------------------------------------------------------------------- |
34
+ | test_products | 16 | id, name, price (REAL), category (3 values: electronics, accessories, office) |
35
+ | test_orders | 20 | id, product_id, total_price, order_date, status |
36
+ | test_measurements | 200 | id, sensor_id (INT 1-5), temperature, humidity, pressure, measured_at |
37
+ | test_events | 100 | id, event_type (page_view, click, purchase, login, search), user_id, event_date |
38
+ | test_locations | 15 | id, name, city (6 cities), latitude, longitude |
39
+
40
+ ## Testing Requirements
41
+
42
+ > [!CAUTION]
43
+ > **Zero tolerance for raw MCP errors.** Report as ❌.
44
+
45
+ 1. **Batched scripting**: Bundle checks with `failures` array.
46
+ 2. **Error path testing**: Every tool with `{}` (Zod) and domain error.
47
+ 3. **Token tracking**: Monitor `metrics.tokenEstimate`.
48
+ 4. **Coverage Matrix**: `| Tool | Happy Path | Domain Error | Zod Error |`
49
+ 5. **Deterministic checklist first**.
50
+ 6. **Code Over Docs**: Fix the handler code if standards (Structured Errors/Zod) are violated. Do NOT change docs/prompts to accommodate broken code.
51
+
52
+ ## Structured Error Response Pattern
53
+
54
+ Handler error ✅ = JSON with `success` + `error`. MCP error ❌ = raw text, `isError: true`.
55
+
56
+ ## Cleanup
57
+
58
+ - No write operations in stats — no cleanup needed.
59
+
60
+ ---
61
+
62
+ ## Phase 1: Core Stats — Happy Paths (batched)
63
+
64
+ > Bundle items 1-14 into 1-2 `sqlite_execute_code` calls.
65
+
66
+ 1. `sqlite.stats.statsBasic({table: "test_measurements", column: "temperature"})` → `count: 200`, `min`, `max`, `avg` present inside `stats` object
67
+ 2. `sqlite.stats.statsCount({table: "test_products"})` → `{count: 16}`
68
+ 3. `sqlite.stats.statsCount({table: "test_products", column: "category", distinct: true})` → 3 (electronics, accessories, office)
69
+ 4. `sqlite.stats.statsGroupBy({table: "test_measurements", groupByColumn: "sensor_id", valueColumn: "temperature", stat: "avg"})` → 5 results in `results` array
70
+ 5. `sqlite.stats.statsHistogram({table: "test_measurements", column: "temperature", buckets: 5})` → 5 buckets
71
+ 6. `sqlite.stats.statsPercentile({table: "test_measurements", column: "temperature", percentiles: [25, 50, 75, 90]})` → 4 values
72
+ 7. `sqlite.stats.statsCorrelation({table: "test_measurements", column1: "temperature", column2: "humidity"})` → value between -1 and 1
73
+ 8. `sqlite.stats.statsTopN({table: "test_products", column: "price", n: 3, orderDirection: "desc"})` → top 3 in `rows` array, `Laptop Pro 15` at 1299.99 first
74
+ 9. `sqlite.stats.statsDistinct({table: "test_locations", column: "city"})` → 6 cities
75
+ 10. `sqlite.stats.statsSummary({table: "test_measurements", columns: ["temperature", "humidity", "pressure"]})` → 3 summaries
76
+ 11. `sqlite.stats.statsFrequency({table: "test_events", column: "event_type"})` → 5 event types in `distribution` array, ~20 each
77
+ 12. `sqlite.stats.statsOutliers({table: "test_measurements", column: "temperature"})` → outlier detection result
78
+ 13. `sqlite.stats.statsRegression({table: "test_measurements", xColumn: "temperature", yColumn: "humidity", degree: 1})` → regression coefficients
79
+ 14. `sqlite.stats.statsHypothesis({table: "test_measurements", column: "temperature", testType: "ttest_one", expectedMean: 25})` → `statistic` and `pValue` present
80
+
81
+ ---
82
+
83
+ ## Phase 2: Anomaly Detection Suite — Happy Paths (batched)
84
+
85
+ 15. `sqlite.stats.detectAnomalies({table: "test_measurements", column: "temperature"})` → `anomalies` array present, `riskLevel` low
86
+ 16. `sqlite.stats.detectBloat()` → bloat detection (may return empty if no bloat)
87
+ 17. `sqlite.stats.detectSchemaRisks()` → `tables` array present, `highRiskCount` 0
88
+
89
+ ---
90
+
91
+ ## Phase 3: Window Functions `[NATIVE ONLY]` — Happy Paths (batched)
92
+
93
+ 18. `sqlite.stats.windowRowNumber({table: "test_products", orderBy: "price DESC"})` → ranked by price
94
+ 19. `sqlite.stats.windowRank({table: "test_products", orderBy: "price DESC"})` → rank with ties
95
+ 20. `sqlite.stats.windowRunningTotal({table: "test_orders", valueColumn: "total_price", orderBy: "order_date"})` → cumulative totals
96
+ 21. `sqlite.stats.windowMovingAvg({table: "test_measurements", valueColumn: "temperature", windowSize: 5, orderBy: "measured_at"})` → moving averages
97
+ 22. `sqlite.stats.windowLagLead({table: "test_orders", column: "total_price", direction: "lag", orderBy: "order_date"})` → lag values
98
+ 23. `sqlite.stats.windowNtile({table: "test_products", buckets: 4, orderBy: "price"})` → quartiles
99
+
100
+ ---
101
+
102
+ ## Phase 4: Stats Domain Errors (batched)
103
+
104
+ 🔴 24. `sqlite.stats.statsBasic({table: "nonexistent_xyz", column: "x"})` → `{success: false}`
105
+ 🔴 25. `sqlite.stats.statsBasic({table: "test_products", column: "nonexistent_col"})` → report behavior
106
+ 🔴 26. `sqlite.stats.statsCorrelation({table: "test_products", column1: "name", column2: "description"})` → error about non-numeric
107
+ 🔴 27. `sqlite.stats.detectAnomalies({table: "nonexistent_xyz", column: "x"})` → `{success: false}`
108
+ 🔴 28. `sqlite.stats.windowRowNumber({table: "nonexistent_xyz", orderBy: "x"})` `[NATIVE ONLY]` → `{success: false}`
109
+
110
+ ---
111
+
112
+ ## Phase 5: Stats Zod Validation (batched)
113
+
114
+ 🔴 29. `sqlite.stats.statsBasic({})` → `{success: false}`
115
+ 🔴 30. `sqlite.stats.statsCount({})` → `{success: false}`
116
+ 🔴 31. `sqlite.stats.statsGroupBy({})` → `{success: false}`
117
+ 🔴 32. `sqlite.stats.statsHistogram({})` → `{success: false}`
118
+ 🔴 33. `sqlite.stats.statsPercentile({})` → `{success: false}`
119
+ 🔴 34. `sqlite.stats.statsCorrelation({})` → `{success: false}`
120
+ 🔴 35. `sqlite.stats.statsTopN({})` → `{success: false}`
121
+ 🔴 36. `sqlite.stats.statsDistinct({})` → `{success: false}`
122
+ 🔴 37. `sqlite.stats.statsSummary({})` → `{success: false}`
123
+ 🔴 38. `sqlite.stats.statsFrequency({})` → `{success: false}`
124
+ 🔴 39. `sqlite.stats.statsOutliers({})` → `{success: false}`
125
+ 🔴 40. `sqlite.stats.statsRegression({})` → `{success: false}`
126
+ 🔴 41. `sqlite.stats.statsHypothesis({})` → `{success: false}`
127
+ 🔴 42. `sqlite.stats.detectAnomalies({})` → `{success: false}`
128
+ 🔴 43. `sqlite.stats.detectBloat({})` → `{success: false}` or success (no required params)
129
+ 🔴 44. `sqlite.stats.detectSchemaRisks({})` → `{success: false}` or success (no required params)
130
+ 🔴 45. `sqlite.stats.windowRowNumber({})` `[NATIVE ONLY]` → `{success: false}`
131
+ 🔴 46. `sqlite.stats.windowRank({})` `[NATIVE ONLY]` → `{success: false}`
132
+ 🔴 47. `sqlite.stats.windowLagLead({})` `[NATIVE ONLY]` → `{success: false}`
133
+ 🔴 48. `sqlite.stats.windowRunningTotal({})` `[NATIVE ONLY]` → `{success: false}`
134
+ 🔴 49. `sqlite.stats.windowMovingAvg({})` `[NATIVE ONLY]` → `{success: false}`
135
+ 🔴 50. `sqlite.stats.windowNtile({})` `[NATIVE ONLY]` → `{success: false}`
136
+
137
+ ---
138
+
139
+ ## Phase 6: Wrong-Type Numeric Coercion (batched)
140
+
141
+ 🔴 51. `sqlite.stats.statsHistogram({table: "test_measurements", column: "temperature", buckets: "abc"})` → coerced default (success) or handler error, NOT raw MCP
142
+ 🔴 52. `sqlite.stats.windowMovingAvg({table: "test_measurements", valueColumn: "temperature", windowSize: "abc", orderBy: "measured_at"})` `[NATIVE ONLY]` → coerced default (success) or handler error
143
+
144
+ ---
145
+
146
+ ## Phase 6.5: Gotcha Edge Cases (batched)
147
+
148
+ 53. `sqlite.stats.statsTopN({table: "test_articles", column: "title", n: 3})` → verify auto-exclusion of long-content columns (`body`, `description`, `notes`, etc.) from output when `selectColumns` is omitted (gotcha #13)
149
+ 54. `sqlite.stats.statsTopN({table: "test_articles", column: "title", n: 3, selectColumns: ["title", "body"]})` → explicit `selectColumns` overrides auto-exclusion — `body` should appear in results (gotcha #13)
150
+ 55. `sqlite.stats.detectBloat({includeZeroRisk: true})` → includes zero-risk tables in output (verify param is accepted and changes result set)
151
+ 56. `sqlite.stats.detectSchemaRisks({includeZeroRisk: true})` → includes zero-risk tables in output (verify param is accepted and changes result set)
152
+
153
+ ---
154
+
155
+ ## Phase 7: Multi-Step Workflow
156
+
157
+ ### 7.1 — Statistical analysis pipeline
158
+
159
+ ```javascript
160
+ const failures = [];
161
+ const basic = await sqlite.stats.statsBasic({
162
+ table: "test_measurements",
163
+ column: "temperature",
164
+ });
165
+ if (!basic || basic.stats.count !== 200)
166
+ failures.push("basic stats: count mismatch");
167
+
168
+ const pct = await sqlite.stats.statsPercentile({
169
+ table: "test_measurements",
170
+ column: "temperature",
171
+ percentiles: [50],
172
+ });
173
+ const corr = await sqlite.stats.statsCorrelation({
174
+ table: "test_measurements",
175
+ column1: "temperature",
176
+ column2: "humidity",
177
+ });
178
+ const top = await sqlite.stats.statsTopN({
179
+ table: "test_products",
180
+ column: "price",
181
+ n: 3,
182
+ });
183
+
184
+ return {
185
+ failures,
186
+ success: failures.length === 0,
187
+ summary: {
188
+ tempRange: `${basic.stats.min} - ${basic.stats.max}`,
189
+ median: pct.percentiles,
190
+ correlation: corr.correlation,
191
+ topProducts: top.rows,
192
+ },
193
+ };
194
+ ```
195
+
196
+ ---
197
+
198
+ ### 7.2 — Empty table boundary
199
+
200
+ ```javascript
201
+ const failures = [];
202
+ await sqlite.core.createTable({
203
+ table: "temp_cm_stats_empty",
204
+ columns: [
205
+ { name: "id", type: "INTEGER", primaryKey: true },
206
+ { name: "value", type: "REAL" },
207
+ ],
208
+ });
209
+ const basic = await sqlite.stats.statsBasic({
210
+ table: "temp_cm_stats_empty",
211
+ column: "value",
212
+ });
213
+ // Should return count: 0 or {success: false} — must not crash
214
+ if (basic.success === false) {
215
+ // Structured error is acceptable for empty table
216
+ } else if (basic.stats?.count !== 0) {
217
+ failures.push("expected count 0 for empty table, got: " + basic.stats?.count);
218
+ }
219
+ await sqlite.core.dropTable({ table: "temp_cm_stats_empty" });
220
+ return { failures, success: failures.length === 0, basicResult: basic };
221
+ ```
222
+
223
+ ---
224
+
225
+ ## Post-Test Procedures
226
+
227
+ 1. **Triage findings**: Create implementation plan if issues found
228
+ 2. **Scope of fixes**: Handler code, server-instructions, this prompt
229
+ 3. **Validate**: Instruct the user to run the test suite (Vitest/Playwright), lint, and typecheck. Do NOT run them yourself.
230
+ 4. **Commit**: Stage and commit — do NOT push
231
+ 5. **Token audit**: Report most expensive block
232
+ 6. **Final summary**: After testing/re-testing
@@ -0,0 +1,237 @@
1
+ # db-mcp Code Mode Testing: [text]
2
+
3
+ > [!IMPORTANT]
4
+ > **Do not track progress in this file.** Track your test progress, coverage matrix, and findings in your internal task tracking system (artifact). However, you SHOULD edit this file to fix any factual errors, broken code, or incorrect assertions in the test prompts.
5
+ > If there is nothing to fix, don't update UNRELEASED.md.
6
+ > We're currently testing Native mode.
7
+
8
+ **Step 1:** Read `C:\Users\chris\Desktop\db-mcp\src\constants\server-instructions\gotchas.md` using `view_file`.
9
+
10
+ **Step 2:** Conduct an exhaustive test of the **text** tool group using ONLY `sqlite_execute_code`. Do not use direct tool calls or terminal.
11
+
12
+ **Step 3:** The agent should update C:\Users\chris\Desktop\db-mcp\UNRELEASED.md with any/all changes/fixes.
13
+
14
+ ## WASM Mode
15
+
16
+ > When testing against a **WASM backend** (`--sqlite` / sql.js), apply these adjustments:
17
+
18
+ - **Skip Phase 2** entirely — all FTS5 tools (items 15-23) are `[NATIVE ONLY]`.
19
+ - **Phase 4** (Domain Errors): Skip item 28 (`ftsSearch`) — `[NATIVE ONLY]`.
20
+ - **Phase 5** (Zod Validation): Skip items 43-47 (`ftsCreate`, `ftsSearch`, `ftsRebuild`, `ftsMatchInfo`, `ftsHeadline`) — `[NATIVE ONLY]`.
21
+ - All other phases (1, 3, 6) are fully WASM-compatible — 14 text tools work identically.
22
+
23
+ ## Reporting Format
24
+
25
+ - ❌ Fail: Tool errors or produces incorrect results
26
+ - ⚠️ Issue: Unexpected behavior or improvement opportunity
27
+ - 📦 Payload: Unnecessarily large response — monitor `metrics.tokenEstimate`.
28
+
29
+ ## Test Database Schema
30
+
31
+ | Table | Rows | Key Columns |
32
+ | ------------- | ---- | ----------------------------------------------------------------- |
33
+ | test_products | 16 | id, name (row 16: `Café Décor Light` — accented), price, category |
34
+ | test_users | 9 | id, username, email, phone, bio |
35
+ | test_articles | 8 | id, title, body, author, category, published_at |
36
+
37
+ **Test data:**
38
+
39
+ - Emails: `@example.com`, `@company.org`, `@gmail.com`, etc. One user (`testuser`) has `test.user@gmail.com`
40
+ - Phone formats: `+1-555-0101`, `+44-20-7123-4567`, `+82-2-1234-5678`
41
+ - FTS searchable terms: `SQLite`, `database`, `JSON`, `FTS`, `vector`, `API`, `search`, `MCP`
42
+
43
+ ## Testing Requirements
44
+
45
+ > [!CAUTION]
46
+ > **Zero tolerance for raw MCP errors.** Report as ❌.
47
+
48
+ 1. **Batched scripting**: Bundle checks with `failures` array.
49
+ 2. **Error path testing**: Every tool with `{}` (Zod) and domain error.
50
+ 3. **Token tracking**: Monitor `metrics.tokenEstimate`.
51
+ 4. **Coverage Matrix**: `| Tool | Happy Path | Domain Error | Zod Error |`
52
+ 5. **Deterministic checklist first**.
53
+ 6. **Code Over Docs**: Fix the handler code if standards (Structured Errors/Zod) are violated. Do NOT change docs/prompts to accommodate broken code.
54
+
55
+ ## Structured Error Response Pattern
56
+
57
+ Handler error ✅ = JSON with `success` + `error`. MCP error ❌ = raw text, `isError: true`.
58
+
59
+ ## Cleanup
60
+
61
+ - Temporary tables: `temp_*` and any created `test_articles_fts`. Drop at end of script using `DROP TABLE IF EXISTS`.
62
+
63
+ ---
64
+
65
+ ## Phase 1: Text Tools — Happy Paths (batched)
66
+
67
+ > Bundle items 1-14 into 1-2 `sqlite_execute_code` calls.
68
+
69
+ 1. `sqlite.text.regexMatch({table: "test_users", column: "email", pattern: "@gmail\\.com$"})` → at least 1 result
70
+ 2. `sqlite.text.regexExtract({table: "test_users", column: "email", pattern: "@([^.]+)\\.", groupIndex: 1})` → domain parts
71
+ 3. `sqlite.text.fuzzyMatch({table: "test_products", column: "name", search: "Laptp", maxDistance: 3})` → `Laptop Pro 15`
72
+ 4. `sqlite.text.phoneticMatch({table: "test_products", column: "name", search: "Labtop"})` → `Laptop Pro 15`
73
+ 5. `sqlite.text.validate({table: "test_users", column: "email", pattern: "email"})` → all 9 valid
74
+ 6. `sqlite.text.validate({table: "test_users", column: "phone", pattern: "phone"})` → valid/invalid counts
75
+ 7. `sqlite.text.case({table: "test_users", column: "username", mode: "upper"})` → uppercased
76
+ 8. `sqlite.text.normalize({table: "test_products", column: "name", mode: "strip_accents"})` → `Café Décor Light` → `Cafe Decor Light`
77
+ 9. `sqlite.text.split({table: "test_users", column: "email", delimiter: "@"})` → local + domain parts
78
+ 10. `sqlite.text.concat({table: "test_users", columns: ["username", "email"], separator: " - "})` → concatenated
79
+ 11. `sqlite.text.trim({table: "test_users", column: "bio"})` → trimmed
80
+ 12. `sqlite.text.substring({table: "test_users", column: "username", start: 1, length: 4})` → first 4 chars
81
+ 13. `sqlite.text.sentiment({text: "I love this product"})` → sentiment scores
82
+ 14. `sqlite.text.advancedSearch({table: "test_products", column: "name", searchTerm: "keyboard", techniques: ["exact", "fuzzy", "phonetic"]})` → finds `Mechanical Keyboard`
83
+
84
+ ---
85
+
86
+ ## Phase 2: FTS5 Tools `[NATIVE ONLY]` — Happy Paths (batched)
87
+
88
+ 15. `sqlite.text.ftsCreate({sourceTable: "test_users", columns: ["username", "bio"], tableName: "temp_cm_fts"})` → created
89
+ 16. `sqlite.text.ftsRebuild({table: "temp_cm_fts"})` → rebuilt
90
+ 17. `sqlite.text.ftsSearch({table: "temp_cm_fts", query: "test*"})` → results
91
+ 18. `sqlite.text.ftsMatchInfo({table: "temp_cm_fts", query: "test*"})` → match info with scoring
92
+ 19. `sqlite.text.ftsHeadline({table: "test_articles_fts", query: "SQLite"})` → highlighted results
93
+ 20. Cleanup: drop `temp_cm_fts` (automatically drops associated sync triggers)
94
+ 21. `sqlite.text.ftsSearch({table: "test_articles_fts", query: "SQLite"})` → at least 1 result
95
+ 22. `sqlite.text.ftsSearch({table: "test_articles_fts", query: "MCP protocol"})` → article 3
96
+ 23. `sqlite.text.ftsSearch({table: "test_articles_fts", query: "nonexistent_term_xyz"})` → 0 results
97
+
98
+ ---
99
+
100
+ ## Phase 3: Text Write Tool (temp table)
101
+
102
+ 24. `sqlite.text.replace({table: "test_users", column: "email", searchPattern: "@example.com", replaceWith: "@test.org", whereClause: "email LIKE '%@example.com'"})` → 1 row affected
103
+ 25. Revert: `sqlite.text.replace({table: "test_users", column: "email", searchPattern: "@test.org", replaceWith: "@example.com", whereClause: "email LIKE '%@test.org'"})` → 1 row reverted
104
+
105
+ ---
106
+
107
+ ## Phase 4: Text Domain Errors (batched)
108
+
109
+ 🔴 26. `sqlite.text.regexMatch({table: "nonexistent_xyz", column: "x", pattern: "."})` → `{success: false}`
110
+ 🔴 27. `sqlite.text.fuzzyMatch({table: "test_users", column: "nonexistent_col", search: "test"})` → `{success: false}`
111
+ 🔴 28. `sqlite.text.ftsSearch({table: "nonexistent_fts_xyz", query: "test"})` `[NATIVE ONLY]` → `{success: false}`
112
+
113
+ ---
114
+
115
+ ## Phase 5: Text Zod Validation (batched)
116
+
117
+ 🔴 29. `sqlite.text.regexExtract({})` → `{success: false}`
118
+ 🔴 30. `sqlite.text.regexMatch({})` → `{success: false}`
119
+ 🔴 31. `sqlite.text.split({})` → `{success: false}`
120
+ 🔴 32. `sqlite.text.concat({})` → `{success: false}`
121
+ 🔴 33. `sqlite.text.replace({})` → `{success: false}`
122
+ 🔴 34. `sqlite.text.trim({})` → `{success: false}`
123
+ 🔴 35. `sqlite.text.case({})` → `{success: false}`
124
+ 🔴 36. `sqlite.text.substring({})` → `{success: false}`
125
+ 🔴 37. `sqlite.text.fuzzyMatch({})` → `{success: false}`
126
+ 🔴 38. `sqlite.text.phoneticMatch({})` → `{success: false}`
127
+ 🔴 39. `sqlite.text.normalize({})` → `{success: false}`
128
+ 🔴 40. `sqlite.text.validate({})` → `{success: false}`
129
+ 🔴 41. `sqlite.text.advancedSearch({})` → `{success: false}`
130
+ 🔴 42. `sqlite.text.sentiment({})` → `{success: false}`
131
+ 🔴 43. `sqlite.text.ftsCreate({})` `[NATIVE ONLY]` → `{success: false}`
132
+ 🔴 44. `sqlite.text.ftsSearch({})` `[NATIVE ONLY]` → `{success: false}`
133
+ 🔴 45. `sqlite.text.ftsRebuild({})` `[NATIVE ONLY]` → `{success: false}`
134
+ 🔴 46. `sqlite.text.ftsMatchInfo({})` `[NATIVE ONLY]` → `{success: false}`
135
+ 🔴 47. `sqlite.text.ftsHeadline({})` `[NATIVE ONLY]` → `{success: false}`
136
+
137
+ ---
138
+
139
+ ## Phase 5.5: Gotcha Edge Cases (batched)
140
+
141
+ 48. `sqlite.text.fuzzyMatch({table: "test_products", column: "name", search: "Laptop Pro 15", tokenize: false, maxDistance: 3})` → full-string matching (default tokenizes into words and matches per-token, gotcha #10)
142
+ 49. `sqlite.text.phoneticMatch({table: "test_products", column: "name", search: "Labtop", algorithm: "metaphone"})` → test Metaphone algorithm variant (default is Soundex)
143
+ 50. `sqlite.text.advancedSearch({table: "test_products", column: "name", searchTerm: "keyboard", techniques: ["phonetic"]})` → single technique instead of all 3 — verify it works in isolation
144
+
145
+ ---
146
+
147
+ ## Phase 6: Multi-Step Workflow
148
+
149
+ ### 6.1 — Text analysis pipeline
150
+
151
+ ```javascript
152
+ const failures = [];
153
+ // Validate emails, extract domains, search fuzzy, combine results
154
+ const validation = await sqlite.text.validate({
155
+ table: "test_users",
156
+ column: "email",
157
+ pattern: "email",
158
+ });
159
+ const domains = await sqlite.text.regexExtract({
160
+ table: "test_users",
161
+ column: "email",
162
+ pattern: "@([^.]+)\\.",
163
+ groupIndex: 1,
164
+ });
165
+ const fuzzy = await sqlite.text.fuzzyMatch({
166
+ table: "test_products",
167
+ column: "name",
168
+ search: "keybord",
169
+ maxDistance: 3,
170
+ });
171
+ if (!validation.success) failures.push("validation failed");
172
+ if (!domains.success) failures.push("domain extraction failed");
173
+ if (!fuzzy.success) failures.push("fuzzy search failed");
174
+ return {
175
+ failures,
176
+ success: failures.length === 0,
177
+ summary: {
178
+ validEmails: validation?.validCount,
179
+ domainCount: domains?.matches?.length,
180
+ fuzzyHits: fuzzy?.matches?.length,
181
+ },
182
+ };
183
+ ```
184
+
185
+ ---
186
+
187
+ ### 6.2 — FTS5 rebuild requirement verification `[NATIVE ONLY]`
188
+
189
+ ```javascript
190
+ const failures = [];
191
+ // Create FTS5 table
192
+ await sqlite.text.ftsCreate({
193
+ sourceTable: "test_users",
194
+ columns: ["username", "bio"],
195
+ tableName: "temp_cm_fts_rebuild",
196
+ });
197
+ // Search BEFORE rebuild — should return 0 results
198
+ const before = await sqlite.text.ftsSearch({
199
+ table: "temp_cm_fts_rebuild",
200
+ query: "admin",
201
+ });
202
+ if (before.rows?.length > 0)
203
+ failures.push(
204
+ "FTS5 search returned results before rebuild — gotcha #5 may not apply",
205
+ );
206
+ // Rebuild to populate index
207
+ await sqlite.text.ftsRebuild({ table: "temp_cm_fts_rebuild" });
208
+ // Search AFTER rebuild — should return results
209
+ const after = await sqlite.text.ftsSearch({
210
+ table: "temp_cm_fts_rebuild",
211
+ query: "admin",
212
+ });
213
+ if (!after.rows || after.rows.length === 0)
214
+ failures.push("FTS5 search returned 0 results after rebuild");
215
+ // Cleanup
216
+ await sqlite.core.writeQuery("DROP TABLE IF EXISTS temp_cm_fts_rebuild");
217
+ return {
218
+ failures,
219
+ success: failures.length === 0,
220
+ beforeCount: before.rows?.length || 0,
221
+ afterCount: after.rows?.length || 0,
222
+ };
223
+ ```
224
+
225
+ Expected: `beforeCount: 0`, `afterCount: > 0` — validates that `ftsRebuild` is required after `ftsCreate` to populate the index (gotcha #5).
226
+
227
+ ---
228
+
229
+ ## Post-Test Procedures
230
+
231
+ 1. **Cleanup**: Drop `temp_*` FTS tables
232
+ 2. **Triage findings**: Create implementation plan if issues found
233
+ 3. **Scope of fixes**: Handler code, server-instructions, this prompt
234
+ 4. **Validate**: Instruct the user to run the test suite (Vitest/Playwright), lint, and typecheck. Do NOT run them yourself.
235
+ 5. **Commit**: Stage and commit — do NOT push
236
+ 6. **Token audit**: Report most expensive block
237
+ 7. **Final summary**: After testing/re-testing