@ngockhoale/ukit 2.5.1 → 2.6.2

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.
Files changed (53) hide show
  1. package/CHANGELOG.md +80 -0
  2. package/manifests/platform.full.yaml +16 -0
  3. package/package.json +1 -1
  4. package/src/cli/commands/install.js +49 -2
  5. package/src/cli/commands/update.js +5 -0
  6. package/src/core/output/index.js +77 -0
  7. package/src/core/update.js +36 -5
  8. package/templates/.claude/agents/code-reviewer.md +12 -1
  9. package/templates/.claude/agents/feature-implementer.md +4 -2
  10. package/templates/.claude/agents/handoff-planner.md +36 -1
  11. package/templates/.claude/commands/ukit/handoff-clear.md +4 -0
  12. package/templates/.claude/commands/ukit/handoff-create.md +23 -7
  13. package/templates/.claude/commands/ukit/handoff-fullstack.md +181 -17
  14. package/templates/.claude/commands/ukit/handoff-implement.md +9 -2
  15. package/templates/.claude/commands/ukit/handoff-review.md +4 -1
  16. package/templates/.claude/commands/ukit/handoff-status.md +6 -2
  17. package/templates/.claude/hooks/auto-allow-bash.sh +17 -5
  18. package/templates/.claude/hooks/block-dangerous.sh +18 -5
  19. package/templates/.claude/hooks/completion-gate.sh +17 -5
  20. package/templates/.claude/hooks/compress-output.sh +15 -6
  21. package/templates/.claude/hooks/context-hardcap-gate.sh +77 -46
  22. package/templates/.claude/hooks/context-window-guard.sh +41 -27
  23. package/templates/.claude/hooks/handoff-model-guard.sh +62 -14
  24. package/templates/.claude/hooks/handoff-resume.sh +20 -7
  25. package/templates/.claude/hooks/post-edit-verify.sh +17 -5
  26. package/templates/.claude/hooks/pre-edit-backup.sh +17 -5
  27. package/templates/.claude/hooks/project-important.sh +18 -1
  28. package/templates/.claude/hooks/protect-files.sh +18 -5
  29. package/templates/.claude/hooks/record-execution.sh +17 -5
  30. package/templates/.claude/hooks/sensitive-data-guard.sh +48 -9
  31. package/templates/.claude/hooks/skill-router.sh +124 -86
  32. package/templates/.claude/hooks/stale-spec-guard.sh +22 -6
  33. package/templates/.claude/hooks/task-watchdog.sh +22 -7
  34. package/templates/.claude/hooks/verification-guard.sh +17 -5
  35. package/templates/.claude/hooks/vision-router.sh +17 -5
  36. package/templates/.claude/settings.json +15 -10
  37. package/templates/.claude/ukit/index/provision-worktree.mjs +30 -2
  38. package/templates/.claude/ukit/runtime/execution-ledger.mjs +99 -1
  39. package/templates/.claude/ukit/runtime/hook-input.sh +25 -0
  40. package/templates/.claude/ukit/runtime/hook-telemetry.mjs +86 -2
  41. package/templates/.claude/ukit/runtime/output-compression.mjs +87 -0
  42. package/templates/.claude/ukit/runtime/stop-coordinator.mjs +201 -11
  43. package/templates/.omp/RULES.md +9 -1
  44. package/templates/.omp/agents/code-reviewer.md +12 -1
  45. package/templates/.omp/agents/feature-implementer.md +4 -2
  46. package/templates/.omp/agents/handoff-planner.md +36 -1
  47. package/templates/.omp/hooks/pre/ukit-bridge.js +110 -3
  48. package/templates/AGENTS.md +14 -0
  49. package/templates/CLAUDE.md +14 -0
  50. package/templates/docs/AI_HANDOFF/RULES.md +37 -4
  51. package/templates/docs/AI_HANDOFF/SPEC.md +98 -0
  52. package/templates/docs/AI_HANDOFF/tasks/_TEMPLATE.md +4 -1
  53. package/templates/ukit/storage/config.json +48 -9
@@ -66,9 +66,10 @@ numbered step completes:
66
66
  Command: handoff-fullstack
67
67
  Goal: <one sentence from the **Problem / feature** section above>
68
68
  Base: <BASE branch>
69
- Phase: <P1|P2|P2.5|P3|I1|I2|I3|I4|R1|R2|R3|R4|R5|done>
69
+ Phase: <P0|P1|P2|P2.5|P3|I1|I2|I3|I4|R1|R2|R3|R4|R4.5|R5|F|done|blocked>
70
70
  Cursor: wave <N> batch <M> — <what just finished>
71
71
  Next: <the exact next step to run>
72
+ QuietScans: <n>/<required> # only while sweeping for stragglers near the end
72
73
  ```
73
74
 
74
75
  This file is the resume contract. It costs one small write per step and is what turns an
@@ -76,13 +77,99 @@ interrupted run into a continuable one.
76
77
 
77
78
  ### Resume — when the run is re-invoked mid-flight
78
79
 
79
- If `docs/AI_HANDOFF/RUN.md` exists with `Phase:` not `done`, this is a **continuation, not a
80
- new cycle**. Do NOT re-plan, do NOT overwrite `PLAN.md`, do NOT ask the human whether to
81
- continue. Read the cursor, then jump straight to `Next:` and carry on. Tasks already `done`
82
- or `pending_review` are skipped; only `ready`, `in_progress`, `changes_requested` and
83
- `blocked` tasks are picked up.
80
+ If `docs/AI_HANDOFF/RUN.md` exists with `Phase:` not `done` or `blocked`, this is a
81
+ **continuation, not a new cycle**. Do NOT re-plan, do NOT overwrite `PLAN.md`, do NOT ask
82
+ the human whether to continue. Read the cursor, then jump straight to `Next:` and carry on.
83
+ Tasks already `done` or `pending_review` are skipped; only `ready`, `in_progress`,
84
+ `changes_requested` and `blocked` tasks are picked up.
84
85
 
85
- Only when `RUN.md` is absent or `Phase: done` does a new request start a fresh cycle.
86
+ `Phase: blocked` is terminal for the run — the machinery (Stop gate, hooks, stop-coordinator)
87
+ all treats it as closed. It is *paused*, not abandoned: an explicit `/ukit:handoff-fullstack`
88
+ re-invocation resumes it as a continuation; `ukit handoff-clear` abandons it. Only when
89
+ `RUN.md` is absent or `Phase: done` does a new request start a fresh cycle.
90
+
91
+ ---
92
+
93
+ ## §0 — Completion contract (terminal outputs)
94
+
95
+ This run has exactly **two** legal ways to end:
96
+
97
+ - `HANDOFF FULLSTACK COMPLETE` — the completion gate in Phase F passed. First line of the
98
+ final report, verbatim.
99
+ - `HANDOFF FULLSTACK BLOCKED` — every remaining task depends on the same external blocker
100
+ (missing credential, dead service, permission wall) AND no safe local work exists. Write
101
+ `Phase: blocked` to RUN.md before emitting it so the Stop gate releases the session.
102
+
103
+ Nothing else ends the run. A recap is a progress notification, not a stopping point. If you
104
+ emit a checkpoint it MUST use this shape and be followed by immediate work:
105
+
106
+ ```
107
+ CHECKPOINT — WORK CONTINUING
108
+ Cycle:
109
+ Completed since last checkpoint:
110
+ Currently executing:
111
+ Exact next action:
112
+ Verification status:
113
+ Run cursor updated: YES
114
+ ```
115
+
116
+ Never stop at `recap: ... Next: ...` while unfinished work remains — the Stop gate now
117
+ bounces that stop straight back into the pipeline, so stopping is not even restful; it
118
+ just costs a round-trip.
119
+
120
+ ---
121
+
122
+ ## Phase 0 — Sweep & Inventory (before planning)
123
+
124
+ Before Phase 1, build the authoritative task inventory — the run owns **all** unfinished
125
+ work, not only the current plan:
126
+
127
+ 1. `docs/AI_HANDOFF/INDEX.md` — every task not `done`/`cancelled_superseded`.
128
+ 2. `docs/AI_HANDOFF/tasks/TASK-*.md` — stale `in_progress` from a dead session, `blocked`,
129
+ `changes_requested`, `needs_executor_report`, `needs_breakdown`.
130
+ 3. Previous cycles — `docs/AI_HANDOFF/HISTORY.md` and `archive/` for cycles closed with
131
+ unfinished tasks; their leftovers join THIS cycle.
132
+ 4. `docs/TASKS.md` — `Ready for AI` items are newly-assigned work; fold them into the plan.
133
+ 5. `git status` / `git diff` — uncommitted work-in-progress that must be finished or
134
+ checkpointed, never silently dropped.
135
+
136
+ The inventory feeds P2: the planner either schedules every discovered item or records why
137
+ it is out of scope (§1/§2 of PLAN.md). Silent omission is a plan defect.
138
+
139
+ **Recovery rule — stuck task records.** For every `pending`, stale `in_progress`, or
140
+ `blocked` task whose record cannot be cleanly resumed (invalid state, orphaned worktree
141
+ gone, contradicting reports):
142
+
143
+ 1. Inspect code/tests/git to see what is actually missing.
144
+ 2. Recoverable → finish the original task in place.
145
+ 3. Not recoverable → mark the old row `cancelled_superseded`, create `TASK-xxx-R1`
146
+ (`-R2`, …) with the same spec references, acceptance criteria and verification, link
147
+ both files' `## Discussion` threads, and execute the replacement in this cycle.
148
+ 4. Never leave stranded work merely because a task record was pending.
149
+
150
+ ---
151
+
152
+ ## Watchdog — surviving idle and new assignments
153
+
154
+ The run must keep itself alive without the human watching.
155
+
156
+ - **Stop gate (Claude Code, automatic):** while `RUN.md` `Phase:` is not `done`/`blocked`,
157
+ `completion-gate.sh` refuses the stop with the cursor's `Next:` step. Recaps and
158
+ premature stops cannot park the run. A stalled-cursor breaker
159
+ (`handoff.fullstack.stopGateMaxStalledBlocks`, default 12) releases the session if the
160
+ cursor has genuinely stopped advancing — the escape hatch, not the norm.
161
+ - **Scheduled wakeup (when the harness offers one):** arm it at run start — Claude Code:
162
+ `CronCreate` a ~`handoff.fullstack.idleWatchdogMin`-minute (default 5) session job with
163
+ prompt `Resume HANDOFF FULLSTACK from docs/AI_HANDOFF/RUN.md — continue the Next: step`;
164
+ `/loop`-style tools: the equivalent interval. Cancel/let it expire once `Phase: done`.
165
+ - **Harnesses with neither:** treat EVERY activation as a watchdog recovery turn — read
166
+ RUN.md first, rescan the inventory, continue `Next:`.
167
+
168
+ **Quiet-period rule.** The run may finish only after the backlog is empty AND
169
+ `handoff.fullstack.quietScansRequired` (default 2) consecutive watchdog scans find no new
170
+ assignment, no recoverable task, and no uncommitted intended change. Record each quiet
171
+ scan in RUN.md as `QuietScans: <n>/<required>`. A scan that finds anything resets the
172
+ counter and starts a new cycle automatically.
86
173
 
87
174
  ---
88
175
 
@@ -117,7 +204,15 @@ The planner agent does the following (use P1 summary — do NOT re-read files):
117
204
  BASE=$(git symbolic-ref --short HEAD)
118
205
  ```
119
206
 
120
- 3. **Write `docs/AI_HANDOFF/PLAN.md`** — all 6 sections mandatory:
207
+ 3. **Write `docs/AI_HANDOFF/SPEC.md`** — the detailed implementation spec, from
208
+ `templates/docs/AI_HANDOFF/SPEC.md`'s section list (or RULES.md §Spec). It must be
209
+ concrete enough that an executor implements without guessing: exact paths, module and
210
+ API names, schemas, validation rules, permissions, empty/error states, migration
211
+ behavior, and test expectations — every applicable section filled, open questions
212
+ resolved to chosen defaults recorded inline. Implementation begins from SPEC+PLAN,
213
+ never from the raw request.
214
+
215
+ 4. **Write `docs/AI_HANDOFF/PLAN.md`** — all 6 sections mandatory:
121
216
  - §1 Intent — problem + success definition
122
217
  - §2 Scope — in / out of scope; same-wave tasks must not modify the same file (prevents merge conflicts)
123
218
  - §3 Approach — solution, trade-offs, alternatives rejected
@@ -131,8 +226,9 @@ The planner agent does the following (use P1 summary — do NOT re-read files):
131
226
  PLANNER_MODEL: <your exact model ID>
132
227
  ```
133
228
 
134
- 4. **Create `docs/AI_HANDOFF/tasks/TASK-001.md`, `TASK-002.md`...** from `_TEMPLATE.md`.
229
+ 5. **Create `docs/AI_HANDOFF/tasks/TASK-001.md`, `TASK-002.md`...** from `_TEMPLATE.md`.
135
230
  Every task MUST have:
231
+ - Spec references (SPEC.md section IDs this task implements)
136
232
  - Target Files (exact paths — no two tasks in same wave share a file)
137
233
  - Dependencies (`TASK-xxx` or `none` — wave structure inferred from this)
138
234
  - Test Cases (Type | Name | Expected — ≥1 happy + ≥2 edge cases of different kinds)
@@ -141,17 +237,21 @@ The planner agent does the following (use P1 summary — do NOT re-read files):
141
237
  - Acceptance Criteria (verifiable checklist)
142
238
  Missing any field → status `needs_breakdown`, never `ready`.
143
239
 
144
- 5. **Update `docs/AI_HANDOFF/INDEX.md`** — one row per task, `status=ready`.
240
+ 6. **Update `docs/AI_HANDOFF/INDEX.md`** — one row per task, `status=ready`. Every
241
+ unfinished item from the Phase 0 sweep is either a row here or superseded by a
242
+ `-R<n>` recovery row.
145
243
 
146
- 6. **Update `docs/AI_HANDOFF/ACTIVE.md`:**
244
+ 7. **Update `docs/AI_HANDOFF/ACTIVE.md`:**
147
245
  ```
148
246
  Cycle: <ID> Date: <YYYY-MM-DD> Base: <BASE>
149
247
  Goal: <1 sentence>
248
+ Spec: docs/AI_HANDOFF/SPEC.md
150
249
  Tasks: <N> total
151
250
  Status: planning_done — ready for executor
152
251
  ```
153
252
 
154
- 7. **Report:** task IDs, dependency graph, any `needs_breakdown` tasks + reason.
253
+ 8. **Report:** task IDs, dependency graph, recovery/superseded tasks, any
254
+ `needs_breakdown` tasks + reason.
155
255
 
156
256
  ### P2.5 — Independent plan review (strong model, separate agent)
157
257
 
@@ -164,9 +264,9 @@ The planner agent does the following (use P1 summary — do NOT re-read files):
164
264
  The gate still does its job — two independent opus passes shape the plan before a line of code
165
265
  is written. What it no longer does is hand a stalled plan back to a human who isn't there.
166
266
 
167
- **Claude Code — MANDATORY, do this before anything else in P2.5:** call the Agent tool with `subagent_type: "code-reviewer"` (omp: the `task` tool with `agent: "code-reviewer"`), passing `REVIEW_TARGET_TYPE=plan` and the path to `docs/AI_HANDOFF/PLAN.md`. This MUST be a separate agent invocation from P2's `handoff-planner` call (fresh context) — same-session self-review defeats the purpose of an independent gate.
267
+ **Claude Code — MANDATORY, do this before anything else in P2.5:** call the Agent tool with `subagent_type: "code-reviewer"` (omp: the `task` tool with `agent: "code-reviewer"`), passing `REVIEW_TARGET_TYPE=plan` and the paths to `docs/AI_HANDOFF/PLAN.md` AND `docs/AI_HANDOFF/SPEC.md`. This MUST be a separate agent invocation from P2's `handoff-planner` call (fresh context) — same-session self-review defeats the purpose of an independent gate.
168
268
 
169
- 1. Reviewer reads `PLAN.md` only (no diff, no task files, no executor report), checks Completeness / Consistency / Clarity / Scope / YAGNI — see `.claude/agents/code-reviewer.md` → Spec/Plan Review — and appends its verdict to PLAN.md's `## Plan Review Log` (new round entry, prior rounds kept).
269
+ 1. Reviewer reads `SPEC.md` + `PLAN.md` only (no diff, no task files, no executor report), checks Completeness / Consistency / Clarity / Scope / YAGNI plus the spec quality gate — every requirement testable, every fullstack layer covered, dependencies explicit, legacy sweep included, no vague instruction left ("improve UI" without defined behavior fails) — see `.claude/agents/code-reviewer.md` → Spec/Plan Review — and appends its verdict to PLAN.md's `## Plan Review Log` (new round entry, prior rounds kept).
170
270
  2. `Issues Found` → route back to P2: `handoff-planner` revises `PLAN.md` and the affected `TASK-xxx.md` files to address every finding, then re-submit for one more P2.5 review round — subject to the loop cap above.
171
271
  3. `Approved` → append `PLAN_REVIEW: Approved by <reviewer model>` to PLAN.md's `## Planner Report` footer, then proceed to P3.
172
272
 
@@ -510,9 +610,50 @@ Force-push stays denied, so this can only ever fast-forward.
510
610
 
511
611
  **If some tasks are `blocked`:** still push. The approved work is reviewed, tested, and
512
612
  committed; withholding it helps no one, and the per-wave commits make any subset revertible.
513
- Name the blocked tasks in the Final Report.
613
+ Name the blocked tasks in the Final Report. Tasks that stayed `blocked` only because their
614
+ record was unrecoverable get the Phase 0 recovery treatment — `cancelled_superseded` plus a
615
+ `-R<n>` replacement — before the run may close.
616
+
617
+ Do NOT set `Phase: done` yet — Phase F still has to close the books.
618
+
619
+ ---
620
+
621
+ ## Phase F — Finalize: quiet period, docs, archive
622
+
623
+ ### F1 — Quiet-period scan
624
+
625
+ Run the sweep from Phase 0 again (INDEX, task files, `docs/TASKS.md`, git status).
626
+ `handoff.fullstack.quietScansRequired` (default 2) consecutive scans must each find:
627
+ no unfinished task, no new assignment, no uncommitted intended change. Record each scan
628
+ in RUN.md (`QuietScans: <n>/<required>`). Anything found resets the counter — fold it in
629
+ through the normal pipeline and keep going.
630
+
631
+ ### F2 — Docs sync
632
+
633
+ Update only the docs the change actually touched: `docs/WORKLOG.md` (always — one entry
634
+ for the cycle), plus `docs/PROJECT.md` / `docs/CODE_MAP.md` / `docs/CHANGELOG.md` when the
635
+ cycle changed architecture, surface area, or shipped behavior. Record: behavior
636
+ implemented, API/schema/UI changes, migration steps, config changes, verification
637
+ evidence, rollback notes.
638
+
639
+ ### F3 — Archive the cycle
514
640
 
515
- Finally set `Phase: done` in `docs/AI_HANDOFF/RUN.md`.
641
+ When `handoff.fullstack.autoArchive` is true (default), archive per `handoff-clear`
642
+ conventions so a future session needs no transcript to understand the cycle:
643
+
644
+ 1. Move the completed cycle's records (PLAN.md, SPEC.md, task files, ACTIVE.md snapshot)
645
+ into `docs/AI_HANDOFF/archive/cycle-<NN>/`.
646
+ 2. If `archive/` holds more than 3 cycle folders → fold the oldest into a one-line
647
+ summary in `HISTORY.md` and remove it.
648
+ 3. Reset `ACTIVE.md` to its empty template; clear `INDEX.md` rows to a fresh header;
649
+ remove `tasks/TASK-*.md`; clear `PLAN.md`/`SPEC.md` (templates stay).
650
+ 4. Commit the docs + archive changes:
651
+ `git add -A && git commit -m "handoff: finalize + archive cycle <ID>"`
652
+
653
+ ### F4 — Close the run
654
+
655
+ Set `Phase: done` in `docs/AI_HANDOFF/RUN.md`, disarm any watchdog job armed at the
656
+ start, and emit the Final Report below.
516
657
 
517
658
  ---
518
659
 
@@ -539,17 +680,40 @@ Re-run assertions after removal to confirm clean state.
539
680
 
540
681
  ## Final Report
541
682
 
683
+ The ONLY legal first lines are:
684
+
685
+ ```
686
+ HANDOFF FULLSTACK COMPLETE
687
+ ```
688
+ or, when every remaining task shares one external blocker and no safe local work exists
689
+ (RUN.md set to `Phase: blocked` first):
690
+ ```
691
+ HANDOFF FULLSTACK BLOCKED
692
+ ```
693
+
694
+ Then the body:
695
+
542
696
  ```
543
- handoff-fullstack complete:
544
697
  Cycle: <ID>
698
+ Spec: docs/AI_HANDOFF/SPEC.md
545
699
  Tasks: <N> approved, <M> blocked
700
+ Recovery/superseded: <TASK-xxx→TASK-xxx-R1, …> | none
701
+ Newly assigned processed: <n> | none
702
+ Quiet scans: <n>/<required>
546
703
  Fix rounds used: <0|1|2>
704
+ Verification: <commands + results>
547
705
  Git: <K> wave commits + pushed to <branch>
706
+ Docs updated: <files>
707
+ Archive: docs/AI_HANDOFF/archive/cycle-<NN>/
548
708
  Worktrees: all cleaned
549
709
  Branches: all cleaned
710
+ Remaining tasks: NONE | <list>
550
711
  Blocked (needs you): <TASK-xxx — one-line reason> | none
551
712
  ```
552
713
 
714
+ A blocked report additionally lists: blocked tasks, exact blocker + evidence, attempted
715
+ fixes, local work completed, and the recovery instructions for the next session.
716
+
553
717
  Then, as the last line of the run:
554
718
 
555
719
  > Cycle finished. Run `/compact` before starting the next cycle — this session is carrying
@@ -59,8 +59,12 @@ If this file already exists with `Phase:` not `done` when the command starts, th
59
59
 
60
60
  Read `docs/AI_HANDOFF/ACTIVE.md` → get `Base: <BASE>`.
61
61
  Read `docs/AI_HANDOFF/INDEX.md` → collect `ready` tasks (or the specific task named in the request above).
62
- If there are no `ready` tasks, check for `changes_requested`/`blocked` ones and run those
63
- instead. Only if nothing is actionable → report and stop.
62
+ If there are no `ready` tasks, check for `changes_requested`/`blocked`/stale `in_progress`
63
+ ones and run those instead. A stuck record that cannot resume (orphaned worktree,
64
+ contradicting reports) gets the recovery treatment: mark it `cancelled_superseded`, create
65
+ `TASK-xxx-R1` with the same spec references/acceptance/verification, and run the
66
+ replacement — never leave stranded work just because a record was pending.
67
+ Only if nothing is actionable → report and stop.
64
68
 
65
69
  Verify working tree state:
66
70
  ```bash
@@ -125,6 +129,9 @@ Each spawned agent works independently in its own worktree — **NO git commit,
125
129
 
126
130
  ```
127
131
  Read docs/AI_HANDOFF/tasks/TASK-xxx.md
132
+ Read the SPEC.md sections named in the task's `Spec references` — the spec is the contract;
133
+ the task file is the slice. If they disagree, implement the spec and note it in the task's
134
+ ## Discussion.
128
135
 
129
136
  TDD — mandatory:
130
137
  1. Write tests from §Test Cases
@@ -86,7 +86,10 @@ git status # overview of modified/new/deleted files
86
86
  ```
87
87
 
88
88
  Review as one unified diff — correctness, regression risk, security, edge cases, maintainability.
89
- Cross-reference with each task's intent in `docs/AI_HANDOFF/tasks/TASK-xxx.md`.
89
+ Cross-reference each task's intent in `docs/AI_HANDOFF/tasks/TASK-xxx.md` AND the
90
+ `Spec references` sections it names in `docs/AI_HANDOFF/SPEC.md` — spec compliance is the
91
+ primary correctness bar; a diff that drifts from the spec's contract fails even when it
92
+ matches the task file's wording.
90
93
 
91
94
  ### 2d — Append verdict to each task file
92
95
 
@@ -12,8 +12,10 @@ Read and display current handoff state. Do NOT modify any files.
12
12
 
13
13
  1. `docs/AI_HANDOFF/ACTIVE.md` → cycle ID, goal, base branch
14
14
  2. `docs/AI_HANDOFF/INDEX.md` → all tasks + statuses
15
- 3. `git worktree list` → open worktrees
16
- 4. `git branch | grep handoff/` → open handoff branches
15
+ 3. `docs/AI_HANDOFF/RUN.md` → fullstack run cursor: `Phase:` ≠ `done`/`blocked` means a run is live and the Stop gate is holding the session to it
16
+ 4. `docs/AI_HANDOFF/SPEC.md` → spec exists/fresh (one line)
17
+ 5. `git worktree list` → open worktrees
18
+ 6. `git branch | grep handoff/` → open handoff branches
17
19
 
18
20
  ## Report
19
21
 
@@ -21,6 +23,8 @@ Read and display current handoff state. Do NOT modify any files.
21
23
  ━━━ Handoff Status ━━━━━━━━━━━━━━━━━━━━━━━━
22
24
  Cycle: <ID> Base: <branch> Date: <date>
23
25
  Goal: <goal>
26
+ Run: <Phase — or "no live cursor">
27
+ Spec: <present | missing>
24
28
 
25
29
  Tasks:
26
30
  ✅ done TASK-001 <name>
@@ -11,7 +11,15 @@ if source "$SCRIPT_DIR/../ukit/runtime/hook-input.sh" 2>/dev/null; then
11
11
  source "$SCRIPT_DIR/../ukit/runtime/hook-telemetry.sh" 2>/dev/null || true
12
12
  trap ukit_cleanup_hook_input EXIT
13
13
  # Bounded stdin (H01): stage before anything reads it; the shell var is capped.
14
- ukit_stage_hook_input 2097152 truncate || exit 0
14
+ ukit_stage_hook_input 2097152 truncate
15
+ __ukit_main_stage_rc=$?
16
+ if [ "$__ukit_main_stage_rc" -ne 0 ]; then
17
+ # Infra failure during staging (mktemp/truncate): payload was never
18
+ # inspected — announce the degrade (SPEC §8), never a silent pass.
19
+ ukit_emit_input_degraded advisory "auto-allow-bash"
20
+ fi
21
+ # BUG-C21-01: a truncated/stalled staged payload must be announced (SPEC §8), never silently passed.
22
+ ukit_input_degraded && ukit_emit_input_degraded advisory "auto-allow-bash"
15
23
  else
16
24
  # Runtime helper missing (pre-install tree): the SAME bounded staging,
17
25
  # inline - `cat >/dev/null` used to block forever on a producer that never
@@ -38,10 +46,14 @@ else
38
46
  wait "$__ukit_waiter" 2>/dev/null
39
47
  exec 8<&-
40
48
  __ukit_size="$(wc -c < "$UKIT_INPUT_FILE" 2>/dev/null | tr -d '[:space:]')"
41
- # A bounded cut (rc != 0) leaves a short payload that the JSON consumer
42
- # already degrades on - the shared helper's truncate posture (R4.5).
43
- if [ "$__ukit_size" -gt 2097152 ]; then
44
- truncate -s 2097152 "$UKIT_INPUT_FILE" 2>/dev/null
49
+ if [ "$__ukit_stage_rc" -ne 0 ] || [ "$__ukit_size" -gt 2097152 ]; then
50
+ # BUG-C21-01 fallback parity (SPEC §8): a truncated/stalled staged payload
51
+ # must be announced, never silently acted on — same shape as the helper
52
+ # path's ukit_emit_input_degraded.
53
+ rm -f "$UKIT_INPUT_FILE"
54
+ UKIT_INPUT_FILE=""
55
+ printf '%s\n' '{"systemMessage":"UKit auto-allow-bash: tool-call payload was truncated or stalled during stdin staging; skipped this pass rather than act on incomplete input."}'
56
+ exit 0
45
57
  fi
46
58
  trap '[ -n "$UKIT_INPUT_FILE" ] && rm -f "$UKIT_INPUT_FILE"' EXIT
47
59
  fi
@@ -11,7 +11,15 @@ if source "$SCRIPT_DIR/../ukit/runtime/hook-input.sh" 2>/dev/null; then
11
11
  source "$SCRIPT_DIR/../ukit/runtime/hook-telemetry.sh" 2>/dev/null || true
12
12
  trap ukit_cleanup_hook_input EXIT
13
13
  # Bounded stdin (H01): stage before anything reads it; the shell var is capped.
14
- ukit_stage_hook_input 2097152 truncate || exit 0
14
+ ukit_stage_hook_input 2097152 truncate
15
+ __ukit_main_stage_rc=$?
16
+ if [ "$__ukit_main_stage_rc" -ne 0 ]; then
17
+ # Infra failure during staging (mktemp/truncate): payload was never
18
+ # inspected — announce the degrade (SPEC §8), never a silent pass.
19
+ ukit_emit_input_degraded failclosed "dangerous-command gate"
20
+ fi
21
+ # BUG-C21-01: a truncated/stalled staged payload must be announced (SPEC §8), never silently passed.
22
+ ukit_input_degraded && ukit_emit_input_degraded failclosed "dangerous-command gate"
15
23
  else
16
24
  # Runtime helper missing (pre-install tree): the SAME bounded staging,
17
25
  # inline - `cat >/dev/null` used to block forever on a producer that never
@@ -38,10 +46,15 @@ else
38
46
  wait "$__ukit_waiter" 2>/dev/null
39
47
  exec 8<&-
40
48
  __ukit_size="$(wc -c < "$UKIT_INPUT_FILE" 2>/dev/null | tr -d '[:space:]')"
41
- # A bounded cut (rc != 0) leaves a short payload that the JSON consumer
42
- # already degrades on - the shared helper's truncate posture (R4.5).
43
- if [ "$__ukit_size" -gt 2097152 ]; then
44
- truncate -s 2097152 "$UKIT_INPUT_FILE" 2>/dev/null
49
+ if [ "$__ukit_stage_rc" -ne 0 ] || [ "$__ukit_size" -gt 2097152 ]; then
50
+ # BUG-C21-01 fallback parity (SPEC §8): a truncated/stalled staged payload
51
+ # must be announced, never silently acted on — same shape as the helper
52
+ # path's ukit_emit_input_degraded.
53
+ rm -f "$UKIT_INPUT_FILE"
54
+ UKIT_INPUT_FILE=""
55
+ printf '%s\n' '{"hookSpecificOutput":{"hookEventName":"PreToolUse","permissionDecision":"ask","permissionDecisionReason":"UKit could not fully read this tool-call payload (stdin staging was cut at the deadline/size bound), so dangerous-command gate cannot prove it safe. UKit defers this to a human decision."}}'
56
+ echo "BLOCKED: dangerous-command gate could not inspect a truncated/stalled payload; deferred to human." >&2
57
+ exit 2
45
58
  fi
46
59
  trap '[ -n "$UKIT_INPUT_FILE" ] && rm -f "$UKIT_INPUT_FILE"' EXIT
47
60
  fi
@@ -22,7 +22,15 @@ if source "$SCRIPT_DIR/../ukit/runtime/hook-input.sh" 2>/dev/null; then
22
22
  source "$SCRIPT_DIR/../ukit/runtime/hook-telemetry.sh" 2>/dev/null || true
23
23
  trap ukit_cleanup_hook_input EXIT
24
24
  # Bounded stdin (H01): stage before anything reads it; the shell var is capped.
25
- ukit_stage_hook_input 2097152 truncate || exit 0
25
+ ukit_stage_hook_input 2097152 truncate
26
+ __ukit_main_stage_rc=$?
27
+ if [ "$__ukit_main_stage_rc" -ne 0 ]; then
28
+ # Infra failure during staging (mktemp/truncate): payload was never
29
+ # inspected — announce the degrade (SPEC §8), never a silent pass.
30
+ ukit_emit_input_degraded advisory "stop gate"
31
+ fi
32
+ # BUG-C21-01: a truncated/stalled staged payload must be announced (SPEC §8), never silently passed.
33
+ ukit_input_degraded && ukit_emit_input_degraded advisory "stop gate"
26
34
  else
27
35
  # wrapper's own fail-loud behavior below alive.
28
36
  # Runtime helper missing (pre-install tree): the SAME bounded staging,
@@ -50,10 +58,14 @@ else
50
58
  wait "$__ukit_waiter" 2>/dev/null
51
59
  exec 8<&-
52
60
  __ukit_size="$(wc -c < "$UKIT_INPUT_FILE" 2>/dev/null | tr -d '[:space:]')"
53
- # A bounded cut (rc != 0) leaves a short payload that the JSON consumer
54
- # already degrades on - the shared helper's truncate posture (R4.5).
55
- if [ "$__ukit_size" -gt 2097152 ]; then
56
- truncate -s 2097152 "$UKIT_INPUT_FILE" 2>/dev/null
61
+ if [ "$__ukit_stage_rc" -ne 0 ] || [ "$__ukit_size" -gt 2097152 ]; then
62
+ # BUG-C21-01 fallback parity (SPEC §8): a truncated/stalled staged payload
63
+ # must be announced, never silently acted on — same shape as the helper
64
+ # path's ukit_emit_input_degraded.
65
+ rm -f "$UKIT_INPUT_FILE"
66
+ UKIT_INPUT_FILE=""
67
+ printf '%s\n' '{"systemMessage":"UKit stop gate: tool-call payload was truncated or stalled during stdin staging; skipped this pass rather than act on incomplete input."}'
68
+ exit 0
57
69
  fi
58
70
  trap '[ -n "$UKIT_INPUT_FILE" ] && rm -f "$UKIT_INPUT_FILE"' EXIT
59
71
  fi
@@ -18,7 +18,15 @@ if [ -f "$SCRIPT_PATH" ]; then
18
18
  trap ukit_cleanup_hook_input EXIT
19
19
  # Bounded stdin (H01): compressed tool output can be large, so the staged
20
20
  # file is the transport and the shell var only mirrors the capped payload.
21
- ukit_stage_hook_input 33554432 temp-file || exit 0
21
+ ukit_stage_hook_input 33554432 temp-file
22
+ __ukit_main_stage_rc=$?
23
+ if [ "$__ukit_main_stage_rc" -ne 0 ]; then
24
+ # Infra failure during staging (mktemp/truncate): payload was never
25
+ # inspected — announce the degrade (SPEC §8), never a silent pass.
26
+ ukit_emit_input_degraded advisory "output compression"
27
+ fi
28
+ # BUG-C21-01: a truncated/stalled staged payload must be announced (SPEC §8), never silently passed.
29
+ ukit_input_degraded && ukit_emit_input_degraded advisory "output compression"
22
30
  else
23
31
  # Runtime helper missing (pre-install tree): the SAME bounded staging,
24
32
  # inline — `cat >/dev/null` used to block forever on a producer that never
@@ -45,14 +53,15 @@ if [ -f "$SCRIPT_PATH" ]; then
45
53
  wait "$__ukit_waiter" 2>/dev/null
46
54
  exec 8<&-
47
55
  __ukit_size="$(wc -c < "$UKIT_INPUT_FILE" 2>/dev/null | tr -d '[:space:]')"
48
- if [ "$__ukit_stage_rc" -ne 0 ]; then
49
- # Unstaged: the hook cannot inspect this payload.
56
+ if [ "$__ukit_stage_rc" -ne 0 ] || [ "$__ukit_size" -gt 33554432 ]; then
57
+ # Unstaged or oversized: the hook cannot inspect this payload — announce
58
+ # the degrade (SPEC §8) instead of silently compressing an empty or
59
+ # truncated input (SUS-02 / BUG-C21-01 fallback parity).
50
60
  rm -f "$UKIT_INPUT_FILE"
51
61
  UKIT_INPUT_FILE=""
62
+ printf '%s\n' '{"systemMessage":"UKit output compression: tool-call payload was truncated or stalled during stdin staging; skipped this pass rather than act on incomplete input."}'
63
+ exit 0
52
64
  else
53
- if [ "$__ukit_size" -gt 33554432 ]; then
54
- truncate -s 33554432 "$UKIT_INPUT_FILE" 2>/dev/null
55
- fi
56
65
  trap '[ -n "$UKIT_INPUT_FILE" ] && rm -f "$UKIT_INPUT_FILE"' EXIT
57
66
  fi
58
67
  fi