@ansvar/ch-organic-regen-mcp 0.1.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 (108) hide show
  1. package/.github/workflows/check-freshness.yml +49 -0
  2. package/.github/workflows/ci.yml +21 -0
  3. package/.github/workflows/codeql.yml +25 -0
  4. package/.github/workflows/ghcr-build.yml +45 -0
  5. package/.github/workflows/gitleaks.yml +18 -0
  6. package/.github/workflows/ingest.yml +59 -0
  7. package/.github/workflows/publish.yml +24 -0
  8. package/CHANGELOG.md +15 -0
  9. package/CODEOWNERS +1 -0
  10. package/COVERAGE.md +47 -0
  11. package/DISCLAIMER.md +67 -0
  12. package/Dockerfile +26 -0
  13. package/LICENSE +17 -0
  14. package/PRIVACY.md +23 -0
  15. package/README.md +117 -0
  16. package/SECURITY.md +25 -0
  17. package/TOOLS.md +141 -0
  18. package/data/coverage.json +22 -0
  19. package/data/database.db +0 -0
  20. package/data/sources.yml +36 -0
  21. package/dist/db.d.ts +25 -0
  22. package/dist/db.d.ts.map +1 -0
  23. package/dist/db.js +184 -0
  24. package/dist/db.js.map +1 -0
  25. package/dist/http-server.d.ts +2 -0
  26. package/dist/http-server.d.ts.map +1 -0
  27. package/dist/http-server.js +263 -0
  28. package/dist/http-server.js.map +1 -0
  29. package/dist/jurisdiction.d.ts +18 -0
  30. package/dist/jurisdiction.d.ts.map +1 -0
  31. package/dist/jurisdiction.js +16 -0
  32. package/dist/jurisdiction.js.map +1 -0
  33. package/dist/metadata.d.ts +11 -0
  34. package/dist/metadata.d.ts.map +1 -0
  35. package/dist/metadata.js +31 -0
  36. package/dist/metadata.js.map +1 -0
  37. package/dist/server.d.ts +3 -0
  38. package/dist/server.d.ts.map +1 -0
  39. package/dist/server.js +209 -0
  40. package/dist/server.js.map +1 -0
  41. package/dist/tools/about.d.ts +15 -0
  42. package/dist/tools/about.d.ts.map +1 -0
  43. package/dist/tools/about.js +27 -0
  44. package/dist/tools/about.js.map +1 -0
  45. package/dist/tools/check-freshness.d.ts +15 -0
  46. package/dist/tools/check-freshness.d.ts.map +1 -0
  47. package/dist/tools/check-freshness.js +26 -0
  48. package/dist/tools/check-freshness.js.map +1 -0
  49. package/dist/tools/get-approved-inputs.d.ts +26 -0
  50. package/dist/tools/get-approved-inputs.d.ts.map +1 -0
  51. package/dist/tools/get-approved-inputs.js +28 -0
  52. package/dist/tools/get-approved-inputs.js.map +1 -0
  53. package/dist/tools/get-conversion-requirements.d.ts +28 -0
  54. package/dist/tools/get-conversion-requirements.d.ts.map +1 -0
  55. package/dist/tools/get-conversion-requirements.js +32 -0
  56. package/dist/tools/get-conversion-requirements.js.map +1 -0
  57. package/dist/tools/get-organic-standards.d.ts +27 -0
  58. package/dist/tools/get-organic-standards.d.ts.map +1 -0
  59. package/dist/tools/get-organic-standards.js +31 -0
  60. package/dist/tools/get-organic-standards.js.map +1 -0
  61. package/dist/tools/get-organic-subsidies.d.ts +28 -0
  62. package/dist/tools/get-organic-subsidies.d.ts.map +1 -0
  63. package/dist/tools/get-organic-subsidies.js +32 -0
  64. package/dist/tools/get-organic-subsidies.js.map +1 -0
  65. package/dist/tools/get-soil-health-guidance.d.ts +25 -0
  66. package/dist/tools/get-soil-health-guidance.d.ts.map +1 -0
  67. package/dist/tools/get-soil-health-guidance.js +27 -0
  68. package/dist/tools/get-soil-health-guidance.js.map +1 -0
  69. package/dist/tools/list-sources.d.ts +18 -0
  70. package/dist/tools/list-sources.d.ts.map +1 -0
  71. package/dist/tools/list-sources.js +61 -0
  72. package/dist/tools/list-sources.js.map +1 -0
  73. package/dist/tools/search-certification-guidance.d.ts +24 -0
  74. package/dist/tools/search-certification-guidance.d.ts.map +1 -0
  75. package/dist/tools/search-certification-guidance.js +24 -0
  76. package/dist/tools/search-certification-guidance.js.map +1 -0
  77. package/dist/tools/search-organic-rules.d.ts +25 -0
  78. package/dist/tools/search-organic-rules.d.ts.map +1 -0
  79. package/dist/tools/search-organic-rules.js +26 -0
  80. package/dist/tools/search-organic-rules.js.map +1 -0
  81. package/docker-compose.yml +12 -0
  82. package/eslint.config.js +26 -0
  83. package/package.json +54 -0
  84. package/scripts/ingest.ts +963 -0
  85. package/server.json +16 -0
  86. package/src/db.ts +225 -0
  87. package/src/http-server.ts +302 -0
  88. package/src/jurisdiction.ts +30 -0
  89. package/src/metadata.ts +45 -0
  90. package/src/server.ts +239 -0
  91. package/src/tools/about.ts +28 -0
  92. package/src/tools/check-freshness.ts +42 -0
  93. package/src/tools/get-approved-inputs.ts +44 -0
  94. package/src/tools/get-conversion-requirements.ts +50 -0
  95. package/src/tools/get-organic-standards.ts +48 -0
  96. package/src/tools/get-organic-subsidies.ts +50 -0
  97. package/src/tools/get-soil-health-guidance.ts +42 -0
  98. package/src/tools/list-sources.ts +75 -0
  99. package/src/tools/search-certification-guidance.ts +41 -0
  100. package/src/tools/search-organic-rules.ts +35 -0
  101. package/tests/db.test.ts +96 -0
  102. package/tests/helpers/seed-db.ts +145 -0
  103. package/tests/jurisdiction.test.ts +35 -0
  104. package/tests/tools/about.test.ts +22 -0
  105. package/tests/tools/check-freshness.test.ts +57 -0
  106. package/tests/tools/list-sources.test.ts +55 -0
  107. package/tests/tools/search-organic-rules.test.ts +56 -0
  108. package/tsconfig.json +19 -0
package/TOOLS.md ADDED
@@ -0,0 +1,141 @@
1
+ # Tools Reference
2
+
3
+ ## Meta Tools
4
+
5
+ ### `about`
6
+
7
+ Get server metadata: name, version, coverage, data sources, and links.
8
+
9
+ **Parameters:** None
10
+
11
+ **Returns:** Server name, version, jurisdiction list, data source names, tool count, homepage/repository links.
12
+
13
+ ---
14
+
15
+ ### `list_sources`
16
+
17
+ List all data sources with authority, URL, license, and freshness info.
18
+
19
+ **Parameters:** None
20
+
21
+ **Returns:** Array of data sources, each with `name`, `authority`, `official_url`, `retrieval_method`, `update_frequency`, `license`, `coverage`, `last_retrieved`.
22
+
23
+ ---
24
+
25
+ ### `check_data_freshness`
26
+
27
+ Check when data was last ingested, staleness status, and how to trigger a refresh.
28
+
29
+ **Parameters:** None
30
+
31
+ **Returns:** `status` (fresh/stale/unknown), `last_ingest`, `days_since_ingest`, `staleness_threshold_days`, `refresh_command`.
32
+
33
+ ---
34
+
35
+ ## Domain Tools
36
+
37
+ ### `search_organic_rules`
38
+
39
+ Volltextsuche ueber Bio-Richtlinien, Umstellung, Bodenfruchtbarkeit und regenerative Landwirtschaft. Search organic farming rules, conversion guidance, soil health, and regenerative agriculture topics.
40
+
41
+ | Parameter | Type | Required | Description |
42
+ |-----------|------|----------|-------------|
43
+ | `query` | string | Yes | Suchbegriff / free-text search query (German or English) |
44
+ | `topic` | string | No | Filter by topic (e.g. knospe, demeter, umstellung, bodenfruchtbarkeit) |
45
+ | `jurisdiction` | string | No | ISO 3166-1 alpha-2 code (default: CH) |
46
+ | `limit` | number | No | Max results (default: 20, max: 50) |
47
+
48
+ **Example:** `{ "query": "Futtermittel Knospe" }`
49
+
50
+ ---
51
+
52
+ ### `get_conversion_requirements`
53
+
54
+ Umstellungsanforderungen fuer den Biobetrieb: Zeitrahmen, Voraussetzungen, Unterstuetzungsmassnahmen. Get organic conversion timeline, requirements, and support measures by farm type.
55
+
56
+ | Parameter | Type | Required | Description |
57
+ |-----------|------|----------|-------------|
58
+ | `farm_type` | string | No | Betriebstyp (e.g. ackerbau, milchwirtschaft, obstbau, rebbau, gemuese) |
59
+ | `current_system` | string | No | Aktuelles System (e.g. oeln, konventionell, ip) |
60
+ | `jurisdiction` | string | No | ISO 3166-1 alpha-2 code (default: CH) |
61
+
62
+ **Returns:** Conversion timeline (years), requirements, and support measures for each matching farm type.
63
+
64
+ **Example:** `{ "farm_type": "milchwirtschaft", "current_system": "oeln" }`
65
+
66
+ ---
67
+
68
+ ### `get_organic_standards`
69
+
70
+ Bio-Richtlinien nach Standard (Knospe, Bio-Verordnung, Demeter) und Produktionsbereich. Get organic production rules comparing Bio Suisse Knospe, federal Bio-Verordnung, and Demeter standards.
71
+
72
+ | Parameter | Type | Required | Description |
73
+ |-----------|------|----------|-------------|
74
+ | `production_type` | string | No | Produktionsbereich (e.g. tierhaltung, pflanzenbau, verarbeitung, futtermittel) |
75
+ | `standard` | string | No | Standard: bio_verordnung, knospe, demeter |
76
+ | `jurisdiction` | string | No | ISO 3166-1 alpha-2 code (default: CH) |
77
+
78
+ **Returns:** Production rules with standard name, rule text, and description for each match.
79
+
80
+ **Example:** `{ "production_type": "tierhaltung", "standard": "knospe" }`
81
+
82
+ ---
83
+
84
+ ### `get_approved_inputs`
85
+
86
+ Zugelassene Betriebsmittel fuer Biolandbau (FiBL-Liste): Duenger, Pflanzenschutz, Futtermittel. Get approved inputs for organic farming from the FiBL Betriebsmittelliste.
87
+
88
+ | Parameter | Type | Required | Description |
89
+ |-----------|------|----------|-------------|
90
+ | `input_type` | string | No | Betriebsmitteltyp: duenger, pflanzenschutz, futtermittel |
91
+ | `jurisdiction` | string | No | ISO 3166-1 alpha-2 code (default: CH) |
92
+
93
+ **Returns:** Product name, description, restrictions, and source for each approved input.
94
+
95
+ **Example:** `{ "input_type": "pflanzenschutz" }`
96
+
97
+ ---
98
+
99
+ ### `get_organic_subsidies`
100
+
101
+ Bio-Beitrag und weitere Direktzahlungen fuer Biobetriebe: Beitragshoehe, Bedingungen, Kumulierung. Get organic farming subsidies (Bio-Beitrag) rates, conditions, and stacking rules.
102
+
103
+ | Parameter | Type | Required | Description |
104
+ |-----------|------|----------|-------------|
105
+ | `subsidy_type` | string | No | Beitragstyp (e.g. bio_beitrag, extenso, bff, tierwohl) |
106
+ | `zone` | string | No | Zone: talzone, huegelzone, bergzone_i, bergzone_ii, bergzone_iii, bergzone_iv |
107
+ | `jurisdiction` | string | No | ISO 3166-1 alpha-2 code (default: CH) |
108
+
109
+ **Returns:** Subsidy type, zone, rate in CHF/ha, conditions, and stacking rules.
110
+
111
+ **Example:** `{ "subsidy_type": "bio_beitrag", "zone": "bergzone_ii" }`
112
+
113
+ ---
114
+
115
+ ### `get_soil_health_guidance`
116
+
117
+ Bodenfruchtbarkeit und regenerative Methoden: Kompost, Gruenduengung, Fruchtfolge, Conservation Agriculture. Get soil health and regenerative agriculture guidance: composting, cover crops, rotation, agroforestry.
118
+
119
+ | Parameter | Type | Required | Description |
120
+ |-----------|------|----------|-------------|
121
+ | `topic` | string | No | Thema (e.g. kompost, gruenduengung, fruchtfolge, agroforstwirtschaft, untersaat, direktsaat) |
122
+ | `jurisdiction` | string | No | ISO 3166-1 alpha-2 code (default: CH) |
123
+
124
+ **Returns:** Topic, guidance text, technique description, and benefits for each match.
125
+
126
+ **Example:** `{ "topic": "kompost" }`
127
+
128
+ ---
129
+
130
+ ### `search_certification_guidance`
131
+
132
+ Zertifizierungsprozess Bio Suisse Knospe: Schritte, Inspektionsstellen, Kosten, Haeufigkeit. Search organic certification guidance: bio.inspecta, Bio Test Agro process, Knospe application steps.
133
+
134
+ | Parameter | Type | Required | Description |
135
+ |-----------|------|----------|-------------|
136
+ | `query` | string | Yes | Suchbegriff (e.g. anmeldung, kontrolle, kosten, inspektion) |
137
+ | `jurisdiction` | string | No | ISO 3166-1 alpha-2 code (default: CH) |
138
+
139
+ **Returns:** Certification step, description, inspector body, frequency, and cost notes.
140
+
141
+ **Example:** `{ "query": "anmeldung" }`
@@ -0,0 +1,22 @@
1
+ {
2
+ "server": "ch-organic-regen-mcp",
3
+ "jurisdiction": "CH",
4
+ "version": "0.1.0",
5
+ "last_ingest": "2026-04-05",
6
+ "data": {
7
+ "organic_standards": 18,
8
+ "conversion_requirements": 7,
9
+ "approved_inputs": 15,
10
+ "organic_subsidies": 14,
11
+ "soil_health": 10,
12
+ "certification_guidance": 8
13
+ },
14
+ "tools": 10,
15
+ "sources": [
16
+ "Bio Suisse Richtlinien (Knospe)",
17
+ "Bio-Verordnung SR 910.18 (BLW)",
18
+ "FiBL Betriebsmittelliste",
19
+ "Demeter Schweiz Richtlinien",
20
+ "DZV Bio-Beitrag (BLW)"
21
+ ]
22
+ }
Binary file
@@ -0,0 +1,36 @@
1
+ # Data sources for ch-organic-regen-mcp
2
+ sources:
3
+ - name: Bio Suisse Richtlinien (Knospe)
4
+ authority: Bio Suisse
5
+ url: https://www.bio-suisse.ch/de/richtlinien
6
+ license: Bio Suisse — public standards document
7
+ update_frequency: annual (Delegiertenversammlung)
8
+ last_retrieved: "2026-04-05"
9
+
10
+ - name: Bio-Verordnung (SR 910.18)
11
+ authority: Bundesamt fuer Landwirtschaft (BLW)
12
+ url: https://www.fedlex.admin.ch/eli/cc/1997/2498_2498_2498/de
13
+ license: Swiss Federal Administration — free reuse
14
+ update_frequency: periodic (with ordinance revisions)
15
+ last_retrieved: "2026-04-05"
16
+
17
+ - name: FiBL Betriebsmittelliste
18
+ authority: Forschungsinstitut fuer biologischen Landbau (FiBL)
19
+ url: https://www.betriebsmittelliste.ch
20
+ license: FiBL — public reference list
21
+ update_frequency: annual
22
+ last_retrieved: "2026-04-05"
23
+
24
+ - name: Demeter Schweiz Richtlinien
25
+ authority: Demeter Schweiz
26
+ url: https://www.demeter.ch/richtlinien
27
+ license: Demeter — public standards document
28
+ update_frequency: periodic
29
+ last_retrieved: "2026-04-05"
30
+
31
+ - name: DZV Bio-Beitrag (Direktzahlungsverordnung)
32
+ authority: Bundesamt fuer Landwirtschaft (BLW)
33
+ url: https://www.blw.admin.ch/blw/de/home/instrumente/direktzahlungen/produktionssystembeitraege.html
34
+ license: Swiss Federal Administration — free reuse
35
+ update_frequency: annual (DZV updates)
36
+ last_retrieved: "2026-04-05"
package/dist/db.d.ts ADDED
@@ -0,0 +1,25 @@
1
+ import BetterSqlite3 from 'better-sqlite3';
2
+ export interface Database {
3
+ get<T>(sql: string, params?: unknown[]): T | undefined;
4
+ all<T>(sql: string, params?: unknown[]): T[];
5
+ run(sql: string, params?: unknown[]): void;
6
+ close(): void;
7
+ readonly instance: BetterSqlite3.Database;
8
+ }
9
+ export declare function createDatabase(dbPath?: string): Database;
10
+ export declare function ftsSearch(db: Database, query: string, limit?: number): {
11
+ title: string;
12
+ body: string;
13
+ topic: string;
14
+ jurisdiction: string;
15
+ rank: number;
16
+ }[];
17
+ /**
18
+ * Tiered FTS5 search with automatic fallback.
19
+ * Tiers: exact phrase -> AND -> prefix -> stemmed prefix -> OR -> LIKE
20
+ */
21
+ export declare function tieredFtsSearch(db: Database, table: string, columns: string[], query: string, limit?: number): {
22
+ tier: string;
23
+ results: Record<string, unknown>[];
24
+ };
25
+ //# sourceMappingURL=db.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"db.d.ts","sourceRoot":"","sources":["../src/db.ts"],"names":[],"mappings":"AAAA,OAAO,aAAa,MAAM,gBAAgB,CAAC;AAI3C,MAAM,WAAW,QAAQ;IACvB,GAAG,CAAC,CAAC,EAAE,GAAG,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,OAAO,EAAE,GAAG,CAAC,GAAG,SAAS,CAAC;IACvD,GAAG,CAAC,CAAC,EAAE,GAAG,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,OAAO,EAAE,GAAG,CAAC,EAAE,CAAC;IAC7C,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,OAAO,EAAE,GAAG,IAAI,CAAC;IAC3C,KAAK,IAAI,IAAI,CAAC;IACd,QAAQ,CAAC,QAAQ,EAAE,aAAa,CAAC,QAAQ,CAAC;CAC3C;AAED,wBAAgB,cAAc,CAAC,MAAM,CAAC,EAAE,MAAM,GAAG,QAAQ,CA4BxD;AA+ED,wBAAgB,SAAS,CACvB,EAAE,EAAE,QAAQ,EACZ,KAAK,EAAE,MAAM,EACb,KAAK,GAAE,MAAW,GACjB;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,YAAY,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,EAAE,CAGtF;AAED;;;GAGG;AACH,wBAAgB,eAAe,CAC7B,EAAE,EAAE,QAAQ,EACZ,KAAK,EAAE,MAAM,EACb,OAAO,EAAE,MAAM,EAAE,EACjB,KAAK,EAAE,MAAM,EACb,KAAK,GAAE,MAAW,GACjB;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,CAAA;CAAE,CA2DtD"}
package/dist/db.js ADDED
@@ -0,0 +1,184 @@
1
+ import BetterSqlite3 from 'better-sqlite3';
2
+ import { join, dirname } from 'path';
3
+ import { fileURLToPath } from 'url';
4
+ export function createDatabase(dbPath) {
5
+ const resolvedPath = dbPath ??
6
+ join(dirname(fileURLToPath(import.meta.url)), '..', 'data', 'database.db');
7
+ const db = new BetterSqlite3(resolvedPath);
8
+ db.pragma('journal_mode = DELETE');
9
+ db.pragma('foreign_keys = ON');
10
+ initSchema(db);
11
+ return {
12
+ get(sql, params = []) {
13
+ return db.prepare(sql).get(...params);
14
+ },
15
+ all(sql, params = []) {
16
+ return db.prepare(sql).all(...params);
17
+ },
18
+ run(sql, params = []) {
19
+ db.prepare(sql).run(...params);
20
+ },
21
+ close() {
22
+ db.close();
23
+ },
24
+ get instance() {
25
+ return db;
26
+ },
27
+ };
28
+ }
29
+ function initSchema(db) {
30
+ db.exec(`
31
+ CREATE TABLE IF NOT EXISTS organic_standards (
32
+ id INTEGER PRIMARY KEY,
33
+ production_type TEXT NOT NULL,
34
+ standard TEXT NOT NULL,
35
+ rule TEXT NOT NULL,
36
+ description TEXT NOT NULL,
37
+ jurisdiction TEXT NOT NULL DEFAULT 'CH'
38
+ );
39
+
40
+ CREATE TABLE IF NOT EXISTS conversion_requirements (
41
+ id INTEGER PRIMARY KEY,
42
+ farm_type TEXT NOT NULL,
43
+ current_system TEXT NOT NULL,
44
+ timeline_years INTEGER NOT NULL,
45
+ requirements TEXT NOT NULL,
46
+ support_measures TEXT,
47
+ jurisdiction TEXT NOT NULL DEFAULT 'CH'
48
+ );
49
+
50
+ CREATE TABLE IF NOT EXISTS approved_inputs (
51
+ id INTEGER PRIMARY KEY,
52
+ input_type TEXT NOT NULL,
53
+ product_name TEXT NOT NULL,
54
+ description TEXT NOT NULL,
55
+ restrictions TEXT,
56
+ source TEXT NOT NULL,
57
+ jurisdiction TEXT NOT NULL DEFAULT 'CH'
58
+ );
59
+
60
+ CREATE TABLE IF NOT EXISTS organic_subsidies (
61
+ id INTEGER PRIMARY KEY,
62
+ subsidy_type TEXT NOT NULL,
63
+ zone TEXT,
64
+ rate_chf_ha REAL NOT NULL,
65
+ conditions TEXT NOT NULL,
66
+ stacking_rules TEXT,
67
+ jurisdiction TEXT NOT NULL DEFAULT 'CH'
68
+ );
69
+
70
+ CREATE TABLE IF NOT EXISTS soil_health (
71
+ id INTEGER PRIMARY KEY,
72
+ topic TEXT NOT NULL,
73
+ guidance TEXT NOT NULL,
74
+ technique TEXT NOT NULL,
75
+ benefits TEXT NOT NULL,
76
+ jurisdiction TEXT NOT NULL DEFAULT 'CH'
77
+ );
78
+
79
+ CREATE TABLE IF NOT EXISTS certification_guidance (
80
+ id INTEGER PRIMARY KEY,
81
+ step TEXT NOT NULL,
82
+ description TEXT NOT NULL,
83
+ inspector TEXT,
84
+ frequency TEXT,
85
+ cost_notes TEXT,
86
+ jurisdiction TEXT NOT NULL DEFAULT 'CH'
87
+ );
88
+
89
+ CREATE VIRTUAL TABLE IF NOT EXISTS search_index USING fts5(
90
+ title, body, topic, jurisdiction
91
+ );
92
+
93
+ CREATE TABLE IF NOT EXISTS db_metadata (
94
+ key TEXT PRIMARY KEY,
95
+ value TEXT
96
+ );
97
+
98
+ INSERT OR IGNORE INTO db_metadata (key, value) VALUES ('schema_version', '1.0');
99
+ INSERT OR IGNORE INTO db_metadata (key, value) VALUES ('mcp_name', 'Switzerland Organic & Regenerative Farming MCP');
100
+ INSERT OR IGNORE INTO db_metadata (key, value) VALUES ('jurisdiction', 'CH');
101
+ `);
102
+ }
103
+ const FTS_COLUMNS = ['title', 'body', 'topic', 'jurisdiction'];
104
+ export function ftsSearch(db, query, limit = 20) {
105
+ const { results } = tieredFtsSearch(db, 'search_index', FTS_COLUMNS, query, limit);
106
+ return results;
107
+ }
108
+ /**
109
+ * Tiered FTS5 search with automatic fallback.
110
+ * Tiers: exact phrase -> AND -> prefix -> stemmed prefix -> OR -> LIKE
111
+ */
112
+ export function tieredFtsSearch(db, table, columns, query, limit = 20) {
113
+ const sanitized = sanitizeFtsInput(query);
114
+ if (!sanitized.trim())
115
+ return { tier: 'empty', results: [] };
116
+ const columnList = columns.join(', ');
117
+ const select = `SELECT ${columnList}, rank FROM ${table}`;
118
+ const order = `ORDER BY rank LIMIT ?`;
119
+ // Tier 1: Exact phrase
120
+ const phrase = `"${sanitized}"`;
121
+ let results = tryFts(db, select, table, order, phrase, limit);
122
+ if (results.length > 0)
123
+ return { tier: 'phrase', results };
124
+ // Tier 2: AND
125
+ const words = sanitized.split(/\s+/).filter(w => w.length > 1);
126
+ if (words.length > 1) {
127
+ const andQuery = words.join(' AND ');
128
+ results = tryFts(db, select, table, order, andQuery, limit);
129
+ if (results.length > 0)
130
+ return { tier: 'and', results };
131
+ }
132
+ // Tier 3: Prefix
133
+ const prefixQuery = words.map(w => `${w}*`).join(' AND ');
134
+ results = tryFts(db, select, table, order, prefixQuery, limit);
135
+ if (results.length > 0)
136
+ return { tier: 'prefix', results };
137
+ // Tier 4: Stemmed prefix
138
+ const stemmed = words.map(w => stemWord(w) + '*');
139
+ const stemmedQuery = stemmed.join(' AND ');
140
+ if (stemmedQuery !== prefixQuery) {
141
+ results = tryFts(db, select, table, order, stemmedQuery, limit);
142
+ if (results.length > 0)
143
+ return { tier: 'stemmed', results };
144
+ }
145
+ // Tier 5: OR
146
+ if (words.length > 1) {
147
+ const orQuery = words.join(' OR ');
148
+ results = tryFts(db, select, table, order, orQuery, limit);
149
+ if (results.length > 0)
150
+ return { tier: 'or', results };
151
+ }
152
+ // Tier 6: LIKE fallback across organic_standards
153
+ const likeConditions = words.map(() => `(rule LIKE ? OR description LIKE ? OR production_type LIKE ?)`).join(' AND ');
154
+ const likeParams = words.flatMap(w => [`%${w}%`, `%${w}%`, `%${w}%`]);
155
+ try {
156
+ const likeResults = db.all(`SELECT rule as title, description as body, production_type as topic, jurisdiction FROM organic_standards WHERE ${likeConditions} LIMIT ?`, [...likeParams, limit]);
157
+ if (likeResults.length > 0)
158
+ return { tier: 'like', results: likeResults };
159
+ }
160
+ catch {
161
+ // LIKE fallback failed
162
+ }
163
+ return { tier: 'none', results: [] };
164
+ }
165
+ function tryFts(db, select, table, order, matchExpr, limit) {
166
+ try {
167
+ return db.all(`${select} WHERE ${table} MATCH ? ${order}`, [matchExpr, limit]);
168
+ }
169
+ catch {
170
+ return [];
171
+ }
172
+ }
173
+ function sanitizeFtsInput(query) {
174
+ return query
175
+ .replace(/["""'',,,,]/g, '"')
176
+ .replace(/[^a-zA-Z0-9\s*"_\u00C0-\u024F-]/g, ' ')
177
+ .replace(/\s+/g, ' ')
178
+ .trim();
179
+ }
180
+ function stemWord(word) {
181
+ return word
182
+ .replace(/(ung|heit|keit|lich|isch|ieren|tion|ment|ness|able|ible|ous|ive|ing|ers|ed|es|er|en|ly|s)$/i, '');
183
+ }
184
+ //# sourceMappingURL=db.js.map
package/dist/db.js.map ADDED
@@ -0,0 +1 @@
1
+ {"version":3,"file":"db.js","sourceRoot":"","sources":["../src/db.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=http-server.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"http-server.d.ts","sourceRoot":"","sources":["../src/http-server.ts"],"names":[],"mappings":""}