@ngockhoale/ukit 3.4.1 → 3.4.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 +23 -0
- package/package.json +1 -1
- package/src/cli/commands/code.js +29 -5
- package/src/cli/commands/decision.js +18 -4
- package/src/cli/commands/doctor.js +7 -3
- package/src/cli/commands/install.js +29 -4
- package/src/cli/commands/memory.js +25 -5
- package/src/cli/commands/telemetry.js +18 -1
- package/src/cli/commands/vm.js +7 -1
- package/src/context/detectProjectContext.js +7 -2
- package/src/core/agentRuntime/contract.js +5 -1
- package/src/core/agentRuntime/eventStore.js +54 -7
- package/src/core/agentRuntime/recovery.js +22 -15
- package/src/core/agentRuntime/supervisor.js +71 -13
- package/src/core/applyPlan.js +11 -1
- package/src/core/codeintel/compiler.js +51 -8
- package/src/core/codeintel/diagnostics.js +124 -33
- package/src/core/codeintel/freshness.js +25 -12
- package/src/core/codeintel/invalidation.js +11 -3
- package/src/core/codeintel/retriever.js +53 -20
- package/src/core/codeintel/router.js +19 -9
- package/src/core/codeintel/summaries.js +4 -3
- package/src/core/codeintel/vectorProvider.js +30 -4
- package/src/core/compact/index.js +24 -7
- package/src/core/compact/threshold.js +49 -14
- package/src/core/diffPlan.js +51 -23
- package/src/core/ensureGitignore.js +19 -2
- package/src/core/fileOps.js +61 -0
- package/src/core/memory/hygiene.js +51 -1
- package/src/core/memory/migrate.js +41 -21
- package/src/core/memory/store.js +96 -61
- package/src/core/metadata.js +37 -2
- package/src/core/observability/adapters/ingest.js +30 -2
- package/src/core/observability/emit/config.js +19 -4
- package/src/core/observability/emit/crash.js +3 -1
- package/src/core/observability/emit/recorder.js +15 -7
- package/src/core/observability/privacy/sanitizeObserved.js +3 -1
- package/src/core/observability/segments/internal.js +36 -8
- package/src/core/observability/segments/retention.js +11 -0
- package/src/core/observability/support/import.js +27 -1
- package/src/core/output/index.js +16 -1
- package/src/core/permissionDoctor.js +72 -9
- package/src/core/repairBrokenHooks.js +15 -2
- package/src/core/reviewPanelAggregate.js +26 -10
- package/src/core/runInstallPipeline.js +71 -22
- package/src/core/runtimeConfig.js +2 -0
- package/src/core/status.js +2 -0
- package/src/core/taskBudgetValidator.js +7 -1
- package/src/core/taskProgressGuard.js +11 -1
- package/src/core/unattendedDoctor.js +36 -5
- package/src/core/uninstall.js +52 -12
- package/src/core/update.js +5 -1
- package/src/decision/client.js +158 -27
- package/src/decision/reviewVerdict.js +23 -7
- package/src/diagnostics/failurePatterns.js +1 -1
- package/src/diagnostics/feedbackEvents.js +1 -1
- package/src/diagnostics/routeOutcomes.js +42 -4
- package/src/diagnostics/skillAccuracy.js +35 -4
- package/src/index/buildIndex.js +123 -26
- package/src/index/fixLoopEscalation.js +3 -0
- package/src/index/gitHooks.js +99 -29
- package/src/index/importResolution.js +7 -1
- package/src/index/playbookRegistry.js +15 -11
- package/src/index/queryIndex.js +28 -10
- package/src/index/routeResolver.js +8 -3
- package/src/index/taskRouting.js +37 -2
- package/src/learning/codeProposals.js +24 -5
- package/src/learning/selfImprove.js +29 -5
- package/src/learning/tunedOverlay.js +18 -7
- package/src/learning/tuning.js +10 -4
- package/src/skill/auditSkill.js +46 -7
- package/template_project/.claude/commands/ukit/handoff-review.md +4 -1
- package/template_project/.claude/hooks/auto-allow-bash.sh +10 -1
- package/template_project/.claude/hooks/block-dangerous.mjs +10 -2
- package/template_project/.claude/hooks/handoff-model-guard.sh +46 -16
- package/template_project/.claude/hooks/reset-compact-pressure.sh +128 -72
- package/template_project/.claude/hooks/sensitive-data-guard.mjs +394 -11
- package/template_project/.claude/hooks/session-episode.sh +60 -28
- package/template_project/.claude/hooks/verification-guard.sh +26 -15
- package/template_project/.claude/skills/pptx/scripts/thumbnail.py +6 -1
- package/template_project/.claude/ukit/index/lib/index-core.mjs +156 -39
- package/template_project/.claude/ukit/index/playbook-registry.mjs +15 -11
- package/template_project/.claude/ukit/index/post-edit-verify.mjs +25 -4
- package/template_project/.claude/ukit/index/pre-edit-backup.mjs +4 -0
- package/template_project/.claude/ukit/index/provision-worktree.mjs +15 -10
- package/template_project/.claude/ukit/index/query-index.mjs +13 -6
- package/template_project/.claude/ukit/index/reset-auto-permissions.mjs +127 -25
- package/template_project/.claude/ukit/index/review-panel-aggregate.mjs +36 -14
- package/template_project/.claude/ukit/index/review-verdict.mjs +93 -19
- package/template_project/.claude/ukit/index/route-resolver.mjs +8 -3
- package/template_project/.claude/ukit/index/route-task.mjs +15 -0
- package/template_project/.claude/ukit/index/safe-patch.mjs +4 -1
- package/template_project/.claude/ukit/index/sidecar-decision.mjs +43 -10
- package/template_project/.claude/ukit/index/stale-spec-check.mjs +13 -3
- package/template_project/.claude/ukit/index/task-budget-validator.mjs +7 -1
- package/template_project/.claude/ukit/index/unic-decision.mjs +179 -28
- package/template_project/.claude/ukit/index/unic-gateway.mjs +33 -8
- package/template_project/.claude/ukit/index/verify-context.mjs +9 -2
- package/template_project/.claude/ukit/index/worktree-sweep.mjs +89 -31
- package/template_project/.claude/ukit/runtime/compact-threshold.mjs +47 -15
- package/template_project/.claude/ukit/runtime/execution-ledger.mjs +63 -30
- package/template_project/.claude/ukit/runtime/hook-field-salvage.mjs +49 -13
- package/template_project/.claude/ukit/runtime/hook-input.sh +48 -13
- package/template_project/.claude/ukit/runtime/hook-telemetry.mjs +92 -7
- package/template_project/.claude/ukit/runtime/memory-freshness.mjs +17 -0
- package/template_project/.claude/ukit/runtime/observability-emit.mjs +38 -10
- package/template_project/.claude/ukit/runtime/output-compression.mjs +11 -0
- package/template_project/.claude/ukit/runtime/reinject-context.mjs +24 -3
- package/template_project/.claude/ukit/runtime/resumable-run.mjs +62 -32
- package/template_project/.claude/ukit/runtime/token-utils.mjs +57 -14
|
@@ -24,23 +24,55 @@ if [ ! -f "$PRESSURE_FILE" ]; then
|
|
|
24
24
|
exit 0
|
|
25
25
|
fi
|
|
26
26
|
|
|
27
|
-
# O1 (TASK-009): armed AFTER the no-pressure-file short-circuit above, so the
|
|
28
|
-
# "nothing to reset" start stays unmeasured while real resets get timed.
|
|
29
|
-
#
|
|
30
|
-
#
|
|
27
|
+
# O1 (TASK-009): armed AFTER the no-pressure-file short-circuit above, so the
|
|
28
|
+
# common "nothing to reset" start stays unmeasured while real resets get timed.
|
|
29
|
+
# W2-D05 (TASK-C91-005): stdin is staged through the shared bounded path — the
|
|
30
|
+
# hook used to wait on the producer's EOF inside node, so a pipe held open
|
|
31
|
+
# parked every SessionStart reset. Payload completeness is proven by staging;
|
|
32
|
+
# a stalled/truncated payload degrades (announced, skipped) rather than acting
|
|
33
|
+
# on input it never fully read — the whole-file escape hatch still works via a
|
|
34
|
+
# CLOSED empty stdin, which stages cleanly.
|
|
31
35
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
36
|
+
if source "$SCRIPT_DIR/../ukit/runtime/hook-input.sh" 2>/dev/null; then
|
|
37
|
+
# shellcheck source=/dev/null
|
|
38
|
+
source "$SCRIPT_DIR/../ukit/runtime/hook-telemetry.sh" 2>/dev/null || true
|
|
39
|
+
trap ukit_cleanup_hook_input EXIT
|
|
40
|
+
ukit_stage_hook_input 65536 truncate
|
|
41
|
+
__ukit_reset_stage_rc=$?
|
|
42
|
+
if [ "$__ukit_reset_stage_rc" -ne 0 ] || ukit_input_degraded; then
|
|
43
|
+
ukit_emit_input_degraded advisory "reset-compact-pressure"
|
|
44
|
+
fi
|
|
45
|
+
else
|
|
46
|
+
# Runtime helper missing (pre-install tree): the SAME bounded staging,
|
|
47
|
+
# inline — cap+1 in the background, bounded wait, never a bare EOF wait.
|
|
48
|
+
UKIT_INPUT_FILE="$(mktemp "${TMPDIR:-/tmp}/ukit-hook-in.XXXXXX")"
|
|
49
|
+
if [ -e /dev/fd/0 ]; then
|
|
50
|
+
exec 8<&0
|
|
51
|
+
head -c 65537 <&8 > "$UKIT_INPUT_FILE" 2>/dev/null &
|
|
52
|
+
else
|
|
53
|
+
head -c 65537 > "$UKIT_INPUT_FILE" 2>/dev/null &
|
|
54
|
+
fi
|
|
55
|
+
__ukit_reader=$!
|
|
56
|
+
__ukit_stage_ms="${UKIT_HOOK_STAGE_MS:-2000}"
|
|
57
|
+
case "$__ukit_stage_ms" in
|
|
58
|
+
''|*[!0-9]*) __ukit_stage_ms=2000 ;;
|
|
59
|
+
esac
|
|
60
|
+
[ "$__ukit_stage_ms" -gt 0 ] 2>/dev/null || __ukit_stage_ms=2000
|
|
61
|
+
printf -v __ukit_stage_s '%d.%03d' $((__ukit_stage_ms / 1000)) $((__ukit_stage_ms % 1000))
|
|
62
|
+
( sleep "$__ukit_stage_s" 2>/dev/null; kill -9 "$__ukit_reader" 2>/dev/null ) <&- >/dev/null 2>&1 &
|
|
63
|
+
__ukit_waiter=$!
|
|
64
|
+
wait "$__ukit_reader" 2>/dev/null
|
|
65
|
+
__ukit_stage_rc=$?
|
|
66
|
+
kill "$__ukit_waiter" 2>/dev/null
|
|
67
|
+
wait "$__ukit_waiter" 2>/dev/null
|
|
68
|
+
exec 8<&-
|
|
69
|
+
if [ "$__ukit_stage_rc" -ne 0 ]; then
|
|
70
|
+
rm -f "$UKIT_INPUT_FILE"
|
|
71
|
+
UKIT_INPUT_FILE=""
|
|
72
|
+
printf '%s\n' '{"systemMessage":"UKit reset-compact-pressure: session payload stalled during stdin staging; skipped this pass rather than act on incomplete input."}'
|
|
73
|
+
exit 0
|
|
74
|
+
fi
|
|
75
|
+
trap '[ -n "$UKIT_INPUT_FILE" ] && rm -f "$UKIT_INPUT_FILE"' EXIT
|
|
44
76
|
fi
|
|
45
77
|
|
|
46
78
|
# The lock directory lives beside the state file; its parent exists because the file does.
|
|
@@ -91,6 +123,11 @@ async function withLock(lockPath, fn) {
|
|
|
91
123
|
const maxWaitMs = 5000;
|
|
92
124
|
const startedAt = Date.now();
|
|
93
125
|
const ownerToken = `${process.pid}-${crypto.randomBytes(8).toString("hex")}`;
|
|
126
|
+
// W2-D04: a read-modify-write that could not take the lock must NOT run
|
|
127
|
+
// unlocked — a half-read pressure file torn under a concurrent writer is how
|
|
128
|
+
// sibling records get lost. The escape hatch survives ONLY as an explicit,
|
|
129
|
+
// operator-chosen override: UKIT_PRESSURE_RESET_FORCE=1.
|
|
130
|
+
const force = process.env.UKIT_PRESSURE_RESET_FORCE === "1";
|
|
94
131
|
let held = false;
|
|
95
132
|
while (!held) {
|
|
96
133
|
try {
|
|
@@ -109,7 +146,15 @@ async function withLock(lockPath, fn) {
|
|
|
109
146
|
}
|
|
110
147
|
held = true;
|
|
111
148
|
} catch (error) {
|
|
112
|
-
if (!error || error.code !== "EEXIST")
|
|
149
|
+
if (!error || error.code !== "EEXIST") {
|
|
150
|
+
// Non-contention failure (EACCES, ENOSPC, EPERM on mkdir/owner write):
|
|
151
|
+
// retrying will not help — fail closed, or run unlocked only when the
|
|
152
|
+
// operator explicitly forced it.
|
|
153
|
+
if (!force) {
|
|
154
|
+
throw new Error(`could not acquire pressure lock (${error?.code ?? error})`);
|
|
155
|
+
}
|
|
156
|
+
return fn();
|
|
157
|
+
}
|
|
113
158
|
try {
|
|
114
159
|
const stat = await fsp.stat(lockPath);
|
|
115
160
|
if (Date.now() - stat.mtimeMs > staleMs) {
|
|
@@ -122,7 +167,12 @@ async function withLock(lockPath, fn) {
|
|
|
122
167
|
}
|
|
123
168
|
}
|
|
124
169
|
} catch {}
|
|
125
|
-
if (Date.now() - startedAt > maxWaitMs)
|
|
170
|
+
if (Date.now() - startedAt > maxWaitMs) {
|
|
171
|
+
if (!force) {
|
|
172
|
+
throw new Error("pressure lock held past the 5s wait bound");
|
|
173
|
+
}
|
|
174
|
+
return fn();
|
|
175
|
+
}
|
|
126
176
|
await new Promise((resolve) => setTimeout(resolve, 3 + Math.floor(Math.random() * 9)));
|
|
127
177
|
}
|
|
128
178
|
}
|
|
@@ -140,12 +190,15 @@ async function withLock(lockPath, fn) {
|
|
|
140
190
|
}
|
|
141
191
|
}
|
|
142
192
|
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
193
|
+
// W2-D05: the payload arrives as a STAGED file (bounded by the shell wrapper),
|
|
194
|
+
// never a live stdin stream — a producer that holds the pipe open cannot park
|
|
195
|
+
// this hook waiting on EOF.
|
|
196
|
+
const inputFile = process.argv[2];
|
|
197
|
+
(async () => {
|
|
146
198
|
const payload = (() => {
|
|
147
199
|
try {
|
|
148
|
-
|
|
200
|
+
const raw = require("fs").readFileSync(inputFile, "utf8");
|
|
201
|
+
return JSON.parse(raw || "{}");
|
|
149
202
|
} catch {
|
|
150
203
|
return {};
|
|
151
204
|
}
|
|
@@ -160,60 +213,63 @@ process.stdin.on("end", () => {
|
|
|
160
213
|
const transcriptKey = typeof payload.transcript_path === "string" ? payload.transcript_path.trim() : "";
|
|
161
214
|
const pressurePath = process.argv[1];
|
|
162
215
|
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
216
|
+
try {
|
|
217
|
+
await withLock(`${pressurePath}.lock`, async () => {
|
|
218
|
+
const raw = await (async () => {
|
|
219
|
+
try {
|
|
220
|
+
return JSON.parse(await fsp.readFile(pressurePath, "utf8"));
|
|
221
|
+
} catch {
|
|
222
|
+
return null;
|
|
223
|
+
}
|
|
224
|
+
})();
|
|
225
|
+
if (!raw || typeof raw !== "object") {
|
|
226
|
+
try {
|
|
227
|
+
await fsp.rm(pressurePath, { force: true });
|
|
228
|
+
} catch {}
|
|
229
|
+
return;
|
|
169
230
|
}
|
|
170
|
-
})();
|
|
171
|
-
if (!raw || typeof raw !== "object") {
|
|
172
|
-
try {
|
|
173
|
-
await fsp.rm(pressurePath, { force: true });
|
|
174
|
-
} catch {}
|
|
175
|
-
return;
|
|
176
|
-
}
|
|
177
231
|
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
232
|
+
// Without a session id (unknown caller) keep the old whole-file reset: this hook is
|
|
233
|
+
// the documented escape hatch for a bricked gate, and it must stay one.
|
|
234
|
+
if (!sessionId || !raw.sessions || typeof raw.sessions !== "object") {
|
|
235
|
+
try {
|
|
236
|
+
await fsp.rm(pressurePath, { force: true });
|
|
237
|
+
} catch {}
|
|
238
|
+
return;
|
|
239
|
+
}
|
|
186
240
|
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
try {
|
|
196
|
-
await fsp.writeFile(
|
|
197
|
-
tempPath,
|
|
198
|
-
JSON.stringify({
|
|
199
|
-
...(newest && typeof newest === "object" ? newest : {}),
|
|
200
|
-
updatedAt: newest?.updatedAt ?? Date.now(),
|
|
201
|
-
v: 2,
|
|
202
|
-
sessions,
|
|
203
|
-
}, null, 2) + "\n",
|
|
204
|
-
);
|
|
205
|
-
await fsp.rename(tempPath, pressurePath);
|
|
206
|
-
} catch (error) {
|
|
241
|
+
const sessions = { ...raw.sessions };
|
|
242
|
+
delete sessions[sessionId];
|
|
243
|
+
if (transcriptKey && transcriptKey !== sessionId) {
|
|
244
|
+
delete sessions[transcriptKey];
|
|
245
|
+
}
|
|
246
|
+
const newest = Object.entries(sessions)
|
|
247
|
+
.sort(([, a], [, b]) => (b?.updatedAt ?? 0) - (a?.updatedAt ?? 0))[0]?.[1] ?? null;
|
|
248
|
+
const tempPath = `${pressurePath}.tmp-${Date.now()}-${Math.random().toString(16).slice(2)}`;
|
|
207
249
|
try {
|
|
208
|
-
await fsp.
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
250
|
+
await fsp.writeFile(
|
|
251
|
+
tempPath,
|
|
252
|
+
JSON.stringify({
|
|
253
|
+
...(newest && typeof newest === "object" ? newest : {}),
|
|
254
|
+
updatedAt: newest?.updatedAt ?? Date.now(),
|
|
255
|
+
v: 2,
|
|
256
|
+
sessions,
|
|
257
|
+
}, null, 2) + "\n",
|
|
258
|
+
);
|
|
259
|
+
await fsp.rename(tempPath, pressurePath);
|
|
260
|
+
} catch (error) {
|
|
261
|
+
try {
|
|
262
|
+
await fsp.rm(tempPath, { force: true });
|
|
263
|
+
} catch {}
|
|
264
|
+
throw error;
|
|
265
|
+
}
|
|
266
|
+
});
|
|
267
|
+
process.exit(0);
|
|
268
|
+
} catch (error) {
|
|
269
|
+
emitDegrade(`reset skipped or failed (${error?.message ?? error}); the compact-pressure record may still be stale — restarting the session retries the reset.`);
|
|
214
270
|
process.exit(0);
|
|
215
|
-
}
|
|
216
|
-
});
|
|
217
|
-
' "$PRESSURE_FILE" 2>/dev/null || true
|
|
271
|
+
}
|
|
272
|
+
})();
|
|
273
|
+
' "$PRESSURE_FILE" "$UKIT_INPUT_FILE" 2>/dev/null || true
|
|
218
274
|
|
|
219
275
|
exit 0
|