@ngockhoale/ukit 2.7.1 → 2.7.3

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 (52) hide show
  1. package/CHANGELOG.md +72 -0
  2. package/README.md +1 -3
  3. package/manifests/hostCapabilities.yaml +0 -10
  4. package/manifests/instructionRules.yaml +0 -16
  5. package/manifests/platform.full.yaml +2 -16
  6. package/package.json +3 -5
  7. package/scripts/perf/audit-perf.mjs +4 -4
  8. package/scripts/perf/perf-findings.json +420 -0
  9. package/src/cli/adapters.js +10 -13
  10. package/src/cli/commands/doctor.js +60 -10
  11. package/src/cli/index.js +2 -2
  12. package/src/core/ompConfigMerge.js +19 -0
  13. package/src/core/opencodeSteer.js +37 -0
  14. package/src/core/permissionDoctor.js +319 -0
  15. package/src/core/permissionPolicy.js +179 -0
  16. package/src/core/projectImportant.js +14 -8
  17. package/src/core/runInstallPipeline.js +25 -0
  18. package/src/core/runtimeConfig.js +43 -4
  19. package/src/core/status.js +6 -4
  20. package/src/core/unattendedDoctor.js +9 -5
  21. package/src/manifest/selectItems.js +0 -1
  22. package/src/render/buildVariables.js +10 -1
  23. package/templates/.claude/agents/bug-debugger.md +1 -1
  24. package/templates/.claude/agents/feature-implementer.md +1 -1
  25. package/templates/.claude/agents/ukit-small-task-maintainer.md +2 -2
  26. package/templates/.claude/commands/ukit/handoff-create.md +4 -4
  27. package/templates/.claude/commands/ukit/handoff-fullstack.md +4 -4
  28. package/templates/.claude/commands/ukit/handoff-implement.md +2 -2
  29. package/templates/.claude/commands/ukit/handoff-review.md +2 -2
  30. package/templates/.claude/settings.json +14 -6
  31. package/templates/.claude/skills/update-status/SKILL.md +1 -1
  32. package/templates/.claude/ukit/index/route-task.mjs +7 -2
  33. package/templates/.claude/ukit/skill-router-state.json +1 -0
  34. package/templates/.codex/README.md +1 -1
  35. package/templates/.codex/settings.json +0 -1
  36. package/templates/.omp/agents/bug-debugger.md +1 -1
  37. package/templates/.omp/agents/feature-implementer.md +1 -1
  38. package/templates/.omp/agents/ukit-small-task-maintainer.md +2 -2
  39. package/templates/.omp/config.yml +13 -7
  40. package/templates/.ukit/storage/cache/hook-latency/unknown.jsonl +4 -0
  41. package/templates/AGENTS.md +4 -13
  42. package/templates/CLAUDE.md +1 -3
  43. package/templates/adapter-presets/claude/settings.local.json +0 -1
  44. package/templates/docs/AI_HANDOFF/INDEX.md +1 -1
  45. package/templates/docs/AI_HANDOFF/RULES.md +2 -2
  46. package/templates/docs/PROJECT.md +1 -1
  47. package/templates/docs/UKIT_INTERNALS.md +1 -1
  48. package/templates/instructions/core.md +1 -3
  49. package/templates/instructions/layout.yaml +2 -4
  50. package/templates/instructions/overlays/agents.md +3 -10
  51. package/templates/ukit/storage/config.json +2 -10
  52. package/templates/adapter-presets/opencode/opencode.template.json +0 -1
@@ -15,6 +15,8 @@ import { ensureGitignore } from './ensureGitignore.js';
15
15
  import { repairBrokenHooks } from './repairBrokenHooks.js';
16
16
  import { applyGatewayResilienceEnv } from './gatewayResilienceEnv.js';
17
17
  import { cleanupEmptyParents, readJsonIfExists, removeFileOrLinkOnly, resolveProjectRelativePath } from './fileOps.js';
18
+ import { findOpencodeArtifacts, opencodeSteerMessage } from './opencodeSteer.js';
19
+ import { loadRuntimeConfig } from './runtimeConfig.js';
18
20
 
19
21
  const AUTO_PRUNE_OBSOLETE_PREFIXES = [
20
22
  '.claude/skills/',
@@ -261,6 +263,15 @@ export async function runInstallPipeline({
261
263
  const installMetaData = await readJsonIfExists(pathConfig.installMetaPath);
262
264
  const isReinstall = Boolean(installMetaData?.tool === 'ukit');
263
265
 
266
+ // SPEC FR-004/§security — a leftover opencode.json is a user file: detect and
267
+ // steer toward manual removal. Install never writes, merges, repairs, or
268
+ // deletes it (obsolete-path pruning excludes it; only `ukit uninstall` may
269
+ // remove it).
270
+ const opencodeArtifacts = findOpencodeArtifacts(pathConfig.projectRoot);
271
+ if (opencodeArtifacts.length > 0) {
272
+ console.warn(opencodeSteerMessage(opencodeArtifacts));
273
+ }
274
+
264
275
  if (isReinstall && !dryRun) {
265
276
  await migrateLegacyRuntimeRoot(pathConfig.projectRoot);
266
277
  }
@@ -269,6 +280,10 @@ export async function runInstallPipeline({
269
280
  const stackContext = await detectStack(pathConfig.projectRoot);
270
281
  const projectContext = await detectProjectContext(pathConfig.projectRoot);
271
282
  const providerContext = await detectProviders(pathConfig.projectRoot);
283
+ // FR-004 — feeds the omp.evalApproval render variable. Missing/invalid config
284
+ // degrades to defaults (allowUnsafeEval: false) inside loadRuntimeConfig, so
285
+ // the rendered eval approval fails closed to `deny`.
286
+ const runtimeConfig = await loadRuntimeConfig(pathConfig.projectRoot);
272
287
 
273
288
  const variables = buildTemplateVariables({
274
289
  projectContext,
@@ -276,6 +291,7 @@ export async function runInstallPipeline({
276
291
  packageVersion,
277
292
  providerContext,
278
293
  withCodegraph,
294
+ runtimeConfig,
279
295
  });
280
296
 
281
297
  const plan = await buildInstallPlan({
@@ -321,6 +337,15 @@ export async function runInstallPipeline({
321
337
  console.log(`[UKit] .omp/config.yml: migrated approval surface (prompt→removed: ${removed}, preserved: ${preserved})`);
322
338
  }
323
339
 
340
+ // TASK-002 / FR-002: any `{{token}}` that survived into merged .omp/config.yml
341
+ // output means the render lane lacked the variable — warn instead of silently
342
+ // persisting the literal (the original autoCompactWindow bug mechanism).
343
+ for (const entry of diffResults) {
344
+ const tokens = entry?.migrationReport?.unresolvedTokens;
345
+ if (!tokens || tokens.length === 0) continue;
346
+ console.warn(`[UKit] Warning: .omp/config.yml contains unresolved template tokens: ${tokens.join(', ')} — report this to UKit maintainers; no value was fabricated.`);
347
+ }
348
+
324
349
  // BUG-C22-19: bound the .bak accumulation — every overwrite_with_backup write
325
350
  // adds one and nothing removed them. Advisory: a prune failure must never
326
351
  // fail the install.
@@ -9,8 +9,10 @@ const require = createRequire(import.meta.url);
9
9
  const { version: PACKAGE_VERSION } = require('../../package.json');
10
10
 
11
11
  // legacy: 'antigravity' stays valid — Antigravity adapter removed in v2.2.0, but an existing
12
- // config.json naming it must not become invalid.
13
- const VALID_AGENTS = new Set(['claude-code', 'codex', 'antigravity', 'opencode', 'omp']);
12
+ // config.json naming it must not become invalid. 'opencode' is NOT kept valid here: the
13
+ // OpenCode lane was removed this cycle, so stale 'opencode' values are normalized away
14
+ // (strip + console.warn) in normalizeLegacyOpencodeEntries instead of hard-failing.
15
+ const VALID_AGENTS = new Set(['claude-code', 'codex', 'antigravity', 'omp']);
14
16
 
15
17
  // Reserved permission modes for orchestration.permissionMode (single source of truth).
16
18
  // 'unattended' is the only mode implemented this cycle; interactive/safe-auto are reserved.
@@ -90,7 +92,6 @@ export function buildDefaultRuntimeConfig(overrides = {}) {
90
92
  mustNotBlockMainTask: true,
91
93
  targets: {
92
94
  claude: { autoCompact: true, mode: 'precompact-reinject', preserveExistingHooks: true },
93
- opencode: { autoCompact: true, mode: 'native-auto-prune', preserveExistingCompaction: true },
94
95
  // 'precompact-reinject' like claude, NOT codex's 'soft-handoff': omp exposes real
95
96
  // pre-compaction hook events, so there is nothing to work around with a state file.
96
97
  omp: { autoCompact: true, mode: 'precompact-reinject', preserveExistingHooks: true },
@@ -142,6 +143,10 @@ export function buildDefaultRuntimeConfig(overrides = {}) {
142
143
  orchestratorModel: 'claude-sonnet-5',
143
144
  advisorEnabled: true,
144
145
  permissionMode: 'unattended',
146
+ // FR-003: eval-subprocess gate for rendered omp approval (tools.approval.eval).
147
+ // Fail-closed: only an explicit user `true` renders `eval: allow`; absent,
148
+ // false, or an unreadable config all render `deny`. Migration never sets it.
149
+ allowUnsafeEval: false,
145
150
  contracts: buildConfigContracts(),
146
151
  modelTiers: {
147
152
  // ompRole is the .omp/config.yml modelRoles key this tier binds to.
@@ -406,6 +411,9 @@ export function validateRuntimeConfig(config) {
406
411
  && !VALID_PERMISSION_MODES.has(config.orchestration.permissionMode)) {
407
412
  errors.push(`orchestration.permissionMode must be one of: ${[...VALID_PERMISSION_MODES].join(', ')}.`);
408
413
  }
414
+ if (config.orchestration.allowUnsafeEval !== undefined) {
415
+ pushBooleanError(errors, config.orchestration.allowUnsafeEval, 'orchestration.allowUnsafeEval');
416
+ }
409
417
  if (!isPlainObject(config.orchestration.contracts)) {
410
418
  errors.push('orchestration.contracts must be an object.');
411
419
  }
@@ -653,6 +661,37 @@ export function validateRuntimeConfig(config) {
653
661
  };
654
662
  }
655
663
 
664
+ // OpenCode was removed as an adapter this cycle. Stale 'opencode' values inside
665
+ // validated agent lists/keys are stripped with a steer warning — never a hard-fail —
666
+ // so an old config.json still loads instead of falling back to defaults.
667
+ function normalizeLegacyOpencodeEntries(config) {
668
+ const warned = { value: false };
669
+ const warnOnce = () => {
670
+ if (!warned.value) {
671
+ console.warn(`[UKit] Removed stale 'opencode' agent entries from UKit runtime config — OpenCode is no longer supported.`);
672
+ warned.value = true;
673
+ }
674
+ };
675
+
676
+ if (isPlainObject(config) && config.agent === 'opencode') {
677
+ warnOnce();
678
+ config.agent = 'claude-code';
679
+ }
680
+
681
+ if (isPlainObject(config) && Array.isArray(config.agents) && config.agents.includes('opencode')) {
682
+ warnOnce();
683
+ config.agents = config.agents.filter((agent) => agent !== 'opencode');
684
+ }
685
+
686
+ const targets = config?.compact?.agentContext?.targets;
687
+ if (isPlainObject(targets) && 'opencode' in targets) {
688
+ warnOnce();
689
+ delete targets.opencode;
690
+ }
691
+
692
+ return config;
693
+ }
694
+
656
695
  export async function inspectRuntimeConfig(projectRoot) {
657
696
  const runtimePaths = buildRuntimePaths(projectRoot);
658
697
  let exists = false;
@@ -670,7 +709,7 @@ export async function inspectRuntimeConfig(projectRoot) {
670
709
  }
671
710
  }
672
711
 
673
- const config = buildDefaultRuntimeConfig(rawConfig ?? {});
712
+ const config = normalizeLegacyOpencodeEntries(buildDefaultRuntimeConfig(rawConfig ?? {}));
674
713
  const validation = validateRuntimeConfig(config);
675
714
  const errors = [
676
715
  ...(parseError ? [`config.json parse error: ${parseError}`] : []),
@@ -8,13 +8,13 @@ import { countMemoryItems } from './memory/store.js';
8
8
  import { buildCompactPressureState } from './compact/threshold.js';
9
9
  import { detectProjectContext } from '../context/detectProjectContext.js';
10
10
  import { inspectProjectImportant, inspectProjectImportantWiring } from './projectImportant.js';
11
+ import { opencodeSteerMessage } from './opencodeSteer.js';
11
12
 
12
13
  function formatPrimaryAgent(agentKey) {
13
14
  if (agentKey === 'claude-code') return 'Claude Code';
14
15
  if (agentKey === 'codex') return 'OpenAI Codex';
15
16
  // legacy: Antigravity adapter removed in this release; existing config.json may still name it
16
17
  if (agentKey === 'antigravity') return 'Antigravity';
17
- if (agentKey === 'opencode') return 'OpenCode';
18
18
  if (agentKey === 'omp') return 'Oh My Pi (omp)';
19
19
  return agentKey || 'Unknown';
20
20
  }
@@ -24,7 +24,6 @@ async function detectAdapterLabels(projectRoot, primaryAgent) {
24
24
 
25
25
  const checks = [
26
26
  { label: 'OpenAI Codex', path: '.codex/settings.json' },
27
- { label: 'OpenCode', path: 'opencode.json' },
28
27
  { label: 'Oh My Pi (omp)', path: '.omp/config.yml' },
29
28
  ];
30
29
 
@@ -171,8 +170,11 @@ export async function buildStatusReport(projectRoot) {
171
170
  claude: piWiring.claude.wired,
172
171
  omp: piWiring.omp.installed ? piWiring.omp.wired : undefined,
173
172
  codex: piWiring.codex.installed ? piWiring.codex.wired : undefined,
174
- opencode: piWiring.opencode.installed ? piWiring.opencode.wired : undefined,
175
173
  },
174
+ // SPEC FR-006 — leftover opencode.json is a user file: steer, never wire.
175
+ opencodeSteer: piWiring.opencodeArtifacts.length > 0
176
+ ? opencodeSteerMessage(piWiring.opencodeArtifacts)
177
+ : null,
176
178
  };
177
179
 
178
180
  return {
@@ -226,7 +228,6 @@ function formatProjectImportantWiring(pi) {
226
228
  const parts = [`Claude runtime ${mark(pi.wiring.claude)}`];
227
229
  if (pi.wiring.omp !== undefined) parts.push(`omp runtime ${mark(pi.wiring.omp)}`);
228
230
  if (pi.wiring.codex !== undefined) parts.push(`Codex fallback ${mark(pi.wiring.codex)}`);
229
- if (pi.wiring.opencode !== undefined) parts.push(`OpenCode fallback ${mark(pi.wiring.opencode)}`);
230
231
  return parts.join(' / ');
231
232
  }
232
233
 
@@ -237,6 +238,7 @@ export function formatStatusReport(report) {
237
238
  `${padLabel('Project')} ${report.projectName}`,
238
239
  `${padLabel('Project rules')} ${formatProjectImportantRules(report.projectImportant)}`,
239
240
  `${padLabel('Rule wiring')} ${formatProjectImportantWiring(report.projectImportant)}`,
241
+ ...(report.projectImportant.opencodeSteer ? [report.projectImportant.opencodeSteer] : []),
240
242
  `${padLabel('Adapters')} ${report.adapters.join(', ')}`,
241
243
  `${padLabel('Memory items')} ${report.memoryCounts.projectCount} project / ${report.memoryCounts.sessionCount} session / ${report.memoryCounts.userCount} user`,
242
244
  `${padLabel('Last compact')} ${report.lastCompact}`,
@@ -70,6 +70,8 @@ export async function inspectUnattendedMode({ projectRoot, ompPath = 'omp' }) {
70
70
  const permissionModeValid = permissionModeRaw === undefined
71
71
  || VALID_PERMISSION_MODES.has(permissionModeRaw);
72
72
  const permissionMode = permissionModeRaw === undefined ? 'unattended' : permissionModeRaw;
73
+ const allowUnsafeEval = runtimeConfig?.orchestration?.allowUnsafeEval === true;
74
+ const expectedEval = allowUnsafeEval ? 'allow' : 'deny';
73
75
 
74
76
  const omp = await readOmpConfig(projectRoot);
75
77
  const tools = omp.parsed?.tools ?? {};
@@ -163,14 +165,16 @@ export async function inspectUnattendedMode({ projectRoot, ompPath = 'omp' }) {
163
165
  'Run ukit install to pin tools.approvalMode: yolo in .omp/config.yml.',
164
166
  { detail: `value: ${effectiveApprovalMode} (source: ${approvalSource})` },
165
167
  ),
166
- // 3 — eval/bash policy allow
168
+ // 3 — eval/bash policy vs compiled posture (FR-004/FR-006): bash stays
169
+ // allow; eval matches the allowUnsafeEval knob (deny by default — fail
170
+ // closed), never prompt.
167
171
  check(
168
- "eval/bash policy 'allow'",
169
- omp.exists && approvalMap.eval === 'allow' && approvalMap.bash === 'allow',
172
+ 'eval/bash policy matches compiled posture',
173
+ omp.exists && approvalMap.bash === 'allow' && approvalMap.eval === expectedEval,
170
174
  'install-repairable',
171
- 'Run ukit install to restore tools.approval.eval/bash: allow.',
175
+ `Run ukit install to restore tools.approval.bash: allow and tools.approval.eval: ${expectedEval}.`,
172
176
  omp.exists
173
- ? {}
177
+ ? { detail: `eval expected ${expectedEval} (allowUnsafeEval=${allowUnsafeEval}), got ${approvalMap.eval ?? 'absent'}` }
174
178
  : { applicable: false, detail: '.omp/config.yml absent' },
175
179
  ),
176
180
  // 4 — zero prompt policies
@@ -5,7 +5,6 @@ export const OPTIONAL_ADAPTER_ITEM_IDS = new Set([
5
5
  'multi-codex-settings-json',
6
6
  'multi-codex-settings-local',
7
7
  'multi-codex-agents-link',
8
- 'multi-opencode-config',
9
8
  'multi-omp-agents-md',
10
9
  'multi-omp-rules',
11
10
  'multi-omp-config',
@@ -13,12 +13,18 @@ UKit routing, memory, and skill activation remain primary. CodeGraph is the inde
13
13
  To disable: rerun \`ukit install\` without \`--with-codegraph\`.
14
14
  `;
15
15
 
16
- export function buildTemplateVariables({ projectContext, stackContext, packageVersion, providerContext, withCodegraph = false }) {
16
+ export function buildTemplateVariables({ projectContext, stackContext, packageVersion, providerContext, withCodegraph = false, runtimeConfig }) {
17
17
  // Derived, never hand-written: .claude/settings.json is read by Claude Code itself and so
18
18
  // cannot consult .ukit/storage/config.json at runtime. Baking the window in here keeps
19
19
  // compact.hardCapTokens the single number that retunes both.
20
20
  const { autoCompactWindow } = loadShippedCompactBudget();
21
21
 
22
+ // FR-004 — fail closed: `allow` only on an explicit user opt-in
23
+ // (`orchestration.allowUnsafeEval === true`). Missing, unreadable, or
24
+ // non-boolean config all resolve `deny` — never `prompt`/`ask`, which would
25
+ // dead-end under unattended (SPEC §14).
26
+ const evalApproval = runtimeConfig?.orchestration?.allowUnsafeEval === true ? 'allow' : 'deny';
27
+
22
28
  return {
23
29
  compact: {
24
30
  autoCompactWindow: String(autoCompactWindow),
@@ -58,6 +64,9 @@ export function buildTemplateVariables({ projectContext, stackContext, packageVe
58
64
  user: {
59
65
  profile: 'default',
60
66
  },
67
+ omp: {
68
+ evalApproval,
69
+ },
61
70
  codegraphSection: withCodegraph ? CODEGRAPH_SECTION : '',
62
71
  };
63
72
  }
@@ -50,7 +50,7 @@ Systematic debugging — understand before fixing.
50
50
 
51
51
  ```
52
52
  STATUS: DONE | BLOCKED | PARTIAL
53
- EXECUTOR_TOOL: [claude-code | kilo-code | codex | opencode | other]
53
+ EXECUTOR_TOOL: [claude-code | kilo-code | codex | other]
54
54
  EXECUTOR_MODEL: [exact model name you are running as. "unknown" if you cannot tell.]
55
55
  EXECUTOR_SUBAGENT: [subagent name within your host, if any, else "-"]
56
56
  SUMMARY: [1-2 sentences — root cause and fix]
@@ -72,7 +72,7 @@ and even then, report it, don't ask about it.
72
72
 
73
73
  ```
74
74
  STATUS: DONE | BLOCKED | PARTIAL
75
- EXECUTOR_TOOL: [claude-code | kilo-code | codex | opencode | other]
75
+ EXECUTOR_TOOL: [claude-code | kilo-code | codex | other]
76
76
  EXECUTOR_MODEL: [exact model name you are running as — e.g. unic-code, claude-sonnet-4-5, gpt-5-mini. If you truly cannot tell, write "unknown" — reviewer treats unknown as suspicious and asks the human to confirm.]
77
77
  EXECUTOR_SUBAGENT: [name of the subagent you are, if your host has multiple — e.g. "Kilo:code", "Claude:feature-implementer". Otherwise "-".]
78
78
  SUMMARY: [1-2 sentences of what was implemented]
@@ -24,7 +24,7 @@ You are UKit's internal small-task maintainer. You run as a sidecar/parallel/non
24
24
  - Summarizing `docs/STATUS.md`, `docs/TASKS.md`, `docs/WORKLOG.md`, or memory snippets into compact handoff notes.
25
25
  - Deciding fast vs slow lane, safe vs risky lane, whether a skill should be activated, and whether the current prompt has enough steps planned.
26
26
  - Choosing compact-now vs compact-later and summarize-vs-keep-detail for UKit context hygiene.
27
- - Keeping agent context compact without removing existing lanes: Claude PreCompact/reinject stays active, OpenCode native auto/prune compaction stays active, and Codex Desktop soft handoffs use `compact.codexContext.compactTarget` (default 150 lines; preferred 120-150; hard max 170) while preserving critical state.
27
+ - Keeping agent context compact without removing existing lanes: Claude PreCompact/reinject stays active and Codex Desktop soft handoffs use `compact.codexContext.compactTarget` (default 150 lines; preferred 120-150; hard max 170) while preserving critical state.
28
28
  - Small, reversible UKit runtime maintenance decisions.
29
29
 
30
30
  ## Never Use For
@@ -45,7 +45,7 @@ You are UKit's internal small-task maintainer. You run as a sidecar/parallel/non
45
45
  5. If safe, make the smallest cleanup/summary/classification/routing decision.
46
46
  6. Follow the CoDev priority order: output quality first, then safety, then speed, then token discipline. Spend tokens/steps when they protect correctness.
47
47
  7. Use small step budgets: trivial = 1 step, simple = up to 2 steps, non-trivial = up to 4 planned steps before the main model reassesses.
48
- 8. For long sessions, preserve existing Claude/OpenCode auto-compact behavior; for Codex Desktop, target a compact handoff of about 120-150 lines, never above 170 lines for this lane, not a full transcript replay.
48
+ 8. For long sessions, preserve existing Claude auto-compact behavior; for Codex Desktop, target a compact handoff of about 120-150 lines, never above 170 lines for this lane, not a full transcript replay.
49
49
  9. Preserve user-owned content; do not delete unclear tasks.
50
50
  10. Report exactly what changed or what decision was made.
51
51
 
@@ -1,7 +1,7 @@
1
1
  # /ukit:handoff-create — Phase 1 + 2: Plan
2
2
 
3
3
  **Role: PLANNER**
4
- **Tool: any** (Claude Code / Codex / omp / Kilo / OpenCode — your choice)
4
+ **Tool: any** (Claude Code / Codex / omp / Kilo — your choice)
5
5
  **Model split:**
6
6
  - Read/understand → lite model (haiku · unic-lite · cheapest available)
7
7
  - Write plan + tasks → strong model (Opus · unic-smart · strongest available)
@@ -49,7 +49,7 @@ After the batched call, run Steps 1–2.5 to completion without further question
49
49
  4. Read `docs/AI_HANDOFF/tasks/_TEMPLATE.md` → task file structure
50
50
  5. Return a compact summary. Do NOT write anything yet.
51
51
 
52
- > **For the human operator, on a tool with no agent support (Codex, OpenCode) — not an instruction to the model:** manually switch to the lite model, run the steps above yourself, keep the summary in context.
52
+ > **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
53
 
54
54
  ---
55
55
 
@@ -122,7 +122,7 @@ The planner agent does the following (use Step 1 summary — do NOT re-read file
122
122
 
123
123
  8. Report: task IDs, dependency graph, any `needs_breakdown` + reason
124
124
 
125
- > **For the human operator, on a tool with no agent support (Codex, OpenCode) — not an instruction to the model:** manually switch to the strong model, execute steps 1–7 above yourself.
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.
126
126
 
127
127
  ---
128
128
 
@@ -143,7 +143,7 @@ intact. What it no longer does is hand a stalled plan back and wait.
143
143
  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`.
144
144
  3. `Approved` → append `PLAN_REVIEW: Approved by <reviewer model>` to PLAN.md's `## Planner Report` footer, then proceed.
145
145
 
146
- > **For the human operator, on a tool with no agent support (Codex, OpenCode) — not an instruction to the model:** manually switch to the strong model in a **separate** chat/session from Step 2, paste PLAN.md, review using the Spec/Plan Review checklist in `.claude/agents/code-reviewer.md`.
146
+ > **For the human operator, on a tool with no agent support (Codex) — not an instruction to the model:** manually switch to the strong model in a **separate** chat/session from Step 2, paste PLAN.md, review using the Spec/Plan Review checklist in `.claude/agents/code-reviewer.md`.
147
147
 
148
148
  ---
149
149
 
@@ -1,7 +1,7 @@
1
1
  # /ukit:handoff-fullstack — Full Pipeline: Plan → Implement → Review
2
2
 
3
3
  **Role: ORCHESTRATOR**
4
- **Tool: any** (Claude Code / Codex / omp / Kilo / OpenCode — your choice)
4
+ **Tool: any** (Claude Code / Codex / omp / Kilo — your choice)
5
5
 
6
6
  ## Model Split
7
7
 
@@ -186,7 +186,7 @@ counter and starts a new cycle automatically.
186
186
 
187
187
  Return a compact summary. Do NOT write any files yet.
188
188
 
189
- > **For the human operator, on a tool with no agent support (Codex, OpenCode) — not an instruction to the model:** manually switch to the lite model and run P1 yourself.
189
+ > **For the human operator, on a tool with no agent support (Codex) — not an instruction to the model:** manually switch to the lite model and run P1 yourself.
190
190
 
191
191
  ### P2 — Write PLAN.md + task files (strong model)
192
192
 
@@ -270,7 +270,7 @@ is written. What it no longer does is hand a stalled plan back to a human who is
270
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.
271
271
  3. `Approved` → append `PLAN_REVIEW: Approved by <reviewer model>` to PLAN.md's `## Planner Report` footer, then proceed to P3.
272
272
 
273
- > **For the human operator, on a tool with no agent support (Codex, OpenCode) — not an instruction to the model:** manually switch to the strong model in a **separate** chat/session from P2, paste PLAN.md, review using the Spec/Plan Review checklist in `.claude/agents/code-reviewer.md`.
273
+ > **For the human operator, on a tool with no agent support (Codex) — not an instruction to the model:** manually switch to the strong model in a **separate** chat/session from P2, paste PLAN.md, review using the Spec/Plan Review checklist in `.claude/agents/code-reviewer.md`.
274
274
 
275
275
  ### P3 — Commit the plan (lite model)
276
276
 
@@ -281,7 +281,7 @@ git add docs/AI_HANDOFF/ && git commit -m "handoff: plan — <goal>"
281
281
 
282
282
  Replace `<goal>` with the one-sentence goal from ACTIVE.md. This locks the plan in git before any implementation begins.
283
283
 
284
- > **For the human operator, on a tool with no agent support (Codex, OpenCode) — not an instruction to the model:** manually switch to the lite model for P2/P3.
284
+ > **For the human operator, on a tool with no agent support (Codex) — not an instruction to the model:** manually switch to the lite model for P2/P3.
285
285
 
286
286
  ---
287
287
 
@@ -1,7 +1,7 @@
1
1
  # /ukit:handoff-implement — Phase 3: Execute
2
2
 
3
3
  **Role: EXECUTOR (orchestrated)**
4
- **Tool: any** (Claude Code / Codex / omp / Kilo / OpenCode — your choice)
4
+ **Tool: any** (Claude Code / Codex / omp / Kilo — your choice)
5
5
  **Model: code model** (Sonnet · unic-code · cheap-smart)
6
6
 
7
7
  > **omp model tiers:** the tiers above map to `.omp/config.yml`'s `modelRoles`, referenced from agent frontmatter as `@smol` / `@default` / `@slow`.
@@ -170,7 +170,7 @@ disk in the task file, which is where the reviewer reads them from. Pasting them
170
170
  second time is what blows up the orchestrator's context window and kills the run mid-wave.
171
171
  ```
172
172
 
173
- > **For the human operator, on a tool with no agent support (Codex, OpenCode) — not an instruction to the model:** open each task in a separate session with the code model.
173
+ > **For the human operator, on a tool with no agent support (Codex) — not an instruction to the model:** open each task in a separate session with the code model.
174
174
 
175
175
  ### 3c — Orchestrator: copy changes to main + IMMEDIATELY delete worktree
176
176
 
@@ -1,7 +1,7 @@
1
1
  # /ukit:handoff-review — Phase 4: Review
2
2
 
3
3
  **Role: REVIEWER**
4
- **Tool: any** (Claude Code / Codex / omp / Kilo / OpenCode — your choice)
4
+ **Tool: any** (Claude Code / Codex / omp / Kilo — your choice)
5
5
  **Model: strong model, MUST differ from executor** (Opus · unic-smart · strongest available)
6
6
 
7
7
  > **omp model tiers:** the tiers above map to `.omp/config.yml`'s `modelRoles`, referenced from agent frontmatter as `@smol` / `@default` / `@slow`.
@@ -191,4 +191,4 @@ This is the only place this command may ask for a compaction. All state lives in
191
191
  `INDEX.md` and `RUN.md`, so a compacted or fresh session resumes with nothing lost.
192
192
 
193
193
  > Orchestrator (this session) handles Step 1, 2e, and 3 directly — those are not delegated.
194
- > **For the human operator, on a tool with no agent support (Codex, OpenCode) — not an instruction to the model:** manually switch to the strong model, run 2a–2d per task (one session at a time).
194
+ > **For the human operator, on a tool with no agent support (Codex) — not an instruction to the model:** manually switch to the strong model, run 2a–2d per task (one session at a time).
@@ -36,18 +36,26 @@
36
36
  "Bash(git worktree:*)",
37
37
  "Bash(jq:*)"
38
38
  ],
39
- "ask": [
40
- "Bash(gh pr create:*)",
41
- "Bash(gh pr comment:*)",
42
- "Bash(gh issue comment:*)"
43
- ],
44
39
  "deny": [
45
40
  "Bash(git reset --hard:*)",
46
41
  "Bash(git push --force:*)",
42
+ "Bash(git push -f:*)",
47
43
  "Bash(git push --force-with-lease:*)",
44
+ "Bash(git clean -fd:*)",
45
+ "Bash(git checkout .:*)",
46
+ "Bash(git restore .:*)",
48
47
  "Bash(rm -rf /:*)",
49
48
  "Bash(rm -rf ~:*)",
50
- "Bash(rm -rf ..:*)"
49
+ "Bash(rm -rf ..:*)",
50
+ "Bash(rm -rf .:*)",
51
+ "Bash(rm -rf:*)",
52
+ "Bash(dd if=/dev/:*)",
53
+ "Bash(*> /dev/sda:*)",
54
+ "Bash(mkfs:*)",
55
+ "Bash(:(){ :|:& };::*)",
56
+ "Bash(gh pr create:*)",
57
+ "Bash(gh pr comment:*)",
58
+ "Bash(gh issue comment:*)"
51
59
  ]
52
60
  },
53
61
  "hooks": {
@@ -48,7 +48,7 @@ When updating meaningful state, update the header:
48
48
  ## Freshness
49
49
 
50
50
  - Last meaningful update: YYYY-MM-DD HH:mm
51
- - Updated by: Claude / Codex / OpenCode / human
51
+ - Updated by: Claude / Codex / human
52
52
  - Status confidence: high / medium / low
53
53
  - Stale after: 72h
54
54
  ```
@@ -816,7 +816,12 @@ async function main() {
816
816
  const lastPrompt = readFlagValue(args, '--last-prompt')
817
817
  ?? previousState?.routingContext?.lastExplicitUserPromptText
818
818
  ?? '';
819
- const adapter = readFlagValue(args, '--adapter') ?? inferAdapterFromInvocation(process.argv[1] || '');
819
+ const KNOWN_ADAPTERS = new Set(['claude', 'codex', 'antigravity']);
820
+ const rawAdapter = readFlagValue(args, '--adapter') ?? inferAdapterFromInvocation(process.argv[1] || '');
821
+ const adapter = KNOWN_ADAPTERS.has(rawAdapter) ? rawAdapter : 'claude';
822
+ if (rawAdapter !== adapter) {
823
+ console.error(`route-task: unknown --adapter "${rawAdapter}" — falling back to claude`);
824
+ }
820
825
  const commandNamespace = adapter === 'codex'
821
826
  ? '.codex'
822
827
  : (adapter === 'antigravity' ? '.antigravity' : '.claude');
@@ -826,7 +831,7 @@ async function main() {
826
831
  .trim();
827
832
 
828
833
  if (!promptText && !commandText && !targetFile) {
829
- console.error('Usage: node .codex/ukit/index/route-task.mjs "<prompt>" [--tool-command <cmd>] [--target <file>] [--type trivial|simple|non-trivial] [--session-id <id>] [--adapter codex|claude|antigravity|opencode]');
834
+ console.error('Usage: node .codex/ukit/index/route-task.mjs "<prompt>" [--tool-command <cmd>] [--target <file>] [--type trivial|simple|non-trivial] [--session-id <id>] [--adapter codex|claude|antigravity]');
830
835
  process.exitCode = 1;
831
836
  return;
832
837
  }
@@ -0,0 +1 @@
1
+ {"fingerprint":"routefp-v3:VKbWKdhO0GSCeYRnXINdC_39mkvjp8UX9-pC9T_y3yQ","ts":1789910232582,"source":"skill-router","activeSkills":[],"routingContext":{"lastExplicitUserPromptText":"","taskType":null,"intentMode":null,"executionMode":null}}
@@ -8,7 +8,7 @@ Auto-generated by UKit for OpenAI Codex.
8
8
  ## Core UKit Rule
9
9
 
10
10
  - Human-facing workflow should optimize for one remembered command: `ukit install`.
11
- - After install, normal work should feel natural inside **Claude/Codex/OpenCode/omp**.
11
+ - After install, normal work should feel natural inside **Claude/Codex/omp**.
12
12
  - **Quality first, then speed, then token discipline**: do not waste reads, logs, or repeated helper output.
13
13
  - **Never stop after read-only steps.** For implement/apply/fix requests, continue to actual Edit/Write and verification in the same turn.
14
14
 
@@ -233,7 +233,6 @@
233
233
  },
234
234
  "agentContext": {
235
235
  "preserveClaudePreCompact": true,
236
- "preserveOpenCodeCompaction": true,
237
236
  "codexSoftHandoff": {
238
237
  "autoCompact": true,
239
238
  "budgetTokens": 100000,
@@ -49,7 +49,7 @@ Systematic debugging — understand before fixing.
49
49
 
50
50
  ```
51
51
  STATUS: DONE | BLOCKED | PARTIAL
52
- EXECUTOR_TOOL: [claude-code | kilo-code | codex | opencode | other]
52
+ EXECUTOR_TOOL: [claude-code | kilo-code | codex | other]
53
53
  EXECUTOR_MODEL: [exact model name you are running as. "unknown" if you cannot tell.]
54
54
  EXECUTOR_SUBAGENT: [subagent name within your host, if any, else "-"]
55
55
  SUMMARY: [1-2 sentences — root cause and fix]
@@ -71,7 +71,7 @@ and even then, report it, don't ask about it.
71
71
 
72
72
  ```
73
73
  STATUS: DONE | BLOCKED | PARTIAL
74
- EXECUTOR_TOOL: [claude-code | kilo-code | codex | opencode | other]
74
+ EXECUTOR_TOOL: [claude-code | kilo-code | codex | other]
75
75
  EXECUTOR_MODEL: [exact model name you are running as — e.g. unic-code, claude-sonnet-4-5, gpt-5-mini. If you truly cannot tell, write "unknown" — reviewer treats unknown as suspicious and asks the human to confirm.]
76
76
  EXECUTOR_SUBAGENT: [name of the subagent you are, if your host has multiple — e.g. "Kilo:code", "Claude:feature-implementer". Otherwise "-".]
77
77
  SUMMARY: [1-2 sentences of what was implemented]
@@ -24,7 +24,7 @@ You are UKit's internal small-task maintainer. You run as a sidecar/parallel/non
24
24
  - Summarizing `docs/STATUS.md`, `docs/TASKS.md`, `docs/WORKLOG.md`, or memory snippets into compact handoff notes.
25
25
  - Deciding fast vs slow lane, safe vs risky lane, whether a skill should be activated, and whether the current prompt has enough steps planned.
26
26
  - Choosing compact-now vs compact-later and summarize-vs-keep-detail for UKit context hygiene.
27
- - Keeping agent context compact without removing existing lanes: Claude PreCompact/reinject stays active, OpenCode native auto/prune compaction stays active, and Codex Desktop soft handoffs use `compact.codexContext.compactTarget` (default 150 lines; preferred 120-150; hard max 170) while preserving critical state.
27
+ - Keeping agent context compact without removing existing lanes: Claude PreCompact/reinject stays active and Codex Desktop soft handoffs use `compact.codexContext.compactTarget` (default 150 lines; preferred 120-150; hard max 170) while preserving critical state.
28
28
  - Small, reversible UKit runtime maintenance decisions.
29
29
 
30
30
  ## Never Use For
@@ -45,7 +45,7 @@ You are UKit's internal small-task maintainer. You run as a sidecar/parallel/non
45
45
  5. If safe, make the smallest cleanup/summary/classification/routing decision.
46
46
  6. Follow the CoDev priority order: output quality first, then safety, then speed, then token discipline. Spend tokens/steps when they protect correctness.
47
47
  7. Use small step budgets: trivial = 1 step, simple = up to 2 steps, non-trivial = up to 4 planned steps before the main model reassesses.
48
- 8. For long sessions, preserve existing Claude/OpenCode auto-compact behavior; for Codex Desktop, target a compact handoff of about 120-150 lines, never above 170 lines for this lane, not a full transcript replay.
48
+ 8. For long sessions, preserve existing Claude auto-compact behavior; for Codex Desktop, target a compact handoff of about 120-150 lines, never above 170 lines for this lane, not a full transcript replay.
49
49
  9. Preserve user-owned content; do not delete unclear tasks.
50
50
  10. Report exactly what changed or what decision was made.
51
51
 
@@ -36,14 +36,17 @@ tools:
36
36
  # TASK-004 hook-bridge deny chain, and this approval map still gate dangerous
37
37
  # ops — `allow` removes interactive prompts, not the deny checks.
38
38
  # Residual risk: yolo + all-allow IS dangerously autonomous and is a
39
- # deliberate, user-chosen posture (unattended mode). `eval` stays `allow`
40
- # (was `prompt` pre-TASK-001): bash.patterns never covers the eval tool, but
41
- # the TASK-004 bridge maps `eval` -> `Bash`, so every eval call is gated by
42
- # the same block-dangerous chain as a bash command.
39
+ # deliberate, user-chosen posture (unattended mode). `eval` renders from the
40
+ # `orchestration.allowUnsafeEval` knob in .ukit/storage/config.json — `allow`
41
+ # only on explicit user opt-in, else `deny` (never `prompt`/`ask`: a prompt
42
+ # dead-ends under unattended). bash.patterns never covers the eval tool —
43
+ # eval payloads (os.remove, shutil.rmtree, subprocess) bypass the text deny —
44
+ # but the TASK-004 bridge maps `eval` -> `Bash`, so an opted-in `allow` is
45
+ # still gated by the same block-dangerous chain as a bash command.
43
46
  approvalMode: yolo
44
47
  approval:
45
48
  bash: allow
46
- eval: allow
49
+ eval: "{{omp.evalApproval}}"
47
50
  task: allow
48
51
  read: allow
49
52
  grep: allow
@@ -61,8 +64,9 @@ bash:
61
64
  # allow is NOT a universal escape hatch: any compound command (`&&`, `;`, `|`) not itself caught
62
65
  # by a `deny`/`prompt` entry falls through to `tools.approvalMode` instead of being auto-allowed.
63
66
  # `bash.patterns` does not cover the `eval` tool at all — that is why TASK-004's bridge
64
- # separately maps `eval` -> `Bash`, and why `tools.approval.eval: allow` above does NOT
65
- # weaken eval safety: eval stays gated by the bridge's Bash chain (TASK-001).
67
+ # separately maps `eval` -> `Bash`, and why an opted-in `tools.approval.eval: allow`
68
+ # (orchestration.allowUnsafeEval) does NOT weaken eval safety: eval stays gated by
69
+ # the bridge's Bash chain (TASK-001). Default render is `deny` (fail-closed).
66
70
  patterns:
67
71
  - match: "rm -rf /*"
68
72
  approval: deny
@@ -74,6 +78,8 @@ bash:
74
78
  approval: deny
75
79
  - match: "git push --force*"
76
80
  approval: deny
81
+ - match: "git push --force-with-lease*"
82
+ approval: deny
77
83
  - match: "git push -f *"
78
84
  approval: deny
79
85
  - match: "git reset --hard*"
@@ -0,0 +1,4 @@
1
+ {"v":1,"ts":1789910222307,"hook":"hook-chain-runner","elapsedMs":2,"outcome":"error","hookEvent":"PreToolUse","toolName":"Edit","toolUseId":null,"budgetMs":28000,"budgetExhausted":false,"scripts":[{"scriptName":"protect-files.sh","code":1,"killed":false,"failureKind":"error","elapsedMs":2}]}
2
+ {"v":1,"ts":1789910232531,"hookEvent":"PreToolUse","toolName":"Edit","toolUseId":null,"hook":"protect-files.sh","elapsedMs":29,"outcome":"ok"}
3
+ {"v":1,"ts":1789910232602,"hookEvent":"PreToolUse","toolName":"Edit","toolUseId":null,"hook":"skill-router.sh","elapsedMs":44,"outcome":"ok"}
4
+ {"v":1,"ts":1789910232609,"hook":"hook-chain-runner","elapsedMs":580,"outcome":"ok","hookEvent":"PreToolUse","toolName":"Edit","toolUseId":null,"budgetMs":28000,"budgetExhausted":false,"scripts":[{"scriptName":"protect-files.sh","code":0,"killed":false,"failureKind":"ok","elapsedMs":510},{"scriptName":"skill-router.sh","code":0,"killed":false,"failureKind":"ok","elapsedMs":70}]}