@evo-dev/evodev 0.0.1-alpha.1 → 0.0.1-alpha.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/dist/.agents/skills/grilling/SKILL.md +10 -0
- package/dist/.claude-plugin/marketplace.json +2 -2
- package/dist/assets/skills/coding/knowledge-distillation/SKILL.md +112 -111
- package/dist/assets/skills/coding/knowledge-distillation/references/knowledge-distillation-methods.md +11 -7
- package/dist/assets/team/agents/code-reviewer.md +48 -0
- package/dist/assets/team/agents/docs-maintainer.md +51 -0
- package/dist/assets/team/agents/implementation-engineer.md +51 -0
- package/dist/assets/team/agents/product-scope-analyst.md +58 -0
- package/dist/assets/team/agents/release-engineer.md +55 -0
- package/dist/assets/team/agents/security-boundary-reviewer.md +50 -0
- package/dist/assets/team/agents/solution-architect.md +51 -0
- package/dist/assets/team/agents/verification-engineer.md +51 -0
- package/dist/assets/team/team.md +102 -0
- package/dist/index.js +8874 -2633
- package/dist/plugins/evodev/.claude-plugin/plugin.json +1 -1
- package/dist/plugins/evodev/.codex-plugin/plugin.json +1 -1
- package/dist/plugins/evodev/hooks/codex-hooks.json +10 -10
- package/dist/plugins/evodev/hooks/codex.ts +206 -3
- package/dist/plugins/evodev/hooks/hooks.json +18 -18
- package/dist/plugins/evodev/hooks/hooks.ts +155 -21
- package/dist/plugins/evodev/hooks/runtime.ts +137 -68
- package/dist/plugins/evodev/hooks/workspace-core.ts +0 -27
- package/dist/plugins/evodev/package.json +1 -1
- package/dist/plugins/evodev/skills/knowledge-distillation/SKILL.md +112 -111
- package/dist/plugins/evodev/skills/knowledge-distillation/references/knowledge-distillation-methods.md +11 -7
- package/package.json +1 -1
|
@@ -1,6 +1,9 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import type { Dirent } from "node:fs";
|
|
2
|
+
import { copyFile, mkdir, readFile, readdir, stat, writeFile } from "node:fs/promises";
|
|
3
|
+
import { dirname, join } from "node:path";
|
|
4
|
+
import { fileURLToPath } from "node:url";
|
|
3
5
|
import {
|
|
6
|
+
CANONICAL_HOOK_EVENT_TYPES,
|
|
4
7
|
type CanonicalHookEventType,
|
|
5
8
|
type HookEventV1,
|
|
6
9
|
type HookInputEventType,
|
|
@@ -76,9 +79,20 @@ export interface CodexHookConfig {
|
|
|
76
79
|
}
|
|
77
80
|
|
|
78
81
|
export const CLAUDE_HOOK_RUNTIME_COMMAND =
|
|
79
|
-
'
|
|
80
|
-
export const
|
|
81
|
-
|
|
82
|
+
'cd "${CLAUDE_PLUGIN_ROOT}" && bun run hooks/runtime.ts hook runtime --target claude';
|
|
83
|
+
export const DEFAULT_CLAUDE_PLUGIN_SELECTOR = "evodev@evo-dev";
|
|
84
|
+
export const CODEX_HOOK_RUNTIME_COMMAND = [
|
|
85
|
+
"sh -lc '",
|
|
86
|
+
'root="${PLUGIN_ROOT:-}"; ',
|
|
87
|
+
'if [ -z "$root" ]; then ',
|
|
88
|
+
'for candidate in "$HOME/.codex/plugins/cache/evodev/evodev"/* "$HOME/.codex/plugins/evodev"; do ',
|
|
89
|
+
'if [ -f "$candidate/hooks/runtime.ts" ]; then root="$candidate"; break; fi; ',
|
|
90
|
+
"done; ",
|
|
91
|
+
"fi; ",
|
|
92
|
+
'if [ -z "$root" ] || [ ! -f "$root/hooks/runtime.ts" ]; then exit 0; fi; ',
|
|
93
|
+
'cd "$root" && bun run hooks/runtime.ts hook runtime --target codex || exit 0',
|
|
94
|
+
"'",
|
|
95
|
+
].join("");
|
|
82
96
|
const CODEX_FEATURE_FLAGS = ["plugins", "plugin_hooks", "hooks"] as const;
|
|
83
97
|
const CLAUDE_TOOL_EVENTS = new Set<CanonicalHookEventType>([
|
|
84
98
|
"PreToolUse",
|
|
@@ -127,6 +141,10 @@ const CODEX_EVENT_MATCHERS: Partial<Record<CanonicalHookEventType, string>> = {
|
|
|
127
141
|
SubagentStop: "*",
|
|
128
142
|
Stop: "*",
|
|
129
143
|
};
|
|
144
|
+
const RUNTIME_HOOK_EVENT_TYPES = [...CANONICAL_HOOK_EVENT_TYPES, "AgentStop"] as const;
|
|
145
|
+
const RUNTIME_HOOK_EVENT_TYPE_BY_KEY = new Map<string, HookInputEventType>(
|
|
146
|
+
RUNTIME_HOOK_EVENT_TYPES.map((type) => [normalizeRuntimeHookEventName(type), type]),
|
|
147
|
+
);
|
|
130
148
|
|
|
131
149
|
export function planClaudeHookInstallDryRun(
|
|
132
150
|
input: ClaudeHookInstallDryRunInput,
|
|
@@ -172,10 +190,15 @@ export async function installClaudeHooks(input: {
|
|
|
172
190
|
homeDir: string;
|
|
173
191
|
paths?: ClaudePaths;
|
|
174
192
|
now?: string;
|
|
193
|
+
pluginSelector?: string;
|
|
175
194
|
}): Promise<HookInstallResult> {
|
|
176
195
|
const paths = input.paths ?? resolveClaudePaths({ homeDir: input.homeDir });
|
|
177
196
|
const existing = await readTextIfExists(paths.settingsPath);
|
|
178
|
-
const
|
|
197
|
+
const runtimeCommand = await resolveClaudeUserHookRuntimeCommand({
|
|
198
|
+
paths,
|
|
199
|
+
pluginSelector: input.pluginSelector ?? DEFAULT_CLAUDE_PLUGIN_SELECTOR,
|
|
200
|
+
});
|
|
201
|
+
const next = mergeClaudeSettingsHooks(existing, createClaudeHookConfig(runtimeCommand).hooks);
|
|
179
202
|
return writeMergedConfig({
|
|
180
203
|
target: "claude",
|
|
181
204
|
path: paths.settingsPath,
|
|
@@ -186,6 +209,101 @@ export async function installClaudeHooks(input: {
|
|
|
186
209
|
});
|
|
187
210
|
}
|
|
188
211
|
|
|
212
|
+
async function resolveClaudeUserHookRuntimeCommand(input: {
|
|
213
|
+
paths: ClaudePaths;
|
|
214
|
+
pluginSelector: string;
|
|
215
|
+
}): Promise<string> {
|
|
216
|
+
const runtimePath =
|
|
217
|
+
(await resolveInstalledClaudePluginRuntimePath(input)) ?? (await resolveLocalRuntimePath());
|
|
218
|
+
const pluginRoot = dirname(dirname(runtimePath));
|
|
219
|
+
return `cd ${shellQuote(pluginRoot)} && bun run hooks/runtime.ts hook runtime --target claude`;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
async function resolveInstalledClaudePluginRuntimePath(input: {
|
|
223
|
+
paths: ClaudePaths;
|
|
224
|
+
pluginSelector: string;
|
|
225
|
+
}): Promise<string | null> {
|
|
226
|
+
const marketplaceName = extractMarketplaceNameFromSelector(input.pluginSelector);
|
|
227
|
+
const pluginName = extractPluginNameFromSelector(input.pluginSelector);
|
|
228
|
+
if (marketplaceName === null || pluginName === null) return null;
|
|
229
|
+
if (!isSafePluginCacheSegment(marketplaceName) || !isSafePluginCacheSegment(pluginName)) {
|
|
230
|
+
return null;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
const cacheRoot = join(input.paths.claudeDir, "plugins", "cache", marketplaceName, pluginName);
|
|
234
|
+
let entries: Dirent[];
|
|
235
|
+
try {
|
|
236
|
+
entries = await readdir(cacheRoot, { withFileTypes: true });
|
|
237
|
+
} catch (error) {
|
|
238
|
+
if (isNotFoundError(error)) return null;
|
|
239
|
+
throw error;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
const candidates = (
|
|
243
|
+
await Promise.all(
|
|
244
|
+
entries
|
|
245
|
+
.filter((entry) => entry.isDirectory())
|
|
246
|
+
.map(async (entry) => {
|
|
247
|
+
const runtimePath = join(cacheRoot, entry.name, "hooks", "runtime.ts");
|
|
248
|
+
try {
|
|
249
|
+
const runtimeStat = await stat(runtimePath);
|
|
250
|
+
return runtimeStat.isFile()
|
|
251
|
+
? { path: runtimePath, mtimeMs: runtimeStat.mtimeMs }
|
|
252
|
+
: null;
|
|
253
|
+
} catch (error) {
|
|
254
|
+
if (isNotFoundError(error)) return null;
|
|
255
|
+
throw error;
|
|
256
|
+
}
|
|
257
|
+
}),
|
|
258
|
+
)
|
|
259
|
+
).filter((candidate): candidate is { path: string; mtimeMs: number } => candidate !== null);
|
|
260
|
+
|
|
261
|
+
if (candidates.length === 0) return null;
|
|
262
|
+
candidates.sort(
|
|
263
|
+
(left, right) => right.mtimeMs - left.mtimeMs || right.path.localeCompare(left.path),
|
|
264
|
+
);
|
|
265
|
+
return candidates[0].path;
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
function extractMarketplaceNameFromSelector(selector: string): string | null {
|
|
269
|
+
const separator = selector.lastIndexOf("@");
|
|
270
|
+
if (separator <= 0 || separator === selector.length - 1) return null;
|
|
271
|
+
const marketplaceName = selector.slice(separator + 1).trim();
|
|
272
|
+
return marketplaceName === "" ? null : marketplaceName;
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
function extractPluginNameFromSelector(selector: string): string | null {
|
|
276
|
+
const separator = selector.lastIndexOf("@");
|
|
277
|
+
const pluginName = (separator <= 0 ? selector : selector.slice(0, separator)).trim();
|
|
278
|
+
return pluginName === "" ? null : pluginName;
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
function isSafePluginCacheSegment(segment: string): boolean {
|
|
282
|
+
return /^[A-Za-z0-9._-]+$/.test(segment) && segment !== "." && segment !== "..";
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
async function resolveLocalRuntimePath(): Promise<string> {
|
|
286
|
+
const currentDir = dirname(fileURLToPath(import.meta.url));
|
|
287
|
+
const candidates = [
|
|
288
|
+
join(currentDir, "runtime.ts"),
|
|
289
|
+
join(currentDir, "plugins", "evodev", "hooks", "runtime.ts"),
|
|
290
|
+
];
|
|
291
|
+
|
|
292
|
+
for (const candidate of candidates) {
|
|
293
|
+
try {
|
|
294
|
+
const candidateStat = await stat(candidate);
|
|
295
|
+
if (candidateStat.isFile()) return candidate;
|
|
296
|
+
} catch (error) {
|
|
297
|
+
if (isNotFoundError(error)) continue;
|
|
298
|
+
throw error;
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
throw new Error(
|
|
303
|
+
`Cannot resolve Claude user-level hook runtime path. Checked: ${candidates.join(", ")}`,
|
|
304
|
+
);
|
|
305
|
+
}
|
|
306
|
+
|
|
189
307
|
export async function installCodexHooks(input: {
|
|
190
308
|
homeDir: string;
|
|
191
309
|
paths?: CodexPaths;
|
|
@@ -225,12 +343,11 @@ export function normalizeClaudeRuntimeHookPayload(input: {
|
|
|
225
343
|
payload: Record<string, unknown>;
|
|
226
344
|
receivedAt?: string;
|
|
227
345
|
}): HookEventV1 {
|
|
228
|
-
const type = input.payload.hook_event_name ?? input.payload.hookEventName;
|
|
229
|
-
if (typeof type !== "string" || type.trim() === "") {
|
|
230
|
-
throw new Error("Claude hook payload is missing hook_event_name.");
|
|
231
|
-
}
|
|
232
346
|
return normalizeClaudeHookPayload({
|
|
233
|
-
type:
|
|
347
|
+
type: readRuntimeHookEventType(
|
|
348
|
+
input.payload.hook_event_name ?? input.payload.hookEventName,
|
|
349
|
+
"Claude",
|
|
350
|
+
),
|
|
234
351
|
payload: input.payload,
|
|
235
352
|
receivedAt: input.receivedAt,
|
|
236
353
|
});
|
|
@@ -254,21 +371,34 @@ export function normalizeCodexRuntimeHookPayload(input: {
|
|
|
254
371
|
payload: Record<string, unknown>;
|
|
255
372
|
receivedAt?: string;
|
|
256
373
|
}): HookEventV1 {
|
|
257
|
-
const type =
|
|
258
|
-
input.payload.hook_event_name ??
|
|
259
|
-
input.payload.hookEventName ??
|
|
260
|
-
input.payload.event ??
|
|
261
|
-
input.payload.name;
|
|
262
|
-
if (typeof type !== "string" || type.trim() === "") {
|
|
263
|
-
throw new Error("Codex hook payload is missing hook_event_name.");
|
|
264
|
-
}
|
|
265
374
|
return normalizeCodexHookPayload({
|
|
266
|
-
type:
|
|
375
|
+
type: readRuntimeHookEventType(
|
|
376
|
+
input.payload.hook_event_name ??
|
|
377
|
+
input.payload.hookEventName ??
|
|
378
|
+
input.payload.event ??
|
|
379
|
+
input.payload.name,
|
|
380
|
+
"Codex",
|
|
381
|
+
),
|
|
267
382
|
payload: input.payload,
|
|
268
383
|
receivedAt: input.receivedAt,
|
|
269
384
|
});
|
|
270
385
|
}
|
|
271
386
|
|
|
387
|
+
function readRuntimeHookEventType(value: unknown, target: "Claude" | "Codex"): HookInputEventType {
|
|
388
|
+
if (typeof value !== "string" || value.trim() === "") {
|
|
389
|
+
throw new Error(`${target} hook payload is missing hook_event_name.`);
|
|
390
|
+
}
|
|
391
|
+
const normalized = RUNTIME_HOOK_EVENT_TYPE_BY_KEY.get(normalizeRuntimeHookEventName(value));
|
|
392
|
+
if (normalized === undefined) {
|
|
393
|
+
throw new Error(`Unsupported hook event type: ${value}`);
|
|
394
|
+
}
|
|
395
|
+
return normalized;
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
function normalizeRuntimeHookEventName(value: string): string {
|
|
399
|
+
return value.replace(/[^A-Za-z0-9]/g, "").toLowerCase();
|
|
400
|
+
}
|
|
401
|
+
|
|
272
402
|
export function createClaudeHookConfig(
|
|
273
403
|
command: string = CLAUDE_HOOK_RUNTIME_COMMAND,
|
|
274
404
|
): ClaudeHookConfig {
|
|
@@ -283,7 +413,7 @@ export function createClaudeHookConfig(
|
|
|
283
413
|
}
|
|
284
414
|
return {
|
|
285
415
|
description:
|
|
286
|
-
"EvoDev observes Claude Code lifecycle events through evodev hook runtime for local trace and
|
|
416
|
+
"EvoDev observes Claude Code lifecycle events through evodev hook runtime for local trace and action-required advisories.",
|
|
287
417
|
hooks,
|
|
288
418
|
};
|
|
289
419
|
}
|
|
@@ -452,6 +582,10 @@ function backupTimestamp(value?: string): string {
|
|
|
452
582
|
return source.replace(/[^0-9A-Za-z]/g, "").slice(0, 20) || "now";
|
|
453
583
|
}
|
|
454
584
|
|
|
585
|
+
function shellQuote(value: string): string {
|
|
586
|
+
return `'${value.replace(/'/g, "'\\''")}'`;
|
|
587
|
+
}
|
|
588
|
+
|
|
455
589
|
async function fileExists(path: string): Promise<boolean> {
|
|
456
590
|
try {
|
|
457
591
|
return (await stat(path)).isFile();
|
|
@@ -1,22 +1,20 @@
|
|
|
1
1
|
#!/usr/bin/env bun
|
|
2
2
|
|
|
3
|
-
import {
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
resolveTraceTeamContext,
|
|
13
|
-
} from "@evo-dev/core";
|
|
14
|
-
import { normalizeClaudeRuntimeHookPayload, normalizeCodexRuntimeHookPayload } from "./hooks.ts";
|
|
3
|
+
import type { HookEventV1, HookRuntimeResult } from "@evo-dev/core";
|
|
4
|
+
|
|
5
|
+
export type HookRuntimeCoreApi = typeof import("@evo-dev/core");
|
|
6
|
+
export type HookRuntimeNormalizerApi = typeof import("./hooks.ts");
|
|
7
|
+
|
|
8
|
+
export interface HookRuntimeApi {
|
|
9
|
+
core: HookRuntimeCoreApi;
|
|
10
|
+
normalizers: HookRuntimeNormalizerApi;
|
|
11
|
+
}
|
|
15
12
|
|
|
16
13
|
export interface HookRuntimeCliOptions {
|
|
17
14
|
argv?: string[];
|
|
18
15
|
environment?: Record<string, string | undefined>;
|
|
19
16
|
homeDir?: string;
|
|
17
|
+
loadRuntimeApi?: () => Promise<HookRuntimeApi>;
|
|
20
18
|
stdin?: string | (() => Promise<string>);
|
|
21
19
|
write?: (message: string) => void;
|
|
22
20
|
}
|
|
@@ -26,7 +24,9 @@ export async function runHookRuntimeCli(options: HookRuntimeCliOptions = {}): Pr
|
|
|
26
24
|
const environment = options.environment ?? process.env;
|
|
27
25
|
const write = options.write ?? process.stdout.write.bind(process.stdout);
|
|
28
26
|
const startedAt = new Date();
|
|
27
|
+
const loadRuntimeApi = options.loadRuntimeApi ?? loadDefaultHookRuntimeApi;
|
|
29
28
|
let homeDir = options.homeDir ?? process.env.HOME ?? "~";
|
|
29
|
+
let runtimeApi: HookRuntimeApi | null = null;
|
|
30
30
|
let target = "unknown";
|
|
31
31
|
let payload: Record<string, unknown> | null = null;
|
|
32
32
|
let stdinBytes: number | null = null;
|
|
@@ -45,8 +45,10 @@ export async function runHookRuntimeCli(options: HookRuntimeCliOptions = {}): Pr
|
|
|
45
45
|
homeDir = options.homeDir ?? process.env.HOME ?? "~";
|
|
46
46
|
const stdin = await readHookRuntimeStdin(options.stdin);
|
|
47
47
|
stdinBytes = Buffer.byteLength(stdin, "utf8");
|
|
48
|
+
runtimeApi = await loadRuntimeApi();
|
|
48
49
|
payload = JSON.parse(stdin) as Record<string, unknown>;
|
|
49
|
-
await
|
|
50
|
+
await appendExecutionEventSafely({
|
|
51
|
+
core: runtimeApi.core,
|
|
50
52
|
homeDir,
|
|
51
53
|
phase: "started",
|
|
52
54
|
target,
|
|
@@ -56,28 +58,31 @@ export async function runHookRuntimeCli(options: HookRuntimeCliOptions = {}): Pr
|
|
|
56
58
|
environment,
|
|
57
59
|
});
|
|
58
60
|
|
|
59
|
-
const settings = await readHookSettings(homeDir);
|
|
61
|
+
const settings = await readHookSettings(runtimeApi.core, homeDir);
|
|
60
62
|
const event =
|
|
61
63
|
flags.target === "codex"
|
|
62
|
-
? normalizeCodexRuntimeHookPayload({
|
|
64
|
+
? runtimeApi.normalizers.normalizeCodexRuntimeHookPayload({
|
|
63
65
|
payload,
|
|
64
66
|
receivedAt: new Date().toISOString(),
|
|
65
67
|
})
|
|
66
|
-
: normalizeClaudeRuntimeHookPayload({
|
|
68
|
+
: runtimeApi.normalizers.normalizeClaudeRuntimeHookPayload({
|
|
67
69
|
payload,
|
|
68
70
|
receivedAt: new Date().toISOString(),
|
|
69
71
|
});
|
|
70
|
-
const result = await handleHookRuntime({
|
|
72
|
+
const result = await runtimeApi.core.handleHookRuntime({
|
|
71
73
|
target: flags.target,
|
|
72
74
|
homeDir,
|
|
73
75
|
settings: settings.hooks,
|
|
76
|
+
teamRuntimeDisplayMode: settings.teamRuntime.displayMode,
|
|
74
77
|
event,
|
|
75
78
|
rawPayload: payload,
|
|
76
79
|
receivedAt: event.time.receivedAt,
|
|
77
80
|
environment,
|
|
81
|
+
runtimeInjectionEnabled: settings.memory.runtimeInjection,
|
|
78
82
|
});
|
|
79
|
-
const formatted = formatHookRuntimeOutput(result);
|
|
80
|
-
await
|
|
83
|
+
const formatted = runtimeApi.core.formatHookRuntimeOutput(result);
|
|
84
|
+
await appendExecutionEventSafely({
|
|
85
|
+
core: runtimeApi.core,
|
|
81
86
|
homeDir,
|
|
82
87
|
phase: "completed",
|
|
83
88
|
target,
|
|
@@ -86,14 +91,14 @@ export async function runHookRuntimeCli(options: HookRuntimeCliOptions = {}): Pr
|
|
|
86
91
|
stdinBytes,
|
|
87
92
|
event,
|
|
88
93
|
result,
|
|
89
|
-
formatted,
|
|
90
94
|
environment,
|
|
91
95
|
durationMs: Date.now() - startedAt.getTime(),
|
|
92
96
|
});
|
|
93
97
|
if (formatted.length > 0) write(formatted);
|
|
94
98
|
return 0;
|
|
95
99
|
} catch (error) {
|
|
96
|
-
await
|
|
100
|
+
await appendExecutionEventSafely({
|
|
101
|
+
core: runtimeApi?.core,
|
|
97
102
|
homeDir,
|
|
98
103
|
phase: "failed",
|
|
99
104
|
target,
|
|
@@ -104,10 +109,15 @@ export async function runHookRuntimeCli(options: HookRuntimeCliOptions = {}): Pr
|
|
|
104
109
|
environment,
|
|
105
110
|
durationMs: Date.now() - startedAt.getTime(),
|
|
106
111
|
});
|
|
107
|
-
|
|
112
|
+
return 0;
|
|
108
113
|
}
|
|
109
114
|
}
|
|
110
115
|
|
|
116
|
+
async function loadDefaultHookRuntimeApi(): Promise<HookRuntimeApi> {
|
|
117
|
+
const [core, normalizers] = await Promise.all([import("@evo-dev/core"), import("./hooks.ts")]);
|
|
118
|
+
return { core, normalizers };
|
|
119
|
+
}
|
|
120
|
+
|
|
111
121
|
function parseHookRuntimeFlags(argv: string[]): { target: string } {
|
|
112
122
|
let target: string | undefined;
|
|
113
123
|
for (let index = 0; index < argv.length; index += 1) {
|
|
@@ -138,18 +148,19 @@ async function readHookRuntimeStdin(stdin: HookRuntimeCliOptions["stdin"]): Prom
|
|
|
138
148
|
return Buffer.concat(chunks).toString("utf8");
|
|
139
149
|
}
|
|
140
150
|
|
|
141
|
-
async function readHookSettings(homeDir: string) {
|
|
151
|
+
async function readHookSettings(core: HookRuntimeCoreApi, homeDir: string) {
|
|
142
152
|
try {
|
|
143
|
-
return await createCoreConfigStore(homeDir).readSettings();
|
|
153
|
+
return await core.createCoreConfigStore(homeDir).readSettings();
|
|
144
154
|
} catch (error) {
|
|
145
155
|
if (isNotFoundError(error)) {
|
|
146
|
-
return createDefaultSettings();
|
|
156
|
+
return core.createDefaultSettings();
|
|
147
157
|
}
|
|
148
|
-
|
|
158
|
+
return core.createDefaultSettings();
|
|
149
159
|
}
|
|
150
160
|
}
|
|
151
161
|
|
|
152
|
-
async function
|
|
162
|
+
async function appendExecutionEventSafely(input: {
|
|
163
|
+
core?: HookRuntimeCoreApi;
|
|
153
164
|
homeDir: string;
|
|
154
165
|
phase: "started" | "completed" | "failed";
|
|
155
166
|
target: string;
|
|
@@ -158,56 +169,115 @@ async function appendTraceLogSafely(input: {
|
|
|
158
169
|
stdinBytes: number | null;
|
|
159
170
|
event?: HookEventV1;
|
|
160
171
|
result?: HookRuntimeResult;
|
|
161
|
-
formatted?: string;
|
|
162
172
|
durationMs?: number | null;
|
|
163
173
|
error?: unknown;
|
|
164
174
|
environment?: Record<string, string | undefined>;
|
|
165
175
|
}): Promise<void> {
|
|
176
|
+
if (input.core === undefined) return;
|
|
177
|
+
|
|
166
178
|
try {
|
|
167
|
-
|
|
179
|
+
const sessionKey =
|
|
180
|
+
input.payload === null ? "session-local" : input.core.resolveTraceSessionKey(input.payload);
|
|
181
|
+
const team = input.core.resolveTraceTeamContext({
|
|
182
|
+
homeDir: input.homeDir,
|
|
183
|
+
environment: input.environment,
|
|
184
|
+
payload: input.payload,
|
|
185
|
+
});
|
|
186
|
+
const traceRefId = await recordTraceRefSafely(input);
|
|
187
|
+
await input.core.appendEvoDevExecutionEvent(
|
|
168
188
|
input.homeDir,
|
|
169
|
-
|
|
170
|
-
|
|
189
|
+
input.core.createEvoDevExecutionEvent({
|
|
190
|
+
eventId: input.event?.eventId,
|
|
171
191
|
target: input.target,
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
payload: input.payload,
|
|
183
|
-
}),
|
|
184
|
-
event:
|
|
185
|
-
input.event === undefined
|
|
186
|
-
? null
|
|
187
|
-
: {
|
|
188
|
-
eventId: input.event.eventId,
|
|
189
|
-
type: input.event.type,
|
|
190
|
-
summary: input.event.payload.summary,
|
|
191
|
-
metadata: input.event.payload.metadata,
|
|
192
|
-
decision: input.event.decision,
|
|
193
|
-
},
|
|
194
|
-
result:
|
|
195
|
-
input.result === undefined
|
|
196
|
-
? null
|
|
197
|
-
: {
|
|
198
|
-
enabled: input.result.enabled,
|
|
199
|
-
summary: input.result.summary,
|
|
200
|
-
stateWrites: input.result.stateWrites,
|
|
201
|
-
warnings: input.result.warnings,
|
|
202
|
-
formattedResponse: input.formatted?.trim() || null,
|
|
203
|
-
durationMs: input.durationMs ?? null,
|
|
204
|
-
},
|
|
205
|
-
error: input.error,
|
|
192
|
+
eventType: input.event?.type ?? runtimePhaseEventType(input.phase),
|
|
193
|
+
sessionKey,
|
|
194
|
+
projectKey: team?.projectKey ?? null,
|
|
195
|
+
runId: team?.runId ?? null,
|
|
196
|
+
roleId: team?.roleId ?? null,
|
|
197
|
+
taskId: input.event?.scope.taskId ?? null,
|
|
198
|
+
summary: summarizeExecutionEvent(input),
|
|
199
|
+
metadata: createExecutionEventMetadata(input),
|
|
200
|
+
decision: input.event?.decision ?? null,
|
|
201
|
+
codeAgentTraceRefId: traceRefId,
|
|
206
202
|
}),
|
|
207
203
|
);
|
|
208
204
|
} catch {
|
|
209
|
-
//
|
|
205
|
+
// Execution event logging must never change hook runtime behavior.
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
async function recordTraceRefSafely(input: {
|
|
210
|
+
core?: HookRuntimeCoreApi;
|
|
211
|
+
homeDir: string;
|
|
212
|
+
target: string;
|
|
213
|
+
payload: Record<string, unknown> | null;
|
|
214
|
+
event?: HookEventV1;
|
|
215
|
+
environment?: Record<string, string | undefined>;
|
|
216
|
+
}): Promise<string | null> {
|
|
217
|
+
if (
|
|
218
|
+
input.core === undefined ||
|
|
219
|
+
input.payload === null ||
|
|
220
|
+
(input.target !== "claude" && input.target !== "codex")
|
|
221
|
+
) {
|
|
222
|
+
return null;
|
|
210
223
|
}
|
|
224
|
+
try {
|
|
225
|
+
const record = await input.core.recordCodeAgentTraceRefFromHook({
|
|
226
|
+
homeDir: input.homeDir,
|
|
227
|
+
target: input.target,
|
|
228
|
+
payload: input.payload,
|
|
229
|
+
environment: input.environment,
|
|
230
|
+
now:
|
|
231
|
+
input.event?.time.receivedAt === undefined || input.event.time.receivedAt === "dry-run"
|
|
232
|
+
? undefined
|
|
233
|
+
: input.event.time.receivedAt,
|
|
234
|
+
});
|
|
235
|
+
return record?.ref.id ?? null;
|
|
236
|
+
} catch {
|
|
237
|
+
return null;
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
function runtimePhaseEventType(phase: "started" | "completed" | "failed"): string {
|
|
242
|
+
if (phase === "started") return "HookRuntimeStarted";
|
|
243
|
+
if (phase === "completed") return "HookRuntimeCompleted";
|
|
244
|
+
return "HookRuntimeFailed";
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
function summarizeExecutionEvent(input: {
|
|
248
|
+
phase: "started" | "completed" | "failed";
|
|
249
|
+
event?: HookEventV1;
|
|
250
|
+
result?: HookRuntimeResult;
|
|
251
|
+
error?: unknown;
|
|
252
|
+
}): string {
|
|
253
|
+
if (input.phase === "failed") return "Hook runtime failed before completion.";
|
|
254
|
+
if (input.result !== undefined) return input.result.summary;
|
|
255
|
+
if (input.event !== undefined) return input.event.payload.summary;
|
|
256
|
+
return "Hook runtime started.";
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
function createExecutionEventMetadata(input: {
|
|
260
|
+
phase: "started" | "completed" | "failed";
|
|
261
|
+
stdinBytes: number | null;
|
|
262
|
+
event?: HookEventV1;
|
|
263
|
+
result?: HookRuntimeResult;
|
|
264
|
+
durationMs?: number | null;
|
|
265
|
+
}): Record<string, unknown> {
|
|
266
|
+
return {
|
|
267
|
+
phase: input.phase,
|
|
268
|
+
runtimeSurface: "plugin",
|
|
269
|
+
stdinBytes: input.stdinBytes,
|
|
270
|
+
durationMs: input.durationMs ?? null,
|
|
271
|
+
enabled: input.result?.enabled,
|
|
272
|
+
stateWriteCount: input.result?.stateWrites.length,
|
|
273
|
+
warningCount: input.result?.warnings.length,
|
|
274
|
+
commandClass: input.event?.payload.metadata.commandClass,
|
|
275
|
+
exitCode: input.event?.payload.metadata.exitCode,
|
|
276
|
+
status: input.event?.payload.metadata.status ?? input.phase,
|
|
277
|
+
redactionCount: input.event?.payload.redactionCount,
|
|
278
|
+
redactionLabels: input.event?.payload.redactions,
|
|
279
|
+
normalizedEventType: input.event?.type,
|
|
280
|
+
};
|
|
211
281
|
}
|
|
212
282
|
|
|
213
283
|
function isNotFoundError(error: unknown): boolean {
|
|
@@ -222,7 +292,6 @@ if (import.meta.main) {
|
|
|
222
292
|
try {
|
|
223
293
|
process.exitCode = await runHookRuntimeCli();
|
|
224
294
|
} catch (error) {
|
|
225
|
-
|
|
226
|
-
process.exitCode = 1;
|
|
295
|
+
process.exitCode = 0;
|
|
227
296
|
}
|
|
228
297
|
}
|
|
@@ -108,7 +108,6 @@ async function writeRuntimeCorePackage(input: {
|
|
|
108
108
|
>;
|
|
109
109
|
const runtimePackageJson = {
|
|
110
110
|
...sourcePackageJson,
|
|
111
|
-
exports: rewritePackageExportsToSource(sourcePackageJson.exports),
|
|
112
111
|
files: ["src", "assets", "package.json"],
|
|
113
112
|
};
|
|
114
113
|
await writeFile(
|
|
@@ -118,32 +117,6 @@ async function writeRuntimeCorePackage(input: {
|
|
|
118
117
|
);
|
|
119
118
|
}
|
|
120
119
|
|
|
121
|
-
function rewritePackageExportsToSource(exportsValue: unknown): unknown {
|
|
122
|
-
if (typeof exportsValue === "string") {
|
|
123
|
-
return rewriteDistPathToSource(exportsValue);
|
|
124
|
-
}
|
|
125
|
-
if (Array.isArray(exportsValue)) {
|
|
126
|
-
return exportsValue.map((value) => rewritePackageExportsToSource(value));
|
|
127
|
-
}
|
|
128
|
-
if (exportsValue !== null && typeof exportsValue === "object") {
|
|
129
|
-
return Object.fromEntries(
|
|
130
|
-
Object.entries(exportsValue).map(([key, value]) => [
|
|
131
|
-
key,
|
|
132
|
-
rewritePackageExportsToSource(value),
|
|
133
|
-
]),
|
|
134
|
-
);
|
|
135
|
-
}
|
|
136
|
-
return exportsValue;
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
function rewriteDistPathToSource(path: string): string {
|
|
140
|
-
if (!path.startsWith("./dist/") || !path.endsWith(".js")) {
|
|
141
|
-
return path;
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
return `./src/${path.slice("./dist/".length, -".js".length)}.ts`;
|
|
145
|
-
}
|
|
146
|
-
|
|
147
120
|
async function isDirectory(path: string): Promise<boolean> {
|
|
148
121
|
try {
|
|
149
122
|
return (await stat(path)).isDirectory();
|