P0.5.1 — δ Intent Scoring — Audit
Step 1 of the 5-step executor chain. Inventory of the surface that P0.5.1 must land into, the two-spec conflict this task resolves, and the seams already in the codebase.
Spec conflict and resolution
Two canonical docs disagree on the Phase 0 scope of δ Intent Scoring:
| Source | Claim |
|---|---|
docs/architecture/decisions/ADR-005-multi-model-defer.md §Decision |
“Router interface is present. δ tools exist in the Phase 0 tool surface as thin stubs that always resolve to Claude. Scoring function returns a constant.” |
docs/guides/implementation/task-breakdown.md §P0.5 header + §P0.5.1 |
“spec-only — deferred per ADR-005 … design only; no src/domains/router/ in Phase 0.” |
ADR-005 wins. ADR is the authoritative source; the task-breakdown.md sentence is an obsolete Wave-G-era interpretation of ADR-005 that conflicts with ADR-005 itself. Evidence:
- ADR-005 §Decision explicitly calls for “thin stubs” in Phase 0.
- ADR-005 §Implementation §”Phase 0 router stub (P0.5)” names
P0.5.1 — router_score stub that returns constant vectoras one of two in-scope Phase 0 tasks. - ADR-005 §Verification §”Phase 0 completion round (R80)” verifies that “no multi-model code exists in
src/domains/router/” — which implies that asrc/domains/router/directory does exist at R80 with single-model code. - task-breakdown.md’s own line 252 lists the “Output (deferred)” as
src/domains/router/scoring.ts— the same path this task targets.
The doc-fix to reconcile task-breakdown.md against ADR-005 is out-of-scope for this PR; it belongs to the Wave I close commit. This task ships the code that ADR-005 §Decision mandates.
Task scope
This task ships the Phase 0 library-only stub for δ intent scoring:
- A pure library module in
src/domains/router/(new directory — first δ lander) scoreIntent(prompt, context)returns a constant{ scores: { claude: 1.0 }, winner: 'claude' }for every input- Determinism trivially holds (output is frozen, constant)
- Library-only: NO new MCP tools (follows ν pattern — ADR-004 post-Wave-H has 14 tools; this task adds zero)
- The
router_scoreMCP tool is Phase 1.5 scope per the ADR-005 transition table
Phase 0 acceptance (this task)
From the ADR-005 invariants:
scoreIntent(prompt, context)returns{ scores: { claude: 1.0 }, winner: 'claude' }for ALL inputs.- Pure function — no I/O, no randomness, no time-dependent state.
- Deterministic — same input → same output (trivially true because the output is constant and frozen).
- Public shape preserved for Phase 1.5 drop-in replacement.
- Zero new MCP tools registered (tool surface stays at 14).
- Zero new env vars in
src/config.ts(stub doesn’t need them; Phase 1.5 scope). - No circular imports; no dependencies beyond types.
What this task does NOT do (Phase 1.5 scope)
- Real intent scoring across multiple models — the “prompt length / complexity keywords / context size / tool requirements” logic in task-breakdown.md §P0.5.1 is Phase 1.5 work.
router_scoreMCP tool registration — Phase 1.5.- Model slots beyond Claude — Phase 1.5.
- Adapter invocation —
scoreIntentdoes not call Claude; it only picks the winner. Calling isP0.5.2scope (a separate, follow-up task) and ultimatelyrouter_callin Phase 1.5.
Existing files this task depends on
| Path | Role here | Key lines / notes |
|---|---|---|
src/domains/integrations/index.ts |
Barrel pattern to follow for a new domain subdirectory. Exports ./notifications.js, ./claude.js, ./mcp-bridge.js. |
whole file — 3 lines |
src/domains/integrations/notifications.ts |
Reference style for a pure library-only ν module that exports a typed function, uses JSDoc header citing donor references, and frees itself from any MCP tool registration. | header + signature patterns |
docs/architecture/decisions/ADR-005-multi-model-defer.md |
Authoritative scope doc for this task. §Decision = invariants; §Implementation §”Phase 0 router stub” = this task’s brief. | L36–46 (Decision), L83–89 (Phase 0 stub), L91–100 (Phase 1.5 upgrade path) |
docs/architecture/decisions/ADR-004-tool-surface.md |
Locks post-Wave-H surface at 14 tools. This task must not grow that number. | L52–69 (the 14-tool table) |
docs/reference/extractions/delta-model-router-extraction.md |
Donor-era δ spec (heritage-only). Not imported; Phase 1.5 will consult it when writing the real scorer. | referenced in task-breakdown.md line 251 |
Files NOT touched by this task
src/server.ts— no tool registration; no changes.src/config.ts— no new env vars; no changes.src/domains/integrations/*— δ does not depend on ν; no changes.src/domains/tasks/*,src/domains/trail/*,src/domains/proof/*,src/domains/skills/*— δ is purely additive and decoupled.src/db/schema.sql— δ does not touch the database in Phase 0.- Migration slots — no migration added.
New files / directories this task creates
src/domains/router/ ← new domain subdirectory (first δ lander)
src/domains/router/scoring.ts ← impl
src/domains/router/index.ts ← barrel: export * from './scoring.js'
src/__tests__/domains/router/ ← new test subdirectory
src/__tests__/domains/router/scoring.test.ts ← tests
docs/audits/p0-5-1-scoring-audit.md ← this file (Step 1)
docs/contracts/p0-5-1-scoring-contract.md ← Step 2
docs/packets/p0-5-1-scoring-packet.md ← Step 3
docs/verification/p0-5-1-scoring-verification.md ← Step 5
Constraints and hazards (memorized)
- Tool surface is locked at 14. ADR-004 post-Wave-H — this task must register zero MCP tools. The
router_scoretool is Phase 1.5 scope. - No env vars. ADR-005 §Implementation §”Phase 1.5 upgrade path” §2 shows that adapter configuration is Phase 1.5 work.
COLIBRI_MODEL_*env vars belong to P0.5.2 at the earliest, and even there are deferred under the ADR-005 umbrella. - Pure function.
scoreIntentdoes no I/O, reads no env, calls no time/random source. The Phase 1.5 replacement will still be pure — the interface promises that. - Frozen output. Return value is
Object.freezed (including the nestedscores). Callers cannot mutate the shared constant and surprise a later caller. This is load-bearing for Phase 1.5 — the moment we swap to a real scorer, callers that accidentally mutated in Phase 0 would start seeing phantom cross-call state. - Determinism is trivially true. Constant output ⇒
scoreIntent(p, c) === scoreIntent(p, c)(structural equality via frozen identity). Tests assert this explicitly because the Phase 1.5 scorer will have the same contract and the test must carry forward unchanged. - Namespace. The underlying concept is δ, but the directory is
src/domains/router/(concept name + domain alias), matching ADR-005 §Implementation §”Phase 1.5 upgrade path” §1 which namessrc/domains/router/scoring.tsas the file to “replace the constant” in Phase 1.5. - Cross-worktree leak guard. Step 1 began with
git statuson the worktree — clean. No non-owned files will be modified. - Type exports. The module exports
ModelId,ScoreContext,IntentScoreas type aliases so Phase 1.5 can widenModelIdto'claude' | 'kimi' | 'codex' | ...without breaking existing imports.ModelIdstays a string-literal union to preserve exhaustive checks. - No import from other domains. The scorer is pure. Importing from
src/domains/integrations/claude.tswould couple the stub to the adapter; Phase 1.5 will do the wiring through a separatesrc/domains/router/fallback.ts(P0.5.2) androuter_calltool (Phase 1.5). - Jest ESM + zod. Not applicable — this module has zero runtime dependencies (no
zod), sojest.isolateModulesAsyncis a non-issue.
Risk ledger
| Risk | Likelihood | Impact | Mitigation |
|---|---|---|---|
| Phase 1.5 breaks the Phase 0 call sites | Low | High | Types are exported with forward-compatible shape; ModelId union widens additively. Tests assert the constant so any widening that changes the Phase 0 constant will fail loudly. |
| Test author relies on mutation of the return value | Low | Medium | Return value is deep-frozen; mutation attempts throw under strict mode. |
| ADR-005 is re-interpreted again (like the Wave-G mis-read) | Low | Low | Audit + contract explicitly cite §Decision and §Implementation; future readers find the fight already settled. |
| task-breakdown.md doc-fix lags | Certain | Low | Out-of-scope for this PR. Wave I close-commit will reconcile §P0.5.1 wording against ADR-005. |
| P0.5.2 collides with this file | Medium | Low | P0.5.2 lives in src/domains/router/fallback.ts and adds export * from './fallback.js'; to the barrel. No overlap with scoring.ts. |
Conclusion
Surface is empty (no src/domains/router/ exists yet). ADR-005 §Decision and §Implementation unambiguously describe a Phase 0 constant-return stub with the exact shape { scores: { claude: 1.0 }, winner: 'claude' }. Step 2 (contract) encodes that as the behavioral contract; Step 3 (packet) lays out the two source files + the test file; Step 4 implements; Step 5 verifies.