@ngockhoale/ukit 3.4.49 → 3.4.51

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,24 @@
2
2
 
3
3
  All notable changes to UKit are documented here.
4
4
 
5
+ ## 3.4.51 - 2026-10-21
6
+
7
+ **Handoff orchestration integrity (CKit/OMP 18.12.5 incident audit).**
8
+ - OMP `Task` preflight (block-only): an ordinary agent carrying `sourceSession`/`model` (any value, even empty) or batch-root `sourceSession`/`model` is blocked with exact remove-field guidance; args are never rewritten, seance stays native-owned. Retries are counted per session + task instance + fingerprint (prose/name/status excluded), hashed, bounded and persisted; STOP guidance after two identical blocks.
9
+ - Stop-gate continuation provenance: only positively native/system-origin continuations (OMP branch `session-stop-continuation` match) reuse the task contract; a user paste of the gate text mints a fresh task. Pending-continuation marker is session/task/reason-hash scoped and lock-serialized.
10
+ - Resumable records now require exact stored task-id equality (lossy filename normalization could collide).
11
+ - Classifier: diagnostic "which config changed" reports are READ_ONLY; forward configuration orders and mixed source mutation + report stay CONFIGURATION / CODE_IMPLEMENTATION.
12
+ - Review-panel aggregation: FAIL/NOT_RUN dominate, underscore verdict aliases normalized, executable N/A becomes NOT_RUN; explicit product / integration-evidence / status-only review kinds. Handoff commands carry retry-fingerprint and pending-review continuity rules.
13
+ - Audit: `docs/analysis/HANDOFF-INTENT-DISPATCH-AUDIT.md`. Historical CKit wrapper/contract origin remains unknown; verified offline on macOS/Node 22 only; no live host proof, no CI dispatch.
14
+
15
+ ## 3.4.50 - 2026-10-21
16
+
17
+ **Verification exit-proof integrity (long-run OMP field audit).**
18
+ - Fix execution-ledger false PASS receipts: a bare `pipefail`/`PIPESTATUS` word (e.g. `echo pipefail`) no longer exempts a masked pipe; only real `set -o pipefail` before the pipe or an immediate `exit ${PIPESTATUS[n]}` with the correct index proves the runner status.
19
+ - A runner followed by `|| …`, backgrounded with `&`, inside a `for`/`while`/`case` flow, behind `bash -c` hiding a masked pipe, or followed by `exit 0`/`true`/`:`/leading `!` is recorded as `exitStatusUnproven` (not passing). Claude `tool_response.interrupted:true` now counts as failure.
20
+ - Bare runners, `cd repo; runner`, `&&` chains, real pipefail and terminal subshell/group runners stay accepted. Sequential consumers (including `; echo "exit=$?"`) and `if runner; then echo ok; fi` no longer receive PASS credit: recognizing a runner attempt does not prove its exit. Loop keywords as runner arguments (`vitest -t case`) are not loops.
21
+ - Audit report: `UKIT_LONG_RUN_ORCHESTRATION_AUDIT.md`. Verified locally on macOS/Node 22 only; OMP live host-ordering attribution remains UNVERIFIED. No CI dispatch.
22
+
5
23
  ## 3.4.49 - 2026-10-19
6
24
 
7
25
  **Conditional consumer verification intent closure.**
@@ -71,6 +71,17 @@ entries:
71
71
  validation: [manual]
72
72
  archive_policy: never
73
73
  notes: release record
74
+ - id: root-long-run-orchestration-audit
75
+ path: UKIT_LONG_RUN_ORCHESTRATION_AUDIT.md
76
+ class: canonical
77
+ audience: [owner, maintainer]
78
+ owner: runtime
79
+ source_of_truth: UKIT_LONG_RUN_ORCHESTRATION_AUDIT.md
80
+ merge_strategy: none
81
+ load_policy: on-demand
82
+ validation: [manual, test:tests/hooks/executionLedger.test.js, test:tests/hooks/verificationExitProof.test.js]
83
+ archive_policy: replace-with-snapshot
84
+ notes: evidence-based long-run field audit; current reproductions are distinct from incident attribution
74
85
  - id: registry-self
75
86
  path: manifests/documentation.yaml
76
87
  class: canonical
@@ -517,6 +528,17 @@ entries:
517
528
  validation: [none]
518
529
  archive_policy: immutable
519
530
  notes: 2026-10-10 orchestration regression test inventory
531
+ - id: docs-analysis-handoff-intent-dispatch-audit
532
+ path: docs/analysis/HANDOFF-INTENT-DISPATCH-AUDIT.md
533
+ class: archive
534
+ audience: [maintainer]
535
+ owner: product
536
+ source_of_truth: docs/analysis/HANDOFF-INTENT-DISPATCH-AUDIT.md
537
+ merge_strategy: none
538
+ load_policy: never
539
+ validation: [none]
540
+ archive_policy: immutable
541
+ notes: 2026-10-21 retained CKit intent/dispatch evidence, repair verification and recovery limits
520
542
  - id: docs-analysis-completion-task-ownership
521
543
  path: docs/analysis/COMPLETION-TASK-OWNERSHIP.md
522
544
  class: archive
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ngockhoale/ukit",
3
- "version": "3.4.49",
3
+ "version": "3.4.51",
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",
@@ -305,14 +305,46 @@ export function classifyTaskSemantics({ promptText = '', targetFile = '' } = {})
305
305
  if (!releasePolicy && !operation && isVerificationOnlyRequest(promptText)) return { taskKind: 'READ_ONLY', verificationOnly: true };
306
306
  // A config MENTION is not a config change: the same clause needs a mutation verb, and a
307
307
  // request that also changes source/code is a coding task (config is part of its tests/fixtures).
308
- const config = !sourceOrder && actionable.some((clause) => /\b(?:configuration|config|toggle|cau hinh)\b/.test(clause)
309
- && /\b(?:set|change|toggle|enable|disable|switch|turn|configure|modify|update|edit|doi|bat|tat|chinh)\b/.test(clause));
308
+ // A DIAGNOSTIC report of past changes ("which parameters/configuration has changed, if any" /
309
+ // "tham so/cau hinh nao da thay doi, neu co") is a question about history, never an authorization
310
+ // to mutate: only a forward order ("change X to Y") stays a config mutation.
311
+ const CONFIG_CHANGE_REPORT_CLAUSE = /\b(?:da|has|have|had|already)\s+(?:doi|thay doi|changed?|updated?)\b|\b(?:nao|gi|which|what|report|bao cao)\b[^.;]*?\b(?:doi|thay doi|changed?|updated?)\b/;
312
+ // A relative past-change clause inside a FORWARD imperative ("Set the config that
313
+ // has changed to dark mode") names WHICH config — it does not make the sentence a
314
+ // report. The override holds only when the clause's FIRST mutation word precedes
315
+ // the config noun AND no reporting/question frame opens the clause or precedes
316
+ // that word: in "Report what change was made to the config" the mutation word is
317
+ // a question's noun, never an order.
318
+ const CONFIG_MUTATION_VERB = /\b(?:set|change|toggle|enable|disable|switch|turn|configure|modify|update|edit|doi|bat|tat|chinh)\b/;
319
+ const CONFIG_NOUN = /\b(?:configuration|config|toggle|cau hinh)\b/;
320
+ const CONFIG_REPORT_FRAME = /^\s*(?:report|show|tell|list|describe|explain|summari[sz]e|bao cao|cho biet|liet ke|mo ta)\b/;
321
+ const CONFIG_QUESTION_FRAME_PREFIX = /\b(?:what|which|how|report|bao cao|nao|gi)\b/;
322
+ const config = !sourceOrder && actionable.some((clause) => {
323
+ if (!CONFIG_NOUN.test(clause) || !CONFIG_MUTATION_VERB.test(clause)) return false;
324
+ if (CONFIG_REPORT_FRAME.test(clause)) return false;
325
+ const verb = CONFIG_MUTATION_VERB.exec(clause);
326
+ if (CONFIG_QUESTION_FRAME_PREFIX.test(clause.slice(0, verb.index))) return false;
327
+ const noun = CONFIG_NOUN.exec(clause);
328
+ return !CONFIG_CHANGE_REPORT_CLAUSE.test(clause) || Boolean(noun && noun.index > verb.index);
329
+ });
310
330
  const repository = Boolean(targetFile) || /[\w.-]+\.(?:json|ya?ml|toml|ini)\b/.test(text);
311
331
  // A status/result lookup ("check the suite output and report") orders no change: it needs a
312
332
  // lookup verb AND no mutation vocabulary anywhere in the request (fail-closed otherwise).
313
333
  const mutationWord = /\b(?:implement|fix|refactor|modify|edit|write|update|add|create|build|make|install|deploy|remove|delete|rename|replace|change|sua|viet|chinh|them|tao|xoa|doi|cai|cap nhat)\b/.test(text);
314
- const statusReport = !mutationWord && !operation && !config
315
- && /\b(?:check|status|report|show|summari[sz]e|result|results|kiem tra|bao cao|trang thai|ket qua|da len chua)\b/.test(text);
334
+ // A bounded, recognizable reporting QUESTION about what changed ("Tham số/cấu hình
335
+ // nào đã thay đổi, nếu có." / "which configuration has changed, if any") is a
336
+ // status report even without a lookup verb — but ONLY when no actionable clause
337
+ // actually orders a mutation: a mixed request ("Add a dark-mode button; report
338
+ // which configuration changed") keeps its implementation contract and evidence.
339
+ // The two lookbehinds keep a question's noun ("what change was made") and the
340
+ // Vietnamese past-tense verb "thay đổi" from counting as mutation orders.
341
+ const ACTIONABLE_MUTATION_ORDER = /(?<!\b(?:what|which|how|nao|gi)\s)(?<!thay )\b(?:implement|fix(?:es)?|refactor|modify|edit|write|update|add|create|build|make|install|deploy|remove|delete|rename|replace|change|sua|viet|chinh|them|tao|xoa|doi|cai|cap nhat)\b/;
342
+ const configReportQuestion = !actionable.some((clause) => ACTIONABLE_MUTATION_ORDER.test(clause))
343
+ && actionable.some((clause) => /\b(?:configuration|config|toggle|cau hinh|tham so)\b/.test(clause)
344
+ && CONFIG_CHANGE_REPORT_CLAUSE.test(clause));
345
+ const statusReport = (configReportQuestion || (!mutationWord
346
+ && /\b(?:check|status|report|show|summari[sz]e|result|results|kiem tra|bao cao|trang thai|ket qua|da len chua)\b/.test(text)))
347
+ && !operation && !config;
316
348
  const readOnly = (/\b(?:audit|inspect|review|analyze|analyse|explain|read|investigate|debug|forensic|root cause|kiem tra|phan tich|dieu tra|doc log|tim hieu)\b/.test(text.replace(/\bread[\s-]*only\b/g, ' ')) || statusReport)
317
349
  && !sourceOrder && !codeChangeVerb && !operation && !config;
318
350
  const taskKind = operation ? (sourceOrder || (nativeDomain !== 'scheduled-job' && codeChangeVerb) ? 'MIXED' : 'OPERATIONAL')
@@ -37,12 +37,73 @@ const SEVERITIES = ['critical', 'important', 'minor'];
37
37
  // counted as absent and named in `unparsed`.
38
38
  export function parseVerdictBlock(text) {
39
39
  const src = String(text ?? '');
40
- const verdictMatch = src.match(/^VERDICT\s*:\s*(APPROVED-WITH-MINOR|APPROVED|CHANGES-REQUESTED|CRITICAL)\s*$/im);
40
+ // `-` and `_` are one vocabulary: the handoff commands and some reviewers
41
+ // write `changes_requested` / `critical_block` while others write the hyphen
42
+ // forms — both spellings parse to the same canonical verdict so a blocking
43
+ // reviewer can never be dropped as unparsed while the panel approves.
44
+ // `[ \t]` (not `\s`) around the colon: `\s` would swallow the newline and
45
+ // let a stray word on the next line fabricate a verdict.
46
+ const verdictMatch = src.match(
47
+ /^VERDICT[ \t]*:[ \t]*(APPROVED[-_]WITH[-_]MINOR|APPROVED|CHANGES[-_]REQUESTED|CRITICAL[-_]BLOCK|CRITICAL)[ \t]*\r?$/im,
48
+ );
41
49
  if (!verdictMatch) return null;
50
+ const rawVerdict = verdictMatch[1].toUpperCase().replace(/_/g, '-');
51
+ const verdict = rawVerdict === 'CRITICAL-BLOCK' ? 'CRITICAL' : rawVerdict;
42
52
 
43
53
  const memberMatch = src.match(/^PANEL_MEMBER\s*:\s*(\S+)\s*$/im);
44
54
  const member = memberMatch && memberMatch[1] !== '-' ? memberMatch[1] : null;
45
55
 
56
+ // VERIFICATION_RERUN status (PASS | FAIL | NOT_RUN | N/A). The status may
57
+ // sit inline on the heading line AND/OR under the indented `result:` line —
58
+ // both positions are read, and an explicit FAIL or NOT_RUN in either wins
59
+ // over a PASS elsewhere (fail closed on contradiction). Bare prose
60
+ // (`N pass / M fail`) and a literal template echo (`VERIFICATION_RERUN:
61
+ // PASS | FAIL | …` — still carrying the `|` vocabulary) parse as no status,
62
+ // leaving the field null (= no required metadata → pre-existing behavior).
63
+ // FAIL = the re-run executed and failed; NOT_RUN = the required verification
64
+ // never executed — either blocks approval.
65
+ //
66
+ // Layout is read exhaustively and conservatively: `[ \t]` (not `\s`) after
67
+ // the heading colon so the match cannot swallow the newline before an
68
+ // indented child (a `result:` FIRST child used to be hidden this way), the
69
+ // global match reads REPEATED VERIFICATION_RERUN headers, and every
70
+ // `result:` child line of each header is collected so a contradictory
71
+ // second line cannot hide behind the first. FAIL/NOT_RUN dominate.
72
+ const STATUS_RANK = { FAIL: 3, NOT_RUN: 2, PASS: 1, 'N/A': 0 };
73
+ const statuses = [];
74
+ const commands = [];
75
+ const vrRe = /^VERIFICATION_RERUN[ \t]*:[ \t]*(.*)$/img;
76
+ let vr;
77
+ while ((vr = vrRe.exec(src)) !== null) {
78
+ const inline = vr[1].trim();
79
+ const inlineStatus = inline.includes('|') ? null : inline.match(/^(PASS|FAIL|NOT_RUN|N\/A)\b/i);
80
+ if (inlineStatus) statuses.push(inlineStatus[1].toUpperCase());
81
+ for (const line of src.slice(vr.index + vr[0].length).split('\n')) {
82
+ if (/^\S/.test(line)) break; // next top-level field ends this header's children
83
+ const result = line.match(/^[ \t]*result[ \t]*:[ \t]*(.*)$/i);
84
+ if (result) {
85
+ const status = result[1].trim().match(/^(PASS|FAIL|NOT_RUN|N\/A)\b/i);
86
+ if (status) statuses.push(status[1].toUpperCase());
87
+ continue; // keep reading — a contradictory later `result:` must still block
88
+ }
89
+ const command = line.match(/^[ \t]*command[ \t]*:[ \t]*(.*)$/i);
90
+ if (command) commands.push(command[1].trim());
91
+ }
92
+ }
93
+ const dominant = statuses.length > 0
94
+ ? [...statuses].sort((a, b) => STATUS_RANK[b] - STATUS_RANK[a])[0]
95
+ : null;
96
+ // Fail closed on applicability: an N/A claim beside a DECLARED executable
97
+ // command contradicts the block's own evidence — the required check exists,
98
+ // so the N/A counts as NOT_RUN (the check was not run). A placeholder
99
+ // command (`-`, `none`, `(none …)`, `n/a …`) or no command line keeps N/A
100
+ // legitimate for genuinely check-free reviews.
101
+ const hasExecutableCommand = commands.some((c) => {
102
+ const t = c.trim().toLowerCase();
103
+ return t !== '' && t !== '-' && t !== 'none' && !t.startsWith('(none') && !t.startsWith('n/a');
104
+ });
105
+ const verificationRerun = dominant === 'N/A' && hasExecutableCommand ? 'NOT_RUN' : dominant;
106
+
46
107
  const findings = { critical: [], important: [], minor: [] };
47
108
  const findingsMatch = src.match(/^FINDINGS:\s*$/im);
48
109
  if (findingsMatch) {
@@ -77,7 +138,7 @@ export function parseVerdictBlock(text) {
77
138
  }
78
139
  }
79
140
 
80
- return { member, verdict: verdictMatch[1].toUpperCase(), findings };
141
+ return { member, verdict, findings, verificationRerun };
81
142
  }
82
143
 
83
144
  // Normalize a finding bullet for identity comparison: two members report the
@@ -127,8 +188,15 @@ export function aggregatePanelVerdicts(verdicts) {
127
188
  const hasCritical =
128
189
  parsed.some((p) => p.verdict === 'CRITICAL') || parsed.some((p) => p.findings.critical.length > 0);
129
190
 
191
+ // Fail closed on explicit verification evidence: a re-run that FAILED, or
192
+ // one never executed (NOT_RUN), cannot sit behind an approving prose
193
+ // verdict — the block stops counting toward approval entirely.
194
+ const hasBlockingVerification = parsed.some(
195
+ (p) => p.verificationRerun === 'FAIL' || p.verificationRerun === 'NOT_RUN',
196
+ );
197
+
130
198
  let verdict;
131
- if (hasCritical || changeRequests >= 1 || parsed.length === 0) verdict = 'changes_requested';
199
+ if (hasCritical || changeRequests >= 1 || hasBlockingVerification || parsed.length === 0) verdict = 'changes_requested';
132
200
  else if (approvals >= 2) verdict = 'approved';
133
201
  else verdict = 'approved_minor';
134
202
 
@@ -16,6 +16,38 @@ You are the independent reviewer for UKit's handoff Quality Gate. Your model is
16
16
  - `code` (default, if not specified) — reviewing a handoff task diff. Follow **Code Review** below, unchanged.
17
17
  - `spec` | `plan` — reviewing a document (e.g. `docs/plans/*.md`), no diff/task file/executor report involved. Skip straight to **Spec/Plan Review** at the end of this file instead.
18
18
 
19
+ ## REVIEW_KIND (code reviews only)
20
+
21
+ Set one kind per review — from the task's own classification when the spawning
22
+ command names one, else from what the diff shows:
23
+
24
+ - `product` (default when the diff changes source or tests) — the full Code
25
+ Review rubric below applies unchanged, including Test Plan adherence and
26
+ RED_OUTPUT evidence.
27
+ - `integration-evidence` — the diff verifies or mirrors behavior already
28
+ accepted elsewhere (template mirrors, parity copies, runtime/docs evidence).
29
+ The new-tests + RED_OUTPUT TDD requirements are inapplicable when the task
30
+ introduces no new behavior under test; review the evidence chain instead and
31
+ never fabricate tests or edits to satisfy them.
32
+ - `status-only` — the task only records status/evidence; an empty diff is
33
+ legitimate. Review the executor report and verification output on disk.
34
+
35
+ The review kind scopes only the implementation-TDD evidence (new tests +
36
+ RED_OUTPUT). It never waives the required executable checks — the task's
37
+ Verification Commands still re-run in your session (fresh PASS or the severity
38
+ ladder applies; `NOT_RUN` on a required command blocks in every kind) — and it
39
+ never lowers the task's original acceptance criteria.
40
+
41
+ **Verdict ≠ acceptance.** The review verdict and the task's original acceptance
42
+ are separate judgments. An `integration-evidence` review may legitimately be
43
+ `APPROVED` as a review (the evidence chain is sound and complete) while the
44
+ verdict's NOTES/BLOCKING explicitly report that product acceptance remains
45
+ unmet or pending external observation — that report flows through
46
+ `NEXT_STATUS_FOR_INDEX`/the task discussion and must not flip the product task
47
+ to `done` or close a pending parent item. A `status-only` recording is
48
+ bookkeeping, not an independent review pass: it never counts as evidence that
49
+ independent review approved the underlying work.
50
+
19
51
  ## Code Review (REVIEW_TARGET_TYPE=code)
20
52
 
21
53
  ### Inputs you expect
@@ -29,7 +61,7 @@ If any input is missing, return `CHANGES-REQUESTED` with reason "incomplete hand
29
61
  ### Review order
30
62
 
31
63
  0. **Verification package completeness** — Check whether the project has a lint or typecheck script (`package.json` scripts, or the stack's equivalent). If it does and the task's Verification Commands don't run it, that is `CHANGES-REQUESTED`: "verification commands missing lint/typecheck — re-run planner or add the command and re-verify" — do this before anything else below.
32
- 1. **Test Plan adherence** — Were all tests in §4 actually implemented, including the ≥2 edge cases required by `handoff.plan.minTestsEdgeCase`? Check the Executor Report's `RED_OUTPUT` field: it must contain actual failing-test output (assertion failure, stack trace, non-zero exit), not a bare claim like "confirmed" or "yes". Missing or vague `RED_OUTPUT` → `CHANGES-REQUESTED`: "no evidence tests were RED before implementation — re-run TDD cycle and paste real output". Then run the tests yourself: `<task Verification Commands>`. Fresh PASS required, no trusting executor's output blindly.
64
+ 1. **Test Plan adherence (REVIEW_KIND=product)** — Were all tests in §4 actually implemented, including the ≥2 edge cases required by `handoff.plan.minTestsEdgeCase`? Check the Executor Report's `RED_OUTPUT` field: it must contain actual failing-test output (assertion failure, stack trace, non-zero exit), not a bare claim like "confirmed" or "yes". Missing or vague `RED_OUTPUT` → `CHANGES-REQUESTED`: "no evidence tests were RED before implementation — re-run TDD cycle and paste real output". For `integration-evidence`/`status-only` reviews that TDD evidence is inapplicable — grade the task's own evidence chain instead of demanding new tests or RED_OUTPUT. Either way, run the required checks yourself: `<task Verification Commands>`. Fresh PASS required, no trusting executor's output blindly.
33
65
  2. **Correctness** — Does the diff implement the requested behavior? Any obvious wrong assumptions, stale refs, missing cases?
34
66
  3. **Regression risk** — What existing behavior could this break? Are shared paths/tests/contracts still aligned? Run the wider suite only when the diff touched shared code; otherwise the task's own targeted commands are the gate and the wave-boundary full `yarn test` (see docs/AI_HANDOFF/RULES.md "Test selection") is the regression net.
35
67
  4. **Safety / security / data loss** — Destructive actions, auth/permission, path handling, unsafe shell/DB/file ops.
@@ -76,13 +108,14 @@ model, so reviewer ≠ executor still stands.
76
108
  ## Reviewer Verdict
77
109
 
78
110
  VERDICT: APPROVED | APPROVED-WITH-MINOR | CHANGES-REQUESTED | CRITICAL
111
+ REVIEW_KIND: product | integration-evidence | status-only
79
112
  REVIEWER_MODEL: [model name actually used]
80
113
  EXECUTOR_MODEL: [from executor report]
81
114
  PANEL_MEMBER: [panel tier — `smart`/`code`/`lite`, or `solo` when the panel degenerates; `-` outside panel runs]
82
115
  AGREEMENT_MAP: [lead member only — finding → panel members reporting it, from review-panel-aggregate.mjs output; `-` otherwise]
83
- VERIFICATION_RERUN:
84
- command: [exact command]
85
- result: [N pass / M fail]
116
+ VERIFICATION_RERUN: PASS | FAIL | NOT_RUN | N/A
117
+ command: [exact command, or `-` when N/A]
118
+ result: [N pass / M fail — or NOT_RUN — <reason>, or N/A — <reason>]
86
119
  TEST_PLAN_COVERAGE: [all-followed | partial — list gaps | missing — list]
87
120
  FINDINGS:
88
121
  critical:
@@ -103,7 +136,7 @@ After writing the verdict, update `docs/AI_HANDOFF/INDEX.md` row for this task:
103
136
  TASK: TASK-xxx
104
137
  VERDICT: approved | approved_minor | changes_requested | critical_block
105
138
  REVIEWER_MODEL: <exact model ID>
106
- VERIFICATION_RERUN: PASS | FAIL
139
+ VERIFICATION_RERUN: PASS | FAIL | NOT_RUN | N/A (+ one-line reason for NOT_RUN/N/A)
107
140
  BLOCKING: <one line per critical/important finding, or "none">
108
141
  ```
109
142
 
@@ -139,7 +172,9 @@ Same model is the most common silent failure. Do not skip this check.
139
172
  ### Rules
140
173
 
141
174
  - **Always re-run** the task's Verification Commands. If they fail, VERDICT = CRITICAL regardless of executor claims.
142
- - If executor said `TEST_PLAN_FOLLOWED: N/A` without a real justification, downgrade to at minimum CHANGES-REQUESTED.
175
+ - **VERIFICATION_RERUN is evidence, not prose.** `NOT_RUN` = the required verification never executed in your session (blocked, timed out, environment unavailable); `FAIL` = the re-run executed and did not pass. Either one must never accompany APPROVED or APPROVED-WITH-MINOR — with `NOT_RUN` the verdict is at most CHANGES-REQUESTED, and with `FAIL` it is CRITICAL per the ladder above. State the blocking status once, with the reason — a contradicting second line (e.g. header `PASS` next to `result: NOT_RUN`) is read fail-closed: the blocking status wins. `N/A` is only legitimate when this review has no executable required checks (e.g. a read-only spec/plan review, with `command: -` or no command line); when the task package defines Verification Commands, the re-run is required, so an `N/A` claim there counts as `NOT_RUN`. An `N/A` result that sits beside a declared executable `command:` (e.g. `command: yarn test`) contradicts its own evidence and counts as `NOT_RUN` — the aggregator fails it closed too. Never fabricate a PASS — a fabricated rerun is a fake test → CRITICAL.
176
+ - If executor said `TEST_PLAN_FOLLOWED: N/A` without a real justification, downgrade to at minimum CHANGES-REQUESTED (REVIEW_KIND=product; a check-free `integration-evidence`/`status-only` review may declare N/A legitimately when no Test Plan applies — the required Verification Commands still re-run).
177
+ - Verdict spelling is `-`/`_`-insensitive at the aggregator: `CHANGES_REQUESTED`, `CRITICAL_BLOCK`, `APPROVED_WITH_MINOR` parse to the same canonical verdicts as the hyphen forms, so write either spelling — a blocking verdict is never dropped as unparsed.
143
178
  - Never approve when test file has no real `expect`/`assert` - that is a fake test -> CRITICAL.
144
179
  - Keep the verdict block <= 30 lines. Findings are bullet points, not essays.
145
180
  - The same-model refusal above is non-negotiable: bypassing it defeats the entire Quality Gate.
@@ -59,8 +59,8 @@ resumable. If compaction does happen, the run resumes from the cursor automatica
59
59
 
60
60
  ### Run cursor — write after every step
61
61
 
62
- Maintain `docs/AI_HANDOFF/RUN.md`. Rewrite it (whole file, 7 lines) immediately after every
63
- numbered step completes:
62
+ Maintain `docs/AI_HANDOFF/RUN.md`. Rewrite it (whole file — the fixed field block below,
63
+ exactly as many lines as the fields in play) immediately after every numbered step completes:
64
64
 
65
65
  ```
66
66
  Command: handoff-fullstack
@@ -71,8 +71,21 @@ Cursor: wave <N> batch <M> — <what just finished>
71
71
  Next: <the exact next step to run>
72
72
  ExitPredicate: all(<term>[,<term>...]) # written once at P2; never relaxed
73
73
  QuietScans: <n>/<required> # only while sweeping for stragglers near the end
74
+ Acceptance: <the acceptance line the CurrentTask must satisfy> # optional
75
+ CurrentTask: <TASK-xxx> # optional; the single task mid-flight right now
76
+ RetryFingerprint: <stable id of the failing step> # optional; retry-loop identity
77
+ RetryCount: <n> # optional; consecutive attempts of that fingerprint
74
78
  ```
75
79
 
80
+ **Compact-carry fields.** `Acceptance`, `CurrentTask`, `RetryFingerprint` and `RetryCount`
81
+ are what a compacted session carries forward — these four lines plus `Goal`/`Next:`, never
82
+ the full subagent prompts. The SessionStart resume hook prints them verbatim after a
83
+ compact. On resume, work from the cursor: read `RUN.md` + `INDEX.md` and make a bounded
84
+ read of only the section for the active phase in this file — never a full re-read of the
85
+ whole command. Retry rule: a `RetryFingerprint` whose `RetryCount` has reached
86
+ `orchestration.maxAttemptsPerFailure` (2) must continue with a changed strategy, never the
87
+ same edit again.
88
+
76
89
  `ExitPredicate` grammar: `all(<term>[,<term>...])` or a single term; terms are
77
90
  `index:all-done`, `git:clean`, `file-exists:<relpath>`; goal-level runs add
78
91
  `agents:none-running` and `verify:pass` (an unprovable term reads as unmet, never
@@ -126,8 +139,10 @@ Wherever a step below says **verify the write**, it means this rule.
126
139
  If `docs/AI_HANDOFF/RUN.md` exists with `Phase:` not `done` or `blocked`, this is a
127
140
  **continuation, not a new cycle**. Do NOT re-plan, do NOT overwrite `PLAN.md`, do NOT ask
128
141
  the human whether to continue. Read the cursor, then jump straight to `Next:` and carry on.
129
- Tasks already `done` or `pending_review` are skipped; only `ready`, `in_progress`,
130
- `changes_requested` and `blocked` tasks are picked up.
142
+ Tasks already `done` are skipped. `pending_review` tasks are NOT re-implemented — they go to
143
+ the R-phase review (R2–R4). `ready`, `in_progress`, `changes_requested` and `blocked` tasks
144
+ are routed to implementation/fix (I-phase). Any nonterminal state gets a branch; none is
145
+ skipped silently.
131
146
 
132
147
  `Phase: blocked` is terminal for the run — the machinery (Stop gate, hooks, stop-coordinator)
133
148
  all treats it as closed. It is *paused*, not abandoned: an explicit `/ukit:handoff-fullstack`
@@ -387,7 +402,15 @@ If the lite agent reports it has no shell/Bash tool (or its shell call is reject
387
402
  - `docs/AI_HANDOFF/ACTIVE.md` → get `Base: <BASE>`
388
403
  - `docs/AI_HANDOFF/INDEX.md` → collect all `ready` tasks
389
404
 
390
- If no ready tasks → report and stop.
405
+ Branch on the nonterminal states — never report-and-stop while work is nonterminal:
406
+ - `pending_review` tasks exist → route to R1/R2 (review), do not implement them.
407
+ - `changes_requested` tasks exist → route to the I-phase fix round for those tasks.
408
+ - `in_progress` / stale `blocked` tasks exist → apply the Recovery rule below (finish in place
409
+ or supersede with `-R1`).
410
+ - No `ready` task but any of the above exists → continue the independent authorized work
411
+ in the other branches; a blocked branch does not block unrelated tasks.
412
+ - Only when every task is `done`/`cancelled_superseded` (or dependency-blocked with a recorded
413
+ reason) does this step end the wave with nothing to implement, and the run moves to R/F.
391
414
 
392
415
  Verify working tree is clean after the plan commit:
393
416
  ```bash
@@ -625,7 +648,12 @@ PLAN_COMMIT=$(git log --format=%H --grep='^handoff: plan' -n 1)
625
648
  git diff $PLAN_COMMIT # all handoff changes: committed waves + anything uncommitted
626
649
  ```
627
650
 
628
- If that diff is empty → implement was not completed. Do not stop: re-enter Phase 3 from the run cursor with the remaining `ready` tasks.
651
+ Classify the review by what the diff can show — an empty diff is NOT automatically "implement incomplete":
652
+ - **Product review** (the tasks change source/tests): an empty diff means implementation did not land. Re-enter Phase 3 from the run cursor with the remaining `ready` tasks.
653
+ - **Integration-evidence review** (the tasks verify behaviour, docs or runtime evidence without source edits): an empty diff is expected. Review the executor reports and the verification output on disk; a read-only evidence verdict is legitimate.
654
+ - **Status-only recording** (the task only moves status / records a verdict): an empty diff is expected. Write the status through the verify-the-write rule; do not edit source to make the diff non-empty.
655
+
656
+ Never make fake edits or fake tests to satisfy an empty diff, and never lower acceptance criteria to pass a review. Declaring P00 complete is not implied by any of the above. The review verdict and the original task acceptance are separate judgments: an integration-evidence review may approve the evidence chain while explicitly reporting product acceptance unmet or pending external observation — that report must not flip the product task to done — and a status-only recording is bookkeeping, never an independent review pass.
629
657
 
630
658
  > Orchestrator (this session) handles the R1 guard check directly.
631
659
 
@@ -650,7 +678,7 @@ The spawned reviewer agent reads `EXECUTOR_MODEL` from each task file `## Execut
650
678
  <each command from §Verification Commands across all pending_review tasks>
651
679
  ```
652
680
 
653
- If any command fails → verdict `critical_block`. Stop. Do NOT proceed to R4.
681
+ If any command fails → `VERDICT: CRITICAL` (`NEXT_STATUS_FOR_INDEX: critical_block`) for this task. This task's R4 write-up stops here — task-local, not run-local: the task enters the R4.5 auto-fix loop and the remaining batch tasks continue. Do NOT proceed to R4 for this task.
654
682
 
655
683
  ### R4 — Review unified diff + append verdict (strong model)
656
684
 
@@ -666,25 +694,29 @@ Stage gate (unic-decision): reviewers still generate findings; when `decisionPla
666
694
  Append verdict to each task file:
667
695
  ```
668
696
  ## Reviewer Verdict
669
- VERDICT: approved | approved_minor | changes_requested | critical_block
697
+ VERDICT: APPROVED | APPROVED-WITH-MINOR | CHANGES-REQUESTED | CRITICAL
670
698
  REVIEWER_MODEL: <your exact model ID>
671
699
  EXECUTOR_MODEL: <from Executor Report>
672
- VERIFICATION_RERUN: PASS | FAIL
700
+ REVIEW_KIND: product | integration-evidence | status-only
701
+ VERIFICATION_RERUN: PASS | FAIL | NOT_RUN | N/A
673
702
  FINDINGS:
674
703
  critical: <file:line — what fails> | none
675
704
  important: <file:line — risk> | none
676
705
  minor: <style/naming> | none
677
- NEXT_STATUS_FOR_INDEX: <status>
706
+ NEXT_STATUS_FOR_INDEX: approved | approved_minor | changes_requested | critical_block
678
707
  ```
679
708
 
709
+ `VERDICT:` uses the aggregator's canonical hyphen vocabulary — underscore spellings (`changes_requested`, `critical_block`, `approved_with_minor`) parse to the same verdicts, so a blocking reviewer is never dropped as unparsed. `NOT_RUN` = the required re-run never executed in your session; `FAIL` = it executed and failed; either blocks approval. `N/A` is legitimate only when this review has no executable required checks (`command: -` or none) — an `N/A` beside a declared executable `command:` counts as `NOT_RUN`. `REVIEW_KIND` comes from the R1 classification; it scopes only the implementation-TDD evidence (new tests + RED_OUTPUT) — required executable checks and the task's original acceptance criteria are never waived, and approving an evidence review never marks product acceptance met.
710
+
680
711
  Then return to the orchestrator **at most 6 lines** — the full verdict is already on disk:
681
712
  ```
682
713
  TASK: TASK-xxx
683
714
  VERDICT: approved | approved_minor | changes_requested | critical_block
684
715
  REVIEWER_MODEL: <exact model ID>
685
- VERIFICATION_RERUN: PASS | FAIL
716
+ VERIFICATION_RERUN: PASS | FAIL | NOT_RUN | N/A (+ one-line reason)
686
717
  BLOCKING: <one line per critical/important finding, or "none">
687
718
  ```
719
+ The return `VERDICT:` carries the NEXT_STATUS_FOR_INDEX vocabulary; the on-disk block's `VERDICT:` field uses the canonical hyphen vocabulary above.
688
720
  Do not paste the diff, the findings prose, or verification logs back into the orchestrator.
689
721
 
690
722
  ### R4.5 — Auto-fix loop (max 2 rounds)
@@ -47,8 +47,9 @@ git diff --stat $PLAN_COMMIT # every handoff change: wave commits + working tr
47
47
  git status # no handoff branches/worktrees should remain
48
48
  ```
49
49
 
50
- If that diff is empty → handoff-implement was not completed. Report which tasks are still
51
- `ready` and stop; there is nothing to review yet.
50
+ Classify by what the diff can show. An empty diff is NOT automatically "implement incomplete":
51
+ - If `pending_review` tasks exist and the diff is empty, check the task kind first. A product task (changes source/tests) with an empty diff means implementation did not land — report those tasks as still `ready`, do not review them. An evidence-only or status-only task with an empty diff is a legitimate read-only review: review the executor report and verification output on disk, never create fake edits or fake tests to make the diff non-empty, and never lower acceptance criteria.
52
+ - If there are no `pending_review` tasks at all, report which tasks are still `ready` and stop; there is nothing to review yet.
52
53
 
53
54
  ## Step 2 — Review the diff
54
55
 
@@ -58,6 +59,20 @@ If that diff is empty → handoff-implement was not completed. Report which task
58
59
 
59
60
  Degenerate panel: if `modelRoles['review-panel']` is empty or has one entry, spawn a single reviewer and require `PANEL_MEMBER: solo` in its verdict.
60
61
 
62
+ **omp `tasks[]` dispatch — ordinary entries only.** When batching reviewers through the omp `task` tool, every `tasks[]` entry carries only the fields ordinary dispatch supports — `agent`, `name`, `task` — and nothing else:
63
+
64
+ ```yaml
65
+ tasks:
66
+ - agent: "code-reviewer"
67
+ name: "TASK-001-smart"
68
+ task: "Review docs/AI_HANDOFF/tasks/TASK-001.md per /ukit:handoff-review Step 2a-2d. PANEL_MEMBER: smart. Return at most 6 lines."
69
+ - agent: "code-reviewer"
70
+ name: "TASK-001-code"
71
+ task: "Review docs/AI_HANDOFF/tasks/TASK-001.md per /ukit:handoff-review Step 2a-2d. PANEL_MEMBER: code. Return at most 6 lines."
72
+ ```
73
+
74
+ Never add fields ordinary dispatch does not support: `sourceSession` and a per-task `model` are not supported there — omp 18.12.5 batch validation rejects them deterministically (confirmed in the installed binary's validator; not advertised as a live-probe pass) — and model binding comes from agent frontmatter via `modelRoles`, never from a task field. There is no seance workaround for reviewer dispatch. A deterministic validation rejection is never retried with identical args. A retry is allowed only after a relevant argument changes — `agent`, or the `tasks[]` entry's unsupported-field set (removing `sourceSession`/`model`). Prompt reword, `read`, and `status` text are NOT relevant changes: they never make a retry legitimate and never reset the failure budget, which stays the existing `maxAttemptsPerFailure: 2`. When that budget is spent, preserve across resume — in the task's `## Discussion` or the `RUN.md` cursor — a compact normalized error (prose stripped) plus the relevant key fingerprint and the attempt count, mark that task's verification `NOT_RUN` (never `PASS` — unverified work is not verified), and continue with independent tasks. Host pre-hook rejection behavior is unknown: never assume a hook will or will not catch the same call.
75
+
61
76
  Each spawned reviewer agent performs 2a–2d below per task:
62
77
 
63
78
  ### 2a — Model isolation check (always first)
@@ -77,7 +92,7 @@ Read `EXECUTOR_MODEL` from each task file `## Executor Report`.
77
92
  <each command from §Verification Commands across all tasks>
78
93
  ```
79
94
 
80
- If any command fails → verdict: `critical_block` for this task. That ends **this task's**
95
+ If any command fails → `VERDICT: CRITICAL` with `NEXT_STATUS_FOR_INDEX: critical_block` for this task. That ends **this task's**
81
96
  review, not the run — the orchestrator picks it up in Step 3's auto-fix loop.
82
97
 
83
98
  ### 2c — Review unified diff
@@ -97,26 +112,30 @@ matches the task file's wording.
97
112
 
98
113
  ```
99
114
  ## Reviewer Verdict
100
- VERDICT: approved | approved_minor | changes_requested | critical_block
115
+ VERDICT: APPROVED | APPROVED-WITH-MINOR | CHANGES-REQUESTED | CRITICAL
101
116
  REVIEWER_MODEL: <your exact model ID>
102
117
  EXECUTOR_MODEL: <from Executor Report>
118
+ REVIEW_KIND: product | integration-evidence | status-only
103
119
  PANEL_MEMBER: <panel tier | solo | ->
104
- VERIFICATION_RERUN: PASS | FAIL
120
+ VERIFICATION_RERUN: PASS | FAIL | NOT_RUN | N/A
105
121
  FINDINGS:
106
122
  critical: <file:line — what fails> | none
107
123
  important: <file:line — risk> | none
108
124
  minor: <style/naming> | none
109
- NEXT_STATUS_FOR_INDEX: <status>
125
+ NEXT_STATUS_FOR_INDEX: approved | approved_minor | changes_requested | critical_block
110
126
  ```
111
127
 
128
+ `VERDICT:` uses the aggregator's canonical hyphen vocabulary — underscore spellings (`changes_requested`, `critical_block`, `approved_with_minor`) parse to the same verdicts, so a blocking reviewer is never dropped as unparsed. `NOT_RUN` = the required re-run never executed in your session; `FAIL` = it executed and failed; either blocks approval. `N/A` is legitimate only when this review has no executable required checks (`command: -` or none) — an `N/A` beside a declared executable `command:` counts as `NOT_RUN`. `REVIEW_KIND` comes from the Step 1 classification; it scopes only the implementation-TDD evidence (new tests + RED_OUTPUT) — required executable checks and the task's original acceptance criteria are never waived, and approving an evidence review never marks product acceptance met.
129
+
112
130
  Then return to the orchestrator **at most 6 lines** — the full verdict is already on disk:
113
131
  ```
114
132
  TASK: TASK-xxx
115
133
  VERDICT: approved | approved_minor | changes_requested | critical_block
116
134
  REVIEWER_MODEL: <exact model ID>
117
- VERIFICATION_RERUN: PASS | FAIL
135
+ VERIFICATION_RERUN: PASS | FAIL | NOT_RUN | N/A (+ one-line reason)
118
136
  BLOCKING: <one line per critical/important finding, or "none">
119
137
  ```
138
+ The return `VERDICT:` carries the NEXT_STATUS_FOR_INDEX vocabulary; the on-disk block's `VERDICT:` field uses the canonical hyphen vocabulary above.
120
139
  Do not paste the diff, the findings prose, or verification logs back into the orchestrator.
121
140
 
122
141
  ### 2e — Aggregate the panel + lead bucketing
@@ -135,7 +154,14 @@ changes, or zero parseable verdict blocks — it fails closed; else
135
154
  (round N)` blocks supersede earlier rounds.
136
155
  `consensus≥2 identical findings = high signal` — a finding reported by two or more
137
156
  members is high-signal. The consensus verdict, not any single member's vote, drives
138
- `NEXT_STATUS_FOR_INDEX`.
157
+ `NEXT_STATUS_FOR_INDEX`. The aggregate reads every `VERIFICATION_RERUN` header and
158
+ `result:` line in a block and fails closed: `FAIL`/`NOT_RUN` in any position —
159
+ including repeated or contradicting headers — block approval, and an `N/A` beside a
160
+ declared executable command counts as `NOT_RUN` (a placeholder `command: -` or no
161
+ command line keeps a genuine no-check review legitimate). Underscore verdict aliases
162
+ parse to the canonical hyphen verdicts, so a member following the template always
163
+ votes; a genuinely unparsable block never votes (named in `UNPARSED`), and zero
164
+ parseable blocks fail closed to `changes_requested`.
139
165
 
140
166
  Then the **lead member** (first entry in `modelRoles['review-panel']` = the judgment
141
167
  tier) applies the lead-judgment buckets to every finding — **Act on** / **Consider** /
@@ -216,6 +216,13 @@ async function emitOrdinaryResume(payload, source) {
216
216
  const base = field('Base');
217
217
  const cursor = field('Cursor');
218
218
  const next = field('Next');
219
+ // Compact-resume carry (bounded): the goal/acceptance, current task and retry
220
+ // fingerprint+count survive compaction without re-reading the full prompt or the
221
+ // whole command file. Absent fields are simply omitted.
222
+ const acceptance = field('Acceptance');
223
+ const currentTask = field('CurrentTask');
224
+ const retryFingerprint = field('RetryFingerprint');
225
+ const retryCount = field('RetryCount');
219
226
 
220
227
  // The session's CURRENT handoff intent — structured transcript evidence, the
221
228
  // same shared module the Stop gate uses. A session whose latest request is
@@ -287,15 +294,22 @@ async function emitOrdinaryResume(payload, source) {
287
294
  ` Phase: ${phase}`,
288
295
  ` Cursor: ${cursor || '(not recorded)'}`,
289
296
  ` Next: ${next || '(not recorded)'}`,
297
+ ...(acceptance ? [` Acceptance: ${acceptance}`] : []),
298
+ ...(currentTask ? [` CurrentTask: ${currentTask}`] : []),
299
+ ...(retryFingerprint ? [` RetryFingerprint: ${retryFingerprint} (RetryCount: ${retryCount || '0'})`] : []),
290
300
  '',
291
301
  'This is a CONTINUATION, not a new cycle. Do not re-plan, do not overwrite PLAN.md or the',
292
302
  'task files, and do not ask the user whether to continue — they already asked for a',
293
303
  'one-shot run and the pipeline was interrupted, not cancelled.',
294
304
  '',
295
305
  'Read docs/AI_HANDOFF/RUN.md and docs/AI_HANDOFF/INDEX.md, then execute the `Next:` step',
296
- 'above using .claude/commands/ukit/handoff-fullstack.md as the procedure. Skip tasks',
297
- 'already `done` or `pending_review`; pick up `ready`, `in_progress`, `changes_requested`',
298
- 'and `blocked` ones. Keep writing the cursor after every step.',
306
+ 'above with a bounded read of only the section for the active phase in',
307
+ '.claude/commands/ukit/handoff-fullstack.md — never a full re-read of the file. Route',
308
+ 'tasks by status: `done` is skipped; `pending_review` goes to the R-phase review (it is',
309
+ 'reviewed there, not re-implemented); `ready`, `in_progress`, `changes_requested` and',
310
+ '`blocked` are picked up for implementation/fix. Keep writing the cursor after every step.',
311
+ 'If a retry loop was live (RetryFingerprint + RetryCount above), continue it: same',
312
+ 'fingerprint at/over two attempts requires a changed strategy, never the same edit again.',
299
313
  ];
300
314
 
301
315
  if (source === 'compact') {