@ngockhoale/ukit 2.1.0 → 2.1.3
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
CHANGED
|
@@ -2,6 +2,41 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to UKit are documented here.
|
|
4
4
|
|
|
5
|
+
## 2.1.3 - 2026-08-16
|
|
6
|
+
|
|
7
|
+
The reason auto-compact never fired in the terminal was UKit itself. This makes it fire.
|
|
8
|
+
|
|
9
|
+
### Fixed
|
|
10
|
+
|
|
11
|
+
- **Auto-compact now triggers below the hard cap instead of never.** `context-hardcap-gate.sh` refuses Edit/Write/Bash at `compact.hardCapTokens` (160,000 — 80% of a 200k window), while Claude Code's own auto-compact fires near the real window limit. The gate froze the session first, so the transcript stopped growing, so the auto-compact threshold was never reached: a deadlock whose only exit was typing `/compact` by hand. Default settings now ship `env.CLAUDE_CODE_AUTO_COMPACT_WINDOW = 150000`, which lands 10,000 tokens *before* the gate — compaction happens on its own, the run keeps going, and the grace window added in 2.1.2 stays as the backstop rather than the normal path.
|
|
12
|
+
- **`autoCompactEnabled: true` is pinned in default settings**, so a stale toggle in `/config` cannot silently reinstate the deadlock.
|
|
13
|
+
|
|
14
|
+
### Added
|
|
15
|
+
|
|
16
|
+
- **`tests/core/autoCompactWindow.test.js`** — locks the ordering invariant `env.CLAUDE_CODE_AUTO_COMPACT_WINDOW < compact.hardCapTokens`, the 100,000–1,000,000 range Claude Code accepts, `autoCompactEnabled`, template/mirror parity, and the `overwrite_with_backup` merge strategy that lets existing installs pick the fix up. Change either number in isolation and the suite fails instead of the deadlock returning silently.
|
|
17
|
+
|
|
18
|
+
### Notes
|
|
19
|
+
|
|
20
|
+
- `env` in `settings.json` applies to **new sessions**. After `npm i -g @ngockhoale/ukit@latest && ukit install`, restart the session (or `/compact` once) before the change takes effect.
|
|
21
|
+
- Still true, and unchanged: no hook can invoke `/compact` — it is client-only. 2.1.3 does not call it; it moves the client's own threshold below UKit's gate so the client compacts on its own.
|
|
22
|
+
|
|
23
|
+
## 2.1.2 - 2026-08-16
|
|
24
|
+
|
|
25
|
+
Follow-up to 2.1.0, which fixed the wrong hook. Runs were still freezing mid-task because the gate that actually blocks tools was never touched.
|
|
26
|
+
|
|
27
|
+
### Fixed
|
|
28
|
+
|
|
29
|
+
- **`context-hardcap-gate.sh` gets a grace window for unfinished runs.** 2.1.0 made the *advisory* `context-window-guard.sh` (UserPromptSubmit) run-cursor aware, but the halt users hit came from `context-hardcap-gate.sh` — a separate PreToolUse hook that hard-refuses Edit/Write/Bash with `exit 2` once `estimatedTotalTokens >= compact.hardCapTokens`. It knew nothing about `docs/AI_HANDOFF/RUN.md`, so it froze runs mid-edit exactly as before 2.1.0. It now reads the run cursor and, while a run is unfinished, allows a bounded `compact.hardCapGraceCalls` (default 10) further calls with an explicit "spend this on landing, not new work" directive — finish the edit, commit, write the cursor, push — then blocks hard as it always did.
|
|
30
|
+
|
|
31
|
+
### Added
|
|
32
|
+
|
|
33
|
+
- **`compact.hardCapGraceCalls`** (default `10`). Budget is per over-cap episode, not per wave: it resets only when the token estimate actually falls (a real compaction happened), so a long run cannot mint itself unlimited grace by advancing its cursor. With no unfinished run the gate's behavior is unchanged.
|
|
34
|
+
- **`tests/hooks/contextHardcapGate.test.js`** — 12 cases covering under-cap pass-through, blocking with no run in flight, `Phase: done` treated as no run, grace grant/exhaustion, no top-up on cursor advance, budget restored after a real compaction, `hardCapBlock=false` escape hatch, non-gated tools, fail-open on malformed input, template/mirror parity, and manifest+settings wiring.
|
|
35
|
+
|
|
36
|
+
### Notes
|
|
37
|
+
|
|
38
|
+
- **No hook can call `/compact`** — it is a client-only command, so UKit cannot auto-compact on your behalf. The supported path is: land inside the grace window → user runs `/compact` (or Claude Code's own auto-compact fires) → the `handoff-resume.sh` SessionStart hook replays the cursor → the run continues. Enabling auto-compact in Claude Code's `/config` makes that middle step automatic.
|
|
39
|
+
|
|
5
40
|
## 2.1.0 - 2026-08-16
|
|
6
41
|
|
|
7
42
|
Handoff autonomy wave: `/ukit:handoff-fullstack` runs a whole goal end-to-end without stopping to ask. Questions are collected once, up front, in the planning phase; everything that used to be a mid-run STOP now resolves automatically and keeps going.
|
package/package.json
CHANGED
|
@@ -16,6 +16,14 @@
|
|
|
16
16
|
# just brick the session with no way out, since only the user (not the agent) can invoke
|
|
17
17
|
# real compaction.
|
|
18
18
|
#
|
|
19
|
+
# Grace window (compact.hardCapGraceCalls, default 10): blocking the very first tool call
|
|
20
|
+
# past the cap strands a handoff run mid-edit — files half-written, nothing committed, no
|
|
21
|
+
# cursor — which is strictly worse than letting it land. When docs/AI_HANDOFF/RUN.md shows
|
|
22
|
+
# an unfinished run, the gate therefore allows a BOUNDED number of further calls so the run
|
|
23
|
+
# can commit, write its cursor and push; after that it blocks exactly as before. The budget
|
|
24
|
+
# is per over-cap episode, not per wave: it only resets when the estimate actually drops
|
|
25
|
+
# (i.e. a real compaction happened), so a run cannot mint itself fresh grace forever.
|
|
26
|
+
#
|
|
19
27
|
# Config toggle: compact.hardCapBlock (default true). Set to false only to debug this
|
|
20
28
|
# gate itself; it must not become a normal escape hatch.
|
|
21
29
|
|
|
@@ -59,6 +67,22 @@ function readJsonSafe(filePath, fallback = null) {
|
|
|
59
67
|
}
|
|
60
68
|
}
|
|
61
69
|
|
|
70
|
+
// An unfinished handoff run — same cursor file the resume hook and the advisory
|
|
71
|
+
// context guard read. `Phase: done` (or no file) means there is nothing in flight.
|
|
72
|
+
function readRunCursor() {
|
|
73
|
+
try {
|
|
74
|
+
const text = fs.readFileSync(path.join(projectRoot, 'docs', 'AI_HANDOFF', 'RUN.md'), 'utf8');
|
|
75
|
+
const runPhase = (text.match(/^Phase:\s*(.+)$/m)?.[1] || '').trim();
|
|
76
|
+
if (!runPhase || /^done$/i.test(runPhase)) return null;
|
|
77
|
+
return {
|
|
78
|
+
phase: runPhase,
|
|
79
|
+
cursor: (text.match(/^Cursor:\s*(.+)$/m)?.[1] || '').trim(),
|
|
80
|
+
};
|
|
81
|
+
} catch {
|
|
82
|
+
return null;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
62
86
|
(async () => {
|
|
63
87
|
const config = readJsonSafe(path.join(projectRoot, '.ukit', 'storage', 'config.json'), {}) || {};
|
|
64
88
|
if (config?.compact?.hardCapBlock === false) {
|
|
@@ -78,15 +102,58 @@ function readJsonSafe(filePath, fallback = null) {
|
|
|
78
102
|
const state = mod.buildCompactPressureState(rawState, config);
|
|
79
103
|
const thresholds = mod.buildCompactThresholds(config);
|
|
80
104
|
|
|
105
|
+
const gracePath = path.join(projectRoot, '.ukit', 'storage', 'cache', 'hardcap-grace.json');
|
|
106
|
+
|
|
81
107
|
if (state.estimatedTotalTokens < thresholds.hardCapTokens) {
|
|
108
|
+
// Back under the cap — the episode is over, so the next one starts with a full budget.
|
|
109
|
+
fs.rmSync(gracePath, { force: true });
|
|
82
110
|
process.exit(0);
|
|
83
111
|
return;
|
|
84
112
|
}
|
|
85
113
|
|
|
114
|
+
const run = readRunCursor();
|
|
115
|
+
if (run) {
|
|
116
|
+
const graceCalls = Number.isFinite(config?.compact?.hardCapGraceCalls)
|
|
117
|
+
? config.compact.hardCapGraceCalls
|
|
118
|
+
: 10;
|
|
119
|
+
const prior = readJsonSafe(gracePath, null);
|
|
120
|
+
// Reset only when the estimate actually fell since grace started: that is the signal a
|
|
121
|
+
// real compaction/shed happened. Advancing the cursor alone must NOT top the budget up,
|
|
122
|
+
// otherwise a long run gets unlimited grace and the ceiling stops meaning anything.
|
|
123
|
+
const carried =
|
|
124
|
+
prior && Number.isFinite(prior.startedAtTokens) && state.estimatedTotalTokens >= prior.startedAtTokens
|
|
125
|
+
? prior
|
|
126
|
+
: { startedAtTokens: state.estimatedTotalTokens, used: 0 };
|
|
127
|
+
|
|
128
|
+
if (carried.used < graceCalls) {
|
|
129
|
+
const used = carried.used + 1;
|
|
130
|
+
try {
|
|
131
|
+
fs.mkdirSync(path.dirname(gracePath), { recursive: true });
|
|
132
|
+
fs.writeFileSync(gracePath, JSON.stringify({ ...carried, used }));
|
|
133
|
+
} catch {
|
|
134
|
+
// Losing the counter must not block the run; worst case grace restarts.
|
|
135
|
+
}
|
|
136
|
+
process.stderr.write(
|
|
137
|
+
[
|
|
138
|
+
`CONTEXT OVER CAP — grace ${used}/${graceCalls} (~${state.estimatedTotalTokens} tokens >= ${thresholds.hardCapTokens}).`,
|
|
139
|
+
`An unfinished run is in flight (Phase: ${run.phase}${run.cursor ? `, Cursor: ${run.cursor}` : ''}), so this call is allowed instead of stranding it mid-edit.`,
|
|
140
|
+
'Spend the remaining grace on LANDING, not on new work: finish the current edit, commit, update docs/AI_HANDOFF/RUN.md, push.',
|
|
141
|
+
'Do NOT start a new task, open new files, or spawn agents. When grace runs out the gate blocks hard.',
|
|
142
|
+
'After the user runs /compact, the SessionStart resume hook replays the cursor and the run continues automatically.',
|
|
143
|
+
].join('\n') + '\n',
|
|
144
|
+
);
|
|
145
|
+
process.exit(0);
|
|
146
|
+
return;
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
86
150
|
const lines = [
|
|
87
151
|
`BLOCKED (context hard cap): estimated context ~${state.estimatedTotalTokens} tokens >= hard cap ${thresholds.hardCapTokens}.`,
|
|
88
152
|
'This is an absolute ceiling (compact.hardCapTokens), separate from the soft/hard advisory phases — those were apparently not followed.',
|
|
89
153
|
`Edit/Write/Bash refused (tool_name=${toolName}) until real compaction happens.`,
|
|
154
|
+
run
|
|
155
|
+
? 'The unfinished-run grace window (compact.hardCapGraceCalls) is already exhausted — progress should be committed and the cursor written by now.'
|
|
156
|
+
: 'No unfinished run in docs/AI_HANDOFF/RUN.md, so there is no grace window to spend.',
|
|
90
157
|
'Remedy (any one): run /compact, or start a new session (SessionStart resets the counter), or delete .ukit/storage/cache/compact-pressure.json.',
|
|
91
158
|
'Do not work around this by summarizing inline and continuing, and do not reach for a non-gated write tool.',
|
|
92
159
|
];
|
|
@@ -9,6 +9,10 @@
|
|
|
9
9
|
"showThinkingSummaries": false,
|
|
10
10
|
"spinnerTipsEnabled": false,
|
|
11
11
|
"defaultView": "chat",
|
|
12
|
+
"autoCompactEnabled": true,
|
|
13
|
+
"env": {
|
|
14
|
+
"CLAUDE_CODE_AUTO_COMPACT_WINDOW": "150000"
|
|
15
|
+
},
|
|
12
16
|
"permissions": {
|
|
13
17
|
"defaultMode": "bypassPermissions",
|
|
14
18
|
"allow": [
|
|
@@ -79,6 +79,9 @@ Next: <bước kế tiếp chính xác>
|
|
|
79
79
|
- Subagent ghi **full log vào task file trên đĩa**, chỉ trả về orchestrator ≤10 dòng (executor) / ≤6 dòng (reviewer). Paste log ngược lại orchestrator là nguyên nhân số 1 làm run chết vì hết context.
|
|
80
80
|
- Hết mỗi wave: commit, ghi cursor, **collapse** wave đó còn 1 dòng/task trong bộ nhớ làm việc, rồi chạy tiếp.
|
|
81
81
|
- Yêu cầu `/compact` **chỉ** được đặt ở cuối command, giữa 2 cycle. Giữa cycle thì tuyệt đối không — state đã nằm hết ở git + `INDEX.md` + `RUN.md` nên compact ở ranh giới cycle không mất gì.
|
|
82
|
+
- Vượt `compact.hardCapTokens` (mặc định 160k) mà `RUN.md` còn run dở: `context-hardcap-gate` cho thêm `compact.hardCapGraceCalls` (mặc định 10) tool call rồi mới chặn cứng. **Grace đó chỉ để hạ cánh** — hoàn tất edit đang dở, commit, ghi cursor, push. Không mở task mới, không đọc thêm file, không spawn agent. Hết grace là chặn thật; budget chỉ reset khi ước lượng token thực sự giảm (có compact thật), không reset theo wave.
|
|
83
|
+
- Không hook nào gọi được `/compact` — đó là lệnh client-only. Nhưng từ 2.1.3, settings mặc định đặt `env.CLAUDE_CODE_AUTO_COMPACT_WINDOW = 150000` < `hardCapTokens` (160k), nên **client tự auto-compact trước khi gate chặn**. Đường thường: auto-compact chạy → `handoff-resume.sh` replay cursor → chạy tiếp, không cần người gõ gì. Grace window ở trên chỉ còn là lưới an toàn.
|
|
84
|
+
- Sửa một trong hai số đó thì phải giữ `autoCompactWindow < hardCapTokens`. Đảo thứ tự là deadlock: gate chặn tool trước → transcript ngừng lớn → ngưỡng auto-compact không bao giờ tới. `tests/core/autoCompactWindow.test.js` khóa bất biến này.
|
|
82
85
|
|
|
83
86
|
### Git
|
|
84
87
|
|
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
"tokenThreshold": 50000,
|
|
12
12
|
"hardCapTokens": 160000,
|
|
13
13
|
"hardCapBlock": true,
|
|
14
|
+
"hardCapGraceCalls": 10,
|
|
14
15
|
"contextRotDetection": true,
|
|
15
16
|
"askBeforeDrop": true,
|
|
16
17
|
"codexContext": {
|
|
@@ -384,7 +385,8 @@
|
|
|
384
385
|
"enabled": "Bật/tắt toàn bộ helper compact của UKit.",
|
|
385
386
|
"tokenThreshold": "Ngưỡng token chung cho runtime compact dùng chung.",
|
|
386
387
|
"hardCapTokens": "Ngưỡng cứng tuyệt đối (mặc định 160000 token ước lượng). Chạm/vượt ngưỡng này thì context coi như quá dài — không phải gợi ý nữa, là bắt buộc. PHẢI thấp hơn context window thật của model (200k), nếu không API sẽ báo lỗi vượt context trước khi gate kịp chặn.",
|
|
387
|
-
"hardCapBlock": "Nếu true, hook context-hardcap-gate chặn cứng Edit/Write/Bash (exit 2) khi vượt hardCapTokens, cho tới khi có compact thật (PreCompact) reset lại bộ đếm.
|
|
388
|
+
"hardCapBlock": "Nếu true, hook context-hardcap-gate chặn cứng Edit/Write/Bash (exit 2) khi vượt hardCapTokens, cho tới khi có compact thật (PreCompact) reset lại bộ đếm.",
|
|
389
|
+
"hardCapGraceCalls": "Số tool call được phép chạy tiếp sau khi vượt hardCapTokens KHI docs/AI_HANDOFF/RUN.md còn run dở (mặc định 10). Dùng để run kịp commit + ghi cursor + push rồi mới bị chặn, thay vì chết giữa lúc đang Edit. Hết grace là chặn cứng như cũ. Budget tính theo mỗi đợt vượt cap, chỉ reset khi ước lượng token thật sự giảm (có compact thật) — không reset theo wave.",
|
|
388
390
|
"contextRotDetection": "Phát hiện context quá dài/dễ mục để giữ lại state quan trọng trước khi AI nhớ sai.",
|
|
389
391
|
"askBeforeDrop": "Giữ thái độ thận trọng trước khi bỏ context quan trọng. Nếu rủi ro thì hand back cho main model.",
|
|
390
392
|
"agentContext": {
|