You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Closes the last two vodka anti-pattern remnants in oddkit_challenge per
P1.3.3, mirroring the matchers and the no-microsecond-caching discipline
gate shipped in 0.20.0.
Item 1 — D5 split-by-fit applied to challenge:
- evaluatePrerequisiteCheck migrated from regex-per-check to stemmed
set intersection over PrereqMatchVocab (parsed once at canon-fetch).
- New parseCheckColumn helper extracts quoted vocabulary -> stemmedTokens
Set and detects the four structural-test hints (URL, numeric,
proper-noun, citation).
- BasePrerequisite + ChallengeTypeDef.prerequisiteOverlays both extended
with PrereqMatchVocab via interface mixin.
- Runtime: tokenize(input) hoisted out of the per-prereq loop;
per-prereq cost is now a Set lookup not a regex compile.
- Strictly additive: every input that matched the prior regex still
matches; stemmed variations newly match; structural side-tests
preserved verbatim from pre-refactor.
Item 2 — D9 applied to cachedChallengeTypeIndex:
- Module-level cachedChallengeTypeIndex + URL companion deleted.
- getOrBuildChallengeTypeIndex function deleted.
- cleanup_storage resets deleted.
- runChallengeAction call site rebuilds the BM25 type index inline per
request (microsecond derivation; plumbing tax removed). Same pattern
gate shipped in 0.20.0.
Item 3 — graduates new canon principle (separate canon PR, merged first):
- klappy://canon/principles/cache-fetches-and-parses live at klappy.dev
3726073 (PR #125). Third deciding-argument recurrence satisfied.
Verification:
- typecheck clean
- governance-parser.test.mjs 105/105 pass
- ~9 new smoke assertions covering stemmed base + per-type matches,
structural-test preservation (URL, proper-noun, citation), rebuild
stability, and pre-refactor backward compat.
- Lockfile re-synced to 0.21.0 (was stale at 0.18.0 since 0.19.0 release).
PRD: /home/claude/work/prd-p1-3-3.md (working dir, not committed).
Handoff: klappy://odd/handoffs/2026-04-20-p1-3-3-challenge-revisit.
Copy file name to clipboardExpand all lines: CHANGELOG.md
+18Lines changed: 18 additions & 0 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -7,6 +7,24 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
8
8
## [Unreleased]
9
9
10
+
## [0.21.0] - 2026-04-20
11
+
12
+
### Changed
13
+
14
+
- **`oddkit_challenge` prerequisite evaluation migrated from regex-per-check to stemmed set intersection** (per PRD D5 from P1.3.2 — split-by-fit). Each prereq now evaluates via `Array.from(prereq.stemmedTokens).some(s => inputStems.has(s))` over a Set computed once at canon-fetch time, with `tokenize(input)` hoisted out of the per-prereq loop. **Strictly additive**: every input that matched the prior regex still matches, plus stemmed variations now do too — `problems identified` satisfies `evidence-cited` (stems `problem` + `identif`), `considered alternatives` satisfies `alternatives-considered` (stems `consid` + `altern`), `acknowledged the risks` satisfies `risk-acknowledged` (stems `acknowledg` + `risk`). The four structural side-tests (URL / numeric / proper-noun / citation) preserved verbatim from the pre-refactor evaluator because they cover cases the keyword vocabulary cannot — `source-named` inputs like `"here's the URL: https://..."` have no stemmed overlap with the vocab `per / according to / from / source: / who said / where i read` but the URL structural test catches them. The conservative no-keyword-no-flag fallback (pass on `input.trim().length >= 20`) also preserved. Same matcher gate shipped in 0.20.0.
15
+
16
+
-**`oddkit_challenge` type-detection BM25 index cache removed** (per PRD D9 from P1.3.2 — don't cache microsecond derivations). `cachedChallengeTypeIndex` and `cachedChallengeTypeIndexKnowledgeBaseUrl` module-level fields deleted; `getOrBuildChallengeTypeIndex` function deleted; `cleanup_storage` resets deleted; the call site in `runChallengeAction` rebuilds the BM25 index inline per request via `buildBM25Index(types.map(t => ({id: t.slug, text: t.detectionText})), vocab.stopWords)`. Same pattern gate shipped in 0.20.0. Removes module-level cache state, URL-keyed invalidation logic, cleanup_storage wiring, and drift risk when source data changes — the four hidden costs enumerated in the new canon principle. Parse-product caches (`cachedChallengeTypes`, `cachedBasePrerequisites`, `cachedNormativeVocabulary`, `cachedStakesCalibration`) remain — those are actual parse work.
17
+
18
+
### Added
19
+
20
+
-**New canon principle:**`klappy://canon/principles/cache-fetches-and-parses` (klappy.dev#125, merged `3726073`). Graduates the "cache fetches and parses, not microsecond derivations" pattern to canon as a tier-2 principle after its third deciding-argument recurrence across the tool sweep: 0.18.0 encode parse-product caching (implicit), 0.20.0 gate D9 (first explicit), 0.21.0 challenge `cachedChallengeTypeIndex` removal (second explicit). Names the two halves of the principle, enumerates the four-cost plumbing tax, and anchors the threshold to current corpus sizes (6–9 challenge types, 4 gate transitions, 8 base prereqs).
21
+
22
+
-**New shared interface `PrereqMatchVocab`** in `workers/src/orchestrate.ts` capturing `stemmedTokens: Set<string>` plus four boolean structural-test flags (`hasURLCheck`, `hasNumericCheck`, `hasProperNounCheck`, `hasCitationCheck`). Mixed into both `BasePrerequisite` and the inline type on `ChallengeTypeDef.prerequisiteOverlays[]` to keep per-type and base-prereq structs in sync. Populated by the new `parseCheckColumn(check: string)` helper at canon-fetch time in both `discoverChallengeTypes` and `fetchBasePrerequisites`.
23
+
24
+
### Known limitations
25
+
26
+
- Same as 0.20.0 — Porter-style stemmer does not reverse consonant gemination (`shipping` → `shipp`, not `ship`); affected vocabulary is fixed at canon tier per `klappy.dev#122` precedent. `getIndex` strict-mode (`skipBaselineFallback`) still pending across encode/challenge/gate (carry-forward O-open P2).
0 commit comments