@ngockhoale/ukit 1.4.4 → 1.5.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 +23 -0
- package/package.json +1 -1
- package/src/core/runtimeConfig.js +3 -1
- package/templates/.claude/ukit/index/post-edit-verify.mjs +12 -3
- package/templates/.claude/ukit/index/stale-spec-check.mjs +12 -2
- package/templates/.claude/ukit/runtime/safe-patch-core.mjs +10 -0
- package/templates/ukit/README.md +1 -1
- package/templates/ukit/storage/config.json +3 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,29 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to UKit are documented here.
|
|
4
4
|
|
|
5
|
+
## 1.5.0 - 2026-05-24
|
|
6
|
+
|
|
7
|
+
### Fixed
|
|
8
|
+
|
|
9
|
+
- Switched safe-patch release regressions to `spawnSync`-based helpers so advisory-mode assertions capture `stderr` correctly even when the hook exits `0`.
|
|
10
|
+
- Raised the packed artifact size budget slightly to preserve the shipped canvas font lane and recent runtime/router surfaces without failing release smoke on a 625-byte overage.
|
|
11
|
+
|
|
12
|
+
### Changed
|
|
13
|
+
|
|
14
|
+
- Bumped package and shared runtime version metadata to `1.5.0`.
|
|
15
|
+
|
|
16
|
+
## 1.4.5 - 2026-05-24
|
|
17
|
+
|
|
18
|
+
### Fixed
|
|
19
|
+
|
|
20
|
+
- Stopped safe-patch hooks (`stale-spec-check`, `post-edit-verify`) from hard-blocking tool calls when configured via the new `safePatch.advisoryOnly` flag or the `UKIT_SAFE_PATCH_ADVISORY=1` env. Removes the mid-task "stop and wait for continue" stalls that hit shared-risk files in `.claude/`, `templates/`, `scripts/`, etc., while preserving the diagnostic message so Claude/Codex can self-correct instead of stalling.
|
|
21
|
+
- Default behavior unchanged (`advisoryOnly: false`): pre-edit corruption guards (shebang-bom, non-text, whole-file Write to shared-risk) and the `protect-files` / `block-dangerous` hooks still hard-block.
|
|
22
|
+
|
|
23
|
+
### Tests
|
|
24
|
+
|
|
25
|
+
- Added regression coverage for advisory-mode exit code and stderr in `tests/hooks/safePatchProtocol.test.js`.
|
|
26
|
+
- Added default + validation assertions for `safePatch.advisoryOnly` in `tests/core/runtimeConfig.test.js`.
|
|
27
|
+
|
|
5
28
|
## 1.4.4 - 2026-05-11
|
|
6
29
|
|
|
7
30
|
### Fixed
|
package/package.json
CHANGED
|
@@ -49,7 +49,7 @@ export function buildDefaultRuntimeConfig(overrides = {}) {
|
|
|
49
49
|
const safeOverrides = isPlainObject(overrides) ? overrides : {};
|
|
50
50
|
|
|
51
51
|
return mergeObjects({
|
|
52
|
-
version: '1.
|
|
52
|
+
version: '1.5.0',
|
|
53
53
|
agent: 'claude-code',
|
|
54
54
|
autonomy: {
|
|
55
55
|
level: 'balanced',
|
|
@@ -183,6 +183,7 @@ export function buildDefaultRuntimeConfig(overrides = {}) {
|
|
|
183
183
|
safePatch: {
|
|
184
184
|
enabled: true,
|
|
185
185
|
strictSharedRisk: true,
|
|
186
|
+
advisoryOnly: false,
|
|
186
187
|
largeFileLineThreshold: 800,
|
|
187
188
|
largeFileByteThreshold: 200_000,
|
|
188
189
|
backupEnabled: true,
|
|
@@ -368,6 +369,7 @@ export function validateRuntimeConfig(config) {
|
|
|
368
369
|
} else {
|
|
369
370
|
pushBooleanError(errors, config.safePatch.enabled, 'safePatch.enabled');
|
|
370
371
|
pushBooleanError(errors, config.safePatch.strictSharedRisk, 'safePatch.strictSharedRisk');
|
|
372
|
+
pushBooleanError(errors, config.safePatch.advisoryOnly, 'safePatch.advisoryOnly');
|
|
371
373
|
pushPositiveNumberError(errors, config.safePatch.largeFileLineThreshold, 'safePatch.largeFileLineThreshold');
|
|
372
374
|
pushPositiveNumberError(errors, config.safePatch.largeFileByteThreshold, 'safePatch.largeFileByteThreshold');
|
|
373
375
|
pushBooleanError(errors, config.safePatch.backupEnabled, 'safePatch.backupEnabled');
|
|
@@ -2,7 +2,7 @@ import fs from 'node:fs/promises';
|
|
|
2
2
|
import path from 'node:path';
|
|
3
3
|
import { fileURLToPath } from 'node:url';
|
|
4
4
|
import { analyzeTextBuffer, analyzeTextFile, publicTextProfile } from '../runtime/text-profile.mjs';
|
|
5
|
-
import { classifySafePatchRisk, parseJsonInput, pathExists, readRuntimeSafePatchConfig, resolveProjectFile } from '../runtime/safe-patch-core.mjs';
|
|
5
|
+
import { classifySafePatchRisk, isSafePatchAdvisoryOnly, parseJsonInput, pathExists, readRuntimeSafePatchConfig, resolveProjectFile } from '../runtime/safe-patch-core.mjs';
|
|
6
6
|
|
|
7
7
|
async function readStdin() {
|
|
8
8
|
return await new Promise((resolve) => {
|
|
@@ -183,8 +183,10 @@ async function main() {
|
|
|
183
183
|
process.stdout.write(json ? `${JSON.stringify({ help: text })}\n` : `${text}\n`);
|
|
184
184
|
return;
|
|
185
185
|
}
|
|
186
|
+
const projectRoot = process.env.CLAUDE_PROJECT_DIR || process.cwd();
|
|
187
|
+
const runtimeConfig = await readRuntimeSafePatchConfig(projectRoot);
|
|
186
188
|
const result = await verifyPostEdit({
|
|
187
|
-
projectRoot
|
|
189
|
+
projectRoot,
|
|
188
190
|
payload: parseJsonInput(await readStdin(), {}),
|
|
189
191
|
});
|
|
190
192
|
if (json) {
|
|
@@ -193,7 +195,14 @@ async function main() {
|
|
|
193
195
|
process.stdout.write(`[ukit-safe-patch] ${result.message}\n`);
|
|
194
196
|
}
|
|
195
197
|
if (result.status === 'blocked') {
|
|
196
|
-
|
|
198
|
+
const advisory = isSafePatchAdvisoryOnly(runtimeConfig);
|
|
199
|
+
const prefix = advisory ? 'ADVISORY' : 'BLOCKED';
|
|
200
|
+
const message = String(result.message || '').replace(/^BLOCKED:/, `${prefix}:`);
|
|
201
|
+
process.stderr.write(`[ukit-safe-patch] ${message}\n`);
|
|
202
|
+
if (advisory) {
|
|
203
|
+
process.stderr.write('[ukit-safe-patch] advisoryOnly=true — change is already written; continue planned follow-up without stopping.\n');
|
|
204
|
+
return;
|
|
205
|
+
}
|
|
197
206
|
process.exit(2);
|
|
198
207
|
}
|
|
199
208
|
}
|
|
@@ -4,6 +4,7 @@ import path from 'node:path';
|
|
|
4
4
|
import {
|
|
5
5
|
classifySafePatchRisk,
|
|
6
6
|
countOccurrences,
|
|
7
|
+
isSafePatchAdvisoryOnly,
|
|
7
8
|
lineNumberForIndex,
|
|
8
9
|
parseJsonInput,
|
|
9
10
|
pathExists,
|
|
@@ -169,7 +170,9 @@ async function main() {
|
|
|
169
170
|
return;
|
|
170
171
|
}
|
|
171
172
|
const payload = parseJsonInput(await readStdin(), {});
|
|
172
|
-
const
|
|
173
|
+
const projectRoot = process.env.CLAUDE_PROJECT_DIR || process.cwd();
|
|
174
|
+
const runtimeConfig = await readRuntimeSafePatchConfig(projectRoot);
|
|
175
|
+
const result = await checkStaleSpec({ projectRoot, payload });
|
|
173
176
|
if (args.json) {
|
|
174
177
|
process.stdout.write(`${JSON.stringify(result)}\n`);
|
|
175
178
|
} else if (result.status === 'ok') {
|
|
@@ -179,7 +182,14 @@ async function main() {
|
|
|
179
182
|
}
|
|
180
183
|
|
|
181
184
|
if (result.status === 'blocked') {
|
|
182
|
-
|
|
185
|
+
const advisory = isSafePatchAdvisoryOnly(runtimeConfig);
|
|
186
|
+
const prefix = advisory ? 'ADVISORY' : 'BLOCKED';
|
|
187
|
+
const message = String(result.message || '').replace(/^BLOCKED:/, `${prefix}:`);
|
|
188
|
+
process.stderr.write(`[ukit-safe-patch] ${message}\n`);
|
|
189
|
+
if (advisory) {
|
|
190
|
+
process.stderr.write('[ukit-safe-patch] advisoryOnly=true — continue with corrected old_string; do not stop and ask the user.\n');
|
|
191
|
+
return;
|
|
192
|
+
}
|
|
183
193
|
process.exit(2);
|
|
184
194
|
}
|
|
185
195
|
}
|
|
@@ -4,6 +4,7 @@ import path from 'node:path';
|
|
|
4
4
|
export const DEFAULT_SAFE_PATCH_CONFIG = {
|
|
5
5
|
enabled: true,
|
|
6
6
|
strictSharedRisk: true,
|
|
7
|
+
advisoryOnly: false,
|
|
7
8
|
largeFileLineThreshold: 800,
|
|
8
9
|
largeFileByteThreshold: 200_000,
|
|
9
10
|
backupEnabled: true,
|
|
@@ -13,6 +14,15 @@ export const DEFAULT_SAFE_PATCH_CONFIG = {
|
|
|
13
14
|
deltaMaxDiffCells: 2_000_000,
|
|
14
15
|
};
|
|
15
16
|
|
|
17
|
+
// When advisoryOnly is true (via config or env UKIT_SAFE_PATCH_ADVISORY=1),
|
|
18
|
+
// safe-patch hooks downgrade exit 2 (block) to exit 0 + WARNING. This keeps
|
|
19
|
+
// Claude/Codex from stalling mid-task on stale/ambiguous spec or delta-budget
|
|
20
|
+
// trips without losing the diagnostic message.
|
|
21
|
+
export function isSafePatchAdvisoryOnly(config = DEFAULT_SAFE_PATCH_CONFIG) {
|
|
22
|
+
if (process.env.UKIT_SAFE_PATCH_ADVISORY === '1') return true;
|
|
23
|
+
return Boolean(config?.advisoryOnly);
|
|
24
|
+
}
|
|
25
|
+
|
|
16
26
|
const SHARED_RISK_PATTERNS = [
|
|
17
27
|
/^\.claude\/hooks\//,
|
|
18
28
|
/^\.claude\/ukit\//,
|
package/templates/ukit/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# UKit Shared Runtime
|
|
2
2
|
|
|
3
|
-
This folder stores shared UKit runtime state for v1.
|
|
3
|
+
This folder stores shared UKit runtime state for v1.5.0 features.
|
|
4
4
|
|
|
5
5
|
- `storage/config.json` — runtime feature flags and defaults
|
|
6
6
|
- `storage/cache/` — prompt-cache, compact history, compact pressure state, output summaries, and preserved raw tool outputs under `storage/cache/tee/`
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
{
|
|
2
|
-
"version": "1.
|
|
2
|
+
"version": "1.5.0",
|
|
3
3
|
"agent": "claude-code",
|
|
4
4
|
"autonomy": {
|
|
5
5
|
"level": "balanced",
|
|
@@ -147,6 +147,7 @@
|
|
|
147
147
|
"safePatch": {
|
|
148
148
|
"enabled": true,
|
|
149
149
|
"strictSharedRisk": true,
|
|
150
|
+
"advisoryOnly": false,
|
|
150
151
|
"largeFileLineThreshold": 800,
|
|
151
152
|
"largeFileByteThreshold": 200000,
|
|
152
153
|
"backupEnabled": true,
|
|
@@ -365,6 +366,7 @@
|
|
|
365
366
|
"safePatch": {
|
|
366
367
|
"enabled": "Bật Safe Patch Protocol: UKit âm thầm chặn patch stale/ambiguous trên file rủi ro và giữ an toàn encoding khi helper nội bộ sửa file.",
|
|
367
368
|
"strictSharedRisk": "Nếu true, file runtime/shared-risk như .claude/hooks hoặc .claude/ukit sẽ bị guard chặt hơn; người dùng vẫn không cần nhớ command mới.",
|
|
369
|
+
"advisoryOnly": "Nếu true (hoặc env UKIT_SAFE_PATCH_ADVISORY=1), guard chỉ in WARNING thay vì exit 2 — tránh Claude/Codex dừng giữa chừng chờ user 'continue'. Vẫn giữ message để model tự retry.",
|
|
368
370
|
"largeFileLineThreshold": "Số dòng từ đó file được xem là lớn và cần anchor/spec guard kỹ hơn.",
|
|
369
371
|
"largeFileByteThreshold": "Kích thước byte từ đó file được xem là lớn/rủi ro khi agent edit.",
|
|
370
372
|
"backupEnabled": "Nếu true, UKit tạo rollback bytes dưới .ukit/storage/backups cho edit rủi ro.",
|