@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.
Files changed (30) hide show
  1. package/CHANGELOG.md +27 -1
  2. package/README.md +7 -5
  3. package/manifests/platform.full.yaml +127 -13
  4. package/package.json +1 -1
  5. package/src/core/runInstallPipeline.js +6 -1
  6. package/src/core/runtimeConfig.js +29 -112
  7. package/src/index/impactCatalog.js +12 -0
  8. package/src/index/taskRouting.js +3 -0
  9. package/templates/.claude/agents/ukit-small-task-maintainer.md +5 -5
  10. package/templates/.claude/hooks/post-edit-verify.sh +13 -0
  11. package/templates/.claude/hooks/pre-edit-backup.sh +13 -0
  12. package/templates/.claude/hooks/stale-spec-guard.sh +13 -0
  13. package/templates/.claude/settings.json +20 -0
  14. package/templates/.claude/ukit/index/anchor-search.mjs +99 -0
  15. package/templates/.claude/ukit/index/lib/index-core.mjs +3 -0
  16. package/templates/.claude/ukit/index/post-edit-verify.mjs +206 -0
  17. package/templates/.claude/ukit/index/pre-edit-backup.mjs +84 -0
  18. package/templates/.claude/ukit/index/route-task.mjs +6 -0
  19. package/templates/.claude/ukit/index/safe-patch.mjs +97 -0
  20. package/templates/.claude/ukit/index/stale-spec-check.mjs +192 -0
  21. package/templates/.claude/ukit/runtime/safe-patch-core.mjs +140 -0
  22. package/templates/.claude/ukit/runtime/text-profile.mjs +139 -0
  23. package/templates/.codex/README.md +4 -3
  24. package/templates/.codex/settings.json +10 -11
  25. package/templates/.gitignore +0 -1
  26. package/templates/AGENTS.md +11 -4
  27. package/templates/CLAUDE.md +12 -4
  28. package/templates/ukit/README.md +1 -1
  29. package/templates/ukit/storage/config.json +153 -4
  30. 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 `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.
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.2.1 Runtime
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.2.1 keeps the same shared runtime contract while adding a local AI task queue alongside living project status routing:
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.2.1 also keeps the fast path improvements from the recent runtime releases:
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 `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
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
- - ukit-env-example
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ngockhoale/ukit",
3
- "version": "1.2.1",
3
+ "version": "1.3.0",
4
4
  "description": "Install/update an index-first AI workspace for Claude Code, Antigravity, OpenAI Codex, and OpenCode.",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -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 AUTO_PRUNE_OBSOLETE_PREFIXES.some((prefix) => relativePath.startsWith(prefix));
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.2.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: 60_000,
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
- 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
- );
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 && !envError && validation.valid,
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',
@@ -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 `UKIT_SMALL_TASK_MODEL` when the host supports model selection for this subagent.
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 env directly, still follow this role and report that the configured model is a hint.
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 `UKIT_CODEX_COMPACT_TARGET` (default 150 lines; preferred 120-150; hard max 170) while preserving critical state.
27
+ - Keeping agent context compact without removing existing lanes: Claude PreCompact/reinject stays active, 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: UKIT_SMALL_TASK_MODEL=<value-or-default>
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": [