@ngockhoale/ukit 3.4.14 → 3.4.15

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -2,6 +2,17 @@
2
2
 
3
3
  All notable changes to UKit are documented here.
4
4
 
5
+ ## 3.4.15 - 2026-10-05
6
+
7
+ **Hook chain no longer masks a real gate block (stops blind-retry token burn).**
8
+ When a mid-chain gate exited 2 with its own reason (e.g. `handoff-model-guard.sh`:
9
+ `PLAN.md has no valid PLANNER_MODEL`), `hook-chain-runner.mjs` treated the unrun later gate
10
+ as "chain broke before fail-closed gate(s) ran: context-hardcap-gate.sh" and discarded the
11
+ real reason. Agents (notably `handoff-planner`) could not tell what to fix and were respawned
12
+ repeatedly. A completed exit-2 gate is now the verdict: its stderr/systemMessage is surfaced
13
+ and later gates stay unrun. Infra failures (timeout/signal/budget) still fail closed with the
14
+ "chain broke" message. Regression test in `tests/hooks/hookChainFailureSurface.test.js`.
15
+
5
16
  ## 3.4.14 - 2026-10-04
6
17
 
7
18
  **New `advisor-plan` skill, a create-session write-scope guard, and a deliberate-future queue contract.**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ngockhoale/ukit",
3
- "version": "3.4.14",
3
+ "version": "3.4.15",
4
4
  "description": "Install/update an index-first AI workspace for Claude Code, OpenAI Codex and omp (Oh My Pi).",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -386,7 +386,16 @@ async function run(payloadText, scriptArgs, { chainMarker = true, decisionShortC
386
386
  // ... unless this break WAS the decision: a decision owns the verdict, so the
387
387
  // unrun gates are not a fail-closed gap (SPEC §FR-001 — the verdict must stay
388
388
  // exit 0 with the decision JSON).
389
- const skippedFailClosed = !decisionEmitted && scriptPaths
389
+ // ... and unless the break was a REAL block: a script that ran to completion and
390
+ // exited 2 (e.g. handoff-model-guard: exit 2 + systemMessage, no decision JSON) is
391
+ // itself the verdict. Reporting "chain broke before <later gate>" instead hides the
392
+ // actual reason, so the agent cannot fix it and retries blind (token burn).
393
+ const brokeAt = results[results.length - 1];
394
+ const realBlock = Boolean(brokeAt)
395
+ && brokeAt.code === 2
396
+ && brokeAt.failureKind === 'exit-code'
397
+ && !brokeAt.killed;
398
+ const skippedFailClosed = !decisionEmitted && !realBlock && scriptPaths
390
399
  .slice(results.length)
391
400
  .some((p) => FAIL_CLOSED_SCRIPTS.has(failClosedName(p)));
392
401