@ngockhoale/ukit 3.4.16 → 3.4.17

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.17 - 2026-10-06
6
+
7
+ **Planners can revise an unimplemented task that still carries the skeleton Executor Report.**
8
+ `handoff-model-guard.sh` blocked every Edit/Write to an existing `TASK-xxx.md` whose
9
+ `## Executor Report` (or `## Reviewer Verdict`) was still the Phase-2 skeleton, demanding
10
+ `EXECUTOR_MODEL` — so `/ukit:handoff-create` could create a task but never amend it after
11
+ plan review. The model gate now fires only when the phase section itself changes to real
12
+ content; edits elsewhere, unchanged skeletons and inert placeholders (`Not started.`,
13
+ `KEY: -`) pass. Real executor/reviewer reports without a valid model, lite-tier models and
14
+ real-model demotion stay blocked. Regression: `tests/hooks/taskSkeletonAmendment.test.js`.
15
+
5
16
  ## 3.4.16 - 2026-10-05
6
17
 
7
18
  **Create-scope guard no longer blocks planner subagents running in a git worktree.**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ngockhoale/ukit",
3
- "version": "3.4.16",
3
+ "version": "3.4.17",
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",
@@ -197,6 +197,31 @@ function isPlaceholderValue(value) {
197
197
  if (!raw || /^[<(]/.test(raw)) return true;
198
198
  return ['-','--','tbd','todo','n/a','na','none','unknown','pending'].includes(raw.toLowerCase());
199
199
  }
200
+
201
+ // Planning amendments (handoff-create revising an unimplemented task) re-submit a task whose
202
+ // `## Executor Report` / `## Reviewer Verdict` is still the Phase-2 skeleton. The model gate
203
+ // exists to stop a skeleton being promoted into a "real" report without a validated model, so it
204
+ // must only fire when the phase section itself is touched with real content — not when the edit
205
+ // leaves the skeleton unchanged or keeps it inert (headings/"Not started."/placeholder fields).
206
+ function phaseSection(text, heading) {
207
+ const m = new RegExp('^##[ \\t]+' + heading + '[ \\t]*$', 'm').exec(text);
208
+ if (!m) return null;
209
+ const rest = text.slice(m.index + m[0].length);
210
+ const next = rest.search(/^##[ \t]+\S/m);
211
+ return (next === -1 ? rest : rest.slice(0, next)).trim();
212
+ }
213
+ function phaseSectionInert(section) {
214
+ if (section === null) return true;
215
+ return section.split(/\r?\n/).map((l) => l.trim()).filter(Boolean).every((line) => {
216
+ if (/^(?:[-*]\s*)?(?:not started|n\/a|tbd|todo|pending|none)\.?$/i.test(line)) return true;
217
+ const kv = line.match(/^(?:[-*]\s*)?\**[A-Za-z_ ]+\**\s*:\s*(.*)$/);
218
+ return Boolean(kv) && isPlaceholderValue(kv[1].replace(/\*+/g, '').trim());
219
+ });
220
+ }
221
+ function phaseSectionUntouched(currentVisible, newVisible, heading) {
222
+ const next = phaseSection(newVisible, heading);
223
+ return phaseSection(currentVisible, heading) === next || phaseSectionInert(next);
224
+ }
200
225
  // W2-D07 fix: `OVERRIDE_APPROVED_BY: human` was plain text inside gated files —
201
226
  // self-authorizable by the gated model. The in-file `## Model-Tier Guard Override`
202
227
  // declaration stays (it is the audit trail), but it only counts when a marker file
@@ -577,7 +602,7 @@ if (toolName === 'Write' || toolName === 'Edit') {
577
602
  if (hasExecutorReport && !currentExecutorReal) {
578
603
  const executorModel = extractField(newVisible, 'EXECUTOR_MODEL');
579
604
  if (isPlaceholderValue(executorModel) || /^unknown$/i.test(executorModel)) {
580
- if (!isFreshTaskFile) {
605
+ if (!isFreshTaskFile && !phaseSectionUntouched(currentVisible, newVisible, 'Executor Report')) {
581
606
  block(`${taskId}: EXECUTOR_MODEL missing/placeholder. Implementation must run via Agent tool subagent_type: "feature-implementer" (sonnet/unic-code) and self-report its model.`);
582
607
  }
583
608
  // Else: skeleton sections copied from a task template at creation time — the
@@ -619,7 +644,7 @@ if (toolName === 'Write' || toolName === 'Edit') {
619
644
  const reviewOverride = await taskHasHumanOverride(newVisible, taskId)
620
645
  || await planHasHumanOverride(stripHtmlComments(planContentForReview));
621
646
  if (isPlaceholderValue(reviewerModel) || /^unknown$/i.test(reviewerModel)) {
622
- if (!isFreshTaskFile) {
647
+ if (!isFreshTaskFile && !phaseSectionUntouched(currentVisible, newVisible, 'Reviewer Verdict')) {
623
648
  block(`${taskId}: REVIEWER_MODEL missing/placeholder. Review must run via Agent tool subagent_type: "code-reviewer" (opus/unic-smart) and self-report its model.`);
624
649
  }
625
650
  // Else: skeleton Reviewer Verdict on a freshly created task file — Phase 4