@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.
@@ -15,6 +15,38 @@ You are the independent reviewer for UKit's handoff Quality Gate. Your model is
15
15
  - `code` (default, if not specified) — reviewing a handoff task diff. Follow **Code Review** below, unchanged.
16
16
  - `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.
17
17
 
18
+ ## REVIEW_KIND (code reviews only)
19
+
20
+ Set one kind per review — from the task's own classification when the spawning
21
+ command names one, else from what the diff shows:
22
+
23
+ - `product` (default when the diff changes source or tests) — the full Code
24
+ Review rubric below applies unchanged, including Test Plan adherence and
25
+ RED_OUTPUT evidence.
26
+ - `integration-evidence` — the diff verifies or mirrors behavior already
27
+ accepted elsewhere (template mirrors, parity copies, runtime/docs evidence).
28
+ The new-tests + RED_OUTPUT TDD requirements are inapplicable when the task
29
+ introduces no new behavior under test; review the evidence chain instead and
30
+ never fabricate tests or edits to satisfy them.
31
+ - `status-only` — the task only records status/evidence; an empty diff is
32
+ legitimate. Review the executor report and verification output on disk.
33
+
34
+ The review kind scopes only the implementation-TDD evidence (new tests +
35
+ RED_OUTPUT). It never waives the required executable checks — the task's
36
+ Verification Commands still re-run in your session (fresh PASS or the severity
37
+ ladder applies; `NOT_RUN` on a required command blocks in every kind) — and it
38
+ never lowers the task's original acceptance criteria.
39
+
40
+ **Verdict ≠ acceptance.** The review verdict and the task's original acceptance
41
+ are separate judgments. An `integration-evidence` review may legitimately be
42
+ `APPROVED` as a review (the evidence chain is sound and complete) while the
43
+ verdict's NOTES/BLOCKING explicitly report that product acceptance remains
44
+ unmet or pending external observation — that report flows through
45
+ `NEXT_STATUS_FOR_INDEX`/the task discussion and must not flip the product task
46
+ to `done` or close a pending parent item. A `status-only` recording is
47
+ bookkeeping, not an independent review pass: it never counts as evidence that
48
+ independent review approved the underlying work.
49
+
18
50
  ## Code Review (REVIEW_TARGET_TYPE=code)
19
51
 
20
52
  ### Inputs you expect
@@ -28,7 +60,7 @@ If any input is missing, return `CHANGES-REQUESTED` with reason "incomplete hand
28
60
  ### Review order
29
61
 
30
62
  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.
31
- 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.
63
+ 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.
32
64
  2. **Correctness** — Does the diff implement the requested behavior? Any obvious wrong assumptions, stale refs, missing cases?
33
65
  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.
34
66
  4. **Safety / security / data loss** — Destructive actions, auth/permission, path handling, unsafe shell/DB/file ops.
@@ -75,13 +107,14 @@ model, so reviewer ≠ executor still stands.
75
107
  ## Reviewer Verdict
76
108
 
77
109
  VERDICT: APPROVED | APPROVED-WITH-MINOR | CHANGES-REQUESTED | CRITICAL
110
+ REVIEW_KIND: product | integration-evidence | status-only
78
111
  REVIEWER_MODEL: [model name actually used]
79
112
  EXECUTOR_MODEL: [from executor report]
80
113
  PANEL_MEMBER: [panel tier — `@slow`/`@default`/`@smol`, or `solo` when the panel degenerates; `-` outside panel runs]
81
114
  AGREEMENT_MAP: [lead member only — finding → panel members reporting it, from review-panel-aggregate.mjs output; `-` otherwise]
82
- VERIFICATION_RERUN:
83
- command: [exact command]
84
- result: [N pass / M fail]
115
+ VERIFICATION_RERUN: PASS | FAIL | NOT_RUN | N/A
116
+ command: [exact command, or `-` when N/A]
117
+ result: [N pass / M fail — or NOT_RUN — <reason>, or N/A — <reason>]
85
118
  TEST_PLAN_COVERAGE: [all-followed | partial — list gaps | missing — list]
86
119
  FINDINGS:
87
120
  critical:
@@ -102,7 +135,7 @@ After writing the verdict, update `docs/AI_HANDOFF/INDEX.md` row for this task:
102
135
  TASK: TASK-xxx
103
136
  VERDICT: approved | approved_minor | changes_requested | critical_block
104
137
  REVIEWER_MODEL: <exact model ID>
105
- VERIFICATION_RERUN: PASS | FAIL
138
+ VERIFICATION_RERUN: PASS | FAIL | NOT_RUN | N/A (+ one-line reason for NOT_RUN/N/A)
106
139
  BLOCKING: <one line per critical/important finding, or "none">
107
140
  ```
108
141
 
@@ -138,7 +171,9 @@ Same model is the most common silent failure. Do not skip this check.
138
171
  ### Rules
139
172
 
140
173
  - **Always re-run** the task's Verification Commands. If they fail, VERDICT = CRITICAL regardless of executor claims.
141
- - If executor said `TEST_PLAN_FOLLOWED: N/A` without a real justification, downgrade to at minimum CHANGES-REQUESTED.
174
+ - **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.
175
+ - 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).
176
+ - 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.
142
177
  - Never approve when test file has no real `expect`/`assert` - that is a fake test -> CRITICAL.
143
178
  - Keep the verdict block <= 30 lines. Findings are bullet points, not essays.
144
179
  - The same-model refusal above is non-negotiable: bypassing it defeats the entire Quality Gate.
@@ -29,6 +29,7 @@ import {
29
29
  // drift apart (a configured 60-120s chain used to be killed here at 14-20s, which
30
30
  // for Edit|Write is a fail-closed transport failure = every edit blocked).
31
31
  import { resolveChainExecTimeoutMs } from '../../../.claude/ukit/runtime/hook-chain-budget.mjs';
32
+ import { preflightOmpTaskDispatch } from '../../../.claude/ukit/runtime/omp-task-dispatch-guard.mjs';
32
33
 
33
34
  // OMP-3 (TASK-002): pi.exec forwards only {cwd, signal, timeout} to its spawn —
34
35
  // an environment option is dropped (verified against the bundled omp exec), and the host's
@@ -276,6 +277,27 @@ function runtimeMetadata(event = {}, context = {}) {
276
277
  };
277
278
  }
278
279
 
280
+ // OMP 18.12.5 before_agent_start has no origin field. Recover only the nearest
281
+ // native prompt-bearing branch entry, never a prefix claimed by prompt text.
282
+ export function promptOriginFromBranch(prompt, context = {}) {
283
+ try {
284
+ const branch = context?.sessionManager?.getBranch?.();
285
+ if (!Array.isArray(branch)) return 'user';
286
+ for (let i = branch.length - 1; i >= Math.max(0, branch.length - 64); i -= 1) {
287
+ const entry = branch[i];
288
+ if (entry?.type === 'message' && entry.message?.role === 'user') return 'user';
289
+ const custom = entry?.type === 'custom_message' ? entry
290
+ : entry?.type === 'message' && entry.message?.role === 'custom' ? entry.message : null;
291
+ if (!custom || custom.customType !== 'session-stop-continuation') continue;
292
+ return custom.attribution === 'agent' && typeof custom.content === 'string'
293
+ && custom.content.trim() === String(prompt ?? '').trim() ? 'system' : 'user';
294
+ }
295
+ } catch {
296
+ // Older hosts or a closed session may not expose branch metadata.
297
+ }
298
+ return 'user';
299
+ }
300
+
279
301
  function buildHookPayload(hookEventName, fields = {}) {
280
302
  const payload = { hook_event_name: hookEventName, ukit_harness: 'omp' };
281
303
  const mappings = [
@@ -290,6 +312,7 @@ function buildHookPayload(hookEventName, fields = {}) {
290
312
  ['prompt', 'prompt'],
291
313
  ['transcriptPath', 'transcript_path'],
292
314
  ['source', 'source'],
315
+ ['promptOrigin', 'ukit_prompt_origin'],
293
316
  ['subagentSession', 'ukit_subagent_session'],
294
317
  ];
295
318
  for (const [field, payloadField] of mappings) {
@@ -892,6 +915,20 @@ export async function runToolCall(pi, event, { projectRoot, context: extensionCo
892
915
  : [];
893
916
 
894
917
  const metadata = runtimeMetadata(event, extensionContext);
918
+ // Only the native OMP task tool uses this schema. Block, never rewrite: ordinary
919
+ // agents must omit seance-only fields, including empty/null placeholders. Some
920
+ // host versions may reject arguments before tool_call; that remains native-owned.
921
+ if (String(rawToolName ?? '').trim().toLowerCase() === 'task') {
922
+ const state = await readRouteState(projectRoot, { session_id: metadata.sessionId });
923
+ const dispatch = await preflightOmpTaskDispatch({
924
+ projectRoot, sessionId: metadata.sessionId, taskInstanceId: state?.taskInstanceId,
925
+ args: event.input,
926
+ });
927
+ if (dispatch.action === 'block') {
928
+ return { block: true, toolName: 'Task', invoked: [], context: [],
929
+ reason: `UKit OMP task preflight: ${dispatch.guidance}` };
930
+ }
931
+ }
895
932
  const payload = buildHookPayload('PreToolUse', {
896
933
  toolName,
897
934
  toolInput: normalizeToolInput(toolName, event.input),
@@ -981,6 +1018,7 @@ export async function runBeforeAgentStart(pi, event, { projectRoot, context: ext
981
1018
  const metadata = runtimeMetadata(event, extensionContext);
982
1019
  const payload = buildHookPayload('UserPromptSubmit', {
983
1020
  prompt: event.prompt,
1021
+ promptOrigin: promptOriginFromBranch(event.prompt, extensionContext),
984
1022
  ...metadata,
985
1023
  });
986
1024
  const result = await runScriptChain(pi, HOOK_EVENT_MAP.before_agent_start, payload, { projectRoot });