diff --git a/docs/current-handoff.md b/docs/current-handoff.md index f720162..7d69cb0 100644 --- a/docs/current-handoff.md +++ b/docs/current-handoff.md @@ -3594,3 +3594,10 @@ can NEVER be true for any decision node, because all decisions have `kind === "u #### Status BLOCKED — State B detection bug blocks 60B.73 implementation from reaching production. Fix needed in `question-formulator.js` line 2010 to correctly distinguish parent decisions from child factors. +#### Experiment 60B.75 — Decision Detection Fix (PASS) + +Fixed the deterministic bug: removed `node.kind !== "unknown"` from State B detection at line 2010 of `question-formulator.js`. The `hasDecisionContext()` predicate used by `selectReasoningPattern()` already provides canonical decision identification; the kind gate was redundant and excluded all real decisions. + +Tests updated to production shape (`kind: "unknown"` for decisions). Added 6 focused tests in 60B.75 block. All 22 tests pass. No schema/prompt/provider/harness changes. + +Status: resolved. diff --git a/docs/experiment-60b75.md b/docs/experiment-60b75.md new file mode 100644 index 0000000..b3ad712 --- /dev/null +++ b/docs/experiment-60b75.md @@ -0,0 +1,54 @@ +# Experiment 60B.75 — Fix Decision Node Detection for Sufficiency Question + +**Date:** 2026-08-14 +**Branch:** `feature/sufficiency-decision-detection-v0.46` +**Preceded by:** Experiment 60B.74 (BLOCKED — State B detection bug) + +## Objective + +Fix the deterministic bug in State B detection so that real kind="unknown" decision nodes can reach the sufficiency confirmation question path, without changing target selection, closure semantics, schema, or prompt behaviour. + +## Root Cause (confirmed by 60B.74) + +The condition `node.kind !== "unknown"` at line 2010 of `question-formulator.js` excludes ALL nodes from State B, including parent decisions, because all decisions have `kind === "unknown"`. + +The reasoningPattern check at the same conditional's first clause (`reasoningPatternSelection.pattern === "decision"`) already uses `hasDecisionContext()` — a text-pattern predicate that identifies decision context via ancestry chain and keywords like "whether to", "launch", "build", etc. The kind gate was redundant but harmful. + +## Fix Applied + +Removed `node.kind !== "unknown"` from the State B conditional at line 2010 of `question-formulator.js`. The reasoningPattern check already provides canonical decision identification via hasDecisionContext(). + +### Files Changed + +- `lib/graph/question-formulator.js` — removed broken kind gate (line 2010) +- `tests/graph/question-formulator.test.js` — updated 60B.73 tests to use production-shaped `kind: "unknown"` for decisions; added new 60B.75 describe block with 6 focused tests + +## Production/tests Commit + +``` +fix(reasoning): recognise decision in sufficiency question +``` + +## Documentation Commit + +``` +docs: record sufficiency decision detection fix +``` + +## Why It Works + +`hasDecisionContext(node, graph, relatedNodes)` at line 972 of `question-formulator.js` examines the parent chain and context text for decision keywords. When `selectReasoningPattern()` returns `pattern: "decision"`, it has already confirmed this node sits in a build/continue/invest/commercial-justification decision context via that predicate. + +Removing `node.kind !== "unknown"` exposes the State B branch to all nodes where reasoningPattern === "decision", including kind="unknown" decisions — which is exactly what was intended. + +## Test Gap (identified and closed) + +The existing 60B.73 tests used `kind: "state"` for decision nodes, which passed the broken gate (`"state" !== "unknown"` = true). Production decisions use `kind: "unknown"`. Tests were corrected to match production shape, so they now fail against the old condition and pass after this fix. + +## Focused Verification + +```bash +npx vitest run tests/graph/question-formulator.test.js tests/graph/apply-proposal.test.js -t "60B.75|60B.73|60B.64|decision_threshold" +``` + +Result: 22 tests pass (0 failures). No Jest, no Watchman, no Ollama calls, no live API calls.