@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.
- package/CHANGELOG.md +72 -0
- package/README.md +1 -3
- package/manifests/hostCapabilities.yaml +0 -10
- package/manifests/instructionRules.yaml +0 -16
- package/manifests/platform.full.yaml +2 -16
- package/package.json +3 -5
- package/scripts/perf/audit-perf.mjs +4 -4
- package/scripts/perf/perf-findings.json +420 -0
- package/src/cli/adapters.js +10 -13
- package/src/cli/commands/doctor.js +60 -10
- package/src/cli/index.js +2 -2
- package/src/core/ompConfigMerge.js +19 -0
- package/src/core/opencodeSteer.js +37 -0
- package/src/core/permissionDoctor.js +319 -0
- package/src/core/permissionPolicy.js +179 -0
- package/src/core/projectImportant.js +14 -8
- package/src/core/runInstallPipeline.js +25 -0
- package/src/core/runtimeConfig.js +43 -4
- package/src/core/status.js +6 -4
- package/src/core/unattendedDoctor.js +9 -5
- package/src/manifest/selectItems.js +0 -1
- package/src/render/buildVariables.js +10 -1
- package/templates/.claude/agents/bug-debugger.md +1 -1
- package/templates/.claude/agents/feature-implementer.md +1 -1
- package/templates/.claude/agents/ukit-small-task-maintainer.md +2 -2
- package/templates/.claude/commands/ukit/handoff-create.md +4 -4
- package/templates/.claude/commands/ukit/handoff-fullstack.md +4 -4
- package/templates/.claude/commands/ukit/handoff-implement.md +2 -2
- package/templates/.claude/commands/ukit/handoff-review.md +2 -2
- package/templates/.claude/settings.json +14 -6
- package/templates/.claude/skills/update-status/SKILL.md +1 -1
- package/templates/.claude/ukit/index/route-task.mjs +7 -2
- package/templates/.claude/ukit/skill-router-state.json +1 -0
- package/templates/.codex/README.md +1 -1
- package/templates/.codex/settings.json +0 -1
- package/templates/.omp/agents/bug-debugger.md +1 -1
- package/templates/.omp/agents/feature-implementer.md +1 -1
- package/templates/.omp/agents/ukit-small-task-maintainer.md +2 -2
- package/templates/.omp/config.yml +13 -7
- package/templates/.ukit/storage/cache/hook-latency/unknown.jsonl +4 -0
- package/templates/AGENTS.md +4 -13
- package/templates/CLAUDE.md +1 -3
- package/templates/adapter-presets/claude/settings.local.json +0 -1
- package/templates/docs/AI_HANDOFF/INDEX.md +1 -1
- package/templates/docs/AI_HANDOFF/RULES.md +2 -2
- package/templates/docs/PROJECT.md +1 -1
- package/templates/docs/UKIT_INTERNALS.md +1 -1
- package/templates/instructions/core.md +1 -3
- package/templates/instructions/layout.yaml +2 -4
- package/templates/instructions/overlays/agents.md +3 -10
- package/templates/ukit/storage/config.json +2 -10
- 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
|
-
|
|
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}`] : []),
|
package/src/core/status.js
CHANGED
|
@@ -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
|
|
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
|
-
|
|
169
|
-
omp.exists && approvalMap.
|
|
172
|
+
'eval/bash policy matches compiled posture',
|
|
173
|
+
omp.exists && approvalMap.bash === 'allow' && approvalMap.eval === expectedEval,
|
|
170
174
|
'install-repairable',
|
|
171
|
-
|
|
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
|
|
@@ -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 |
|
|
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 |
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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 /
|
|
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
|
|
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
|
|
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/
|
|
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
|
|
|
@@ -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 |
|
|
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 |
|
|
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
|
|
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
|
|
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`
|
|
40
|
-
#
|
|
41
|
-
#
|
|
42
|
-
#
|
|
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:
|
|
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`
|
|
65
|
-
# weaken eval safety: eval stays gated by
|
|
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}]}
|