@ngockhoale/ukit 1.1.8 → 1.2.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +18 -0
- package/README.md +9 -5
- package/manifests/platform.full.yaml +28 -0
- package/package.json +1 -1
- package/src/cli/commands/doctor.js +2 -0
- package/src/cli/commands/install.js +8 -7
- package/src/cli/commands/uninstall.js +1 -1
- package/src/core/ensureGitignore.js +2 -0
- package/src/core/runInstallPipeline.js +6 -1
- package/src/core/runtimeConfig.js +147 -3
- package/src/core/uninstall.js +1 -1
- package/src/index/routeCatalog.js +4 -4
- package/src/index/taskRouting.js +35 -9
- package/templates/.claude/agents/ukit-small-task-maintainer.md +72 -0
- package/templates/.claude/skills/docs-quality/SKILL.md +15 -1
- package/templates/.claude/skills/next-step/SKILL.md +13 -5
- package/templates/.claude/skills/update-status/SKILL.md +16 -2
- package/templates/.claude/ukit/index/route-catalog.mjs +4 -4
- package/templates/.claude/ukit/index/route-task.mjs +35 -9
- package/templates/.codex/README.md +5 -2
- package/templates/.codex/settings.json +125 -4
- package/templates/.gitignore +2 -1
- package/templates/AGENTS.md +13 -3
- package/templates/CLAUDE.md +13 -3
- package/templates/docs/INSTALL.md +2 -0
- package/templates/docs/PROJECT.md +5 -4
- package/templates/docs/STATUS.md +1 -1
- package/templates/docs/TASKS.md +79 -0
- package/templates/docs/UKIT_USAGE_GUIDE.md +4 -3
- package/templates/ukit/README.md +1 -1
- package/templates/ukit/storage/config.json +243 -2
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,24 @@ All notable changes to UKit are documented here.
|
|
|
4
4
|
|
|
5
5
|
## Unreleased
|
|
6
6
|
|
|
7
|
+
## 1.2.2 - 2026-05-05
|
|
8
|
+
|
|
9
|
+
- Removed the `.claude/ukit/.env` / `.env.example` config surface so UKit runtime tuning lives in `.ukit/storage/config.json` only.
|
|
10
|
+
- Added full Vietnamese `_help` guidance inside `templates/ukit/storage/config.json`, including common edits for changing the internal model and Codex compact thresholds.
|
|
11
|
+
- Set Codex Desktop compact budget default to 100,000 tokens as a quality/safety balance, with compact handoff target 150 lines and hard max 170 lines.
|
|
12
|
+
- Preserved Claude PreCompact/reinject and OpenCode native auto/prune compaction while keeping `ukit-small-task-maintainer` as a non-blocking sidecar lane.
|
|
13
|
+
|
|
14
|
+
## 1.2.1 - 2026-05-05
|
|
15
|
+
|
|
16
|
+
### Local AI Task Queue
|
|
17
|
+
|
|
18
|
+
- Added gitignored `docs/TASKS.md` as a local AI task queue for deferred work that one AI can write and another AI/agent can implement later.
|
|
19
|
+
- Installed `docs/TASKS.md` with skip merge semantics so end-user task notes are never overwritten or uninstall-owned.
|
|
20
|
+
- Added safe default cleanup rules: remove exact duplicates, prune `Done Recently` to 10 compact lines, move stale/vague work to deferred review, and never delete unfinished human-authored tasks unless explicitly asked and clearly obsolete/duplicated.
|
|
21
|
+
- Updated `next-step`, `update-status`, and `docs-quality` guidance plus route signals so queued-task selection goes to `next-step` while task cleanup/editing goes to `docs-quality`.
|
|
22
|
+
- Added `docs/STATUS.md` and `docs/TASKS.md` to generated gitignore handling so living local AI state does not pollute project commits.
|
|
23
|
+
- Added the internal `ukit-small-task-maintainer` subagent, powered by optional `subagents.smallTaskModel=unic-lite`, for safe UKit decisions such as task cleanup, compact decisions, doc summarization, classification, auto-triage, queue maintenance, and small reversible cleanup while keeping risky/security/release work on the main model.
|
|
24
|
+
|
|
7
25
|
## 1.1.8 - 2026-05-05
|
|
8
26
|
|
|
9
27
|
### Living Project Status
|
package/README.md
CHANGED
|
@@ -33,6 +33,7 @@ ukit install
|
|
|
33
33
|
- `docs/PROJECT.md`
|
|
34
34
|
- `docs/MEMORY.md`
|
|
35
35
|
- `docs/STATUS.md`
|
|
36
|
+
- `docs/TASKS.md`
|
|
36
37
|
- `docs/WORKLOG.md`
|
|
37
38
|
5. Open your AI tool and work in natural language.
|
|
38
39
|
|
|
@@ -65,13 +66,13 @@ If maintainers roll out a newer CLI build, the in-project workflow still stays t
|
|
|
65
66
|
**Project support files**
|
|
66
67
|
- `.claude/ukit/.ukit/` — installer manifests, metadata, backups
|
|
67
68
|
- `.ukit/` — hidden shared runtime storage for config, cache, and cross-agent memory
|
|
68
|
-
- `docs/` — PROJECT / MEMORY / STATUS / WORKLOG baseline
|
|
69
|
+
- `docs/` — PROJECT / MEMORY / STATUS / TASKS / WORKLOG baseline
|
|
69
70
|
|
|
70
|
-
## UKit v1.
|
|
71
|
+
## UKit v1.2.2 Runtime
|
|
71
72
|
|
|
72
73
|
UKit now installs a hidden shared local runtime at `.ukit/` for production-oriented state that should survive across agent sessions:
|
|
73
74
|
|
|
74
|
-
- `.ukit/storage/config.json` — runtime defaults for compact/router/memory/validation
|
|
75
|
+
- `.ukit/storage/config.json` — runtime defaults for compact/router/memory/validation/subagent hints
|
|
75
76
|
- `.ukit/storage/cache/` — reusable prompt-cache, compact history, compact-pressure state, and output summaries
|
|
76
77
|
- `.ukit/storage/memory/` — cross-agent local memory
|
|
77
78
|
|
|
@@ -84,17 +85,18 @@ When long sessions approach the compact threshold, UKit now uses a conservative
|
|
|
84
85
|
- compact only safe-zone history/noise
|
|
85
86
|
- preserve active task, rules, decisions, and current code focus
|
|
86
87
|
|
|
87
|
-
UKit v1.
|
|
88
|
+
UKit v1.2.2 keeps the same shared runtime contract while adding a local AI task queue alongside living project status routing:
|
|
88
89
|
|
|
89
90
|
- install globally with `npm install -g @ngockhoale/ukit`
|
|
90
91
|
- keep using the exact same human workflow inside projects: `ukit install`
|
|
91
92
|
- preserve the same `ukit` binary, hooks, and install-first orchestration while standardizing the runtime root as hidden `.ukit/`
|
|
92
93
|
- install `docs/STATUS.md` as a compact living state file for active work, debug threads, blockers, verification, and next candidates
|
|
94
|
+
- install gitignored `docs/TASKS.md` as a local AI task queue for deferred/ready work, with safe AI cleanup rules that remove duplicates and prune completed history without deleting unfinished human intent
|
|
93
95
|
- auto-route open-ended “what next?” / “continue” prompts to the `next-step` skill with a visible freshness cue when status may be stale
|
|
94
96
|
- auto-route explicit handoff/wrap-up requests to the `update-status` skill while skipping trivial/no-state-change tasks
|
|
95
97
|
- keep concrete debug/implementation/review prompts primary, so project status never replaces source/index-first task work
|
|
96
98
|
|
|
97
|
-
UKit v1.
|
|
99
|
+
UKit v1.2.2 also keeps the fast path improvements from the recent runtime releases:
|
|
98
100
|
|
|
99
101
|
- Vietnamese prompts now normalize more effectively for English-heavy code symbols and paths
|
|
100
102
|
- localized simple direct-target lanes skip extra previous-context / recent-output work when it would not change the next action
|
|
@@ -129,6 +131,8 @@ UKit is built so the team can stop memorizing UKit subcommands and focus on prod
|
|
|
129
131
|
- let Claude Code / Codex / OpenCode auto-detect and use the right project-local skill from the prompt and the files/tools involved
|
|
130
132
|
- let the AI prefer targeted verification first, then widen only when shared/risky scope justifies it
|
|
131
133
|
- let the AI selectively auto-delegate internal subagents only when that actually reduces context/noise or unlocks parallel progress; small localized work should stay direct
|
|
134
|
+
- keep long sessions compact across agents: Claude keeps PreCompact/reinject, OpenCode keeps native auto/prune compaction, and Codex Desktop uses internal `compact.codexContext.compactTarget` soft handoffs (default 150 lines; 120-150 preferred, hard max 170), without asking end users to manage context manually
|
|
135
|
+
- let UKit internally use the `ukit-small-task-maintainer` subagent with `subagents.smallTaskModel=unic-lite` for safe task cleanup, fast-vs-slow/safe-vs-risky lane hints, skill-routing/step-budget hints, agent context-budget decisions, compact decisions, doc summarization, classification, and queue maintenance while risky/security/release/quality-risk work stays on the main model
|
|
132
136
|
- rerun `ukit install` when you need to refresh the workspace
|
|
133
137
|
|
|
134
138
|
End users should **not** need to know or memorize skill names.
|
|
@@ -159,6 +159,19 @@ items:
|
|
|
159
159
|
packs:
|
|
160
160
|
- core
|
|
161
161
|
|
|
162
|
+
- id: docs-tasks
|
|
163
|
+
type: config
|
|
164
|
+
sourceTemplate: docs/TASKS.md
|
|
165
|
+
targetPath: docs/TASKS.md
|
|
166
|
+
requires:
|
|
167
|
+
- docs-status
|
|
168
|
+
mergeStrategy: skip
|
|
169
|
+
variables:
|
|
170
|
+
- project.name
|
|
171
|
+
enabledByDefault: true
|
|
172
|
+
packs:
|
|
173
|
+
- core
|
|
174
|
+
|
|
162
175
|
- id: docs-bugfix
|
|
163
176
|
type: config
|
|
164
177
|
sourceTemplate: docs/BUGFIX.md
|
|
@@ -483,6 +496,7 @@ items:
|
|
|
483
496
|
requires:
|
|
484
497
|
- docs-quality-skill
|
|
485
498
|
- docs-status
|
|
499
|
+
- docs-tasks
|
|
486
500
|
mergeStrategy: overwrite_with_backup
|
|
487
501
|
variables: []
|
|
488
502
|
enabledByDefault: true
|
|
@@ -496,6 +510,7 @@ items:
|
|
|
496
510
|
requires:
|
|
497
511
|
- docs-quality-skill
|
|
498
512
|
- docs-status
|
|
513
|
+
- docs-tasks
|
|
499
514
|
mergeStrategy: overwrite_with_backup
|
|
500
515
|
variables: []
|
|
501
516
|
enabledByDefault: true
|
|
@@ -792,6 +807,19 @@ items:
|
|
|
792
807
|
packs:
|
|
793
808
|
- core
|
|
794
809
|
|
|
810
|
+
- id: agent-ukit-small-task-maintainer
|
|
811
|
+
type: agent
|
|
812
|
+
sourceTemplate: .claude/agents/ukit-small-task-maintainer.md
|
|
813
|
+
targetPath: .claude/agents/ukit-small-task-maintainer.md
|
|
814
|
+
requires:
|
|
815
|
+
- runtime-config
|
|
816
|
+
mergeStrategy: overwrite_with_backup
|
|
817
|
+
variables:
|
|
818
|
+
- ukit.version
|
|
819
|
+
enabledByDefault: true
|
|
820
|
+
packs:
|
|
821
|
+
- core
|
|
822
|
+
|
|
795
823
|
- id: provider-compat-config
|
|
796
824
|
type: config
|
|
797
825
|
sourceTemplate: .claude/config/providers.md
|
package/package.json
CHANGED
|
@@ -63,6 +63,7 @@ export async function runDoctor({ packageRoot, projectRoot, argv = [] }) {
|
|
|
63
63
|
docsProjectExists: await pathExists(path.join(projectRoot, 'docs', 'PROJECT.md')),
|
|
64
64
|
docsMemoryExists: await pathExists(path.join(projectRoot, 'docs', 'MEMORY.md')),
|
|
65
65
|
docsStatusExists: await pathExists(path.join(projectRoot, 'docs', 'STATUS.md')),
|
|
66
|
+
docsTasksExists: await pathExists(path.join(projectRoot, 'docs', 'TASKS.md')),
|
|
66
67
|
docsWorklogExists: await pathExists(path.join(projectRoot, 'docs', 'WORKLOG.md')),
|
|
67
68
|
allProvidersConfigured: providers.allSupported,
|
|
68
69
|
...(codexAdapterTracked
|
|
@@ -105,6 +106,7 @@ export async function runDoctor({ packageRoot, projectRoot, argv = [] }) {
|
|
|
105
106
|
console.log(`[UKit] ${ok(checks.docsProjectExists)} docs/PROJECT.md`);
|
|
106
107
|
console.log(`[UKit] ${ok(checks.docsMemoryExists)} docs/MEMORY.md`);
|
|
107
108
|
console.log(`[UKit] ${ok(checks.docsStatusExists)} docs/STATUS.md`);
|
|
109
|
+
console.log(`[UKit] ${ok(checks.docsTasksExists)} docs/TASKS.md`);
|
|
108
110
|
console.log(`[UKit] ${ok(checks.docsWorklogExists)} docs/WORKLOG.md`);
|
|
109
111
|
if (codexAdapterTracked) {
|
|
110
112
|
console.log(`[UKit] ${ok(checks.codexReadmeExists)} .codex/README.md`);
|
|
@@ -241,13 +241,14 @@ export async function runInstall({ packageRoot, projectRoot, packageVersion, arg
|
|
|
241
241
|
.join(', ');
|
|
242
242
|
console.log(`[UKit] Providers: ${providerStatus}, all=${result.providerContext.allSupported}`);
|
|
243
243
|
|
|
244
|
-
const
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
244
|
+
const docsLabels = [
|
|
245
|
+
'docs/PROJECT.md',
|
|
246
|
+
'docs/MEMORY.md',
|
|
247
|
+
'docs/STATUS.md',
|
|
248
|
+
'docs/TASKS.md',
|
|
249
|
+
'docs/WORKLOG.md',
|
|
249
250
|
];
|
|
250
|
-
const
|
|
251
|
+
const docsPaths = docsLabels.map((label) => path.join(projectRoot, ...label.split('/')));
|
|
251
252
|
const missingDocs = [];
|
|
252
253
|
for (let i = 0; i < docsPaths.length; i++) {
|
|
253
254
|
if (!(await pathExists(docsPaths[i]))) {
|
|
@@ -258,7 +259,7 @@ export async function runInstall({ packageRoot, projectRoot, packageVersion, arg
|
|
|
258
259
|
if (missingDocs.length > 0) {
|
|
259
260
|
console.log(`[UKit] Missing docs — fill these in before first use: ${missingDocs.join(', ')}`);
|
|
260
261
|
} else {
|
|
261
|
-
console.log('[UKit] Docs baseline ready: docs/PROJECT.md, docs/MEMORY.md, docs/STATUS.md, docs/WORKLOG.md');
|
|
262
|
+
console.log('[UKit] Docs baseline ready: docs/PROJECT.md, docs/MEMORY.md, docs/STATUS.md, docs/TASKS.md, docs/WORKLOG.md');
|
|
262
263
|
console.log('[UKit] Fill them once with real project context for the best results.');
|
|
263
264
|
}
|
|
264
265
|
|
|
@@ -47,5 +47,5 @@ export async function runUninstall({ projectRoot, argv = [] }) {
|
|
|
47
47
|
}
|
|
48
48
|
|
|
49
49
|
console.log(`[UKit] Uninstall complete. Removed ${result.removed}/${result.attempted} managed paths.`);
|
|
50
|
-
console.log('[UKit] Note: docs/PROJECT.md, docs/MEMORY.md, docs/STATUS.md, docs/WORKLOG.md contain user content and were preserved. Delete manually if needed.');
|
|
50
|
+
console.log('[UKit] Note: docs/PROJECT.md, docs/MEMORY.md, docs/STATUS.md, docs/TASKS.md, docs/WORKLOG.md contain user content and were preserved. Delete manually if needed.');
|
|
51
51
|
}
|
|
@@ -18,6 +18,10 @@ const AUTO_PRUNE_OBSOLETE_PREFIXES = [
|
|
|
18
18
|
'.claude/skills/',
|
|
19
19
|
];
|
|
20
20
|
|
|
21
|
+
const AUTO_PRUNE_OBSOLETE_PATHS = new Set([
|
|
22
|
+
'.claude/ukit/.env.example',
|
|
23
|
+
]);
|
|
24
|
+
|
|
21
25
|
function normalizeRelativePath(relativePath) {
|
|
22
26
|
if (typeof relativePath !== 'string') {
|
|
23
27
|
return null;
|
|
@@ -49,7 +53,8 @@ function buildTrackedManagedRelativePathSet(plan, projectRoot) {
|
|
|
49
53
|
}
|
|
50
54
|
|
|
51
55
|
function shouldAutoPruneObsoletePath(relativePath) {
|
|
52
|
-
return
|
|
56
|
+
return AUTO_PRUNE_OBSOLETE_PATHS.has(relativePath)
|
|
57
|
+
|| AUTO_PRUNE_OBSOLETE_PREFIXES.some((prefix) => relativePath.startsWith(prefix));
|
|
53
58
|
}
|
|
54
59
|
|
|
55
60
|
async function isDirEmpty(dirPath) {
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import fs from 'node:fs/promises';
|
|
2
|
+
import path from 'node:path';
|
|
2
3
|
import { buildRuntimePaths } from './runtimePaths.js';
|
|
3
4
|
|
|
4
5
|
const VALID_AGENTS = new Set(['claude-code', 'codex', 'antigravity', 'opencode']);
|
|
@@ -38,17 +39,61 @@ function pushPositiveNumberError(errors, value, label) {
|
|
|
38
39
|
}
|
|
39
40
|
}
|
|
40
41
|
|
|
42
|
+
function pushNonEmptyStringError(errors, value, label) {
|
|
43
|
+
if (typeof value !== 'string' || value.trim() === '') {
|
|
44
|
+
errors.push(`${label} must be a non-empty string.`);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
41
48
|
export function buildDefaultRuntimeConfig(overrides = {}) {
|
|
42
49
|
const safeOverrides = isPlainObject(overrides) ? overrides : {};
|
|
43
50
|
|
|
44
51
|
return mergeObjects({
|
|
45
|
-
version: '1.
|
|
52
|
+
version: '1.2.2',
|
|
46
53
|
agent: 'claude-code',
|
|
47
54
|
compact: {
|
|
48
55
|
enabled: true,
|
|
49
56
|
tokenThreshold: 100_000,
|
|
50
57
|
contextRotDetection: true,
|
|
51
58
|
askBeforeDrop: true,
|
|
59
|
+
agentContext: {
|
|
60
|
+
enabled: true,
|
|
61
|
+
decisionAgent: 'ukit-small-task-maintainer',
|
|
62
|
+
executionMode: 'sidecar-parallel',
|
|
63
|
+
mustNotBlockMainTask: true,
|
|
64
|
+
targets: {
|
|
65
|
+
claude: { autoCompact: true, mode: 'precompact-reinject', preserveExistingHooks: true },
|
|
66
|
+
opencode: { autoCompact: true, mode: 'native-auto-prune', preserveExistingCompaction: true },
|
|
67
|
+
codex: {
|
|
68
|
+
autoCompact: true,
|
|
69
|
+
mode: 'soft-handoff',
|
|
70
|
+
compactTarget: 150,
|
|
71
|
+
compactTargetUnit: 'lines',
|
|
72
|
+
compactTargetRecommendedRange: [120, 170],
|
|
73
|
+
compactTargetMax: 170,
|
|
74
|
+
},
|
|
75
|
+
},
|
|
76
|
+
},
|
|
77
|
+
codexContext: {
|
|
78
|
+
enabled: true,
|
|
79
|
+
autoCompact: true,
|
|
80
|
+
budgetTokens: 100_000,
|
|
81
|
+
compactTarget: 150,
|
|
82
|
+
compactTargetUnit: 'lines',
|
|
83
|
+
compactTargetRecommendedRange: [120, 170],
|
|
84
|
+
compactTargetMax: 170,
|
|
85
|
+
decisionAgent: 'ukit-small-task-maintainer',
|
|
86
|
+
mode: 'soft-handoff',
|
|
87
|
+
preserve: [
|
|
88
|
+
'current-goal',
|
|
89
|
+
'non-negotiable-rules',
|
|
90
|
+
'active-files',
|
|
91
|
+
'decisions',
|
|
92
|
+
'unresolved-failures',
|
|
93
|
+
'verification-evidence',
|
|
94
|
+
'next-actions',
|
|
95
|
+
],
|
|
96
|
+
},
|
|
52
97
|
},
|
|
53
98
|
tokenPipeline: {
|
|
54
99
|
inputCompression: true,
|
|
@@ -77,6 +122,53 @@ export function buildDefaultRuntimeConfig(overrides = {}) {
|
|
|
77
122
|
maxRetries: 1,
|
|
78
123
|
confidenceThreshold: 50,
|
|
79
124
|
},
|
|
125
|
+
subagents: {
|
|
126
|
+
enabled: true,
|
|
127
|
+
smallTaskModel: 'unic-lite',
|
|
128
|
+
smallTaskAgent: 'ukit-small-task-maintainer',
|
|
129
|
+
smallTaskUseCases: [
|
|
130
|
+
'task-cleanup',
|
|
131
|
+
'compact-decision',
|
|
132
|
+
'codex-context-budget',
|
|
133
|
+
'doc-summarization',
|
|
134
|
+
'classification',
|
|
135
|
+
'small-decision',
|
|
136
|
+
'auto-triage',
|
|
137
|
+
'queue-maintenance',
|
|
138
|
+
'workspace-maintenance',
|
|
139
|
+
],
|
|
140
|
+
keepMainModelFor: [
|
|
141
|
+
'security',
|
|
142
|
+
'risky-code-change',
|
|
143
|
+
'release',
|
|
144
|
+
'data-loss',
|
|
145
|
+
'architecture-decision',
|
|
146
|
+
'deep-reasoning',
|
|
147
|
+
],
|
|
148
|
+
decisionPolicy: {
|
|
149
|
+
nonBlocking: true,
|
|
150
|
+
endUserInvisible: true,
|
|
151
|
+
handBackOnRisk: true,
|
|
152
|
+
executionMode: 'sidecar-parallel',
|
|
153
|
+
mustNotBlockMainTask: true,
|
|
154
|
+
maxSidecarWaitMs: 0,
|
|
155
|
+
optimizeOrder: ['quality', 'safety', 'speed', 'token-discipline'],
|
|
156
|
+
decisions: [
|
|
157
|
+
'fast-vs-slow-lane',
|
|
158
|
+
'safe-vs-risky-lane',
|
|
159
|
+
'skill-routing-needed',
|
|
160
|
+
'step-budget-enough',
|
|
161
|
+
'compact-now-or-later',
|
|
162
|
+
'codex-context-budget',
|
|
163
|
+
'summarize-docs-or-keep-detail',
|
|
164
|
+
],
|
|
165
|
+
stepBudgets: {
|
|
166
|
+
trivial: { maxSteps: 1, verification: 'skip-unless-risky' },
|
|
167
|
+
simple: { maxSteps: 2, verification: 'targeted-if-covered' },
|
|
168
|
+
nonTrivial: { maxSteps: 4, verification: 'targeted-then-widen-on-risk' },
|
|
169
|
+
},
|
|
170
|
+
},
|
|
171
|
+
},
|
|
80
172
|
}, safeOverrides);
|
|
81
173
|
}
|
|
82
174
|
|
|
@@ -102,6 +194,38 @@ export function validateRuntimeConfig(config) {
|
|
|
102
194
|
pushPositiveNumberError(errors, config.compact.tokenThreshold, 'compact.tokenThreshold');
|
|
103
195
|
pushBooleanError(errors, config.compact.contextRotDetection, 'compact.contextRotDetection');
|
|
104
196
|
pushBooleanError(errors, config.compact.askBeforeDrop, 'compact.askBeforeDrop');
|
|
197
|
+
if (!isPlainObject(config.compact.agentContext)) {
|
|
198
|
+
errors.push('compact.agentContext must be an object.');
|
|
199
|
+
} else {
|
|
200
|
+
pushBooleanError(errors, config.compact.agentContext.enabled, 'compact.agentContext.enabled');
|
|
201
|
+
pushNonEmptyStringError(errors, config.compact.agentContext.decisionAgent, 'compact.agentContext.decisionAgent');
|
|
202
|
+
pushNonEmptyStringError(errors, config.compact.agentContext.executionMode, 'compact.agentContext.executionMode');
|
|
203
|
+
pushBooleanError(errors, config.compact.agentContext.mustNotBlockMainTask, 'compact.agentContext.mustNotBlockMainTask');
|
|
204
|
+
if (!isPlainObject(config.compact.agentContext.targets)) {
|
|
205
|
+
errors.push('compact.agentContext.targets must be an object.');
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
if (!isPlainObject(config.compact.codexContext)) {
|
|
209
|
+
errors.push('compact.codexContext must be an object.');
|
|
210
|
+
} else {
|
|
211
|
+
pushBooleanError(errors, config.compact.codexContext.enabled, 'compact.codexContext.enabled');
|
|
212
|
+
pushBooleanError(errors, config.compact.codexContext.autoCompact, 'compact.codexContext.autoCompact');
|
|
213
|
+
pushPositiveNumberError(errors, config.compact.codexContext.budgetTokens, 'compact.codexContext.budgetTokens');
|
|
214
|
+
pushPositiveNumberError(errors, config.compact.codexContext.compactTarget, 'compact.codexContext.compactTarget');
|
|
215
|
+
pushNonEmptyStringError(errors, config.compact.codexContext.compactTargetUnit, 'compact.codexContext.compactTargetUnit');
|
|
216
|
+
if (!Array.isArray(config.compact.codexContext.compactTargetRecommendedRange)) {
|
|
217
|
+
errors.push('compact.codexContext.compactTargetRecommendedRange must be an array.');
|
|
218
|
+
}
|
|
219
|
+
pushPositiveNumberError(errors, config.compact.codexContext.compactTargetMax, 'compact.codexContext.compactTargetMax');
|
|
220
|
+
if (Number(config.compact.codexContext.compactTarget) > Number(config.compact.codexContext.compactTargetMax)) {
|
|
221
|
+
errors.push('compact.codexContext.compactTarget must be <= compact.codexContext.compactTargetMax.');
|
|
222
|
+
}
|
|
223
|
+
pushNonEmptyStringError(errors, config.compact.codexContext.decisionAgent, 'compact.codexContext.decisionAgent');
|
|
224
|
+
pushNonEmptyStringError(errors, config.compact.codexContext.mode, 'compact.codexContext.mode');
|
|
225
|
+
if (!Array.isArray(config.compact.codexContext.preserve)) {
|
|
226
|
+
errors.push('compact.codexContext.preserve must be an array.');
|
|
227
|
+
}
|
|
228
|
+
}
|
|
105
229
|
}
|
|
106
230
|
|
|
107
231
|
if (!isPlainObject(config.tokenPipeline)) {
|
|
@@ -147,6 +271,23 @@ export function validateRuntimeConfig(config) {
|
|
|
147
271
|
pushPositiveNumberError(errors, config.validation.confidenceThreshold, 'validation.confidenceThreshold');
|
|
148
272
|
}
|
|
149
273
|
|
|
274
|
+
if (!isPlainObject(config.subagents)) {
|
|
275
|
+
errors.push('subagents must be an object.');
|
|
276
|
+
} else {
|
|
277
|
+
pushBooleanError(errors, config.subagents.enabled, 'subagents.enabled');
|
|
278
|
+
pushNonEmptyStringError(errors, config.subagents.smallTaskModel, 'subagents.smallTaskModel');
|
|
279
|
+
pushNonEmptyStringError(errors, config.subagents.smallTaskAgent, 'subagents.smallTaskAgent');
|
|
280
|
+
if (!Array.isArray(config.subagents.smallTaskUseCases)) {
|
|
281
|
+
errors.push('subagents.smallTaskUseCases must be an array.');
|
|
282
|
+
}
|
|
283
|
+
if (!Array.isArray(config.subagents.keepMainModelFor)) {
|
|
284
|
+
errors.push('subagents.keepMainModelFor must be an array.');
|
|
285
|
+
}
|
|
286
|
+
if (!isPlainObject(config.subagents.decisionPolicy)) {
|
|
287
|
+
errors.push('subagents.decisionPolicy must be an object.');
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
|
|
150
291
|
return {
|
|
151
292
|
valid: errors.length === 0,
|
|
152
293
|
errors,
|
|
@@ -170,9 +311,12 @@ export async function inspectRuntimeConfig(projectRoot) {
|
|
|
170
311
|
}
|
|
171
312
|
}
|
|
172
313
|
|
|
173
|
-
const config = buildDefaultRuntimeConfig(rawConfig);
|
|
314
|
+
const config = buildDefaultRuntimeConfig(rawConfig ?? {});
|
|
174
315
|
const validation = validateRuntimeConfig(config);
|
|
175
|
-
const errors =
|
|
316
|
+
const errors = [
|
|
317
|
+
...(parseError ? [`config.json parse error: ${parseError}`] : []),
|
|
318
|
+
...validation.errors,
|
|
319
|
+
];
|
|
176
320
|
|
|
177
321
|
return {
|
|
178
322
|
exists,
|
package/src/core/uninstall.js
CHANGED
|
@@ -200,7 +200,7 @@ export async function uninstallUkit({ projectRoot, dryRun = false }) {
|
|
|
200
200
|
}
|
|
201
201
|
} else {
|
|
202
202
|
// Old format (no files list): fall back to hardcoded managed paths.
|
|
203
|
-
// NOTE: docs/PROJECT.md, MEMORY.md, STATUS.md, WORKLOG.md are intentionally excluded.
|
|
203
|
+
// NOTE: docs/PROJECT.md, MEMORY.md, STATUS.md, TASKS.md, WORKLOG.md are intentionally excluded.
|
|
204
204
|
// These are user-created content (mergeStrategy: skip) — deleting them
|
|
205
205
|
// would cause data loss. Users must remove them manually if desired.
|
|
206
206
|
const fallback = buildFallbackPaths(projectRoot);
|
|
@@ -223,8 +223,8 @@ export const ROUTE_CATALOG = [
|
|
|
223
223
|
path: '.claude/skills/docs-quality/SKILL.md',
|
|
224
224
|
order: 12,
|
|
225
225
|
signals: [
|
|
226
|
-
{ type: 'prompt', regex: /\b(docs|documentation|readme|changelog|handoff|worklog|memory|code map|status\.md)\b/i, score: 4 },
|
|
227
|
-
{ type: 'file', regex: /\bdocs\/|readme\.md$|project\.md$|memory\.md$|status\.md$|worklog\.md$|code_map\.md$/i, score: 4 },
|
|
226
|
+
{ type: 'prompt', regex: /\b(docs|documentation|readme|changelog|handoff|worklog|memory|code map|status\.md|tasks\.md|task queue|clean tasks|cleanup tasks|dọn tasks|dọn task)\b/i, score: 4 },
|
|
227
|
+
{ type: 'file', regex: /\bdocs\/|readme\.md$|project\.md$|memory\.md$|status\.md$|tasks\.md$|worklog\.md$|code_map\.md$/i, score: 4 },
|
|
228
228
|
],
|
|
229
229
|
},
|
|
230
230
|
{
|
|
@@ -233,8 +233,8 @@ export const ROUTE_CATALOG = [
|
|
|
233
233
|
order: 12.1,
|
|
234
234
|
contextMode: 'standalone',
|
|
235
235
|
signals: [
|
|
236
|
-
{ type: 'prompt', regex: /\b(what(?:'s| is)? next|next steps?|project status|current status|where are we|continue(?: from)?(?: last session)?|roadmap|status\.md)\b/i, score: 7 },
|
|
237
|
-
{ type: 'prompt', regex: /\b(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)\b/i, score: 7 },
|
|
236
|
+
{ type: 'prompt', regex: /\b(what(?:'s| is)? next|next steps?|project status|current status|where are we|continue(?: from)?(?: last session)?|roadmap|status\.md|task queue|tasks\.md|next queued task|pick next task|work from tasks)\b/i, score: 7 },
|
|
237
|
+
{ type: 'prompt', regex: /\b(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)\b/i, score: 7 },
|
|
238
238
|
],
|
|
239
239
|
},
|
|
240
240
|
{
|
package/src/index/taskRouting.js
CHANGED
|
@@ -175,6 +175,7 @@ export function buildRouteSummary({
|
|
|
175
175
|
formatCompactSegment('targets', primaryTargets),
|
|
176
176
|
formatCompactSegment('tests', relatedTests),
|
|
177
177
|
formatCompactSegment('styles', styleFiles),
|
|
178
|
+
delegationRecommendation?.hint ? `delegate=${delegationRecommendation.hint}` : null,
|
|
178
179
|
policyMode ? `policy=${policyMode}` : null,
|
|
179
180
|
].filter(Boolean).join(' | ');
|
|
180
181
|
|
|
@@ -311,10 +312,11 @@ function getSignalTexts(signalType, routeSignals = {}) {
|
|
|
311
312
|
function deriveIntentMode({ promptText = '', commandText = '', targetFile = null } = {}) {
|
|
312
313
|
const lower = buildRouteSignalText(promptText, commandText);
|
|
313
314
|
const raw = `${promptText ?? ''}\n${commandText ?? ''}`.toLowerCase();
|
|
314
|
-
const
|
|
315
|
+
const taskQueueNext = hasTaskQueueNextSignal(lower, raw);
|
|
316
|
+
const docsSpecific = hasDocsSpecificTaskSignal(lower, raw, targetFile, { taskQueueNext });
|
|
315
317
|
const statusUpdate = hasStatusUpdateSignal(lower, raw);
|
|
316
|
-
const openEndedStatus = hasOpenEndedStatusSignal(lower, raw);
|
|
317
|
-
const concreteTask = hasConcreteTaskSignal(lower, raw, targetFile);
|
|
318
|
+
const openEndedStatus = hasOpenEndedStatusSignal(lower, raw) || taskQueueNext;
|
|
319
|
+
const concreteTask = hasConcreteTaskSignal(lower, raw, targetFile, { taskQueueNext });
|
|
318
320
|
|
|
319
321
|
if (docsSpecific) {
|
|
320
322
|
return 'docs-specific';
|
|
@@ -356,13 +358,18 @@ function hasStatusUpdateSignal(lower, raw) {
|
|
|
356
358
|
}
|
|
357
359
|
|
|
358
360
|
function hasOpenEndedStatusSignal(lower, raw) {
|
|
359
|
-
return /\b(what next|what is next|what's next|next step|next steps|project status|current status|where are we|continue|continue from last session|roadmap|status\.md)\b/.test(lower)
|
|
360
|
-
|| /\b(lam gi tiep|buoc tiep theo|tiep theo lam gi|lam tiep|dang o dau|trang thai project|tinh trang project)\b/.test(lower)
|
|
361
|
-
|| /\b(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)\b/.test(raw);
|
|
361
|
+
return /\b(what next|what is next|what's next|next step|next steps|project status|current status|where are we|continue|continue from last session|roadmap|status\.md|task queue|tasks\.md|next queued task|pick next task|work from tasks)\b/.test(lower)
|
|
362
|
+
|| /\b(lam gi tiep|buoc tiep theo|tiep theo lam gi|lam tiep|dang o dau|trang thai project|tinh trang project|task tiep theo|viec tiep theo trong tasks)\b/.test(lower)
|
|
363
|
+
|| /\b(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)\b/.test(raw);
|
|
362
364
|
}
|
|
363
365
|
|
|
364
|
-
function
|
|
365
|
-
|
|
366
|
+
function hasTaskQueueNextSignal(lower, raw) {
|
|
367
|
+
return /\b(task queue|tasks\.md|next queued task|pick next task|work from tasks|next task from tasks|ready for ai)\b/.test(lower)
|
|
368
|
+
|| /\b(task tiếp theo|việc tiếp theo trong tasks|làm task trong tasks|lấy task tiếp theo)\b/.test(raw);
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
function hasConcreteTaskSignal(lower, raw, targetFile, { taskQueueNext = false } = {}) {
|
|
372
|
+
if (targetFile && !isStatusFileTarget(targetFile) && !(taskQueueNext && isTasksFileTarget(targetFile))) {
|
|
366
373
|
return true;
|
|
367
374
|
}
|
|
368
375
|
|
|
@@ -370,8 +377,13 @@ function hasConcreteTaskSignal(lower, raw, targetFile) {
|
|
|
370
377
|
|| /\b(sửa|fix|lỗi|bug|debug|implement|cài|thêm|review|kiểm tra|soát|đăng nhập)\b/.test(raw);
|
|
371
378
|
}
|
|
372
379
|
|
|
373
|
-
function hasDocsSpecificTaskSignal(lower, raw, targetFile) {
|
|
380
|
+
function hasDocsSpecificTaskSignal(lower, raw, targetFile, { taskQueueNext = false } = {}) {
|
|
374
381
|
if (!targetFile || !isDocsTarget(targetFile)) {
|
|
382
|
+
return /\b(clean tasks|cleanup tasks|prune tasks|dedupe tasks|clear completed tasks|dọn tasks|dọn task)\b/.test(lower)
|
|
383
|
+
|| /\b(dọn tasks|dọn task|dọn danh sách task|xóa task đã xong)\b/.test(raw);
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
if (taskQueueNext && isTasksFileTarget(targetFile) && !/\b(clean|cleanup|prune|dedupe|edit|template|wording|format|structure)\b/.test(lower)) {
|
|
375
387
|
return false;
|
|
376
388
|
}
|
|
377
389
|
|
|
@@ -413,6 +425,10 @@ function isStatusFileTarget(targetFile) {
|
|
|
413
425
|
return /(?:^|\/)docs\/STATUS\.md$|(?:^|\/)STATUS\.md$/i.test(String(targetFile || ''));
|
|
414
426
|
}
|
|
415
427
|
|
|
428
|
+
function isTasksFileTarget(targetFile) {
|
|
429
|
+
return /(?:^|\/)docs\/TASKS\.md$|(?:^|\/)TASKS\.md$/i.test(String(targetFile || ''));
|
|
430
|
+
}
|
|
431
|
+
|
|
416
432
|
function isDocsTarget(targetFile) {
|
|
417
433
|
const normalized = String(targetFile || '').replaceAll('\\', '/');
|
|
418
434
|
return /(?:^|\/)docs\/.+\.md$|(?:^|\/)(?:README|CHANGELOG|AGENTS|CLAUDE|STATUS)\.md$/i.test(normalized);
|
|
@@ -589,6 +605,16 @@ function deriveDelegationRecommendation({
|
|
|
589
605
|
const hasRelatedTests = (preview.relatedTests ?? []).length > 0;
|
|
590
606
|
const when = contextRecommendation?.command ? 'after-context' : 'now';
|
|
591
607
|
|
|
608
|
+
const smallTaskMaintenanceSignal = /\b(?:ukit|docs\/tasks\.md|tasks\.md|task queue|queued tasks?|compact(?:ion)?|summari[sz](?:e|ation)|doc(?:ument)? summary|cleanup|clean up|dọn rác|dọn dẹp|auto[- ]?triage|queue maintenance|small decision|ra quyết định)\b/.test(lower)
|
|
609
|
+
&& /\b(?:task|queue|compact(?:ion)?|summari[sz](?:e|ation)|doc(?:ument)?s?|cleanup|clean up|dọn rác|dọn dẹp|classif(?:y|ication)|triage|maintenance|small decision|ra quyết định)\b/.test(lower);
|
|
610
|
+
if (smallTaskMaintenanceSignal) {
|
|
611
|
+
return {
|
|
612
|
+
hint: 'ukit-small-task-maintainer',
|
|
613
|
+
when,
|
|
614
|
+
reason: 'Low-risk UKit maintenance decisions should use the configured small-task model without blocking the main AI flow.',
|
|
615
|
+
};
|
|
616
|
+
}
|
|
617
|
+
|
|
592
618
|
if (routingContext.taskType === 'trivial') {
|
|
593
619
|
return null;
|
|
594
620
|
}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: ukit-small-task-maintainer
|
|
3
|
+
description: "Internal UKit maintenance subagent for low-risk, reversible UKit decisions. Use proactively when UKit needs to decide or perform safe cleanup such as pruning docs/TASKS.md, classifying queued work, choosing whether compact/summarization is appropriate, summarizing local docs/status, or maintaining small UKit runtime queues. Do not use for product implementation, security, release/publish, data-loss, architecture, or risky/shared code changes."
|
|
4
|
+
model: inherit
|
|
5
|
+
color: cyan
|
|
6
|
+
tools: ["Read", "Grep", "Glob", "Edit", "Write"]
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
You are UKit's internal small-task maintainer. You run as a sidecar/parallel/non-blocking lane for safe, reversible UKit orchestration chores so the end user can stay focused on product work and only remember `ukit install`. Runtime tuning lives in `.ukit/storage/config.json`. You must follow `docs/UKIT_CODEV_PRINCIPLES.md`: hide complexity, preserve output quality, and never trade correctness for speed or token savings.
|
|
10
|
+
|
|
11
|
+
## Model Policy
|
|
12
|
+
|
|
13
|
+
- Use the model selected by `subagents.smallTaskModel` when the host supports model selection for this subagent.
|
|
14
|
+
- Default intended model: `unic-lite`.
|
|
15
|
+
- If the host cannot bind a model from config directly, still follow this role and report that the configured model is a hint.
|
|
16
|
+
- This lane must run separately from the user's main task model. It must never replace, pause, or slow down the main task.
|
|
17
|
+
|
|
18
|
+
## Use For
|
|
19
|
+
|
|
20
|
+
- Cleaning and deduplicating `docs/TASKS.md`.
|
|
21
|
+
- Pruning stale local AI queue/status noise.
|
|
22
|
+
- Classifying queued items by readiness/risk.
|
|
23
|
+
- Deciding whether compacting or summarizing context/docs is appropriate.
|
|
24
|
+
- Summarizing `docs/STATUS.md`, `docs/TASKS.md`, `docs/WORKLOG.md`, or memory snippets into compact handoff notes.
|
|
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
|
+
- 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.
|
|
28
|
+
- Small, reversible UKit runtime maintenance decisions.
|
|
29
|
+
|
|
30
|
+
## Never Use For
|
|
31
|
+
|
|
32
|
+
- Security/auth/permission/secrets work.
|
|
33
|
+
- Release, tag, publish, package ownership, or irreversible registry decisions.
|
|
34
|
+
- Data deletion beyond clearly safe local queue/doc cleanup.
|
|
35
|
+
- Architecture decisions, roadmap trade-offs, or deep product reasoning.
|
|
36
|
+
- Product code changes or risky/shared implementation edits.
|
|
37
|
+
- Any action where the consequence is not easy to inspect and undo.
|
|
38
|
+
|
|
39
|
+
## Workflow
|
|
40
|
+
|
|
41
|
+
1. Read the smallest relevant local state file first.
|
|
42
|
+
2. Identify whether the requested action is safe and reversible.
|
|
43
|
+
3. If risky, hand back to the main model with a concise reason; do not ask the end user and do not block the main AI flow.
|
|
44
|
+
4. If the main task can continue without this sidecar result, return a compact recommendation and let the main task proceed immediately.
|
|
45
|
+
5. If safe, make the smallest cleanup/summary/classification/routing decision.
|
|
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
|
+
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.
|
|
49
|
+
9. Preserve user-owned content; do not delete unclear tasks.
|
|
50
|
+
10. Report exactly what changed or what decision was made.
|
|
51
|
+
|
|
52
|
+
## Output Format
|
|
53
|
+
|
|
54
|
+
```
|
|
55
|
+
STATUS: DONE | SKIPPED | HAND_BACK
|
|
56
|
+
MODEL_HINT: subagents.smallTaskModel=<value-or-default>
|
|
57
|
+
SUMMARY: [one sentence]
|
|
58
|
+
CHANGES:
|
|
59
|
+
- [file/area]: [what changed]
|
|
60
|
+
HAND_BACK_REASON: [only if HAND_BACK]
|
|
61
|
+
NEXT: [small follow-up or "none"]
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
## Guardrails
|
|
65
|
+
|
|
66
|
+
- Keep UKit invisible: never ask end users to remember this agent, model name, or config path.
|
|
67
|
+
- Never block AI decision-making; escalate to the main model instead of waiting for user input when risk exceeds this lane.
|
|
68
|
+
- Never block, replace, or slow the user's main task; this lane is a sidecar hint/maintenance worker only.
|
|
69
|
+
- Prefer no-op over destructive cleanup when uncertain.
|
|
70
|
+
- Do not invent product requirements.
|
|
71
|
+
- Never choose cheap/fast if it risks wrong context, weak verification, over-compaction, or lower answer quality.
|
|
72
|
+
- Do not widen reads beyond local UKit state unless the main model explicitly scoped the handoff.
|