@ngockhoale/ukit 3.4.12 → 3.4.14

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,70 @@
2
2
 
3
3
  All notable changes to UKit are documented here.
4
4
 
5
+ ## 3.4.14 - 2026-10-04
6
+
7
+ **New `advisor-plan` skill, a create-session write-scope guard, and a deliberate-future queue contract.**
8
+ `advisor-plan` turns an advisor opinion, reference document, roadmap or replan request
9
+ into a plan and nothing else (planning-only; it never implements). It builds a requirement
10
+ LEDGER where every row is dispositioned (`feasible`/`adapted`/`deferred`/`blocker`),
11
+ backed by feasibility evidence read from the current source and `git log`/`diff`/`status`;
12
+ drafts 2-3 candidate plans and scores them before picking one; gives every task detailed
13
+ TDD test cases (happy, edge, error) instead of "tests pass"; and applies a queue
14
+ threshold — at most 10 active tasks, the rest become queued blueprints. Ledger coverage
15
+ means rows dispositioned, not implemented.
16
+
17
+ - **Create write-scope guard.** While a session's latest handoff intent is `create`
18
+ (`/ukit:handoff-create`), `handoff-model-guard.sh` (`createScopeVerdict`) allows docs
19
+ and test files only and blocks source/config/hook/agent/skill/command/`AGENTS.md`
20
+ writes. Claude Code is enforced through the PreToolUse `Edit|Write` and `Bash` chains;
21
+ omp is enforced for the tools its bridge maps (`apply_patch`/`ast_edit` → `Edit`);
22
+ Codex has no pre-tool hooks, so the scope is advisory only (instruction-mediated) and
23
+ is not enforced. Patch-body edits (`*** Update File:`, unified-diff headers) are parsed
24
+ for their targets. Known gaps: the shell check is a regex, not a parser, so writes via
25
+ `node -e`, `python -c`, `ruby -e`, `wget -O`, `tar -x`, `unzip`, `git pull|switch|worktree add`, git aliases, `eval`/`bash -c` indirection and similar slip through, and a `>` or
26
+ the word `install` in an argument can block a harmless command; a missing transcript
27
+ fails open silently and an inconclusive intent scan fails open with a systemMessage.
28
+ Kill switch: `handoff.createScopeGuard`.
29
+ - **Automatic routing.** A deterministic EN/VN classifier (`classifyAdvisorRequest`, three
30
+ identical copies) force-loads `advisor-plan` into the first active-skill slot for advisor /
31
+ reference / replan hand-offs and explicit "plan this task" requests; implementation orders
32
+ that merely mention a plan or advisor do not load it. Known gap: `unic-decision` is not
33
+ consulted for this recognition (its `route.intent-kind.v1` has no advisor value); wiring it
34
+ in is queued follow-up work.
35
+ - **Deliberate-future queue.** Queued blueprints under `docs/AI_HANDOFF/queued/<slug>/`
36
+ may carry `QueueIntent: deliberate-future`, `QueueOrder`, `QueueDrain` and
37
+ `QueueSource` in their `HANDOFF.md` header. `/ukit:handoff-fullstack` reports them as
38
+ `Backlog: <slug>` instead of folding them into the current cycle (an explicit slug
39
+ drains one); blueprints without the header keep the legacy fold-in behaviour. No new
40
+ INDEX status was added.
41
+ - **Route catalog.** `advisor-plan` (order 12.15) with a matching `advisor-plan-skill`
42
+ manifest item. `classifyAdvisorRequest` stamps `advisorIntent` (`advisor` | `reference` |
43
+ `replan`) on the route summary in English and Vietnamese (diacritics folded; "advisory
44
+ lock", "legal advisor" and bug-fix-verb prompts stay null); mirrored in `taskRouting.js`,
45
+ `route-task.mjs` and `skill-router.sh`.
46
+ - Docs: `docs/HOST_CAPABILITY_MATRIX.md` gains a per-host "Create write-scope guard
47
+ (C94)" note; `STATUS.md`, `CODE_MAP.md` and `skill-audits/advisor-plan/` updated.
48
+ Tests: `advisorPlanSkill`, `createScopeGuard`, `advisorReleaseDocs`, `advisorIntentRouting`,
49
+ `advisorStatusDocs`, `routeCatalog`.
50
+
51
+ ## 3.4.13 - 2026-10-04
52
+
53
+ **`/ukit:handoff-create` is strictly planning-only; the Stop gate follows the session's CURRENT handoff intent.**
54
+ `handoff-create` writes SPEC/PLAN/task files under `docs/AI_HANDOFF/` and stops with tasks
55
+ `ready` (queued) — it never edits product code, launches executors, or escalates itself
56
+ into implementation, even when an unfinished `RUN.md` exists or after compact/resume.
57
+ New shared `handoff-intent.mjs` classifies a transcript by its MOST RECENT structured
58
+ intent (`/ukit:handoff-create` vs `/ukit:handoff-fullstack` command tag, `Skill` call,
59
+ SessionStart resume banner). The Stop gate and `handoff-resume.sh` both use it: a session
60
+ that planned after an earlier fullstack run is no longer bounced into the stale run,
61
+ while a session actually driving `handoff-fullstack` (including create → fullstack
62
+ re-acquire) is still held to completion — the completion gate and ExitPredicate are
63
+ unchanged. Unknown/unreadable/windowed-out transcripts stay fail-closed. Claude Code,
64
+ Codex and omp share the same runtime, with native transcript envelopes normalized.
65
+ Planning alongside a live run preserves its cursor, index and tasks and writes a
66
+ separate queued plan instead. Tests: `handoffIntentSeparation`,
67
+ `handoffResumeIntent`, `handoffCreateQueueing`.
68
+
5
69
  ## 3.4.12 - 2026-10-02
6
70
 
7
71
  **Handoff Stop gate is session-scoped — a stale `RUN.md` no longer hijacks unrelated sessions.**
@@ -405,6 +405,16 @@ entries:
405
405
  load_policy: never
406
406
  validation: [none]
407
407
  archive_policy: immutable
408
+ - id: docs-skill-audits
409
+ path: docs/skill-audits/
410
+ class: archive
411
+ audience: [maintainer]
412
+ owner: product
413
+ source_of_truth: docs/skill-audits/
414
+ merge_strategy: none
415
+ load_policy: never
416
+ validation: [none]
417
+ archive_policy: immutable
408
418
 
409
419
  # ── template_project/ canonical authoring sources ───────────────────────────────
410
420
  - id: tpl-claude-md
@@ -555,6 +555,19 @@ items:
555
555
  packs:
556
556
  - core
557
557
 
558
+ - id: advisor-plan-skill
559
+ type: skill
560
+ sourceTemplate: .claude/skills/advisor-plan
561
+ targetPath: .claude/skills/advisor-plan
562
+ requires:
563
+ - docs-quality-skill
564
+ - docs-ai-handoff
565
+ mergeStrategy: overwrite_with_backup
566
+ variables: []
567
+ enabledByDefault: true
568
+ packs:
569
+ - core
570
+
558
571
  - id: docs-manager-skill
559
572
  type: skill
560
573
  sourceTemplate: .claude/skills/docs-manager
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ngockhoale/ukit",
3
- "version": "3.4.12",
3
+ "version": "3.4.14",
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",
@@ -237,6 +237,24 @@ export const ROUTE_CATALOG = [
237
237
  { type: 'prompt', regex: /(?<![A-Za-z0-9_])(làm gì tiếp|bước tiếp theo|tiếp theo làm gì|làm tiếp|đang ở đâu|trạng thái project|tình trạng project|task tiếp theo|việc tiếp theo trong tasks)(?![A-Za-z0-9_])/i, score: 7 },
238
238
  ],
239
239
  },
240
+ {
241
+ id: 'advisor-plan',
242
+ path: '.claude/skills/advisor-plan/SKILL.md',
243
+ order: 12.15,
244
+ contextMode: 'standalone',
245
+ signals: [
246
+ { type: 'prompt', regex: /\b(?:advis[eo]r)(?:'s|s)?\s+(?:\w+\s+){0,3}(?:docs?|documents?|opinions?|feedback|notes?|plans?|roadmaps?|recommendations?|reports?)\b/i, score: 9 },
247
+ { type: 'prompt', regex: /\b(?:turn|convert|translate|break(?: down)?|distill)\b.{0,48}\b(?:roadmap|reference|advis[eo]r|external plan)\b.{0,48}\b(?:into|to)\b.{0,16}\b(?:plan|tasks?|ledger)\b/i, score: 9 },
248
+ { type: 'prompt', regex: /\b(?:roadmap|reference|external plan)\s+(?:material|docs?|documents?)\b.{0,48}\bplan\b/i, score: 9 },
249
+ { type: 'prompt', regex: /\bplan(?:ning)?\s+(?:from|using|based on|per|according to)\b.{0,48}\b(?:reference|roadmap|advis[eo]r|external|document)\b/i, score: 9 },
250
+ { type: 'prompt', regex: /\b(?:replan|re-plan|plan again)\b.{0,60}\b(?:advis[eo]r|feedback|documents?|reference|roadmap)\b/i, score: 7 },
251
+ { type: 'prompt', regex: /\b(?:planning[- ]only|plan[- ]only|plan (?:any|this|the|a) (?:task|feature|work).{0,48}(?:do not|don't|without|never) implement\w*)\b/i, score: 7 },
252
+ { type: 'prompt', regex: /\bplan\s+(?:this|these|the following)\s+(?:task|tasks|work|request|feature)\b/i, score: 7 },
253
+ { type: 'prompt', regex: /(?<![A-Za-z0-9_])(?:lập kế hoạch|lap ke hoach|lên kế hoạch|len ke hoach)\s+cho\s+(?:task|việc|viec|tính năng|tinh nang|yêu cầu|yeu cau|feature)(?![A-Za-z0-9_])/i, score: 7 },
254
+ { type: 'prompt', regex: /(?<![A-Za-z0-9_])(?:lập kế hoạch|lap ke hoach|lên lại kế hoạch|len lai ke hoach|lên kế hoạch lại|len ke hoach lai).{0,60}(?:tài liệu|tai lieu|tư vấn|tu van|advisor|tham khảo|tham khao|roadmap)/i, score: 9 },
255
+ { type: 'prompt', regex: /(?<![A-Za-z0-9_])(?:tài liệu tư vấn|tai lieu tu van|tư vấn của advisor|tu van cua advisor|lên lại kế hoạch|len lai ke hoach|lập kế hoạch cho bất kỳ task nào|lap ke hoach cho bat ky task nao)/i, score: 7 },
256
+ ],
257
+ },
240
258
  {
241
259
  id: 'update-status',
242
260
  path: '.claude/skills/update-status/SKILL.md',
@@ -755,6 +755,10 @@ export function buildRouteSummary({
755
755
  const modelRolesBlock = MODEL_ROLES_DELEGATING_MODES.has(executionMode)
756
756
  ? formatModelRolesBlock(modelRolesResolution.roles)
757
757
  : null;
758
+ const advisorIntent = classifyAdvisorRequest({
759
+ promptText: routingContext.promptText,
760
+ commandText: routingContext.commandText,
761
+ });
758
762
  const summaryLine = [
759
763
  routingContext.taskType ? `task=${routingContext.taskType}` : null,
760
764
  handoffFile ? `handoff=${handoffFile}` : null,
@@ -832,6 +836,9 @@ export function buildRouteSummary({
832
836
  // only when subagentOrchestrator.roleAdvice.stage != 'off' (conditional
833
837
  // spread keeps the stage-off route byte-identical).
834
838
  ...(delegationAdvice ? { delegationAdvice } : {}),
839
+ // FR-004 additive field — present only when the prompt is an advisor/reference/replan
840
+ // request, so every other route stays byte-identical.
841
+ ...(advisorIntent ? { advisorIntent } : {}),
835
842
  line: summaryLine || 'task=unknown',
836
843
  };
837
844
  }
@@ -1106,6 +1113,60 @@ function hasAdvisoryOnlyAssertion({ promptText = '', commandText = '' } = {}) {
1106
1113
  const mutationOrder = /(?<![A-Za-z0-9_])(?:implement|apply|update|modify|add|create|ship|deliver|fix|refactor|remove|delete|rename|change|write|build|make|install|run|deploy|execute|edit|sua|them|tao|xoa|doi|thay\s+the|cap\s+nhat|viet|chay|cai|chinh|trien\s+khai|cau\s+hinh)(?![A-Za-z0-9_])/.test(residue);
1107
1114
  return !mutationOrder;
1108
1115
  }
1116
+ // <advisor-intent:begin>
1117
+ // FR-004: an advisor/reference/replan request hands over a document or roadmap
1118
+ // and wants a plan back (the advisor-plan route). Deterministic, no I/O.
1119
+ // Every kind needs plan/document context, so "advisory lock", "hang-advisory",
1120
+ // a "legal advisor" label or "replan the SQL index" stay null. Three copies
1121
+ // (taskRouting.js, route-task.mjs, skill-router.sh) — keep them identical.
1122
+ export function classifyAdvisorRequest({ promptText = '', commandText = '' } = {}) {
1123
+ const folded = `${promptText ?? ''}\n${commandText ?? ''}`
1124
+ .toLowerCase()
1125
+ .normalize('NFD')
1126
+ .replace(/[\u0300-\u036f]/g, '')
1127
+ .replace(/\u0111/g, 'd')
1128
+ .trim();
1129
+ if (!folded) return null;
1130
+ // A prompt that opens with a bug-fix/edit verb is a code change, not a plan request.
1131
+ if (/^(?:please\s+|pls\s+)?(?:fix|debug|rename|remove|delete|refactor|edit|sua\s+loi|xoa|doi\s+ten)(?![a-z0-9_])/.test(folded)) return null;
1132
+ const text = folded.replace(/\b(?:legal|financial|tax|investment|academic|career|mortgage)\s+advisors?\b/g, ' ');
1133
+ const PLAN = '(?:plans?|planning|ke\\s+hoach|phuong\\s+an|lo\\s+trinh|roadmaps?|blueprints?|specs?)';
1134
+ const hasPlan = new RegExp(`\\b${PLAN}\\b`).test(text);
1135
+ const hasDoc = /\b(?:tai\s+lieu|documents?|docs?|feedback|gop\s+y)\b|\.md\b/.test(text);
1136
+ const replanToken = /\bre-?plan(?:ning|ned)?\b/;
1137
+ if ((replanToken.test(text)
1138
+ && new RegExp(`\\b(?:advisors?|feedback|gop\\s+y|reference|handoff(?:-create)?|backlog|${PLAN})\\b`).test(text.replace(replanToken, ' ')))
1139
+ || /\b(?:lap|len|vach|xay\s+dung)\s+lai\s+(?:ke\s+hoach|plan|phuong\s+an|lo\s+trinh|roadmap)\b/.test(text)
1140
+ || /\b(?:revise|rework|redo|rewrite)\s+(?:(?:the|this|my|our|that)\s+)?(?:plan|roadmap)\b/.test(text)) {
1141
+ return 'replan';
1142
+ }
1143
+ // An explicit "plan this task" order stands on its own; the document kinds below
1144
+ // are gated so an implementation order that merely mentions a plan, spec, doc or
1145
+ // advisor ("implement the plan from the spec") stays null. Plan-making verbs
1146
+ // ("create a plan", "handoff-create") are not implementation orders.
1147
+ if (/\bplan\s+(?:this|these|the\s+following)\s+(?:task|tasks|work|request|feature)\b/.test(text)
1148
+ || /\b(?:lap|len)\s+ke\s+hoach\s+cho\s+(?:(?:task|viec|yeu\s+cau)\s+nay|task|viec|tinh\s+nang|yeu\s+cau|feature)\b/.test(text)) {
1149
+ return 'advisor';
1150
+ }
1151
+ const orderText = text
1152
+ .replace(/\bhandoff-create\b/g, ' handoff ')
1153
+ .replace(/\b(?:create|build|make|write|draft|produce|generate|tao|viet|soan|lap)\s+(?:(?:a|an|the|me|my|our|one|new|detailed|full|giup|cho|toi|ra)\s+){0,3}(?:plans?|roadmaps?|blueprints?|specs?|ke\s+hoach|phuong\s+an|lo\s+trinh)\b/g, ' ');
1154
+ if (hasMutationOrder({ promptText: orderText })) return null;
1155
+ if ((/\badvisors?\b/.test(text) && (hasPlan || hasDoc || /\bhandoff-create\b/.test(text)))
1156
+ || (/\b(?:tu|co)\s+van\b(?!\s+de\b)/.test(text) && (hasPlan || /\btai\s+lieu\b|\bhandoff-create\b/.test(text)))
1157
+ || (/\bhandoff-create\b/.test(text) && (hasPlan || hasDoc))) {
1158
+ return 'advisor';
1159
+ }
1160
+ if (/\breference\s+(?:docs?|documents?|roadmaps?|plans?|specs?|material|brief)\b/.test(text)
1161
+ || /\b(?:roadmap|plan|spec|document|doc)\s+(?:as|for)\s+(?:a\s+|the\s+)?reference\b/.test(text)
1162
+ || (/\btai\s+lieu\s+tham\s+khao\b/.test(text) && hasPlan)
1163
+ || /\b(?:plan|ke\s+hoach)\b[^.\n]{0,40}\b(?:from|based\s+on|according\s+to|theo|dua\s+(?:tren|vao))\s+(?:(?:this|the|a|nay)\s+)?(?:roadmap|spec|brief|document|doc|tai\s+lieu)\b/.test(text)) {
1164
+ return 'reference';
1165
+ }
1166
+ return null;
1167
+ }
1168
+ // <advisor-intent:end>
1169
+
1109
1170
  // C85-019: mutation vocabulary ≠ mutation order. The same residue logic the
1110
1171
  // advisory gate uses — quotes, negated clauses, conditionals (nếu/để/cho/if),
1111
1172
  // reported demands (yêu cầu/ép/requires), necessity phrases (cần được/must be),
@@ -1529,9 +1590,29 @@ async function selectActiveSkills({ rootDir, promptText, commandText, targetFile
1529
1590
  .filter((entry) => entry.score > 0)
1530
1591
  .filter((entry) => shouldKeepRouteEntryForIntent(entry, intentMode))
1531
1592
  .sort((a, b) => b.score - a.score || a.order - b.order);
1593
+ // FR-004: a classified advisor/reference/replan hand-off always loads
1594
+ // advisor-plan, even when no catalog signal fired. It takes the FIRST of the
1595
+ // MAX_ACTIVE_ROUTE_SKILLS slots; the best-scoring other route fills the second.
1596
+ const advisorKind = classifyAdvisorRequest({ promptText, commandText });
1597
+ const advisorCatalogEntry = advisorKind
1598
+ ? ROUTE_CATALOG.find((entry) => entry.id === 'advisor-plan')
1599
+ : null;
1600
+ // A catalog-only advisor-plan hit on an implementation order ("implement the
1601
+ // advisor plan in docs/plan.md") must not load a planning-only skill.
1602
+ const dropCatalogAdvisor = !advisorKind
1603
+ && hasMutationOrder({ promptText, commandText })
1604
+ && !/\b(?:planning[- ]only|plan[- ]only)\b|\b(?:turn|convert|translate|break(?: down)?|distill)\b.{0,48}\b(?:into|to)\b.{0,16}\b(?:plan|tasks?|ledger)\b/i.test(String(promptText ?? ''));
1605
+ const candidates = advisorCatalogEntry
1606
+ ? [
1607
+ scoreSkillRouteEntry(advisorCatalogEntry, routeSignals),
1608
+ ...scoredEntries.filter((entry) => entry.id !== 'advisor-plan'),
1609
+ ]
1610
+ : dropCatalogAdvisor
1611
+ ? scoredEntries.filter((entry) => entry.id !== 'advisor-plan')
1612
+ : scoredEntries;
1532
1613
  const active = [];
1533
1614
 
1534
- for (const entry of scoredEntries) {
1615
+ for (const entry of candidates) {
1535
1616
  if (await pathExists(path.join(rootDir, entry.path))) {
1536
1617
  active.push(entry);
1537
1618
  if (active.length >= MAX_ACTIVE_ROUTE_SKILLS) {
@@ -14,6 +14,103 @@ $ARGUMENTS
14
14
  > **Can be run multiple times.** If the plan or tasks aren't satisfactory after the first run, re-run `/ukit:handoff-create` to refine until the plan is good — as long as no task has been implemented yet.
15
15
  > **NO git commit. NO git push.** This phase only writes docs files. No code changes.
16
16
 
17
+ ## Planning-only contract — this command never implements
18
+
19
+ `/ukit:handoff-create` produces **SPEC.md, PLAN.md, and decomposed task files under
20
+ `docs/AI_HANDOFF/` — nothing else.** It never edits product code, never launches an
21
+ executor, and never escalates itself into implementation. Long plans are simply
22
+ **queued**: after Step 2.5 the command stops with the tasks in `status=ready`, and
23
+ implementation waits for an explicit human action.
24
+
25
+ This holds even when `docs/AI_HANDOFF/RUN.md` already carries an unfinished
26
+ handoff-fullstack cursor. Running this command **makes the session's handoff intent
27
+ planning**, so the Stop gate and the SessionStart compact/resume hook both keep quiet
28
+ until the human explicitly resumes.
29
+
30
+ ### A live fullstack run is never overwritten — the new plan is QUEUED
31
+
32
+ Before writing anything, read `docs/AI_HANDOFF/RUN.md`. If it exists and its `Phase:`
33
+ is **not** `done`/`blocked`, a fullstack run is live and owns the canonical plan:
34
+
35
+ - **Never touch** `RUN.md`, `INDEX.md`, `PLAN.md`, `SPEC.md`, `ACTIVE.md`, or
36
+ `tasks/TASK-*.md`. Overwriting them — which the "all tasks are `ready`" rule below
37
+ would otherwise allow — destroys in-flight tasks, executor reports and verdicts.
38
+ - Write the whole new plan under the **queued path** instead:
39
+ `docs/AI_HANDOFF/queued/<slug>/` — `SPEC.md`, `PLAN.md`, `INDEX.md`, `HANDOFF.md`
40
+ (activation notes: what it depends on, when to start) and `tasks/`. This is a
41
+ blueprint, not a runnable cycle: no `tasks/TASK-*.md` row is created and nothing is
42
+ executed.
43
+ - Report the queued path; the live run continues untouched.
44
+
45
+ **Explicit abandonment is the only way to replace a live run.** Only when the human
46
+ explicitly says to abandon/clear the current run, first run `/ukit:handoff-clear`
47
+ (sets `Phase: done` / removes `RUN.md`), then plan against the canonical files. A mere
48
+ mention of the new work is not abandonment — absent that explicit instruction, always
49
+ take the queued path.
50
+
51
+ ### Planning-only output root (decided here, passed to the planner)
52
+
53
+ The planner writes to exactly ONE root, chosen above: the **canonical root**
54
+ `docs/AI_HANDOFF/` when no run is live, else the **queued root**
55
+ `docs/AI_HANDOFF/queued/<slug>/`. Pass it to the planner as its planning-only output
56
+ root; the planner writes nothing outside it and never launches an executor.
57
+
58
+ - To implement later: `/ukit:handoff-implement` (plan-only flow), or
59
+ `/ukit:handoff-fullstack` (one-shot pipeline) — that explicit invocation reacquires
60
+ ownership, resumes a live run, or sweeps a queued blueprint in once the run is closed.
61
+ - To abandon a paused run first: `/ukit:handoff-clear`.
62
+
63
+ ### Write scope — documentation and tests only
64
+
65
+ This command may add or update **documentation** (`docs/**`, root `README*.md` /
66
+ `CHANGELOG.md`) and **tests**/testing files only. Inside `docs/` the guard still denies
67
+ script extensions (`docs/**/*.{sh,js,mjs,cjs,ts,py}`), any path with a hidden segment
68
+ (`docs/.x/...`) and extensions outside the doc allowlist (`.md .mdx .txt .json .yaml .yml
69
+ .png .svg`; e.g. `.html` is denied) — plan those as prose or a `.md` file instead. Under
70
+ `tests/` the guard likewise denies shell/runtime extensions (`.sh .ps1 .cmd ...`) and hidden segments. It must **never** edit source, config,
71
+ hooks, `package.json`, skills, commands, agents, or `AGENTS.md` / `CLAUDE.md` — those are
72
+ executable instructions for future sessions. There is no GREEN step and no escalation into
73
+ implementation: urgency, hook friction or "just one line" do not widen the scope; changing
74
+ source takes another command from the human.
75
+
76
+ Guard strength per host: **Claude Code** and **omp** enforce this through the PreToolUse
77
+ chain (omp via its bridge; unmapped shell paths are best effort). The guard checks the target
78
+ path of Write/Edit/MultiEdit/NotebookEdit and the file headers inside an `apply_patch` /
79
+ unified-diff body (`*** Add|Update|Delete File:`, `*** Move to:`, `diff --git`, `---`/`+++`);
80
+ a patch-like body with no parseable path is denied. Shell commands are a regex check, not a
81
+ parser: `node -e`, `python -c` and similar interpreter one-liners are an accepted gap. **Codex** has no pre-tool
82
+ hooks, so the rule is advisory there — instruction-mediated only. Codex also has no per-phase
83
+ model selection: never claim a guard blocked anything, or that a different model ran a phase.
84
+
85
+ ### Advisor intake — large advisor / reference / replan documents
86
+
87
+ When the router flags advisor, reference-document or replan intent (or the human asks to plan
88
+ any task on request), load the `advisor-plan` skill; it adds no new slash command. It turns
89
+ the document into a LEDGER, a bounded task set and a queue, and never implements:
90
+
91
+ - **Phase A — gather (lite tier):** chunk the document, extract candidate requirements, and
92
+ collect feasibility evidence from the **current source** (index-first) plus `git log`,
93
+ `git diff` and `git status` of the touched areas. Gatherers decide nothing.
94
+ - **Phase B — decide (smart tier):** write the LEDGER at `${OUTPUT_ROOT}LEDGER.md` — every
95
+ requirement gets exactly one disposition (`feasible`/`adapted`/`deferred`/`blocker`); the
96
+ footer reads `Coverage: N/N rows dispositioned`; note that
97
+ disposition coverage is not implementation coverage. Draft 2-3 candidate plans, score them, pick the best and record
98
+ the rejected ones in `PLAN.md`. Every task carries detailed TDD test cases (happy, edge,
99
+ error with expected results) that prove it works.
100
+ - **Phase C — review:** the independent plan review in Step 2.5.
101
+ - **Phase D — implement (code tier, separate phase):** never in this session; it runs later
102
+ under `/ukit:handoff-fullstack` or `/ukit:handoff-implement`.
103
+ - **Queue threshold:** at most 10 tasks in total: all active (`ready`). More than 10: the
104
+ first 10 by dependency order are active and the rest become queued blueprints under
105
+ `docs/AI_HANDOFF/queued/<slug>/` whose `HANDOFF.md` header carries
106
+ `QueueIntent: deliberate-future`. Queue contract: the header also carries `QueueOrder: <int>`
107
+ (drain order, lowest first), `QueueDrain: separate-backlog` and `QueueSource: <R-IDs/epics>`;
108
+ `/ukit:handoff-fullstack` reports such a blueprint as `Backlog: <slug>`, never folds it into
109
+ the current cycle, and drains it only when the human names the slug (one blueprint per invocation).
110
+
111
+ Model split on Claude Code: haiku/`unic-lite` gathers, opus/`unic-smart` decides, and
112
+ sonnet/`unic-code` implements later; omp maps these to `@smol`/`@slow`/`@default`.
113
+
17
114
  ---
18
115
 
19
116
  ## Question Policy — this is the one phase that may ask
@@ -47,7 +144,8 @@ After the batched call, run Steps 1–2.5 to completion without further question
47
144
  2. Read `docs/AI_HANDOFF/ACTIVE.md` → active cycle info (or "no active cycle")
48
145
  3. Read `docs/AI_HANDOFF/RULES.md` → PLAN.md 6-section format + Task Gate required fields
49
146
  4. Read `docs/AI_HANDOFF/tasks/_TEMPLATE.md` → task file structure
50
- 5. Return a compact summary. Do NOT write anything yet.
147
+ 5. Read `docs/AI_HANDOFF/RUN.md` → its `Phase:` line (or "no run"). This decides the output root.
148
+ 6. Return a compact summary. Do NOT write anything yet.
51
149
 
52
150
  > **For the human operator, on a tool with no agent support (Codex) — not an instruction to the model:** manually switch to the lite model, run the steps above yourself, keep the summary in context.
53
151
 
@@ -55,11 +153,19 @@ After the batched call, run Steps 1–2.5 to completion without further question
55
153
 
56
154
  ## Step 2 — Write plan + tasks (strong model)
57
155
 
58
- **Claude Code — MANDATORY, do this before anything else:** call the Agent tool with `subagent_type: "handoff-planner"` (omp: the `task` tool with `agent: "handoff-planner"`), passing it the Step 1 summary and the problem/feature description. Do NOT write PLAN.md or task files yourself in the current session — this step is contracted to the strong tier (opus/unic-smart), which only the spawned agent's frontmatter model guarantees.
156
+ **Claude Code — MANDATORY, do this before anything else:** call the Agent tool with `subagent_type: "handoff-planner"` (omp: the `task` tool with `agent: "handoff-planner"`), passing it the Step 1 summary, the problem/feature description, and the **planning-only output root** (`docs/AI_HANDOFF/` when no run is live, else `docs/AI_HANDOFF/queued/<slug>/`) decided in the live-run guard below. Do NOT write PLAN.md or task files yourself in the current session — this step is contracted to the strong tier (opus/unic-smart), which only the spawned agent's frontmatter model guarantees. The planner never launches an executor.
59
157
 
60
158
  The planner agent does the following (use Step 1 summary — do NOT re-read files):
61
159
 
62
- 1. Check INDEX.md task statuses:
160
+ 0. **Live-run guard — decide OUTPUT_ROOT first (see "A live fullstack run is never
161
+ overwritten").** If Step 1 reported `RUN.md` with a `Phase:` that is not `done`/`blocked`,
162
+ a fullstack run is live: set `OUTPUT_ROOT = docs/AI_HANDOFF/queued/<slug>/` (`<slug>` =
163
+ a short kebab name for this work), write ONLY there, and skip steps 1, 6 and 7 — the
164
+ canonical `RUN.md`/`INDEX.md`/`PLAN.md`/`SPEC.md`/`ACTIVE.md`/`tasks/` stay untouched.
165
+ Otherwise `OUTPUT_ROOT = docs/AI_HANDOFF/`. Everything below writes under `OUTPUT_ROOT`.
166
+
167
+ 1. Check INDEX.md task statuses (canonical root only — skip when the live-run guard routed
168
+ you to the queued root):
63
169
  - All tasks are `ready` (planning only) → **re-run allowed**: overwrite PLAN.md and TASK-xxx.md freely — this is iterative refinement.
64
170
  - Any task is `pending_review`, `changes_requested`, `merge_conflict`, or `blocked` → **do not overwrite**: that cycle is mid-flight and its task files carry executor reports and verdicts. Report which tasks are in flight and point the human at `/ukit:handoff-review` (to finish the cycle) or `/ukit:handoff-clear` (to abandon it). Planning is the one phase where stopping is correct — there is nothing safe to do automatically here.
65
171
  - No tasks → fresh cycle, proceed normally.
@@ -69,7 +175,7 @@ The planner agent does the following (use Step 1 summary — do NOT re-read file
69
175
  BASE=$(git symbolic-ref --short HEAD)
70
176
  ```
71
177
 
72
- 3. Write `docs/AI_HANDOFF/SPEC.md` — the detailed implementation spec (BẮT BUỘC before tasks).
178
+ 3. Write `${OUTPUT_ROOT}SPEC.md` — the detailed implementation spec (BẮT BUỘC before tasks).
73
179
  Follow `docs/AI_HANDOFF/SPEC.md`'s template sections (or `template_project/docs/AI_HANDOFF/SPEC.md`
74
180
  on a fresh tree): problem/context, goals, non-goals, user journeys, functional requirements
75
181
  with Given/When/Then + error cases, fullstack scope (backend, schema/migrations, API
@@ -83,7 +189,7 @@ The planner agent does the following (use Step 1 summary — do NOT re-read file
83
189
  resolved to a chosen default recorded inline — the plan phase is the only question
84
190
  window, so anything left "TBD" becomes a guess downstream.
85
191
 
86
- 4. Write `docs/AI_HANDOFF/PLAN.md` — all 6 sections mandatory:
192
+ 4. Write `${OUTPUT_ROOT}PLAN.md` — all 6 sections mandatory:
87
193
  - §1 Intent — problem + success definition
88
194
  - §2 Scope — in / out of scope. **Add a constraint**: same-wave tasks must not modify the same file (prevents merge conflicts). If two tasks need the same file, make one depend on the other.
89
195
  - §3 Approach — solution, trade-offs, alternatives rejected
@@ -97,7 +203,7 @@ The planner agent does the following (use Step 1 summary — do NOT re-read file
97
203
  PLANNER_MODEL: <your exact model ID>
98
204
  ```
99
205
 
100
- 5. Create `docs/AI_HANDOFF/tasks/TASK-001.md`, `TASK-002.md`... from `_TEMPLATE.md`
206
+ 5. Create `${OUTPUT_ROOT}tasks/TASK-001.md`, `TASK-002.md`... from `_TEMPLATE.md`
101
207
  Every task MUST have:
102
208
  - Spec references (SPEC.md section IDs this task implements)
103
209
  - Target Files (exact paths — no two tasks in same wave share a file)
@@ -108,9 +214,12 @@ The planner agent does the following (use Step 1 summary — do NOT re-read file
108
214
  - Acceptance Criteria (verifiable checklist)
109
215
  Missing any field → status: `needs_breakdown`, never `ready`
110
216
 
111
- 6. Update `INDEX.md` — one row per task, `status=ready`
217
+ 6. Update `${OUTPUT_ROOT}INDEX.md` — one row per task, `status=ready`. **Skip entirely when
218
+ the live-run guard routed you to the queued root** — a blueprint carries no runnable
219
+ `ready` row; its `HANDOFF.md` activation notes are the record instead.
112
220
 
113
- 7. Update `ACTIVE.md`:
221
+ 7. Update `ACTIVE.md` (canonical root only — **skip when queued**; the queued blueprint's
222
+ `HANDOFF.md` replaces it):
114
223
  ```
115
224
  Cycle: <ID> Date: <YYYY-MM-DD> Base: <BASE>
116
225
  Goal: <1 sentence>
@@ -120,9 +229,10 @@ The planner agent does the following (use Step 1 summary — do NOT re-read file
120
229
  ```
121
230
  Note: wave structure is inferred from task Dependencies fields — not stored here.
122
231
 
123
- 8. Report: task IDs, dependency graph, any `needs_breakdown` + reason
232
+ 8. Report: task IDs, dependency graph, any `needs_breakdown` + reason — and, when queued,
233
+ the `OUTPUT_ROOT` path plus why it was queued (the live run it must not disturb).
124
234
 
125
- > **For the human operator, on a tool with no agent support (Codex) — not an instruction to the model:** manually switch to the strong model, execute steps 1–7 above yourself.
235
+ > **For the human operator, on a tool with no agent support (Codex) — not an instruction to the model:** manually switch to the strong model, execute steps 0–8 above yourself.
126
236
 
127
237
  ---
128
238
 
@@ -137,7 +247,7 @@ The planner agent does the following (use Step 1 summary — do NOT re-read file
137
247
  Two independent strong-model passes shape the plan before any code is written — that gate is
138
248
  intact. What it no longer does is hand a stalled plan back and wait.
139
249
 
140
- **Claude Code — MANDATORY, do this before anything else:** 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 Step 2's `handoff-planner` call (fresh context) — same-session self-review defeats the purpose of an independent gate.
250
+ **Claude Code — MANDATORY, do this before anything else:** 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 `${OUTPUT_ROOT}PLAN.md` AND `${OUTPUT_ROOT}SPEC.md` (canonical root, or the queued root when one was chosen). This MUST be a separate agent invocation from Step 2's `handoff-planner` call (fresh context) — same-session self-review defeats the purpose of an independent gate.
141
251
 
142
252
  1. Reviewer reads `SPEC.md` + `PLAN.md` (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, no vague instruction left — 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).
143
253
  2. `Issues Found` → route back to Step 2: planner revises `PLAN.md` and the affected `TASK-xxx.md` files to address every finding, then re-submit for another Step 2.5 review (this becomes the next round). Do NOT commit or hand off to executor on `Issues Found`.
@@ -147,4 +257,13 @@ intact. What it no longer does is hand a stalled plan back and wait.
147
257
 
148
258
  ---
149
259
 
150
- **Next:** switch to code model → `/ukit:handoff-implement`
260
+ **Next:** the plan is complete and every task is `ready` (queued — nothing has been
261
+ implemented). To start implementation, the human runs `/ukit:handoff-implement` for the
262
+ plan-only flow, or `/ukit:handoff-fullstack` to drive plan → implement → review as one
263
+ one-shot pipeline. This command does not do either on its own.
264
+
265
+ When a live fullstack run forced the plan into `docs/AI_HANDOFF/queued/<slug>/`, that
266
+ blueprint is dormant: `/ukit:handoff-fullstack` sweeps it into the canonical plan once the
267
+ current run is closed (or the human explicitly abandons it with `/ukit:handoff-clear`),
268
+ copying the queued tasks into `tasks/` at that point — exactly like any other queued
269
+ blueprint. Nothing is dropped, and nothing is auto-implemented before then.
@@ -111,6 +111,23 @@ all treats it as closed. It is *paused*, not abandoned: an explicit `/ukit:hando
111
111
  re-invocation resumes it as a continuation; `ukit handoff-clear` abandons it. Only when
112
112
  `RUN.md` is absent or `Phase: done` does a new request start a fresh cycle.
113
113
 
114
+ ### Explicit invocation reacquires ownership
115
+
116
+ `/ukit:handoff-create` is planning-only and makes a session's handoff intent *planning* — so
117
+ while the human is planning, neither the Stop gate nor the SessionStart compact/resume hook
118
+ forces a paused run back into implementation. This command is the explicit counter-action:
119
+
120
+ - Invoking `/ukit:handoff-fullstack` (or running the `handoff-fullstack` skill) makes the
121
+ session's handoff intent *implementation* again — ownership is reacquired, the queued
122
+ `ready` tasks from the create phase are picked up, and a `Phase:` outside `done`/`blocked`
123
+ in `RUN.md` is once more held to completion by the Stop gate.
124
+ - Work queued by planning is **not lost**: it waits in `INDEX.md`/`PLAN.md` until this
125
+ explicit invocation (or `/ukit:handoff-implement`) claims it.
126
+
127
+ Enforcement is therefore never weakened — it is scoped to *which* command the session
128
+ actually invoked. A session driving this pipeline still cannot stop early; a session that
129
+ only planned never gets bounced into a run it did not ask for.
130
+
114
131
  ---
115
132
 
116
133
  ## §0 — Completion contract (terminal outputs)
@@ -153,11 +170,29 @@ work, not only the current plan:
153
170
  3. Previous cycles — `docs/AI_HANDOFF/HISTORY.md` and `archive/` for cycles closed with
154
171
  unfinished tasks; their leftovers join THIS cycle.
155
172
  4. `docs/TASKS.md` — `Ready for AI` items are newly-assigned work; fold them into the plan.
156
- 5. `git status` / `git diff` — uncommitted work-in-progress that must be finished or
173
+ 5. `docs/AI_HANDOFF/queued/*/` — dormant blueprints. A planning run that could **not**
174
+ touch the canonical plan (a live run was in flight) queues its SPEC/PLAN/tasks under
175
+ `docs/AI_HANDOFF/queued/<slug>/` instead of overwriting (see `/ukit:handoff-create`).
176
+ When this run is planning a fresh cycle (P2), sweep those blueprints in: copy a
177
+ blueprint's tasks into `tasks/TASK-xxx.md` and schedule them like any other item. Never
178
+ overwrite a blueprint — a blueprint the run decides to defer stays queued.
179
+ Blueprints WITHOUT the `QueueIntent` metadata are still folded in as described above.
180
+ A blueprint whose header carries `QueueIntent: deliberate-future` is the exception: it is
181
+ **not folded** in; report it as `Backlog: <slug>` and leave it untouched.
182
+ Backlog items do not count toward `index:all-done` / `ExitPredicate`, so they never block completion. Only
183
+ an explicit slug in the invocation drains one (one blueprint per invocation). The
184
+ create phase of this command applies the same advisor intake as `/ukit:handoff-create`.
185
+ 6. `git status` / `git diff` — uncommitted work-in-progress that must be finished or
157
186
  checkpointed, never silently dropped.
158
187
 
159
188
  The inventory feeds P2: the planner either schedules every discovered item or records why
160
- it is out of scope (§1/§2 of PLAN.md). Silent omission is a plan defect.
189
+ it is out of scope (§1/§2 of PLAN.md). Silent omission is a plan defect — a queued
190
+ blueprint dropped without either scheduling or a recorded reason is exactly that defect.
191
+
192
+ > **Never overwrite a live run to consume a queued blueprint.** A blueprint is folded in
193
+ > only through P2's normal plan-write path, and only when no run is mid-flight; on a
194
+ > Resume (RUN.md `Phase:` not `done`/`blocked`) the run continues from its own cursor and
195
+ > leaves the queued blueprint untouched for the next cycle.
161
196
 
162
197
  **Recovery rule — stuck task records.** For every `pending`, stale `in_progress`, or
163
198
  `blocked` task whose record cannot be cleanly resumed (invalid state, orphaned worktree