@capsiynau/intelligence 0.2.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.
package/CHANGELOG.md ADDED
@@ -0,0 +1,112 @@
1
+ # Changelog
2
+
3
+ All notable changes to `@capsiynau/intelligence` go here. Format follows
4
+ [Keep a Changelog](https://keepachangelog.com/en/1.1.0/). Versions follow
5
+ [SemVer](https://semver.org/).
6
+
7
+ ## [Unreleased]
8
+
9
+ ### Fixed
10
+
11
+ - `corrections/captureCorrections` now binds the `live_segments` lookup
12
+ to `(id, session_id)` together when `segmentId` is provided, not
13
+ `id` alone. A stale or mismatched `segmentId` from a client race or
14
+ tampered payload could otherwise reference a row in a different
15
+ session within the same org and contaminate the `word_boost_approved`
16
+ bucket with that other segment's language. RLS prevents cross-org
17
+ leakage but not cross-session-within-org. Codex follow-up on PR #511.
18
+
19
+ ### Added
20
+
21
+ - `corrections/captureCorrections` now accepts an optional `segmentId`
22
+ in its options. When provided, the function looks up the segment's
23
+ per-row `language` column (from `live_segments.language`, shipped in
24
+ Capsiynau PR #509) and uses it as the bucket for the new
25
+ `word_boost_approved` row. Falls back to `session.source_language`
26
+ if `segmentId` is absent or the segment's language is null.
27
+ - Why: in a mixed-language session (Phase 3 of notes-app#262 ships
28
+ per-chunk Whisper auto-detect), a correction in the non-primary
29
+ language would otherwise land in the wrong bucket. Phase 2 of the
30
+ sprint plumbs the segment-aware path now so corrections start
31
+ landing correctly the moment Phase 3 ships.
32
+ - Backwards-compatible: existing callers that pass only `sessionId`
33
+ keep working exactly as before. The new parameter is opt-in.
34
+
35
+ ### Documented
36
+
37
+ - Contract docstring on `corrections/tracker.looksLikeProperNoun` and
38
+ `corrections/tracker.extractProperNounCorrections` formalises the
39
+ language-agnostic invariant the relay's `word_boost_approved` loader
40
+ depends on. Proper nouns persisted by this heuristic are read across
41
+ languages so a name corrected in a Welsh session biases the next
42
+ English session in the same org. Loosening the heuristic to match
43
+ per-language vocabulary requires a coordinated change on the read
44
+ side - see the docstring for the two options (per-language filter
45
+ or row-level `cross_language` discriminator per Phase 1 of
46
+ notes-app#262).
47
+ - No behaviour change. Companion to Capsiynau PR #504 on
48
+ `feature/phase-15-live-relay` which ships the read-side fix.
49
+
50
+ ## [0.2.0] - 2026-05-17
51
+
52
+ ### Changed (BREAKING)
53
+
54
+ - `corrections/captureCorrections(before, after, opts)` now requires
55
+ `opts.supabase` (caller-provided Supabase client). Previously the
56
+ function imported `@/api/supabaseClient` directly, which coupled the
57
+ package to a host-app-specific module alias. Removing that import
58
+ unblocks the first npm publish and lets Nodiadau consume
59
+ `captureCorrections` without a `@/api/supabaseClient` shim.
60
+
61
+ ### Fixed
62
+
63
+ - `corrections/normaliseLanguageBucket` now matches `cy` prefixes
64
+ case-insensitively. Legacy / externally-written `source_language`
65
+ values like `'CY-GB'` or `'Cy-GB-north'` were previously classified
66
+ as `en-GB`, sending Welsh learned terms into the wrong bias bucket
67
+ (Codex catch on notes-app#261). One-line `.toLowerCase()` fix; no
68
+ behaviour change for already-lowercase values.
69
+
70
+ **Migration**:
71
+
72
+ ```js
73
+ // Before (0.1.0):
74
+ await captureCorrections(before, after, { sessionId })
75
+
76
+ // After (0.2.0):
77
+ import { supabase } from '@/api/supabaseClient' // or your own client
78
+ await captureCorrections(before, after, { sessionId, supabase })
79
+ ```
80
+
81
+ The function now calls `console.warn` and returns `[]` when
82
+ `opts.supabase` is missing — never throws. All other behaviour
83
+ (proper-noun heuristic, RPC fallback, NULL-language en-GB default)
84
+ is unchanged.
85
+
86
+ - README "Known issues" block on `captureCorrections` removed — the
87
+ Supabase coupling is gone in this release.
88
+
89
+ ## [0.1.0] - 2026-05-15
90
+
91
+ Initial extraction from `src/lib/` and `api/_lib/`. No behaviour change
92
+ versus the original in-repo paths — old import paths still resolve via
93
+ re-export shims during Phase A.
94
+
95
+ ### Added
96
+
97
+ - `welsh/normaliser` — Welsh text normalisation (diacritics, basic forms).
98
+ Moved from `src/lib/welshNormaliser.js` + `api/_lib/welshNormaliser.js`.
99
+ - `welsh/mutations` — soft / nasal / aspirate mutation helpers.
100
+ Extracted from the normaliser.
101
+ - `welsh/digraphs` — handling for `ch`, `dd`, `ff`, `ng`, `ll`, `ph`, `rh`, `th`.
102
+ - `corrections/extractProperNounCorrections` — Title-Case proper-noun
103
+ heuristic. Pure function.
104
+ - `corrections/captureCorrections` — writes promoted corrections to
105
+ `word_boost_approved`. **Currently couples to Supabase via direct
106
+ import** — see Unreleased / Known issues above.
107
+ - `glossary/*` — pure utilities for the Glossary Builder pipeline:
108
+ `parse`, `prompts`, `tools`, `url`, `import`, `persist`.
109
+ - `spellcheck/core` — `checkWord`, `checkSentence`, `tokenise`, `suggest`.
110
+ Mutation-aware. Accepts a caller-provided nspell instance (the package
111
+ itself does not bundle `nspell` or dictionary data — consumers bring
112
+ their own via the optional peer deps).
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Aled Parry / Capsiynau
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,134 @@
1
+ # @capsiynau/intelligence
2
+
3
+ Bilingual (Welsh + English) intelligence layer: text normalisation, mutation
4
+ handling, digraph splitting, spellcheck primitives, and glossary / correction
5
+ utilities.
6
+
7
+ **Pure functions, runs in Node or browser.** No DOM dependencies, no
8
+ network calls, no Supabase coupling — the one export that writes to
9
+ Supabase (`captureCorrections`) takes the client as an argument so the
10
+ package itself stays universal.
11
+
12
+ ## Install
13
+
14
+ ```bash
15
+ npm install @capsiynau/intelligence
16
+
17
+ # Spellcheck consumers also need the optional peer deps:
18
+ npm install nspell dictionary-cy dictionary-en-gb
19
+ ```
20
+
21
+ The dictionary peer-deps are optional — only required if you use the
22
+ `/spellcheck` subpath. Welsh normalisation, mutations, digraphs, and the
23
+ glossary helpers all work without them.
24
+
25
+ ## Quick start
26
+
27
+ ```js
28
+ // Welsh normalisation (whitespace, casing, common typos for downstream
29
+ // comparison — see SCHEMA.md for the exact rules)
30
+ import { normaliseWelsh } from '@capsiynau/intelligence/welsh'
31
+
32
+ normaliseWelsh(' Gymraeg ') // → 'Gymraeg'
33
+
34
+ // Mutation helpers — possibleRoots returns every plausible root form
35
+ // for a possibly-mutated surface word, tagged with the mutation kind
36
+ import { possibleRoots } from '@capsiynau/intelligence/welsh'
37
+
38
+ possibleRoots('Gymraeg')
39
+ // → [{ root: 'Gymraeg', kind: null }, { root: 'Cymraeg', kind: 'soft' }]
40
+
41
+ // Digraph-aware letter splitting (Welsh treats `ch`/`dd`/`ll`/... as single letters)
42
+ import { splitIntoLetters, welshLength } from '@capsiynau/intelligence/welsh'
43
+
44
+ splitIntoLetters('llyfr') // → ['ll', 'y', 'f', 'r']
45
+ welshLength('llyfr') // → 4 (not 5)
46
+
47
+ // Spellcheck (caller provides the nspell instance — dictionary-cy v2+ is
48
+ // already a parsed `{aff, dic}` object, so the construction is synchronous)
49
+ import nspell from 'nspell'
50
+ import cyDict from 'dictionary-cy'
51
+ import { checkSentence, suggest } from '@capsiynau/intelligence/spellcheck'
52
+
53
+ const cy = nspell(cyDict)
54
+
55
+ // `accept` is a Set of proper nouns / glossary terms / learned-vocab
56
+ // (lowercased) that must never be flagged. `welsh: true` enables
57
+ // mutation-aware fallback so `gath` resolves to `cath` via soft-mutation
58
+ // reversal before the word is declared misspelled.
59
+ const errors = checkSentence('Mae cymareg yn iath pwysig', {
60
+ nspell: cy,
61
+ accept: new Set(),
62
+ welsh: true,
63
+ })
64
+ // → [{ word: 'cymareg', start: 4, end: 11 }, { word: 'iath', start: 15, end: 19 }]
65
+
66
+ // Suggestions per misspelled token
67
+ const fixes = suggest('cymareg', { nspell: cy, max: 6 })
68
+ // → ['Cymraeg']
69
+
70
+ // Proper-noun extraction (correction-learning heuristic)
71
+ import { extractProperNounCorrections } from '@capsiynau/intelligence/corrections'
72
+
73
+ const corrections = extractProperNounCorrections(
74
+ 'Allard Perry spoke at the event',
75
+ 'Aled Parry spoke at the event',
76
+ )
77
+ // → ['Aled Parry']
78
+
79
+ // And to persist into word_boost_approved (caller provides the Supabase
80
+ // client; the package itself has zero Supabase coupling):
81
+ import { captureCorrections } from '@capsiynau/intelligence/corrections'
82
+ import { supabase } from './your-supabase-client' // your own client
83
+
84
+ const persisted = await captureCorrections(before, after, { sessionId, supabase })
85
+ // → array of terms written (silent on duplicates; never throws)
86
+ ```
87
+
88
+ ## Subpaths
89
+
90
+ | Import | Provides |
91
+ |--------|----------|
92
+ | `@capsiynau/intelligence/welsh` | `normaliseWelsh`, `possibleRoots` + mutation helpers, `splitIntoLetters` + digraph utilities |
93
+ | `@capsiynau/intelligence/spellcheck` | `checkWord`, `checkSentence`, `tokenise`, `suggest` — mutation-aware, accepts caller's nspell instance |
94
+ | `@capsiynau/intelligence/corrections` | `extractProperNounCorrections` (pure), `captureCorrections` (accepts caller-provided Supabase client) |
95
+ | `@capsiynau/intelligence/glossary` | Glossary Builder pipeline helpers: `parse`, `prompts`, `tools`, `url`, `import`, `persist` |
96
+
97
+ ## Design
98
+
99
+ - **Caller-provided dictionaries.** The package doesn't bundle nspell or
100
+ hunspell data — those are ~700 KB Welsh + ~250 KB English. Consumers
101
+ bring their own so they can lazy-load, cache in IndexedDB, or use
102
+ WebAssembly variants without the package dictating choice.
103
+ - **Pure where possible.** Welsh / spellcheck / glossary modules are
104
+ side-effect-free. Easy to test, easy to compose, runs in any JS runtime.
105
+ - **Mutation-aware spellcheck.** A Welsh spellchecker that doesn't
106
+ understand initial consonant mutations is useless on real Welsh text.
107
+ `checkWord` reverses soft / nasal / aspirate mutations before looking
108
+ up the root, so `Gymraeg` and `Nghymraeg` both resolve to `Cymraeg`.
109
+
110
+ ## Versioning
111
+
112
+ - **Patch** (`0.1.x`) — bug fixes, no API change
113
+ - **Minor** (`0.x.0`) — additive API, no consumer changes required
114
+ - **Major** (`x.0.0`) — breaking changes
115
+
116
+ Pre-1.0: the API is firming up. Pin a minor version in production
117
+ consumers (`"@capsiynau/intelligence": "~0.1.0"`) and watch the
118
+ CHANGELOG for breaking moves.
119
+
120
+ ## Supabase schema contract
121
+
122
+ See `SCHEMA.md` for the public contract on the tables the glossary +
123
+ corrections modules interact with (when wired up by the caller).
124
+
125
+ ## Cross-repo sync (Capsiynau ecosystem)
126
+
127
+ Capsiynau consumes this package directly via the monorepo path.
128
+ Nodiadau consumes via either `npm install @capsiynau/intelligence`
129
+ (once published) or `git subtree pull` (canonical pattern documented
130
+ at `docs/publish-intelligence-package.md`).
131
+
132
+ ## License
133
+
134
+ MIT. See [LICENSE](./LICENSE).
package/SCHEMA.md ADDED
@@ -0,0 +1,75 @@
1
+ # @capsiynau/intelligence — Supabase schema contract
2
+
3
+ This document records the Supabase tables the intelligence layer reads from and writes to. **The package itself is stateless** — it does not own these tables. Both Capsiynau and Nodiadau provision them via their own migrations against the shared Supabase project.
4
+
5
+ Treat this file as the **public contract**: bumping a column type, dropping a column, or changing RLS semantics requires a major-version bump on `@capsiynau/intelligence` and a coordinated Nodiadau sync.
6
+
7
+ ## Tables
8
+
9
+ | Table | Purpose | Consumer | Authoritative migration |
10
+ |-------|---------|----------|--------------------------|
11
+ | `word_boost_approved` | Approved learned vocabulary, ranked by `confirmed_by_count`. Read at session start by live-relay to bias Whisper. | live-relay, suggestion UI | `20260515_word_boost_approved.sql` + `..._confidence.sql` + `..._realtime.sql` |
12
+ | `word_boost_candidates` | Pending vocab from correctionTracker, awaiting moderator approval. | LiveSessionMonitor, AdminMonitor | `20260515_word_boost_candidates_org_scope.sql` |
13
+ | `correction_logs` | Full edit history per segment. Source signal for learning loops. | LiveSessionMonitor, paraphrase pipeline | `schema.sql` (since launch) |
14
+ | `paraphrase_corrections` | Refinement memory: which paraphrase patterns to favour / suppress. | live-polish pipeline (PR #436) | `20260515_paraphrase_corrections.sql` (#435) |
15
+ | `series_memory` | Long-form context across recurring shows/sessions. | refinement worker | `20260515_series_memory.sql` |
16
+ | `glossaries` + `terms` + `term_votes` | Manually curated org/global glossaries. Higher priority than learned vocab. | editor, glossary UI | `schema.sql` |
17
+ | `metadata_reviews` | QA approvals on AI-generated metadata. | metadata pipeline | `schema.sql` |
18
+
19
+ ## Isolation invariants
20
+
21
+ Every table above is **org-scoped via `get_user_org_ids()`** in its RLS policy. Cross-org leakage is impossible from the client. Server writes use the admin client + explicit `.eq('organisation_id', ...)` to enforce the boundary.
22
+
23
+ **Never** widen an RLS policy to query `memberships` directly — it causes 42P17 infinite recursion. See root `CLAUDE.md` for the rule.
24
+
25
+ ## Reading vs writing — which layer
26
+
27
+ The intelligence library only contains **client-side helpers** (heuristic logic for what to learn from a correction, normalisation, mutation). The actual database writes happen in **app code**:
28
+
29
+ | Operation | Where it lives | Why |
30
+ |-----------|----------------|-----|
31
+ | `extractProperNounCandidates(diff)` (pure logic) | `@capsiynau/intelligence/corrections` | Heuristic — shared |
32
+ | Inserting into `word_boost_candidates` | `src/pages/LiveSessionMonitor.jsx` (Capsiynau) / Nodiadau equivalent | Needs admin Supabase client; org_id from auth context |
33
+ | Promoting candidate → approved | Server endpoint (admin moderation UI) | Privileged, audited |
34
+ | Reading approved vocab for STT bias | `worker/live-relay.js` (Capsiynau-specific) | Pipeline integration |
35
+
36
+ This split is deliberate. The library stays universal (no Supabase dependency in the universal entry point) and the apps own their I/O.
37
+
38
+ ## Versioning policy
39
+
40
+ - **Patch** — no schema impact (rename a helper, fix a bug)
41
+ - **Minor** — additive only (new helper, new exported constant). No DB change required.
42
+ - **Major** — semantic change to any contract above. Triggers:
43
+ - Dropping a column the library references
44
+ - Changing the meaning of a column (e.g. `confirmed_by_count` becomes a sum vs a count)
45
+ - Tightening or loosening RLS in a way the library assumes
46
+ - Renaming a table
47
+
48
+ Major bumps require: PR description listing the breaking change, Nodiadau-side companion PR ready before merge, paired migration with explicit migration notes for operators.
49
+
50
+ ## Schema drift detection
51
+
52
+ Until automated, the manual check before any major release:
53
+
54
+ ```sql
55
+ -- Snapshot the relevant tables' columns
56
+ SELECT table_name, column_name, data_type, is_nullable
57
+ FROM information_schema.columns
58
+ WHERE table_name IN (
59
+ 'word_boost_approved', 'word_boost_candidates', 'correction_logs',
60
+ 'paraphrase_corrections', 'series_memory',
61
+ 'glossaries', 'terms', 'term_votes', 'metadata_reviews'
62
+ )
63
+ ORDER BY table_name, ordinal_position;
64
+
65
+ -- And the policies
66
+ SELECT tablename, policyname, cmd, qual
67
+ FROM pg_policies
68
+ WHERE tablename IN (
69
+ 'word_boost_approved', 'word_boost_candidates', 'correction_logs',
70
+ 'paraphrase_corrections', 'series_memory',
71
+ 'glossaries', 'terms', 'term_votes', 'metadata_reviews'
72
+ );
73
+ ```
74
+
75
+ Diff against the previous release's snapshot. Any breaking change → major version bump.
package/package.json ADDED
@@ -0,0 +1,64 @@
1
+ {
2
+ "name": "@capsiynau/intelligence",
3
+ "version": "0.2.0",
4
+ "description": "Bilingual (Welsh + English) intelligence layer: text normalisation, mutation handling, digraph splitting, spellcheck primitives, and glossary/correction utilities. Pure functions, runs in Node or browser.",
5
+ "type": "module",
6
+ "license": "MIT",
7
+ "author": "Aled Parry <aled@aledparry.com>",
8
+ "homepage": "https://github.com/aledprysparry/Capsiynau_V2_ClaudeAI/tree/main/packages/intelligence",
9
+ "repository": {
10
+ "type": "git",
11
+ "url": "git+https://github.com/aledprysparry/Capsiynau_V2_ClaudeAI.git",
12
+ "directory": "packages/intelligence"
13
+ },
14
+ "bugs": {
15
+ "url": "https://github.com/aledprysparry/Capsiynau_V2_ClaudeAI/issues"
16
+ },
17
+ "keywords": [
18
+ "welsh",
19
+ "cymraeg",
20
+ "bilingual",
21
+ "spellcheck",
22
+ "mutation",
23
+ "digraph",
24
+ "normaliser",
25
+ "glossary",
26
+ "nlp",
27
+ "capsiynau"
28
+ ],
29
+ "main": "./src/index.js",
30
+ "exports": {
31
+ ".": "./src/index.js",
32
+ "./welsh": "./src/welsh/index.js",
33
+ "./corrections": "./src/corrections/index.js",
34
+ "./glossary": "./src/glossary/index.js",
35
+ "./spellcheck": "./src/spellcheck/index.js"
36
+ },
37
+ "files": [
38
+ "src",
39
+ "README.md",
40
+ "SCHEMA.md",
41
+ "CHANGELOG.md",
42
+ "LICENSE"
43
+ ],
44
+ "engines": {
45
+ "node": ">=18"
46
+ },
47
+ "peerDependencies": {
48
+ "nspell": "^2.1.0",
49
+ "dictionary-cy": "^2.0.0",
50
+ "dictionary-en-gb": "^3.0.0"
51
+ },
52
+ "peerDependenciesMeta": {
53
+ "nspell": { "optional": true },
54
+ "dictionary-cy": { "optional": true },
55
+ "dictionary-en-gb": { "optional": true }
56
+ },
57
+ "publishConfig": {
58
+ "access": "public"
59
+ },
60
+ "scripts": {
61
+ "test": "vitest run __tests__"
62
+ },
63
+ "sideEffects": false
64
+ }
@@ -0,0 +1,6 @@
1
+ /**
2
+ * @capsiynau/intelligence/corrections
3
+ *
4
+ * Public surface for the correction-learning loop.
5
+ */
6
+ export * from './tracker.js'