@ngockhoale/ukit 3.4.6 → 3.4.8

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,18 @@
2
2
 
3
3
  All notable changes to UKit are documented here.
4
4
 
5
+ ## 3.4.7 - 2026-10-01
6
+
7
+ **Completion gate no longer demands an edit for read-only questions.** The
8
+ skill router now stamps `routingContext.mutationOrder` (was the prompt a real
9
+ mutation order?) and the Stop gate releases with a loud reason when a
10
+ no-mutation prompt was misrouted into a write-debt mode — ending the
11
+ Stop → "make an Edit" → AskUserQuestion → Stop loop on questions like
12
+ "nút Excel gọi hàm nào?". A real edit attempt keeps the normal gate.
13
+
14
+ ## 3.4.6 - 2026-09-30
15
+
16
+ Republish of 3.4.4 — npm staged-version E409 limbo (same content, version bump only; 3.4.5 hit the same wall).
5
17
 
6
18
  ## 3.4.4 - 2026-09-30
7
19
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ngockhoale/ukit",
3
- "version": "3.4.6",
3
+ "version": "3.4.8",
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",
@@ -2837,6 +2837,13 @@ setTimeout(() => process.exit(0), HOOK_DEADLINE_MS).unref();
2837
2837
  taskType: routingContext.taskType || null,
2838
2838
  intentMode: routingContext.intentMode || null,
2839
2839
  executionMode: routingContext.executionMode || null,
2840
+ // The ONLY signal the Stop gate needs: was this prompt an actual mutation
2841
+ // order? A misrouted "find the function that runs on click" question
2842
+ // mints write-evidence debt the turn can never satisfy — the gate reads
2843
+ // this flag to release read-only stops loud instead of demanding an edit.
2844
+ ...(typeof routingContext.mutationOrder === 'boolean'
2845
+ ? { mutationOrder: routingContext.mutationOrder }
2846
+ : {}),
2840
2847
  };
2841
2848
  }
2842
2849
 
@@ -3507,6 +3514,10 @@ setTimeout(() => process.exit(0), HOOK_DEADLINE_MS).unref();
3507
3514
  contextIntent: '',
3508
3515
  taskType: '',
3509
3516
  ...((informationalNoTargetPrompt || deliveryOnlyNoRoute) ? { executionMode: 'informational' } : {}),
3517
+ // Read-only/carried contexts: stamp whether THIS prompt ordered a
3518
+ // mutation so the Stop gate can release a misrouted write-debt instead
3519
+ // of looping "make an Edit" on an answer-only question.
3520
+ mutationOrder: promptText.trim() ? hasMutationOrder({ promptText, commandText }) : null,
3510
3521
  };
3511
3522
  const previousContext = await buildPreviousContextSnapshot({
3512
3523
  projectRoot,
@@ -3601,6 +3612,9 @@ setTimeout(() => process.exit(0), HOOK_DEADLINE_MS).unref();
3601
3612
  executionScores,
3602
3613
  executionCandidates,
3603
3614
  executionMode,
3615
+ // Was this prompt a real mutation order? The Stop gate reads this to
3616
+ // release read-only stops that were misrouted into a write-debt mode.
3617
+ mutationOrder: hasMutationOrder({ promptText, commandText }),
3604
3618
  };
3605
3619
  const useIndexedContext = shouldUseIndexedContext({
3606
3620
  activeSkills: selected,
@@ -326,7 +326,14 @@ function hasUnfinishedCompletion(state = {}, ledger = {}) {
326
326
  if (!IMPLEMENT_MODES.has(mode)) return false;
327
327
  const required = requiredEvidence(state);
328
328
  if (required.length === 0) return false;
329
- const unfinished = required.some((item) => !evidenceSatisfied(item, ledger, state));
329
+ // Read-only prompts misrouted into a mutating mode stamp
330
+ // routingContext.mutationOrder === false; their write-debt is a misroute, not
331
+ // unfinished work (see evaluateCompletion's 'no-mutation-order' valve).
332
+ const effectiveRequired = state?.routingContext?.mutationOrder === false
333
+ && ledger?.writeAttempted !== true
334
+ ? required.filter((item) => item !== 'write-evidence')
335
+ : required;
336
+ const unfinished = effectiveRequired.some((item) => !evidenceSatisfied(item, ledger, state));
330
337
  if (unfinished) return true;
331
338
  // TASK-009 (BL-011): a bug-fix route is also unfinished while its floor is
332
339
  // missing — the generic evidence alone never releases a bug fix.
@@ -2630,6 +2637,33 @@ export function evaluateCompletion({ state = {}, ledger = {}, cwd } = {}) {
2630
2637
  };
2631
2638
  }
2632
2639
 
2640
+ // USER-REPORTED (2026-10-01): a read-only question can still be routed into a
2641
+ // mutating mode (find-cause/local-fix) when the prompt mentions mutation or
2642
+ // failure vocabulary as SUBJECT matter ("nút Excel gọi hàm nào?", "find the
2643
+ // function that runs on click") — the router's question-shape heuristics
2644
+ // miss it, and the gate then demands a write the turn can never honestly
2645
+ // produce: Stop → "make an Edit" → AskUserQuestion → Stop. The router now
2646
+ // stamps routingContext.mutationOrder (hasMutationOrder on the raw prompt);
2647
+ // `false` means the request never ordered a mutation, so write-debt is a
2648
+ // misroute, not unfinished work. `null`/absent keeps the pre-stamp
2649
+ // conservative path (no release) — only a positively-computed false opens
2650
+ // the valve, and only when no mutation was actually attempted (a real edit
2651
+ // keeps the normal gate regardless of prompt wording).
2652
+ const mutationOrder = state?.routingContext?.mutationOrder;
2653
+ if (
2654
+ mutationOrder === false
2655
+ && missingEvidence.includes('write-evidence')
2656
+ && !effectiveLedger.writeAttempted
2657
+ ) {
2658
+ return {
2659
+ continue: false,
2660
+ notify: true,
2661
+ gateRelease: 'no-mutation-order',
2662
+ missingEvidence,
2663
+ reason: 'UKit completion gate released a read-only stop: this prompt ordered no mutation, so missing write-evidence is a misroute, not unfinished work. If an edit IS needed, re-send the task as an explicit change request.',
2664
+ };
2665
+ }
2666
+
2633
2667
  const gated = IMPLEMENT_MODES.has(mode);
2634
2668
  if (!gated) {
2635
2669
  return {