@ngockhoale/ukit 1.1.7 → 1.2.1
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 +22 -0
- package/README.md +14 -7
- package/manifests/platform.full.yaml +82 -0
- package/package.json +1 -1
- package/src/cli/commands/doctor.js +4 -0
- package/src/cli/commands/install.js +8 -6
- package/src/cli/commands/uninstall.js +1 -1
- package/src/core/ensureGitignore.js +2 -0
- package/src/core/runtimeConfig.js +256 -4
- package/src/core/uninstall.js +1 -1
- package/src/index/routeCatalog.js +24 -2
- package/src/index/taskRouting.js +163 -1
- package/templates/.claude/agents/ukit-small-task-maintainer.md +72 -0
- package/templates/.claude/skills/docs-quality/SKILL.md +23 -1
- package/templates/.claude/skills/next-step/SKILL.md +86 -0
- package/templates/.claude/skills/update-status/SKILL.md +102 -0
- package/templates/.claude/ukit/.env.example +17 -0
- package/templates/.claude/ukit/index/route-catalog.mjs +24 -2
- package/templates/.claude/ukit/index/route-task.mjs +163 -1
- package/templates/.codex/README.md +9 -1
- package/templates/.codex/settings.json +134 -5
- package/templates/.gitignore +2 -0
- package/templates/AGENTS.md +23 -2
- package/templates/CLAUDE.md +23 -2
- package/templates/docs/INSTALL.md +4 -0
- package/templates/docs/PROJECT.md +6 -4
- package/templates/docs/STATUS.md +81 -0
- package/templates/docs/TASKS.md +79 -0
- package/templates/docs/UKIT_USAGE_GUIDE.md +17 -0
- package/templates/ukit/README.md +1 -1
- package/templates/ukit/storage/config.json +116 -2
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,28 @@ All notable changes to UKit are documented here.
|
|
|
4
4
|
|
|
5
5
|
## Unreleased
|
|
6
6
|
|
|
7
|
+
## 1.2.1 - 2026-05-05
|
|
8
|
+
|
|
9
|
+
### Local AI Task Queue
|
|
10
|
+
|
|
11
|
+
- 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.
|
|
12
|
+
- Installed `docs/TASKS.md` with skip merge semantics so end-user task notes are never overwritten or uninstall-owned.
|
|
13
|
+
- 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.
|
|
14
|
+
- 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`.
|
|
15
|
+
- Added `docs/STATUS.md` and `docs/TASKS.md` to generated gitignore handling so living local AI state does not pollute project commits.
|
|
16
|
+
- Added the internal `ukit-small-task-maintainer` subagent, powered by optional `UKIT_SMALL_TASK_MODEL=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.
|
|
17
|
+
|
|
18
|
+
## 1.1.8 - 2026-05-05
|
|
19
|
+
|
|
20
|
+
### Living Project Status
|
|
21
|
+
|
|
22
|
+
- Added `docs/STATUS.md` as a compact living project-state baseline for active work, debug threads, blockers, verification, and next candidates.
|
|
23
|
+
- Added `next-step` and `update-status` skills so Claude/Codex/OpenCode can use project status automatically without adding any user-facing commands beyond `ukit install`.
|
|
24
|
+
- Made next-step routing freshness-aware: stale or missing status must be treated as orientation only and verified against source/index before recommendations.
|
|
25
|
+
- Added concrete-task precedence so prompts like “fix login bug but not sure next step” stay on the debug/implementation/review workflow instead of being hijacked by global roadmap suggestions.
|
|
26
|
+
- Registered status docs/skills in the manifest with skip semantics for the living doc, and kept source route catalogs aligned with installed helper mirrors.
|
|
27
|
+
- Updated install/routing/manifest regression coverage for open-ended status prompts, explicit handoff updates, concrete debug edge cases, and default skill installation.
|
|
28
|
+
|
|
7
29
|
## 1.1.7 - 2026-04-25
|
|
8
30
|
|
|
9
31
|
### Impact Confidence
|
package/README.md
CHANGED
|
@@ -32,6 +32,8 @@ ukit install
|
|
|
32
32
|
4. Fill in the generated docs baseline:
|
|
33
33
|
- `docs/PROJECT.md`
|
|
34
34
|
- `docs/MEMORY.md`
|
|
35
|
+
- `docs/STATUS.md`
|
|
36
|
+
- `docs/TASKS.md`
|
|
35
37
|
- `docs/WORKLOG.md`
|
|
36
38
|
5. Open your AI tool and work in natural language.
|
|
37
39
|
|
|
@@ -64,13 +66,13 @@ If maintainers roll out a newer CLI build, the in-project workflow still stays t
|
|
|
64
66
|
**Project support files**
|
|
65
67
|
- `.claude/ukit/.ukit/` — installer manifests, metadata, backups
|
|
66
68
|
- `.ukit/` — hidden shared runtime storage for config, cache, and cross-agent memory
|
|
67
|
-
- `docs/` — PROJECT / MEMORY / WORKLOG baseline
|
|
69
|
+
- `docs/` — PROJECT / MEMORY / STATUS / TASKS / WORKLOG baseline
|
|
68
70
|
|
|
69
|
-
## UKit v1.1
|
|
71
|
+
## UKit v1.2.1 Runtime
|
|
70
72
|
|
|
71
73
|
UKit now installs a hidden shared local runtime at `.ukit/` for production-oriented state that should survive across agent sessions:
|
|
72
74
|
|
|
73
|
-
- `.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
|
|
74
76
|
- `.ukit/storage/cache/` — reusable prompt-cache, compact history, compact-pressure state, and output summaries
|
|
75
77
|
- `.ukit/storage/memory/` — cross-agent local memory
|
|
76
78
|
|
|
@@ -83,15 +85,18 @@ When long sessions approach the compact threshold, UKit now uses a conservative
|
|
|
83
85
|
- compact only safe-zone history/noise
|
|
84
86
|
- preserve active task, rules, decisions, and current code focus
|
|
85
87
|
|
|
86
|
-
UKit v1.1
|
|
88
|
+
UKit v1.2.1 keeps the same shared runtime contract while adding a local AI task queue alongside living project status routing:
|
|
87
89
|
|
|
88
90
|
- install globally with `npm install -g @ngockhoale/ukit`
|
|
89
91
|
- keep using the exact same human workflow inside projects: `ukit install`
|
|
90
92
|
- preserve the same `ukit` binary, hooks, and install-first orchestration while standardizing the runtime root as hidden `.ukit/`
|
|
91
|
-
-
|
|
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
|
|
95
|
+
- auto-route open-ended “what next?” / “continue” prompts to the `next-step` skill with a visible freshness cue when status may be stale
|
|
96
|
+
- auto-route explicit handoff/wrap-up requests to the `update-status` skill while skipping trivial/no-state-change tasks
|
|
97
|
+
- keep concrete debug/implementation/review prompts primary, so project status never replaces source/index-first task work
|
|
93
98
|
|
|
94
|
-
UKit v1.1
|
|
99
|
+
UKit v1.2.1 also keeps the fast path improvements from the recent runtime releases:
|
|
95
100
|
|
|
96
101
|
- Vietnamese prompts now normalize more effectively for English-heavy code symbols and paths
|
|
97
102
|
- localized simple direct-target lanes skip extra previous-context / recent-output work when it would not change the next action
|
|
@@ -126,6 +131,8 @@ UKit is built so the team can stop memorizing UKit subcommands and focus on prod
|
|
|
126
131
|
- let Claude Code / Codex / OpenCode auto-detect and use the right project-local skill from the prompt and the files/tools involved
|
|
127
132
|
- let the AI prefer targeted verification first, then widen only when shared/risky scope justifies it
|
|
128
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 `UKIT_CODEX_COMPACT_TARGET` 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 `UKIT_SMALL_TASK_MODEL=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
|
|
129
136
|
- rerun `ukit install` when you need to refresh the workspace
|
|
130
137
|
|
|
131
138
|
End users should **not** need to know or memorize skill names.
|
|
@@ -77,6 +77,18 @@ items:
|
|
|
77
77
|
packs:
|
|
78
78
|
- core
|
|
79
79
|
|
|
80
|
+
- id: ukit-env-example
|
|
81
|
+
type: config
|
|
82
|
+
sourceTemplate: .claude/ukit/.env.example
|
|
83
|
+
targetPath: .claude/ukit/.env.example
|
|
84
|
+
requires:
|
|
85
|
+
- runtime-config
|
|
86
|
+
mergeStrategy: overwrite_with_backup
|
|
87
|
+
variables: []
|
|
88
|
+
enabledByDefault: true
|
|
89
|
+
packs:
|
|
90
|
+
- core
|
|
91
|
+
|
|
80
92
|
- id: root-claude-md
|
|
81
93
|
type: config
|
|
82
94
|
sourceTemplate: CLAUDE.md
|
|
@@ -143,6 +155,35 @@ items:
|
|
|
143
155
|
packs:
|
|
144
156
|
- core
|
|
145
157
|
|
|
158
|
+
- id: docs-status
|
|
159
|
+
type: config
|
|
160
|
+
sourceTemplate: docs/STATUS.md
|
|
161
|
+
targetPath: docs/STATUS.md
|
|
162
|
+
requires:
|
|
163
|
+
- docs-project
|
|
164
|
+
- docs-memory
|
|
165
|
+
- docs-worklog
|
|
166
|
+
- docs-code-map
|
|
167
|
+
mergeStrategy: skip
|
|
168
|
+
variables:
|
|
169
|
+
- project.name
|
|
170
|
+
enabledByDefault: true
|
|
171
|
+
packs:
|
|
172
|
+
- core
|
|
173
|
+
|
|
174
|
+
- id: docs-tasks
|
|
175
|
+
type: config
|
|
176
|
+
sourceTemplate: docs/TASKS.md
|
|
177
|
+
targetPath: docs/TASKS.md
|
|
178
|
+
requires:
|
|
179
|
+
- docs-status
|
|
180
|
+
mergeStrategy: skip
|
|
181
|
+
variables:
|
|
182
|
+
- project.name
|
|
183
|
+
enabledByDefault: true
|
|
184
|
+
packs:
|
|
185
|
+
- core
|
|
186
|
+
|
|
146
187
|
- id: docs-bugfix
|
|
147
188
|
type: config
|
|
148
189
|
sourceTemplate: docs/BUGFIX.md
|
|
@@ -460,6 +501,34 @@ items:
|
|
|
460
501
|
packs:
|
|
461
502
|
- core
|
|
462
503
|
|
|
504
|
+
- id: next-step-skill
|
|
505
|
+
type: skill
|
|
506
|
+
sourceTemplate: .claude/skills/next-step/SKILL.md
|
|
507
|
+
targetPath: .claude/skills/next-step/SKILL.md
|
|
508
|
+
requires:
|
|
509
|
+
- docs-quality-skill
|
|
510
|
+
- docs-status
|
|
511
|
+
- docs-tasks
|
|
512
|
+
mergeStrategy: overwrite_with_backup
|
|
513
|
+
variables: []
|
|
514
|
+
enabledByDefault: true
|
|
515
|
+
packs:
|
|
516
|
+
- core
|
|
517
|
+
|
|
518
|
+
- id: update-status-skill
|
|
519
|
+
type: skill
|
|
520
|
+
sourceTemplate: .claude/skills/update-status/SKILL.md
|
|
521
|
+
targetPath: .claude/skills/update-status/SKILL.md
|
|
522
|
+
requires:
|
|
523
|
+
- docs-quality-skill
|
|
524
|
+
- docs-status
|
|
525
|
+
- docs-tasks
|
|
526
|
+
mergeStrategy: overwrite_with_backup
|
|
527
|
+
variables: []
|
|
528
|
+
enabledByDefault: true
|
|
529
|
+
packs:
|
|
530
|
+
- core
|
|
531
|
+
|
|
463
532
|
- id: docs-manager-skill
|
|
464
533
|
type: skill
|
|
465
534
|
sourceTemplate: .claude/skills/docs-manager
|
|
@@ -750,6 +819,19 @@ items:
|
|
|
750
819
|
packs:
|
|
751
820
|
- core
|
|
752
821
|
|
|
822
|
+
- id: agent-ukit-small-task-maintainer
|
|
823
|
+
type: agent
|
|
824
|
+
sourceTemplate: .claude/agents/ukit-small-task-maintainer.md
|
|
825
|
+
targetPath: .claude/agents/ukit-small-task-maintainer.md
|
|
826
|
+
requires:
|
|
827
|
+
- ukit-env-example
|
|
828
|
+
mergeStrategy: overwrite_with_backup
|
|
829
|
+
variables:
|
|
830
|
+
- ukit.version
|
|
831
|
+
enabledByDefault: true
|
|
832
|
+
packs:
|
|
833
|
+
- core
|
|
834
|
+
|
|
753
835
|
- id: provider-compat-config
|
|
754
836
|
type: config
|
|
755
837
|
sourceTemplate: .claude/config/providers.md
|
package/package.json
CHANGED
|
@@ -62,6 +62,8 @@ export async function runDoctor({ packageRoot, projectRoot, argv = [] }) {
|
|
|
62
62
|
sessionMemoryDirExists: await pathExists(runtimePaths.sessionsDir),
|
|
63
63
|
docsProjectExists: await pathExists(path.join(projectRoot, 'docs', 'PROJECT.md')),
|
|
64
64
|
docsMemoryExists: await pathExists(path.join(projectRoot, 'docs', 'MEMORY.md')),
|
|
65
|
+
docsStatusExists: await pathExists(path.join(projectRoot, 'docs', 'STATUS.md')),
|
|
66
|
+
docsTasksExists: await pathExists(path.join(projectRoot, 'docs', 'TASKS.md')),
|
|
65
67
|
docsWorklogExists: await pathExists(path.join(projectRoot, 'docs', 'WORKLOG.md')),
|
|
66
68
|
allProvidersConfigured: providers.allSupported,
|
|
67
69
|
...(codexAdapterTracked
|
|
@@ -103,6 +105,8 @@ export async function runDoctor({ packageRoot, projectRoot, argv = [] }) {
|
|
|
103
105
|
console.log(`[UKit] ${ok(checks.sessionMemoryDirExists)} .ukit/storage/memory/sessions/`);
|
|
104
106
|
console.log(`[UKit] ${ok(checks.docsProjectExists)} docs/PROJECT.md`);
|
|
105
107
|
console.log(`[UKit] ${ok(checks.docsMemoryExists)} docs/MEMORY.md`);
|
|
108
|
+
console.log(`[UKit] ${ok(checks.docsStatusExists)} docs/STATUS.md`);
|
|
109
|
+
console.log(`[UKit] ${ok(checks.docsTasksExists)} docs/TASKS.md`);
|
|
106
110
|
console.log(`[UKit] ${ok(checks.docsWorklogExists)} docs/WORKLOG.md`);
|
|
107
111
|
if (codexAdapterTracked) {
|
|
108
112
|
console.log(`[UKit] ${ok(checks.codexReadmeExists)} .codex/README.md`);
|
|
@@ -241,12 +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
|
-
|
|
244
|
+
const docsLabels = [
|
|
245
|
+
'docs/PROJECT.md',
|
|
246
|
+
'docs/MEMORY.md',
|
|
247
|
+
'docs/STATUS.md',
|
|
248
|
+
'docs/TASKS.md',
|
|
249
|
+
'docs/WORKLOG.md',
|
|
248
250
|
];
|
|
249
|
-
const
|
|
251
|
+
const docsPaths = docsLabels.map((label) => path.join(projectRoot, ...label.split('/')));
|
|
250
252
|
const missingDocs = [];
|
|
251
253
|
for (let i = 0; i < docsPaths.length; i++) {
|
|
252
254
|
if (!(await pathExists(docsPaths[i]))) {
|
|
@@ -257,7 +259,7 @@ export async function runInstall({ packageRoot, projectRoot, packageVersion, arg
|
|
|
257
259
|
if (missingDocs.length > 0) {
|
|
258
260
|
console.log(`[UKit] Missing docs — fill these in before first use: ${missingDocs.join(', ')}`);
|
|
259
261
|
} else {
|
|
260
|
-
console.log('[UKit] Docs baseline ready: docs/PROJECT.md, docs/MEMORY.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');
|
|
261
263
|
console.log('[UKit] Fill them once with real project context for the best results.');
|
|
262
264
|
}
|
|
263
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/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
|
}
|
|
@@ -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,155 @@ 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
|
+
|
|
48
|
+
function parseRuntimeEnv(content = '') {
|
|
49
|
+
const result = {};
|
|
50
|
+
for (const rawLine of String(content || '').split(/\r?\n/)) {
|
|
51
|
+
const line = rawLine.trim();
|
|
52
|
+
if (!line || line.startsWith('#') || !line.includes('=')) {
|
|
53
|
+
continue;
|
|
54
|
+
}
|
|
55
|
+
const index = line.indexOf('=');
|
|
56
|
+
const key = line.slice(0, index).trim();
|
|
57
|
+
let value = line.slice(index + 1).trim();
|
|
58
|
+
if (
|
|
59
|
+
(value.startsWith('"') && value.endsWith('"'))
|
|
60
|
+
|| (value.startsWith("'") && value.endsWith("'"))
|
|
61
|
+
) {
|
|
62
|
+
value = value.slice(1, -1);
|
|
63
|
+
}
|
|
64
|
+
result[key] = value;
|
|
65
|
+
}
|
|
66
|
+
return result;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
async function loadRuntimeEnv(projectRoot) {
|
|
70
|
+
const envPath = path.join(projectRoot, '.claude', 'ukit', '.env');
|
|
71
|
+
try {
|
|
72
|
+
return parseRuntimeEnv(await fs.readFile(envPath, 'utf8'));
|
|
73
|
+
} catch (error) {
|
|
74
|
+
if (error?.code !== 'ENOENT') {
|
|
75
|
+
throw error;
|
|
76
|
+
}
|
|
77
|
+
return {};
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function parsePositiveIntegerEnv(value) {
|
|
82
|
+
if (typeof value !== 'string' || value.trim() === '') {
|
|
83
|
+
return null;
|
|
84
|
+
}
|
|
85
|
+
const parsed = Number.parseInt(value.trim(), 10);
|
|
86
|
+
return Number.isFinite(parsed) && parsed > 0 ? parsed : null;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function buildEnvOverrides(env = process.env) {
|
|
90
|
+
const overrides = {};
|
|
91
|
+
const smallTaskModel = env.UKIT_SMALL_TASK_MODEL;
|
|
92
|
+
if (typeof smallTaskModel === 'string' && smallTaskModel.trim() !== '') {
|
|
93
|
+
overrides.subagents = {
|
|
94
|
+
...(overrides.subagents ?? {}),
|
|
95
|
+
smallTaskModel: smallTaskModel.trim(),
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
const rawCodexCompactTarget = parsePositiveIntegerEnv(env.UKIT_CODEX_COMPACT_TARGET);
|
|
100
|
+
const codexCompactTarget = rawCodexCompactTarget ? Math.min(rawCodexCompactTarget, 170) : null;
|
|
101
|
+
const codexContextBudget = parsePositiveIntegerEnv(env.UKIT_CODEX_CONTEXT_BUDGET);
|
|
102
|
+
const codexAutoCompact = typeof env.UKIT_CODEX_AUTO_COMPACT === 'string'
|
|
103
|
+
? !['0', 'false', 'no', 'off'].includes(env.UKIT_CODEX_AUTO_COMPACT.trim().toLowerCase())
|
|
104
|
+
: null;
|
|
105
|
+
|
|
106
|
+
if (codexCompactTarget || codexContextBudget || codexAutoCompact !== null) {
|
|
107
|
+
overrides.compact = {
|
|
108
|
+
...(overrides.compact ?? {}),
|
|
109
|
+
agentContext: {
|
|
110
|
+
enabled: true,
|
|
111
|
+
decisionModelEnv: 'UKIT_SMALL_TASK_MODEL',
|
|
112
|
+
decisionAgent: 'ukit-small-task-maintainer',
|
|
113
|
+
executionMode: 'sidecar-parallel',
|
|
114
|
+
mustNotBlockMainTask: true,
|
|
115
|
+
targets: {
|
|
116
|
+
claude: { autoCompact: true, mode: 'precompact-reinject', preserveExistingHooks: true },
|
|
117
|
+
opencode: { autoCompact: true, mode: 'native-auto-prune', preserveExistingCompaction: true },
|
|
118
|
+
codex: {
|
|
119
|
+
autoCompact: true,
|
|
120
|
+
mode: 'soft-handoff',
|
|
121
|
+
compactTarget: 150,
|
|
122
|
+
compactTargetUnit: 'lines',
|
|
123
|
+
compactTargetRecommendedRange: [120, 170],
|
|
124
|
+
compactTargetMax: 170,
|
|
125
|
+
},
|
|
126
|
+
},
|
|
127
|
+
},
|
|
128
|
+
codexContext: {
|
|
129
|
+
...(overrides.compact?.codexContext ?? {}),
|
|
130
|
+
...(codexCompactTarget ? { compactTarget: codexCompactTarget } : {}),
|
|
131
|
+
...(codexContextBudget ? { budgetTokens: codexContextBudget } : {}),
|
|
132
|
+
...(codexAutoCompact !== null ? { autoCompact: codexAutoCompact } : {}),
|
|
133
|
+
},
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
return overrides;
|
|
138
|
+
}
|
|
139
|
+
|
|
41
140
|
export function buildDefaultRuntimeConfig(overrides = {}) {
|
|
42
141
|
const safeOverrides = isPlainObject(overrides) ? overrides : {};
|
|
43
142
|
|
|
44
143
|
return mergeObjects({
|
|
45
|
-
version: '1.1
|
|
144
|
+
version: '1.2.1',
|
|
46
145
|
agent: 'claude-code',
|
|
47
146
|
compact: {
|
|
48
147
|
enabled: true,
|
|
49
148
|
tokenThreshold: 100_000,
|
|
50
149
|
contextRotDetection: true,
|
|
51
150
|
askBeforeDrop: true,
|
|
151
|
+
agentContext: {
|
|
152
|
+
enabled: true,
|
|
153
|
+
decisionModelEnv: 'UKIT_SMALL_TASK_MODEL',
|
|
154
|
+
decisionAgent: 'ukit-small-task-maintainer',
|
|
155
|
+
executionMode: 'sidecar-parallel',
|
|
156
|
+
mustNotBlockMainTask: true,
|
|
157
|
+
targets: {
|
|
158
|
+
claude: { autoCompact: true, mode: 'precompact-reinject', preserveExistingHooks: true },
|
|
159
|
+
opencode: { autoCompact: true, mode: 'native-auto-prune', preserveExistingCompaction: true },
|
|
160
|
+
codex: {
|
|
161
|
+
autoCompact: true,
|
|
162
|
+
mode: 'soft-handoff',
|
|
163
|
+
compactTarget: 150,
|
|
164
|
+
compactTargetUnit: 'lines',
|
|
165
|
+
compactTargetRecommendedRange: [120, 170],
|
|
166
|
+
compactTargetMax: 170,
|
|
167
|
+
},
|
|
168
|
+
},
|
|
169
|
+
},
|
|
170
|
+
codexContext: {
|
|
171
|
+
enabled: true,
|
|
172
|
+
autoCompact: true,
|
|
173
|
+
budgetTokens: 60_000,
|
|
174
|
+
compactTarget: 150,
|
|
175
|
+
compactTargetUnit: 'lines',
|
|
176
|
+
compactTargetRecommendedRange: [120, 170],
|
|
177
|
+
compactTargetMax: 170,
|
|
178
|
+
decisionModelEnv: 'UKIT_SMALL_TASK_MODEL',
|
|
179
|
+
decisionAgent: 'ukit-small-task-maintainer',
|
|
180
|
+
mode: 'soft-handoff',
|
|
181
|
+
preserve: [
|
|
182
|
+
'current-goal',
|
|
183
|
+
'non-negotiable-rules',
|
|
184
|
+
'active-files',
|
|
185
|
+
'decisions',
|
|
186
|
+
'unresolved-failures',
|
|
187
|
+
'verification-evidence',
|
|
188
|
+
'next-actions',
|
|
189
|
+
],
|
|
190
|
+
},
|
|
52
191
|
},
|
|
53
192
|
tokenPipeline: {
|
|
54
193
|
inputCompression: true,
|
|
@@ -77,6 +216,53 @@ export function buildDefaultRuntimeConfig(overrides = {}) {
|
|
|
77
216
|
maxRetries: 1,
|
|
78
217
|
confidenceThreshold: 50,
|
|
79
218
|
},
|
|
219
|
+
subagents: {
|
|
220
|
+
enabled: true,
|
|
221
|
+
smallTaskModel: 'unic-lite',
|
|
222
|
+
smallTaskAgent: 'ukit-small-task-maintainer',
|
|
223
|
+
smallTaskUseCases: [
|
|
224
|
+
'task-cleanup',
|
|
225
|
+
'compact-decision',
|
|
226
|
+
'codex-context-budget',
|
|
227
|
+
'doc-summarization',
|
|
228
|
+
'classification',
|
|
229
|
+
'small-decision',
|
|
230
|
+
'auto-triage',
|
|
231
|
+
'queue-maintenance',
|
|
232
|
+
'workspace-maintenance',
|
|
233
|
+
],
|
|
234
|
+
keepMainModelFor: [
|
|
235
|
+
'security',
|
|
236
|
+
'risky-code-change',
|
|
237
|
+
'release',
|
|
238
|
+
'data-loss',
|
|
239
|
+
'architecture-decision',
|
|
240
|
+
'deep-reasoning',
|
|
241
|
+
],
|
|
242
|
+
decisionPolicy: {
|
|
243
|
+
nonBlocking: true,
|
|
244
|
+
endUserInvisible: true,
|
|
245
|
+
handBackOnRisk: true,
|
|
246
|
+
executionMode: 'sidecar-parallel',
|
|
247
|
+
mustNotBlockMainTask: true,
|
|
248
|
+
maxSidecarWaitMs: 0,
|
|
249
|
+
optimizeOrder: ['quality', 'safety', 'speed', 'token-discipline'],
|
|
250
|
+
decisions: [
|
|
251
|
+
'fast-vs-slow-lane',
|
|
252
|
+
'safe-vs-risky-lane',
|
|
253
|
+
'skill-routing-needed',
|
|
254
|
+
'step-budget-enough',
|
|
255
|
+
'compact-now-or-later',
|
|
256
|
+
'codex-context-budget',
|
|
257
|
+
'summarize-docs-or-keep-detail',
|
|
258
|
+
],
|
|
259
|
+
stepBudgets: {
|
|
260
|
+
trivial: { maxSteps: 1, verification: 'skip-unless-risky' },
|
|
261
|
+
simple: { maxSteps: 2, verification: 'targeted-if-covered' },
|
|
262
|
+
nonTrivial: { maxSteps: 4, verification: 'targeted-then-widen-on-risk' },
|
|
263
|
+
},
|
|
264
|
+
},
|
|
265
|
+
},
|
|
80
266
|
}, safeOverrides);
|
|
81
267
|
}
|
|
82
268
|
|
|
@@ -102,6 +288,40 @@ export function validateRuntimeConfig(config) {
|
|
|
102
288
|
pushPositiveNumberError(errors, config.compact.tokenThreshold, 'compact.tokenThreshold');
|
|
103
289
|
pushBooleanError(errors, config.compact.contextRotDetection, 'compact.contextRotDetection');
|
|
104
290
|
pushBooleanError(errors, config.compact.askBeforeDrop, 'compact.askBeforeDrop');
|
|
291
|
+
if (!isPlainObject(config.compact.agentContext)) {
|
|
292
|
+
errors.push('compact.agentContext must be an object.');
|
|
293
|
+
} else {
|
|
294
|
+
pushBooleanError(errors, config.compact.agentContext.enabled, 'compact.agentContext.enabled');
|
|
295
|
+
pushNonEmptyStringError(errors, config.compact.agentContext.decisionModelEnv, 'compact.agentContext.decisionModelEnv');
|
|
296
|
+
pushNonEmptyStringError(errors, config.compact.agentContext.decisionAgent, 'compact.agentContext.decisionAgent');
|
|
297
|
+
pushNonEmptyStringError(errors, config.compact.agentContext.executionMode, 'compact.agentContext.executionMode');
|
|
298
|
+
pushBooleanError(errors, config.compact.agentContext.mustNotBlockMainTask, 'compact.agentContext.mustNotBlockMainTask');
|
|
299
|
+
if (!isPlainObject(config.compact.agentContext.targets)) {
|
|
300
|
+
errors.push('compact.agentContext.targets must be an object.');
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
if (!isPlainObject(config.compact.codexContext)) {
|
|
304
|
+
errors.push('compact.codexContext must be an object.');
|
|
305
|
+
} else {
|
|
306
|
+
pushBooleanError(errors, config.compact.codexContext.enabled, 'compact.codexContext.enabled');
|
|
307
|
+
pushBooleanError(errors, config.compact.codexContext.autoCompact, 'compact.codexContext.autoCompact');
|
|
308
|
+
pushPositiveNumberError(errors, config.compact.codexContext.budgetTokens, 'compact.codexContext.budgetTokens');
|
|
309
|
+
pushPositiveNumberError(errors, config.compact.codexContext.compactTarget, 'compact.codexContext.compactTarget');
|
|
310
|
+
pushNonEmptyStringError(errors, config.compact.codexContext.compactTargetUnit, 'compact.codexContext.compactTargetUnit');
|
|
311
|
+
if (!Array.isArray(config.compact.codexContext.compactTargetRecommendedRange)) {
|
|
312
|
+
errors.push('compact.codexContext.compactTargetRecommendedRange must be an array.');
|
|
313
|
+
}
|
|
314
|
+
pushPositiveNumberError(errors, config.compact.codexContext.compactTargetMax, 'compact.codexContext.compactTargetMax');
|
|
315
|
+
if (Number(config.compact.codexContext.compactTarget) > Number(config.compact.codexContext.compactTargetMax)) {
|
|
316
|
+
errors.push('compact.codexContext.compactTarget must be <= compact.codexContext.compactTargetMax.');
|
|
317
|
+
}
|
|
318
|
+
pushNonEmptyStringError(errors, config.compact.codexContext.decisionModelEnv, 'compact.codexContext.decisionModelEnv');
|
|
319
|
+
pushNonEmptyStringError(errors, config.compact.codexContext.decisionAgent, 'compact.codexContext.decisionAgent');
|
|
320
|
+
pushNonEmptyStringError(errors, config.compact.codexContext.mode, 'compact.codexContext.mode');
|
|
321
|
+
if (!Array.isArray(config.compact.codexContext.preserve)) {
|
|
322
|
+
errors.push('compact.codexContext.preserve must be an array.');
|
|
323
|
+
}
|
|
324
|
+
}
|
|
105
325
|
}
|
|
106
326
|
|
|
107
327
|
if (!isPlainObject(config.tokenPipeline)) {
|
|
@@ -147,6 +367,23 @@ export function validateRuntimeConfig(config) {
|
|
|
147
367
|
pushPositiveNumberError(errors, config.validation.confidenceThreshold, 'validation.confidenceThreshold');
|
|
148
368
|
}
|
|
149
369
|
|
|
370
|
+
if (!isPlainObject(config.subagents)) {
|
|
371
|
+
errors.push('subagents must be an object.');
|
|
372
|
+
} else {
|
|
373
|
+
pushBooleanError(errors, config.subagents.enabled, 'subagents.enabled');
|
|
374
|
+
pushNonEmptyStringError(errors, config.subagents.smallTaskModel, 'subagents.smallTaskModel');
|
|
375
|
+
pushNonEmptyStringError(errors, config.subagents.smallTaskAgent, 'subagents.smallTaskAgent');
|
|
376
|
+
if (!Array.isArray(config.subagents.smallTaskUseCases)) {
|
|
377
|
+
errors.push('subagents.smallTaskUseCases must be an array.');
|
|
378
|
+
}
|
|
379
|
+
if (!Array.isArray(config.subagents.keepMainModelFor)) {
|
|
380
|
+
errors.push('subagents.keepMainModelFor must be an array.');
|
|
381
|
+
}
|
|
382
|
+
if (!isPlainObject(config.subagents.decisionPolicy)) {
|
|
383
|
+
errors.push('subagents.decisionPolicy must be an object.');
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
|
|
150
387
|
return {
|
|
151
388
|
valid: errors.length === 0,
|
|
152
389
|
errors,
|
|
@@ -170,16 +407,31 @@ export async function inspectRuntimeConfig(projectRoot) {
|
|
|
170
407
|
}
|
|
171
408
|
}
|
|
172
409
|
|
|
173
|
-
|
|
410
|
+
let runtimeEnv = {};
|
|
411
|
+
let envError = null;
|
|
412
|
+
try {
|
|
413
|
+
runtimeEnv = await loadRuntimeEnv(projectRoot);
|
|
414
|
+
} catch (error) {
|
|
415
|
+
envError = error?.message ?? String(error);
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
const config = buildDefaultRuntimeConfig(
|
|
419
|
+
mergeObjects(rawConfig ?? {}, buildEnvOverrides({ ...runtimeEnv, ...process.env })),
|
|
420
|
+
);
|
|
174
421
|
const validation = validateRuntimeConfig(config);
|
|
175
|
-
const errors =
|
|
422
|
+
const errors = [
|
|
423
|
+
...(parseError ? [`config.json parse error: ${parseError}`] : []),
|
|
424
|
+
...(envError ? [`runtime .env error: ${envError}`] : []),
|
|
425
|
+
...validation.errors,
|
|
426
|
+
];
|
|
176
427
|
|
|
177
428
|
return {
|
|
178
429
|
exists,
|
|
179
430
|
rawConfig,
|
|
431
|
+
runtimeEnv,
|
|
180
432
|
config,
|
|
181
433
|
parseError,
|
|
182
|
-
valid: exists && !parseError && validation.valid,
|
|
434
|
+
valid: exists && !parseError && !envError && validation.valid,
|
|
183
435
|
errors,
|
|
184
436
|
};
|
|
185
437
|
}
|
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, 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,30 @@ 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)\b/i, score: 4 },
|
|
227
|
-
{ type: 'file', regex: /\bdocs\/|readme\.md$|project\.md$|memory\.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
|
+
],
|
|
229
|
+
},
|
|
230
|
+
{
|
|
231
|
+
id: 'next-step',
|
|
232
|
+
path: '.claude/skills/next-step/SKILL.md',
|
|
233
|
+
order: 12.1,
|
|
234
|
+
contextMode: 'standalone',
|
|
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|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
|
+
],
|
|
239
|
+
},
|
|
240
|
+
{
|
|
241
|
+
id: 'update-status',
|
|
242
|
+
path: '.claude/skills/update-status/SKILL.md',
|
|
243
|
+
order: 12.2,
|
|
244
|
+
contextMode: 'standalone',
|
|
245
|
+
signals: [
|
|
246
|
+
{ type: 'prompt', regex: /\b(update|refresh|write|sync|record|capture|summari[sz]e).{0,48}\b(status\.md|project status|current state|next candidates?|session state)\b/i, score: 9 },
|
|
247
|
+
{ type: 'prompt', regex: /\b(status\.md|project status).{0,48}\b(update|refresh|write|sync|record|capture|summari[sz]e)\b/i, score: 9 },
|
|
248
|
+
{ type: 'prompt', regex: /\b(wrap up|handoff|end(?:ing)? this session|session summary|before final)\b/i, score: 7 },
|
|
249
|
+
{ type: 'prompt', regex: /\b(cập nhật|ghi lại|tổng kết|chốt session|bàn giao).{0,48}\b(status|trạng thái|việc tiếp theo)\b/i, score: 9 },
|
|
228
250
|
],
|
|
229
251
|
},
|
|
230
252
|
{
|