@ngockhoale/ukit 2.7.3 → 2.7.5
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 +66 -0
- package/manifests/platform.full.yaml +43 -0
- package/package.json +1 -1
- package/src/cli/commands/doctor.js +14 -0
- package/src/core/hookChainDoctor.js +149 -0
- package/templates/.claude/hooks/block-dangerous.mjs +377 -0
- package/templates/.claude/hooks/block-dangerous.sh +24 -223
- package/templates/.claude/hooks/record-execution.mjs +141 -0
- package/templates/.claude/hooks/record-execution.sh +14 -9
- package/templates/.claude/hooks/sensitive-data-guard.mjs +513 -0
- package/templates/.claude/hooks/sensitive-data-guard.sh +1 -353
- package/templates/.claude/hooks/session-episode.sh +26 -1
- package/templates/.claude/settings.json +6 -6
- package/templates/.claude/ukit/runtime/execution-ledger.mjs +17 -1
- package/templates/.claude/ukit/runtime/hook-chain-runner.mjs +134 -26
- package/templates/.claude/ukit/runtime/hook-field-salvage.mjs +119 -0
- package/templates/.omp/hooks/pre/ukit-bridge.js +20 -12
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
// record-execution.mjs — C40 TASK-004 dual-mode port of record-execution.sh
|
|
2
|
+
// (SPEC §FR-004, §8): persist session-scoped source/write/verification receipts
|
|
3
|
+
// through ukit/runtime/execution-ledger.mjs.
|
|
4
|
+
//
|
|
5
|
+
// * `runHook(ctx)` — in-proc chain step. ctx = { payload, payloadText,
|
|
6
|
+
// projectRoot, env, deadlineMs, signal } -> { code, stdout, stderr }.
|
|
7
|
+
// ADVISORY: always code 0. A missing/failed runtime announces the same
|
|
8
|
+
// "runtime script failed — run: ukit install" systemMessage the .sh emits —
|
|
9
|
+
// loud, never a silent pass. In-proc mode collapses the .sh's separate
|
|
10
|
+
// "missing" vs "failed" branches into the "failed" message (an import that
|
|
11
|
+
// cannot find the module IS a failed runtime; deliberate parity note).
|
|
12
|
+
// * `isDirectRun()` CLI mode — the .sh thin wrapper execs
|
|
13
|
+
// `node record-execution.mjs` with the env contract INPUT_FILE,
|
|
14
|
+
// PROJECT_ROOT, UKIT_HOOK_DEADLINE_MS, UKIT_HARNESS (`--record` accepted
|
|
15
|
+
// for symmetry with the ledger's own CLI).
|
|
16
|
+
//
|
|
17
|
+
// Sweep-failure routing (FR-004): while this step runs it installs
|
|
18
|
+
// setLedgerMessageEmitter so a sampled exec-ledger dir-sweep failure lands in
|
|
19
|
+
// the returned stdout instead of fd 1 (which would corrupt the runner's
|
|
20
|
+
// verdict channel). The emitter is always reset in `finally`.
|
|
21
|
+
|
|
22
|
+
import fs from 'node:fs';
|
|
23
|
+
import fsp from 'node:fs/promises';
|
|
24
|
+
import path from 'node:path';
|
|
25
|
+
import { pathToFileURL, fileURLToPath } from 'node:url';
|
|
26
|
+
|
|
27
|
+
export const hookFailClosed = false;
|
|
28
|
+
|
|
29
|
+
const MODULE_DIR = path.dirname(fileURLToPath(import.meta.url));
|
|
30
|
+
const DEFAULT_LEDGER_PATH = path.join(MODULE_DIR, '..', 'ukit', 'runtime', 'execution-ledger.mjs');
|
|
31
|
+
|
|
32
|
+
// Byte-pinned parity with the .sh's `STATUS != 0` branch.
|
|
33
|
+
const RUNTIME_FAILED =
|
|
34
|
+
'{"systemMessage":"UKit record execution: runtime script failed — run: ukit install"}\n';
|
|
35
|
+
|
|
36
|
+
function parsePayload(rawInput) {
|
|
37
|
+
try {
|
|
38
|
+
const parsed = JSON.parse(rawInput);
|
|
39
|
+
return parsed && typeof parsed === 'object' && !Array.isArray(parsed) ? parsed : null;
|
|
40
|
+
} catch {
|
|
41
|
+
return null;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// Shared advisory body for both modes. `rawInput` that fails to parse is a
|
|
46
|
+
// silent pass — identical to the .sh piping garbage into `node … --record`,
|
|
47
|
+
// which JSON.parse-falls back to `{}` and early-returns on the missing
|
|
48
|
+
// tool_name.
|
|
49
|
+
async function record({ rawInput, projectRoot, env, signal, deadlineMs }) {
|
|
50
|
+
const payload = parsePayload(rawInput) ?? {};
|
|
51
|
+
const ledgerPath = env.UKIT_EXECUTION_LEDGER_PATH || DEFAULT_LEDGER_PATH;
|
|
52
|
+
let ledger;
|
|
53
|
+
try {
|
|
54
|
+
ledger = await import(pathToFileURL(ledgerPath).href);
|
|
55
|
+
} catch {
|
|
56
|
+
return { code: 0, stdout: RUNTIME_FAILED, stderr: '' };
|
|
57
|
+
}
|
|
58
|
+
if (typeof ledger.recordExecutionReceipt !== 'function') {
|
|
59
|
+
return { code: 0, stdout: RUNTIME_FAILED, stderr: '' };
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const emitted = [];
|
|
63
|
+
const emit = (line) => emitted.push(line);
|
|
64
|
+
if (typeof ledger.setLedgerMessageEmitter === 'function') {
|
|
65
|
+
ledger.setLedgerMessageEmitter(emit);
|
|
66
|
+
}
|
|
67
|
+
try {
|
|
68
|
+
await ledger.recordExecutionReceipt({
|
|
69
|
+
projectRoot,
|
|
70
|
+
payload,
|
|
71
|
+
toolName: payload.tool_name,
|
|
72
|
+
harness: env.UKIT_HARNESS || 'claude-code',
|
|
73
|
+
signal,
|
|
74
|
+
deadlineMs,
|
|
75
|
+
});
|
|
76
|
+
} catch {
|
|
77
|
+
return { code: 0, stdout: RUNTIME_FAILED, stderr: '' };
|
|
78
|
+
} finally {
|
|
79
|
+
if (typeof ledger.setLedgerMessageEmitter === 'function') {
|
|
80
|
+
ledger.setLedgerMessageEmitter(null);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
return { code: 0, stdout: emitted.join(''), stderr: '' };
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// --- SPEC §8 chain-step entry ------------------------------------------------
|
|
87
|
+
export async function runHook(ctx) {
|
|
88
|
+
const env = (ctx && ctx.env) || {};
|
|
89
|
+
const projectRoot = (ctx && ctx.projectRoot) || env.PROJECT_ROOT || process.cwd();
|
|
90
|
+
let rawInput;
|
|
91
|
+
try {
|
|
92
|
+
rawInput =
|
|
93
|
+
typeof (ctx && ctx.payloadText) === 'string' && ctx.payloadText.length > 0
|
|
94
|
+
? ctx.payloadText
|
|
95
|
+
: JSON.stringify((ctx && ctx.payload) ?? {});
|
|
96
|
+
} catch {
|
|
97
|
+
rawInput = '{}';
|
|
98
|
+
}
|
|
99
|
+
return record({
|
|
100
|
+
rawInput,
|
|
101
|
+
projectRoot,
|
|
102
|
+
env,
|
|
103
|
+
signal: ctx && ctx.signal,
|
|
104
|
+
deadlineMs: ctx && ctx.deadlineMs,
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// --- direct-run CLI mode (the .sh thin wrapper's target) ---------------------
|
|
109
|
+
export function isDirectRun() {
|
|
110
|
+
try {
|
|
111
|
+
const invoked = process.argv[1] ? fs.realpathSync(process.argv[1]) : '';
|
|
112
|
+
const self = fs.realpathSync(fileURLToPath(import.meta.url));
|
|
113
|
+
return invoked === self;
|
|
114
|
+
} catch {
|
|
115
|
+
return false;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
async function main() {
|
|
120
|
+
const env = process.env;
|
|
121
|
+
let rawInput = '';
|
|
122
|
+
try {
|
|
123
|
+
rawInput = await fsp.readFile(env.INPUT_FILE || '', 'utf8');
|
|
124
|
+
} catch {
|
|
125
|
+
// No staged payload — silent advisory pass, same as an unreadable INPUT.
|
|
126
|
+
}
|
|
127
|
+
const projectRoot = env.PROJECT_ROOT || process.cwd();
|
|
128
|
+
const verdict = await record({ rawInput, projectRoot, env });
|
|
129
|
+
if (verdict.stdout) process.stdout.write(verdict.stdout);
|
|
130
|
+
process.exit(0);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
if (isDirectRun()) {
|
|
134
|
+
main().catch(() => {
|
|
135
|
+
// Advisory hook: even a catastrophic error exits 0 with the loud message.
|
|
136
|
+
try {
|
|
137
|
+
process.stdout.write(RUNTIME_FAILED);
|
|
138
|
+
} catch { /* stdout already gone */ }
|
|
139
|
+
process.exit(0);
|
|
140
|
+
});
|
|
141
|
+
}
|
|
@@ -1,8 +1,17 @@
|
|
|
1
1
|
#!/bin/bash
|
|
2
2
|
# PostToolUse hook: persist session-scoped source/write/verification receipts.
|
|
3
3
|
# ADVISORY ONLY — always exit 0. A missing or failing runtime must be loud, not a silent pass.
|
|
4
|
+
#
|
|
5
|
+
# C40 TASK-004 (SPEC §FR-004, §14): thin wrapper — stdin staging + env, then
|
|
6
|
+
# delegate to record-execution.mjs (single source of truth, shared with the
|
|
7
|
+
# in-proc chain step). The .sh used to distinguish "runtime script missing"
|
|
8
|
+
# from "runtime script failed"; the in-proc contract collapses both into the
|
|
9
|
+
# "failed" systemMessage, so this wrapper now emits "failed" for any non-zero
|
|
10
|
+
# node exit (a missing .mjs is a failed runtime too).
|
|
4
11
|
|
|
5
12
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
13
|
+
HOOK_DIR="$SCRIPT_DIR"
|
|
14
|
+
PROJECT_ROOT="${CLAUDE_PROJECT_DIR:-$PWD}"
|
|
6
15
|
# shellcheck source=/dev/null
|
|
7
16
|
if source "$SCRIPT_DIR/../ukit/runtime/hook-input.sh" 2>/dev/null; then
|
|
8
17
|
# TASK-019: arm advisory latency telemetry; the finish row is written by the
|
|
@@ -58,19 +67,14 @@ else
|
|
|
58
67
|
fi
|
|
59
68
|
trap '[ -n "$UKIT_INPUT_FILE" ] && rm -f "$UKIT_INPUT_FILE"' EXIT
|
|
60
69
|
fi
|
|
61
|
-
INPUT="$(cat "$UKIT_INPUT_FILE")"
|
|
62
|
-
PROJECT_ROOT="${CLAUDE_PROJECT_DIR:-$PWD}"
|
|
63
|
-
SCRIPT="$PROJECT_ROOT/.claude/ukit/runtime/execution-ledger.mjs"
|
|
64
|
-
|
|
65
|
-
if [ ! -f "$SCRIPT" ]; then
|
|
66
|
-
printf '%s\n' '{"systemMessage":"UKit record execution: runtime script missing — run: ukit install"}'
|
|
67
|
-
exit 0
|
|
68
|
-
fi
|
|
69
70
|
|
|
70
|
-
OUTPUT=$(
|
|
71
|
+
OUTPUT=$(INPUT_FILE="$UKIT_INPUT_FILE" PROJECT_ROOT="$PROJECT_ROOT" UKIT_HARNESS="${UKIT_HARNESS:-claude-code}" UKIT_HOOK_DEADLINE_MS="${UKIT_HOOK_DEADLINE_MS:-3000}" node "$HOOK_DIR/record-execution.mjs")
|
|
71
72
|
STATUS=$?
|
|
72
73
|
|
|
73
74
|
if [ "$STATUS" -ne 0 ]; then
|
|
75
|
+
# In-proc parity (SPEC §FR-004): the old "runtime script missing" branch is
|
|
76
|
+
# folded into "failed" — an .mjs that is absent or crashed is the same
|
|
77
|
+
# failed-runtime condition, and the module emits this exact line itself.
|
|
74
78
|
printf '%s\n' '{"systemMessage":"UKit record execution: runtime script failed — run: ukit install"}'
|
|
75
79
|
exit 0
|
|
76
80
|
fi
|
|
@@ -78,3 +82,4 @@ fi
|
|
|
78
82
|
if [ -n "$OUTPUT" ]; then
|
|
79
83
|
printf '%s\n' "$OUTPUT"
|
|
80
84
|
fi
|
|
85
|
+
exit 0
|