@ngockhoale/ukit 1.2.1 → 1.3.0
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 +27 -1
- package/README.md +7 -5
- package/manifests/platform.full.yaml +127 -13
- package/package.json +1 -1
- package/src/core/runInstallPipeline.js +6 -1
- package/src/core/runtimeConfig.js +29 -112
- package/src/index/impactCatalog.js +12 -0
- package/src/index/taskRouting.js +3 -0
- package/templates/.claude/agents/ukit-small-task-maintainer.md +5 -5
- package/templates/.claude/hooks/post-edit-verify.sh +13 -0
- package/templates/.claude/hooks/pre-edit-backup.sh +13 -0
- package/templates/.claude/hooks/stale-spec-guard.sh +13 -0
- package/templates/.claude/settings.json +20 -0
- package/templates/.claude/ukit/index/anchor-search.mjs +99 -0
- package/templates/.claude/ukit/index/lib/index-core.mjs +3 -0
- package/templates/.claude/ukit/index/post-edit-verify.mjs +206 -0
- package/templates/.claude/ukit/index/pre-edit-backup.mjs +84 -0
- package/templates/.claude/ukit/index/route-task.mjs +6 -0
- package/templates/.claude/ukit/index/safe-patch.mjs +97 -0
- package/templates/.claude/ukit/index/stale-spec-check.mjs +192 -0
- package/templates/.claude/ukit/runtime/safe-patch-core.mjs +140 -0
- package/templates/.claude/ukit/runtime/text-profile.mjs +139 -0
- package/templates/.codex/README.md +4 -3
- package/templates/.codex/settings.json +10 -11
- package/templates/.gitignore +0 -1
- package/templates/AGENTS.md +11 -4
- package/templates/CLAUDE.md +12 -4
- package/templates/ukit/README.md +1 -1
- package/templates/ukit/storage/config.json +153 -4
- package/templates/.claude/ukit/.env.example +0 -17
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,32 @@ All notable changes to UKit are documented here.
|
|
|
4
4
|
|
|
5
5
|
## Unreleased
|
|
6
6
|
|
|
7
|
+
## 1.3.0 - 2026-05-06
|
|
8
|
+
|
|
9
|
+
### Added
|
|
10
|
+
|
|
11
|
+
- Added UKit Safe Patch Protocol guardrails for risky AI edits: stale/ambiguous `old_string` detection, whole-file shared-risk `Write` blocking, anchor-search helper, safe patch helper, text-profile detection, and pre-edit rollback bytes for shared-risk files.
|
|
12
|
+
- Added post-edit delta verification for backed-up shared-risk edits so unexpected broad rewrites are flagged against the Safe Patch hunk/changed-line budget and rollback manifests record after hashes.
|
|
13
|
+
- Added Safe Patch backup retention and bounded delta-diff fallback so rollback data does not grow forever and very large edits avoid unbounded LCS memory use.
|
|
14
|
+
- Added byte-first UTF-8/BOM/newline profile handling so helper-mediated patches preserve UTF-8 BOM/no-BOM, LF/CRLF, Vietnamese/CJK/emoji content, and reject unsafe binary/invalid UTF-8 edit paths.
|
|
15
|
+
- Added `safePatch` runtime config defaults under `.ukit/storage/config.json` while keeping the end-user workflow unchanged: normal users still only need `ukit install`; helpers remain internal guardrails.
|
|
16
|
+
|
|
17
|
+
### Changed
|
|
18
|
+
|
|
19
|
+
- Promoted shared-risk edit routing to surface an internal `editGuard=anchor-required` hint for installed runtime/template targets.
|
|
20
|
+
- Extended impact-risk classification to installed runtime paths such as `.claude/hooks/**`, `.claude/ukit/**`, and `.codex/**`.
|
|
21
|
+
|
|
22
|
+
### Tests
|
|
23
|
+
|
|
24
|
+
- Added focused Safe Patch Protocol hook/helper coverage and install wiring coverage.
|
|
25
|
+
|
|
26
|
+
## 1.2.2 - 2026-05-05
|
|
27
|
+
|
|
28
|
+
- Removed the `.claude/ukit/.env` / `.env.example` config surface so UKit runtime tuning lives in `.ukit/storage/config.json` only.
|
|
29
|
+
- Added full Vietnamese `_help` guidance inside `templates/ukit/storage/config.json`, including common edits for changing the internal model and Codex compact thresholds.
|
|
30
|
+
- 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.
|
|
31
|
+
- Preserved Claude PreCompact/reinject and OpenCode native auto/prune compaction while keeping `ukit-small-task-maintainer` as a non-blocking sidecar lane.
|
|
32
|
+
|
|
7
33
|
## 1.2.1 - 2026-05-05
|
|
8
34
|
|
|
9
35
|
### Local AI Task Queue
|
|
@@ -13,7 +39,7 @@ All notable changes to UKit are documented here.
|
|
|
13
39
|
- 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
40
|
- 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
41
|
- 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 `
|
|
42
|
+
- 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.
|
|
17
43
|
|
|
18
44
|
## 1.1.8 - 2026-05-05
|
|
19
45
|
|
package/README.md
CHANGED
|
@@ -68,13 +68,14 @@ If maintainers roll out a newer CLI build, the in-project workflow still stays t
|
|
|
68
68
|
- `.ukit/` — hidden shared runtime storage for config, cache, and cross-agent memory
|
|
69
69
|
- `docs/` — PROJECT / MEMORY / STATUS / TASKS / WORKLOG baseline
|
|
70
70
|
|
|
71
|
-
## UKit v1.
|
|
71
|
+
## UKit v1.3.0 Runtime
|
|
72
72
|
|
|
73
73
|
UKit now installs a hidden shared local runtime at `.ukit/` for production-oriented state that should survive across agent sessions:
|
|
74
74
|
|
|
75
75
|
- `.ukit/storage/config.json` — runtime defaults for compact/router/memory/validation/subagent hints
|
|
76
76
|
- `.ukit/storage/cache/` — reusable prompt-cache, compact history, compact-pressure state, and output summaries
|
|
77
77
|
- `.ukit/storage/memory/` — cross-agent local memory
|
|
78
|
+
- `.ukit/storage/backups/` — rollback bytes for risky Safe Patch Protocol edits
|
|
78
79
|
|
|
79
80
|
If an older repo still has a visible legacy `ukit/` runtime folder, rerunning `ukit install` now migrates the shared runtime into hidden `.ukit/` when the target paths are free.
|
|
80
81
|
|
|
@@ -85,7 +86,7 @@ When long sessions approach the compact threshold, UKit now uses a conservative
|
|
|
85
86
|
- compact only safe-zone history/noise
|
|
86
87
|
- preserve active task, rules, decisions, and current code focus
|
|
87
88
|
|
|
88
|
-
UKit v1.
|
|
89
|
+
UKit v1.3.0 keeps the same shared runtime contract while adding Safe Patch Protocol guardrails and the local AI task queue alongside living project status routing:
|
|
89
90
|
|
|
90
91
|
- install globally with `npm install -g @ngockhoale/ukit`
|
|
91
92
|
- keep using the exact same human workflow inside projects: `ukit install`
|
|
@@ -95,8 +96,9 @@ UKit v1.2.1 keeps the same shared runtime contract while adding a local AI task
|
|
|
95
96
|
- auto-route open-ended “what next?” / “continue” prompts to the `next-step` skill with a visible freshness cue when status may be stale
|
|
96
97
|
- auto-route explicit handoff/wrap-up requests to the `update-status` skill while skipping trivial/no-state-change tasks
|
|
97
98
|
- keep concrete debug/implementation/review prompts primary, so project status never replaces source/index-first task work
|
|
99
|
+
- quietly guard risky AI edits with Safe Patch Protocol: stale/ambiguous specs are blocked, shared-risk whole-file writes are discouraged, and internal helpers preserve UTF-8 BOM/no-BOM plus LF/CRLF for multilingual text
|
|
98
100
|
|
|
99
|
-
UKit v1.
|
|
101
|
+
UKit v1.3.0 also keeps the fast path improvements from the recent runtime releases:
|
|
100
102
|
|
|
101
103
|
- Vietnamese prompts now normalize more effectively for English-heavy code symbols and paths
|
|
102
104
|
- localized simple direct-target lanes skip extra previous-context / recent-output work when it would not change the next action
|
|
@@ -131,8 +133,8 @@ UKit is built so the team can stop memorizing UKit subcommands and focus on prod
|
|
|
131
133
|
- let Claude Code / Codex / OpenCode auto-detect and use the right project-local skill from the prompt and the files/tools involved
|
|
132
134
|
- let the AI prefer targeted verification first, then widen only when shared/risky scope justifies it
|
|
133
135
|
- 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 `
|
|
135
|
-
- let UKit internally use the `ukit-small-task-maintainer` subagent with `
|
|
136
|
+
- 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
|
|
137
|
+
- 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
|
|
136
138
|
- rerun `ukit install` when you need to refresh the workspace
|
|
137
139
|
|
|
138
140
|
End users should **not** need to know or memorize skill names.
|
|
@@ -77,18 +77,6 @@ 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
|
-
|
|
92
80
|
- id: root-claude-md
|
|
93
81
|
type: config
|
|
94
82
|
sourceTemplate: CLAUDE.md
|
|
@@ -824,7 +812,7 @@ items:
|
|
|
824
812
|
sourceTemplate: .claude/agents/ukit-small-task-maintainer.md
|
|
825
813
|
targetPath: .claude/agents/ukit-small-task-maintainer.md
|
|
826
814
|
requires:
|
|
827
|
-
-
|
|
815
|
+
- runtime-config
|
|
828
816
|
mergeStrategy: overwrite_with_backup
|
|
829
817
|
variables:
|
|
830
818
|
- ukit.version
|
|
@@ -850,6 +838,9 @@ items:
|
|
|
850
838
|
targetPath: .claude/settings.json
|
|
851
839
|
requires:
|
|
852
840
|
- hook-protect-files
|
|
841
|
+
- hook-stale-spec-guard
|
|
842
|
+
- hook-pre-edit-backup
|
|
843
|
+
- hook-post-edit-verify
|
|
853
844
|
- hook-auto-allow-bash
|
|
854
845
|
- hook-block-dangerous
|
|
855
846
|
- hook-auto-prune-bash
|
|
@@ -881,6 +872,42 @@ items:
|
|
|
881
872
|
packs:
|
|
882
873
|
- core
|
|
883
874
|
|
|
875
|
+
- id: hook-stale-spec-guard
|
|
876
|
+
type: hook
|
|
877
|
+
sourceTemplate: .claude/hooks/stale-spec-guard.sh
|
|
878
|
+
targetPath: .claude/hooks/stale-spec-guard.sh
|
|
879
|
+
requires:
|
|
880
|
+
- ukit-index-stale-spec-check-script
|
|
881
|
+
mergeStrategy: overwrite_with_backup
|
|
882
|
+
variables: []
|
|
883
|
+
enabledByDefault: true
|
|
884
|
+
packs:
|
|
885
|
+
- core
|
|
886
|
+
|
|
887
|
+
- id: hook-pre-edit-backup
|
|
888
|
+
type: hook
|
|
889
|
+
sourceTemplate: .claude/hooks/pre-edit-backup.sh
|
|
890
|
+
targetPath: .claude/hooks/pre-edit-backup.sh
|
|
891
|
+
requires:
|
|
892
|
+
- ukit-index-pre-edit-backup-script
|
|
893
|
+
mergeStrategy: overwrite_with_backup
|
|
894
|
+
variables: []
|
|
895
|
+
enabledByDefault: true
|
|
896
|
+
packs:
|
|
897
|
+
- core
|
|
898
|
+
|
|
899
|
+
- id: hook-post-edit-verify
|
|
900
|
+
type: hook
|
|
901
|
+
sourceTemplate: .claude/hooks/post-edit-verify.sh
|
|
902
|
+
targetPath: .claude/hooks/post-edit-verify.sh
|
|
903
|
+
requires:
|
|
904
|
+
- ukit-index-post-edit-verify-script
|
|
905
|
+
mergeStrategy: overwrite_with_backup
|
|
906
|
+
variables: []
|
|
907
|
+
enabledByDefault: true
|
|
908
|
+
packs:
|
|
909
|
+
- core
|
|
910
|
+
|
|
884
911
|
- id: hook-auto-allow-bash
|
|
885
912
|
type: hook
|
|
886
913
|
sourceTemplate: .claude/hooks/auto-allow-bash.sh
|
|
@@ -973,6 +1000,93 @@ items:
|
|
|
973
1000
|
packs:
|
|
974
1001
|
- core
|
|
975
1002
|
|
|
1003
|
+
- id: ukit-runtime-text-profile-script
|
|
1004
|
+
type: config
|
|
1005
|
+
sourceTemplate: .claude/ukit/runtime/text-profile.mjs
|
|
1006
|
+
targetPath: .claude/ukit/runtime/text-profile.mjs
|
|
1007
|
+
requires: []
|
|
1008
|
+
mergeStrategy: overwrite_with_backup
|
|
1009
|
+
variables: []
|
|
1010
|
+
enabledByDefault: true
|
|
1011
|
+
packs:
|
|
1012
|
+
- core
|
|
1013
|
+
|
|
1014
|
+
- id: ukit-runtime-safe-patch-core-script
|
|
1015
|
+
type: config
|
|
1016
|
+
sourceTemplate: .claude/ukit/runtime/safe-patch-core.mjs
|
|
1017
|
+
targetPath: .claude/ukit/runtime/safe-patch-core.mjs
|
|
1018
|
+
requires:
|
|
1019
|
+
- ukit-runtime-text-profile-script
|
|
1020
|
+
mergeStrategy: overwrite_with_backup
|
|
1021
|
+
variables: []
|
|
1022
|
+
enabledByDefault: true
|
|
1023
|
+
packs:
|
|
1024
|
+
- core
|
|
1025
|
+
|
|
1026
|
+
- id: ukit-index-anchor-search-script
|
|
1027
|
+
type: config
|
|
1028
|
+
sourceTemplate: .claude/ukit/index/anchor-search.mjs
|
|
1029
|
+
targetPath: .claude/ukit/index/anchor-search.mjs
|
|
1030
|
+
requires:
|
|
1031
|
+
- ukit-runtime-text-profile-script
|
|
1032
|
+
- ukit-runtime-safe-patch-core-script
|
|
1033
|
+
mergeStrategy: overwrite_with_backup
|
|
1034
|
+
variables: []
|
|
1035
|
+
enabledByDefault: true
|
|
1036
|
+
packs:
|
|
1037
|
+
- core
|
|
1038
|
+
|
|
1039
|
+
- id: ukit-index-safe-patch-script
|
|
1040
|
+
type: config
|
|
1041
|
+
sourceTemplate: .claude/ukit/index/safe-patch.mjs
|
|
1042
|
+
targetPath: .claude/ukit/index/safe-patch.mjs
|
|
1043
|
+
requires:
|
|
1044
|
+
- ukit-index-anchor-search-script
|
|
1045
|
+
mergeStrategy: overwrite_with_backup
|
|
1046
|
+
variables: []
|
|
1047
|
+
enabledByDefault: true
|
|
1048
|
+
packs:
|
|
1049
|
+
- core
|
|
1050
|
+
|
|
1051
|
+
- id: ukit-index-stale-spec-check-script
|
|
1052
|
+
type: config
|
|
1053
|
+
sourceTemplate: .claude/ukit/index/stale-spec-check.mjs
|
|
1054
|
+
targetPath: .claude/ukit/index/stale-spec-check.mjs
|
|
1055
|
+
requires:
|
|
1056
|
+
- ukit-runtime-text-profile-script
|
|
1057
|
+
- ukit-runtime-safe-patch-core-script
|
|
1058
|
+
mergeStrategy: overwrite_with_backup
|
|
1059
|
+
variables: []
|
|
1060
|
+
enabledByDefault: true
|
|
1061
|
+
packs:
|
|
1062
|
+
- core
|
|
1063
|
+
|
|
1064
|
+
- id: ukit-index-pre-edit-backup-script
|
|
1065
|
+
type: config
|
|
1066
|
+
sourceTemplate: .claude/ukit/index/pre-edit-backup.mjs
|
|
1067
|
+
targetPath: .claude/ukit/index/pre-edit-backup.mjs
|
|
1068
|
+
requires:
|
|
1069
|
+
- ukit-runtime-text-profile-script
|
|
1070
|
+
- ukit-runtime-safe-patch-core-script
|
|
1071
|
+
mergeStrategy: overwrite_with_backup
|
|
1072
|
+
variables: []
|
|
1073
|
+
enabledByDefault: true
|
|
1074
|
+
packs:
|
|
1075
|
+
- core
|
|
1076
|
+
|
|
1077
|
+
- id: ukit-index-post-edit-verify-script
|
|
1078
|
+
type: config
|
|
1079
|
+
sourceTemplate: .claude/ukit/index/post-edit-verify.mjs
|
|
1080
|
+
targetPath: .claude/ukit/index/post-edit-verify.mjs
|
|
1081
|
+
requires:
|
|
1082
|
+
- ukit-runtime-text-profile-script
|
|
1083
|
+
- ukit-runtime-safe-patch-core-script
|
|
1084
|
+
mergeStrategy: overwrite_with_backup
|
|
1085
|
+
variables: []
|
|
1086
|
+
enabledByDefault: true
|
|
1087
|
+
packs:
|
|
1088
|
+
- core
|
|
1089
|
+
|
|
976
1090
|
- id: ukit-index-core-lib
|
|
977
1091
|
type: config
|
|
978
1092
|
sourceTemplate: .claude/ukit/index/lib/index-core.mjs
|
package/package.json
CHANGED
|
@@ -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) {
|
|
@@ -45,103 +45,11 @@ function pushNonEmptyStringError(errors, value, label) {
|
|
|
45
45
|
}
|
|
46
46
|
}
|
|
47
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
|
-
|
|
140
48
|
export function buildDefaultRuntimeConfig(overrides = {}) {
|
|
141
49
|
const safeOverrides = isPlainObject(overrides) ? overrides : {};
|
|
142
50
|
|
|
143
51
|
return mergeObjects({
|
|
144
|
-
version: '1.
|
|
52
|
+
version: '1.3.0',
|
|
145
53
|
agent: 'claude-code',
|
|
146
54
|
compact: {
|
|
147
55
|
enabled: true,
|
|
@@ -150,7 +58,6 @@ export function buildDefaultRuntimeConfig(overrides = {}) {
|
|
|
150
58
|
askBeforeDrop: true,
|
|
151
59
|
agentContext: {
|
|
152
60
|
enabled: true,
|
|
153
|
-
decisionModelEnv: 'UKIT_SMALL_TASK_MODEL',
|
|
154
61
|
decisionAgent: 'ukit-small-task-maintainer',
|
|
155
62
|
executionMode: 'sidecar-parallel',
|
|
156
63
|
mustNotBlockMainTask: true,
|
|
@@ -170,12 +77,11 @@ export function buildDefaultRuntimeConfig(overrides = {}) {
|
|
|
170
77
|
codexContext: {
|
|
171
78
|
enabled: true,
|
|
172
79
|
autoCompact: true,
|
|
173
|
-
budgetTokens:
|
|
80
|
+
budgetTokens: 100_000,
|
|
174
81
|
compactTarget: 150,
|
|
175
82
|
compactTargetUnit: 'lines',
|
|
176
83
|
compactTargetRecommendedRange: [120, 170],
|
|
177
84
|
compactTargetMax: 170,
|
|
178
|
-
decisionModelEnv: 'UKIT_SMALL_TASK_MODEL',
|
|
179
85
|
decisionAgent: 'ukit-small-task-maintainer',
|
|
180
86
|
mode: 'soft-handoff',
|
|
181
87
|
preserve: [
|
|
@@ -216,6 +122,17 @@ export function buildDefaultRuntimeConfig(overrides = {}) {
|
|
|
216
122
|
maxRetries: 1,
|
|
217
123
|
confidenceThreshold: 50,
|
|
218
124
|
},
|
|
125
|
+
safePatch: {
|
|
126
|
+
enabled: true,
|
|
127
|
+
strictSharedRisk: true,
|
|
128
|
+
largeFileLineThreshold: 800,
|
|
129
|
+
largeFileByteThreshold: 200_000,
|
|
130
|
+
backupEnabled: true,
|
|
131
|
+
backupRetentionDays: 30,
|
|
132
|
+
deltaMaxChangedLines: 120,
|
|
133
|
+
deltaMaxHunks: 3,
|
|
134
|
+
deltaMaxDiffCells: 2_000_000,
|
|
135
|
+
},
|
|
219
136
|
subagents: {
|
|
220
137
|
enabled: true,
|
|
221
138
|
smallTaskModel: 'unic-lite',
|
|
@@ -292,7 +209,6 @@ export function validateRuntimeConfig(config) {
|
|
|
292
209
|
errors.push('compact.agentContext must be an object.');
|
|
293
210
|
} else {
|
|
294
211
|
pushBooleanError(errors, config.compact.agentContext.enabled, 'compact.agentContext.enabled');
|
|
295
|
-
pushNonEmptyStringError(errors, config.compact.agentContext.decisionModelEnv, 'compact.agentContext.decisionModelEnv');
|
|
296
212
|
pushNonEmptyStringError(errors, config.compact.agentContext.decisionAgent, 'compact.agentContext.decisionAgent');
|
|
297
213
|
pushNonEmptyStringError(errors, config.compact.agentContext.executionMode, 'compact.agentContext.executionMode');
|
|
298
214
|
pushBooleanError(errors, config.compact.agentContext.mustNotBlockMainTask, 'compact.agentContext.mustNotBlockMainTask');
|
|
@@ -315,7 +231,6 @@ export function validateRuntimeConfig(config) {
|
|
|
315
231
|
if (Number(config.compact.codexContext.compactTarget) > Number(config.compact.codexContext.compactTargetMax)) {
|
|
316
232
|
errors.push('compact.codexContext.compactTarget must be <= compact.codexContext.compactTargetMax.');
|
|
317
233
|
}
|
|
318
|
-
pushNonEmptyStringError(errors, config.compact.codexContext.decisionModelEnv, 'compact.codexContext.decisionModelEnv');
|
|
319
234
|
pushNonEmptyStringError(errors, config.compact.codexContext.decisionAgent, 'compact.codexContext.decisionAgent');
|
|
320
235
|
pushNonEmptyStringError(errors, config.compact.codexContext.mode, 'compact.codexContext.mode');
|
|
321
236
|
if (!Array.isArray(config.compact.codexContext.preserve)) {
|
|
@@ -367,6 +282,20 @@ export function validateRuntimeConfig(config) {
|
|
|
367
282
|
pushPositiveNumberError(errors, config.validation.confidenceThreshold, 'validation.confidenceThreshold');
|
|
368
283
|
}
|
|
369
284
|
|
|
285
|
+
if (!isPlainObject(config.safePatch)) {
|
|
286
|
+
errors.push('safePatch must be an object.');
|
|
287
|
+
} else {
|
|
288
|
+
pushBooleanError(errors, config.safePatch.enabled, 'safePatch.enabled');
|
|
289
|
+
pushBooleanError(errors, config.safePatch.strictSharedRisk, 'safePatch.strictSharedRisk');
|
|
290
|
+
pushPositiveNumberError(errors, config.safePatch.largeFileLineThreshold, 'safePatch.largeFileLineThreshold');
|
|
291
|
+
pushPositiveNumberError(errors, config.safePatch.largeFileByteThreshold, 'safePatch.largeFileByteThreshold');
|
|
292
|
+
pushBooleanError(errors, config.safePatch.backupEnabled, 'safePatch.backupEnabled');
|
|
293
|
+
pushPositiveNumberError(errors, config.safePatch.backupRetentionDays, 'safePatch.backupRetentionDays');
|
|
294
|
+
pushPositiveNumberError(errors, config.safePatch.deltaMaxChangedLines, 'safePatch.deltaMaxChangedLines');
|
|
295
|
+
pushPositiveNumberError(errors, config.safePatch.deltaMaxHunks, 'safePatch.deltaMaxHunks');
|
|
296
|
+
pushPositiveNumberError(errors, config.safePatch.deltaMaxDiffCells, 'safePatch.deltaMaxDiffCells');
|
|
297
|
+
}
|
|
298
|
+
|
|
370
299
|
if (!isPlainObject(config.subagents)) {
|
|
371
300
|
errors.push('subagents must be an object.');
|
|
372
301
|
} else {
|
|
@@ -407,31 +336,19 @@ export async function inspectRuntimeConfig(projectRoot) {
|
|
|
407
336
|
}
|
|
408
337
|
}
|
|
409
338
|
|
|
410
|
-
|
|
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
|
-
);
|
|
339
|
+
const config = buildDefaultRuntimeConfig(rawConfig ?? {});
|
|
421
340
|
const validation = validateRuntimeConfig(config);
|
|
422
341
|
const errors = [
|
|
423
342
|
...(parseError ? [`config.json parse error: ${parseError}`] : []),
|
|
424
|
-
...(envError ? [`runtime .env error: ${envError}`] : []),
|
|
425
343
|
...validation.errors,
|
|
426
344
|
];
|
|
427
345
|
|
|
428
346
|
return {
|
|
429
347
|
exists,
|
|
430
348
|
rawConfig,
|
|
431
|
-
runtimeEnv,
|
|
432
349
|
config,
|
|
433
350
|
parseError,
|
|
434
|
-
valid: exists && !parseError &&
|
|
351
|
+
valid: exists && !parseError && validation.valid,
|
|
435
352
|
errors,
|
|
436
353
|
};
|
|
437
354
|
}
|
|
@@ -1,4 +1,16 @@
|
|
|
1
1
|
const SHARED_IMPACT_PATTERNS = [
|
|
2
|
+
{
|
|
3
|
+
regex: /^\.claude\/hooks\//,
|
|
4
|
+
label: 'installed-hook-runtime',
|
|
5
|
+
},
|
|
6
|
+
{
|
|
7
|
+
regex: /^\.claude\/ukit\//,
|
|
8
|
+
label: 'installed-helper-runtime',
|
|
9
|
+
},
|
|
10
|
+
{
|
|
11
|
+
regex: /^\.codex\//,
|
|
12
|
+
label: 'installed-codex-runtime',
|
|
13
|
+
},
|
|
2
14
|
{
|
|
3
15
|
regex: /^src\/index\//,
|
|
4
16
|
label: 'shared-index-runtime',
|
package/src/index/taskRouting.js
CHANGED
|
@@ -157,6 +157,7 @@ export function buildRouteSummary({
|
|
|
157
157
|
?? [...primaryCommands, ...fallbackCommands],
|
|
158
158
|
);
|
|
159
159
|
const policyMode = verificationRecommendation?.executionPolicy?.policyMode ?? null;
|
|
160
|
+
const editGuardHint = isSharedImpactFile(routingContext.targetFile) ? 'anchor-required' : null;
|
|
160
161
|
const compactHelperLane = nextAction?.type === 'pull-indexed-context'
|
|
161
162
|
&& typeof contextRecommendation?.command === 'string'
|
|
162
163
|
&& contextRecommendation.command.trim();
|
|
@@ -175,6 +176,7 @@ export function buildRouteSummary({
|
|
|
175
176
|
formatCompactSegment('targets', primaryTargets),
|
|
176
177
|
formatCompactSegment('tests', relatedTests),
|
|
177
178
|
formatCompactSegment('styles', styleFiles),
|
|
179
|
+
editGuardHint ? `editGuard=${editGuardHint}` : null,
|
|
178
180
|
delegationRecommendation?.hint ? `delegate=${delegationRecommendation.hint}` : null,
|
|
179
181
|
policyMode ? `policy=${policyMode}` : null,
|
|
180
182
|
].filter(Boolean).join(' | ');
|
|
@@ -184,6 +186,7 @@ export function buildRouteSummary({
|
|
|
184
186
|
fallbackCommands,
|
|
185
187
|
preferredOrder,
|
|
186
188
|
policyMode,
|
|
189
|
+
editGuardHint,
|
|
187
190
|
intentMode: routingContext.intentMode ?? null,
|
|
188
191
|
delegateHint: delegationRecommendation?.hint ?? null,
|
|
189
192
|
nextActionType: nextAction?.type ?? null,
|
|
@@ -6,13 +6,13 @@ color: cyan
|
|
|
6
6
|
tools: ["Read", "Grep", "Glob", "Edit", "Write"]
|
|
7
7
|
---
|
|
8
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`. You must follow `docs/UKIT_CODEV_PRINCIPLES.md`: hide complexity, preserve output quality, and never trade correctness for speed or token savings.
|
|
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
10
|
|
|
11
11
|
## Model Policy
|
|
12
12
|
|
|
13
|
-
- Use the model selected by `
|
|
13
|
+
- Use the model selected by `subagents.smallTaskModel` when the host supports model selection for this subagent.
|
|
14
14
|
- Default intended model: `unic-lite`.
|
|
15
|
-
- If the host cannot bind a model from
|
|
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
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
17
|
|
|
18
18
|
## Use For
|
|
@@ -24,7 +24,7 @@ You are UKit's internal small-task maintainer. You run as a sidecar/parallel/non
|
|
|
24
24
|
- Summarizing `docs/STATUS.md`, `docs/TASKS.md`, `docs/WORKLOG.md`, or memory snippets into compact handoff notes.
|
|
25
25
|
- Deciding fast vs slow lane, safe vs risky lane, whether a skill should be activated, and whether the current prompt has enough steps planned.
|
|
26
26
|
- Choosing compact-now vs compact-later and summarize-vs-keep-detail for UKit context hygiene.
|
|
27
|
-
- Keeping agent context compact without removing existing lanes: Claude PreCompact/reinject stays active, OpenCode native auto/prune compaction stays active, and Codex Desktop soft handoffs use `
|
|
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
28
|
- Small, reversible UKit runtime maintenance decisions.
|
|
29
29
|
|
|
30
30
|
## Never Use For
|
|
@@ -53,7 +53,7 @@ You are UKit's internal small-task maintainer. You run as a sidecar/parallel/non
|
|
|
53
53
|
|
|
54
54
|
```
|
|
55
55
|
STATUS: DONE | SKIPPED | HAND_BACK
|
|
56
|
-
MODEL_HINT:
|
|
56
|
+
MODEL_HINT: subagents.smallTaskModel=<value-or-default>
|
|
57
57
|
SUMMARY: [one sentence]
|
|
58
58
|
CHANGES:
|
|
59
59
|
- [file/area]: [what changed]
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# PostToolUse hook: verify risky Edit|Write delta after rollback bytes exist.
|
|
3
|
+
# Matched on: Edit|Write
|
|
4
|
+
|
|
5
|
+
INPUT=$(cat)
|
|
6
|
+
PROJECT_ROOT="${CLAUDE_PROJECT_DIR:-$(pwd)}"
|
|
7
|
+
SCRIPT="$PROJECT_ROOT/.claude/ukit/index/post-edit-verify.mjs"
|
|
8
|
+
|
|
9
|
+
if [ ! -f "$SCRIPT" ]; then
|
|
10
|
+
exit 0
|
|
11
|
+
fi
|
|
12
|
+
|
|
13
|
+
printf '%s' "$INPUT" | node "$SCRIPT"
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# PreToolUse hook: create rollback bytes for risky Edit|Write operations.
|
|
3
|
+
# Matched on: Edit|Write
|
|
4
|
+
|
|
5
|
+
INPUT=$(cat)
|
|
6
|
+
PROJECT_ROOT="${CLAUDE_PROJECT_DIR:-$(pwd)}"
|
|
7
|
+
SCRIPT="$PROJECT_ROOT/.claude/ukit/index/pre-edit-backup.mjs"
|
|
8
|
+
|
|
9
|
+
if [ ! -f "$SCRIPT" ]; then
|
|
10
|
+
exit 0
|
|
11
|
+
fi
|
|
12
|
+
|
|
13
|
+
printf '%s' "$INPUT" | node "$SCRIPT"
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# PreToolUse hook: prevent stale/ambiguous risky Edit specs and whole-file risky Write.
|
|
3
|
+
# Matched on: Edit|Write
|
|
4
|
+
|
|
5
|
+
INPUT=$(cat)
|
|
6
|
+
PROJECT_ROOT="${CLAUDE_PROJECT_DIR:-$(pwd)}"
|
|
7
|
+
SCRIPT="$PROJECT_ROOT/.claude/ukit/index/stale-spec-check.mjs"
|
|
8
|
+
|
|
9
|
+
if [ ! -f "$SCRIPT" ]; then
|
|
10
|
+
exit 0
|
|
11
|
+
fi
|
|
12
|
+
|
|
13
|
+
printf '%s' "$INPUT" | node "$SCRIPT"
|
|
@@ -62,6 +62,16 @@
|
|
|
62
62
|
"command": "\"$CLAUDE_PROJECT_DIR/.claude/hooks/protect-files.sh\"",
|
|
63
63
|
"timeout": 8
|
|
64
64
|
},
|
|
65
|
+
{
|
|
66
|
+
"type": "command",
|
|
67
|
+
"command": "\"$CLAUDE_PROJECT_DIR/.claude/hooks/stale-spec-guard.sh\"",
|
|
68
|
+
"timeout": 8
|
|
69
|
+
},
|
|
70
|
+
{
|
|
71
|
+
"type": "command",
|
|
72
|
+
"command": "\"$CLAUDE_PROJECT_DIR/.claude/hooks/pre-edit-backup.sh\"",
|
|
73
|
+
"timeout": 8
|
|
74
|
+
},
|
|
65
75
|
{
|
|
66
76
|
"type": "command",
|
|
67
77
|
"command": "\"$CLAUDE_PROJECT_DIR/.claude/hooks/skill-router.sh\"",
|
|
@@ -96,6 +106,16 @@
|
|
|
96
106
|
}
|
|
97
107
|
],
|
|
98
108
|
"PostToolUse": [
|
|
109
|
+
{
|
|
110
|
+
"matcher": "Edit|Write",
|
|
111
|
+
"hooks": [
|
|
112
|
+
{
|
|
113
|
+
"type": "command",
|
|
114
|
+
"command": "\"$CLAUDE_PROJECT_DIR/.claude/hooks/post-edit-verify.sh\"",
|
|
115
|
+
"timeout": 8
|
|
116
|
+
}
|
|
117
|
+
]
|
|
118
|
+
},
|
|
99
119
|
{
|
|
100
120
|
"matcher": "Bash",
|
|
101
121
|
"hooks": [
|