Problem
ADR-002 (four-tier progressive disclosure) defines Tier-2 as context-gated loading: detect-context.sh sets ACTIVE_CONTEXTS from file signatures, and skills/instructions declare the contexts they load under. The whole mechanism works only if the two sides agree on a shared vocabulary of context tokens.
That contract is currently implicit and already broken:
bin/detect-context.sh emits 17 hardcoded tokens (nextjs, expo, react-native, react, node, typescript, php, sanity, better-auth, prisma, docker, python, laravel, cmd, ...).
skills/mobile-dev/SKILL.md declares contexts: [expo, react-native, mobile] — but mobile is not a token detect-context.sh can ever emit, so that gate is silently inert. A consumer matching ACTIVE_CONTEXTS against the frontmatter never fires on mobile and never fails loudly; the skill's context-gating is dead for one of its three declared contexts.
- There is no single source of truth. The token list lives inline inside one 200-line bash script (with no test), is re-derived by hand in
detect-client.sh, is referenced loosely in instructions/README.md, and is duplicated in skill frontmatter across the repo. Nothing validates that a skill/instruction's declared contexts are members of the detector's emit set, or vice versa.
Because detection is a pure, branching decision function feeding skill-loading, HUD context events, and ACTIVE_CONTEXTS, a misspelled or stale context token is a silent classification failure — exactly the class ADR-002 was written to prevent.
Proposed change
- Define one canonical context-token registry (e.g.
detect-context-data.sh, mirroring the existing pipeline-label-data.sh pattern: generated from a TS source via render-pipeline-artifacts.ts, or a simple checked-in list owned by the detector). It declares the full emit set of tokens plus the file-signature predicates that produce each.
- Make
detect-context.sh consume the registry instead of hardcoding tokens inline, so the emit set is derived from one place.
- Add a validation pass (extend
bin/validate-skills.sh, which already parses frontmatter) that cross-checks every contexts: declaration in skills/ and instructions/ against the registry and fails on unknown tokens — turning the mobile case into an explicit, flagged error rather than silent dead weight.
- Add a contract test (shell, in
test/) that runs detect-context.sh over a small matrix of fixture directories (nextjs vs. react vs. react-native/expo vs. php vs. none) and asserts the exact ACTIVE_CONTEXTS string, protecting the decision logic and the registry from regressions.
Acceptance criteria
detect-context.sh reads its token set from the single registry; no inline context token list remains in the script.
bin/validate-skills.sh fails (or warns) on any contexts: entry not present in the registry; the existing mobile breakage is resolved either by adding the token to the detector or by removing it from the frontmatter — decided against the registry.
- A
test/ fixture matrix asserts the emit set per directory type.
- ADR-002's Tier-2 table and
instructions/README.md are updated to reference the registry as canonical.
Why now
The pipeline-label state machine already solved this exact shape of problem — a vocabulary duplicated across shell/TS with drift risk — by generating a single artifact (pipeline-label-data.sh) from one source and enforcing it in CI. Context detection is the same class of boundary, unaddressed, and already showing a concrete drift case. Fixing it now makes adding a context-triggered skill or a new detector signature a one-file, lint-checked change instead of an invisible gamble.
Related: ADR-002 (binding), bin/detect-context.sh, bin/detect-client.sh, bin/validate-skills.sh, bin/pipeline-label-data.sh, shft/engine/lib/render-pipeline-artifacts.ts, instructions/README.md, skills/mobile-dev/SKILL.md.
Problem
ADR-002 (four-tier progressive disclosure) defines Tier-2 as context-gated loading:
detect-context.shsetsACTIVE_CONTEXTSfrom file signatures, and skills/instructions declare the contexts they load under. The whole mechanism works only if the two sides agree on a shared vocabulary of context tokens.That contract is currently implicit and already broken:
bin/detect-context.shemits 17 hardcoded tokens (nextjs, expo, react-native, react, node, typescript, php, sanity, better-auth, prisma, docker, python, laravel, cmd, ...).skills/mobile-dev/SKILL.mddeclarescontexts: [expo, react-native, mobile]— butmobileis not a tokendetect-context.shcan ever emit, so that gate is silently inert. A consumer matchingACTIVE_CONTEXTSagainst the frontmatter never fires onmobileand never fails loudly; the skill's context-gating is dead for one of its three declared contexts.detect-client.sh, is referenced loosely ininstructions/README.md, and is duplicated in skill frontmatter across the repo. Nothing validates that a skill/instruction's declared contexts are members of the detector's emit set, or vice versa.Because detection is a pure, branching decision function feeding skill-loading, HUD context events, and
ACTIVE_CONTEXTS, a misspelled or stale context token is a silent classification failure — exactly the class ADR-002 was written to prevent.Proposed change
detect-context-data.sh, mirroring the existingpipeline-label-data.shpattern: generated from a TS source viarender-pipeline-artifacts.ts, or a simple checked-in list owned by the detector). It declares the full emit set of tokens plus the file-signature predicates that produce each.detect-context.shconsume the registry instead of hardcoding tokens inline, so the emit set is derived from one place.bin/validate-skills.sh, which already parses frontmatter) that cross-checks everycontexts:declaration inskills/andinstructions/against the registry and fails on unknown tokens — turning themobilecase into an explicit, flagged error rather than silent dead weight.test/) that runsdetect-context.shover a small matrix of fixture directories (nextjs vs. react vs. react-native/expo vs. php vs. none) and asserts the exactACTIVE_CONTEXTSstring, protecting the decision logic and the registry from regressions.Acceptance criteria
detect-context.shreads its token set from the single registry; no inline context token list remains in the script.bin/validate-skills.shfails (or warns) on anycontexts:entry not present in the registry; the existingmobilebreakage is resolved either by adding the token to the detector or by removing it from the frontmatter — decided against the registry.test/fixture matrix asserts the emit set per directory type.instructions/README.mdare updated to reference the registry as canonical.Why now
The pipeline-label state machine already solved this exact shape of problem — a vocabulary duplicated across shell/TS with drift risk — by generating a single artifact (
pipeline-label-data.sh) from one source and enforcing it in CI. Context detection is the same class of boundary, unaddressed, and already showing a concrete drift case. Fixing it now makes adding a context-triggered skill or a new detector signature a one-file, lint-checked change instead of an invisible gamble.Related: ADR-002 (binding),
bin/detect-context.sh,bin/detect-client.sh,bin/validate-skills.sh,bin/pipeline-label-data.sh,shft/engine/lib/render-pipeline-artifacts.ts,instructions/README.md,skills/mobile-dev/SKILL.md.