feat(kb): schema + validator + smoke for progression levels and licks (task C-20)

Optional additive shapes: progression level (foundation|intermediate)
and per-pack licks (8-word technique vocab, tab notes string 1-6 /
fret 0-15, ids global with progressions). Validator exports checkLick
as a lib for smoke (KB_VALIDATE_AS_LIB guard); smoke 776 -> 787.
Critic PASS (independent sabotage re-proof: injected bad lick exit 1
with named errors; weakened vocab fails smoke 2/787).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
vadimwit
2026-07-08 17:07:10 +01:00
parent 96dda6a3ef
commit 793e60ab05
3 changed files with 302 additions and 3 deletions
+79
View File
@@ -46,12 +46,20 @@ export default [
mode: 'major', // major | minor | dorian | mixolydian | …
songs: ['Autumn Leaves'],
tip: 'One transferable idea.',
level: 'intermediate', // OPTIONAL: 'foundation' | 'intermediate'
},
]
```
48 progressions per style. Cross-check `docs/progression-repertoire.md` §1.
**`level` (optional).** Tags the progression's difficulty for the in-app level
filter. Allowed values: `'foundation'` (the style's bread-and-butter loops) or
`'intermediate'` (secondary dominants, chained iiVs, backdoor, borrowed
chords…). **Omitting the field is fine and means `'foundation'`** — consumers
treat an absent `level` as foundation, so existing styles need no edits. The
validator only checks the value when the field is present.
## Instrument packs
Common envelope:
@@ -126,6 +134,76 @@ Voicings are degree recipes resolved through the chord quality. Degrees: `'1' '3
}
```
## Licks (optional, guitar first)
A style's instrument pack may also teach short, named licks — the ordered-note
phrases the Licks & Techniques cards render. **The whole section is optional**:
a pack without licks is complete and valid.
**How to register licks:** add a `licks` array as one more top-level key on the
instrument pack's default export (next to `styleIntro`/`comping`/`plays`/`improv`
in e.g. `src/data/kb/blues/guitar.js`). No change to `src/data/kb/index.js` is
needed — the registry already exposes the whole pack, and the UI reads
`kb[style].instruments.guitar.licks ?? []`.
```js
export default {
styleIntro: '…',
comping: [ /* … */ ],
plays: { /* … */ },
improv: { /* … */ },
// OPTIONAL — structured licks (this section):
licks: [
{
id: 'blues-box1-bb-answer', // '<style>-<slug>', globally unique
// (shares ONE id namespace with progression ids)
name: 'B.B. box answer phrase',
level: 'foundation', // 'foundation' | 'intermediate' (required)
chordContext: 'over the I7', // which chord/station it fits — free text,
// e.g. 'dom7' or 'over the V7 turnaround'
techniques: ['bend', 'vibrato'], // summary tags, from the fixed vocabulary below
source: 'the B.B. King box, e.g. "The Thrill Is Gone" fills', // recommended attribution
tab: [ // the ORDERED note sequence (played first → last)
{ string: 2, fret: 8 }, // string 1 = high e … 6 = low E
{ string: 1, fret: 8, technique: 'bend' }, // fret 0 (open) … 15
{ string: 1, fret: 10, technique: 'vibrato' }, // technique is optional per note
{ string: 2, fret: 8 },
],
},
],
}
```
Field rules (all enforced by `node scripts/validate-kb.mjs` when `licks` is present):
| Field | Rule |
|---|---|
| `id` | starts with `'<style>-'`; globally unique across **all** progression and lick ids in the whole KB |
| `name` | required, non-empty |
| `level` | `'foundation'` or `'intermediate'` — nothing else |
| `chordContext` | required, non-empty string — tells the player *where* the lick lands |
| `techniques` | array; every entry from the fixed vocabulary below (empty array = plain-picked) |
| `tab` | non-empty ordered array of `{string, fret, technique?}` |
| `tab[].string` | integer 16 (**1 = high e, 6 = low E** — standard tab convention; note `rootStr` in shapes counts the same way) |
| `tab[].fret` | integer 015 (0 = open string) |
| `tab[].technique` | optional; from the vocabulary; must **also** appear in the lick's `techniques[]` summary so card tags stay honest |
| `source` | optional but recommended — name where the lick comes from (checklist: nothing invented) |
**Fixed technique vocabulary** (both for `techniques[]` and per-note `technique`
— the validator rejects anything else):
`hammer-on` · `pull-off` · `slide` · `bend` · `double-stop` · `ghost-note` · `chromatic-approach` · `vibrato`
Like shapes, licks are **key-agnostic in spirit**: write them where they sit in
the style's home position and say in `chordContext` which chord they fit; the
tab renders as absolute string/fret positions.
Note: the older freeform `improv.licks` (prose `{tab/notation, description,
over, source}`) is unchanged and still welcome — it feeds the improv text
section. This top-level `licks` array is the *structured* shape that the lick
cards render and the validator checks.
## Musician checklist (self-review before committing)
- [ ] Plays per progression genuinely differ in register/density/technique
@@ -133,4 +211,5 @@ Voicings are degree recipes resolved through the chord quality. Degrees: `'1' '3
- [ ] The style is recognizable from the rhythm descriptions alone (bossa ≠ jazz with new labels)
- [ ] Every tip teaches a transferable idea (voice leading, register, space), not just "play this"
- [ ] Songs/licks have sources; nothing invented
- [ ] Lick techniques use only the fixed vocabulary; every lick is playable as written (strings 16, frets 015, in order)
- [ ] `node scripts/validate-kb.mjs` green; `npm run build` green