@evo-dev/evodev 0.0.1-alpha → 0.0.1-alpha.10
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/.claude-plugin/marketplace.json +2 -2
- package/dist/assets/agents/review/code-reviewer/examples.md +1 -1
- package/dist/assets/agents/review/code-reviewer/prompt.md +1 -1
- package/dist/assets/agents/review/code-reviewer/verification.md +1 -1
- package/dist/assets/skills/coding/knowledge-distillation/SKILL.md +251 -0
- package/dist/assets/skills/coding/knowledge-distillation/manifest.json +10 -0
- package/dist/assets/skills/coding/knowledge-distillation/references/knowledge-distillation-methods.md +126 -0
- 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/assets/workflows/rd-bug-fix/WORKFLOW.json +1 -1
- package/dist/assets/workflows/rd-code-review/WORKFLOW.json +1 -1
- package/dist/assets/workflows/rd-docs-update/WORKFLOW.json +1 -1
- package/dist/assets/workflows/rd-feature-implementation/WORKFLOW.json +1 -1
- package/dist/assets/workflows/rd-refactor/WORKFLOW.json +1 -1
- package/dist/assets/workflows/rd-release-readiness/WORKFLOW.json +1 -1
- package/dist/assets/workflows/rd-security-boundary-review/WORKFLOW.json +2 -2
- package/dist/assets/workflows/rd-test-generation/WORKFLOW.json +1 -1
- package/dist/index.js +29774 -9310
- package/dist/plugins/evodev/.claude-plugin/plugin.json +2 -2
- package/dist/plugins/evodev/.codex-plugin/plugin.json +8 -6
- package/dist/plugins/evodev/.mcp.json +6 -0
- package/dist/plugins/evodev/hooks/codex-hooks.json +10 -10
- package/dist/plugins/evodev/hooks/codex.ts +596 -34
- package/dist/plugins/evodev/hooks/hooks.json +18 -18
- package/dist/plugins/evodev/hooks/hooks.ts +470 -25
- package/dist/plugins/evodev/hooks/index.ts +15 -0
- package/dist/plugins/evodev/hooks/paths.ts +44 -1
- package/dist/plugins/evodev/hooks/plugin.ts +160 -9
- package/dist/plugins/evodev/hooks/runtime.ts +234 -43
- package/dist/plugins/evodev/hooks/transform-agent.ts +30 -0
- package/dist/plugins/evodev/hooks/workspace-core.ts +154 -0
- package/dist/plugins/evodev/package.json +3 -3
- package/dist/plugins/evodev/skills/engineering-discipline/SKILL.md +63 -0
- package/dist/plugins/evodev/skills/engineering-discipline/anti-patterns.md +21 -0
- package/dist/plugins/evodev/skills/engineering-discipline/examples.md +19 -0
- package/dist/plugins/evodev/skills/engineering-discipline/verification.md +11 -0
- package/dist/plugins/evodev/skills/knowledge-distillation/SKILL.md +251 -0
- package/dist/plugins/evodev/skills/knowledge-distillation/references/knowledge-distillation-methods.md +126 -0
- package/dist/ui/app.js +9 -0
- package/dist/ui/styles.css +2 -0
- package/package.json +9 -7
- package/dist/evodev +0 -11
|
@@ -23,6 +23,11 @@ import {
|
|
|
23
23
|
} from "./paths.ts";
|
|
24
24
|
import { transformClaudeAgent } from "./transform-agent.ts";
|
|
25
25
|
import { transformClaudeSkill } from "./transform-skill.ts";
|
|
26
|
+
import {
|
|
27
|
+
type WorkspaceCoreHydrationResult,
|
|
28
|
+
hydrateWorkspaceCoreDependency,
|
|
29
|
+
isSafePluginCacheSegment,
|
|
30
|
+
} from "./workspace-core.ts";
|
|
26
31
|
|
|
27
32
|
export interface ClaudePathAccessResult {
|
|
28
33
|
exists: boolean;
|
|
@@ -37,7 +42,7 @@ export interface ClaudePathAccess {
|
|
|
37
42
|
|
|
38
43
|
export interface ClaudeSyncFileSystem {
|
|
39
44
|
readFile(path: string): Promise<string>;
|
|
40
|
-
writeFile(path: string, content: string): Promise<void>;
|
|
45
|
+
writeFile(path: string, content: string, options?: { overwrite?: boolean }): Promise<void>;
|
|
41
46
|
fileExists(path: string): Promise<boolean>;
|
|
42
47
|
ensureDir(path: string): Promise<void>;
|
|
43
48
|
}
|
|
@@ -142,8 +147,15 @@ const nodeSyncFileSystem: ClaudeSyncFileSystem = {
|
|
|
142
147
|
async readFile(path: string): Promise<string> {
|
|
143
148
|
return readFile(path, "utf8");
|
|
144
149
|
},
|
|
145
|
-
async writeFile(
|
|
146
|
-
|
|
150
|
+
async writeFile(
|
|
151
|
+
path: string,
|
|
152
|
+
content: string,
|
|
153
|
+
options: { overwrite?: boolean } = {},
|
|
154
|
+
): Promise<void> {
|
|
155
|
+
await writeFile(path, content, {
|
|
156
|
+
encoding: "utf8",
|
|
157
|
+
flag: options.overwrite === true ? "w" : "wx",
|
|
158
|
+
});
|
|
147
159
|
},
|
|
148
160
|
async fileExists(path: string): Promise<boolean> {
|
|
149
161
|
try {
|
|
@@ -174,7 +186,7 @@ async function installClaudeCodePlugin(
|
|
|
174
186
|
input: PluginInstallInput,
|
|
175
187
|
options: Pick<
|
|
176
188
|
ClaudePluginOptions,
|
|
177
|
-
"commandRunner" | "pluginSelector" | "pluginMarketplaceSource" | "pluginScope"
|
|
189
|
+
"commandRunner" | "paths" | "pluginSelector" | "pluginMarketplaceSource" | "pluginScope"
|
|
178
190
|
>,
|
|
179
191
|
): Promise<PluginInstallResult> {
|
|
180
192
|
const command = "claude";
|
|
@@ -182,6 +194,7 @@ async function installClaudeCodePlugin(
|
|
|
182
194
|
const scope = options.pluginScope ?? "user";
|
|
183
195
|
const args = ["plugin", "install", selector, "--scope", scope];
|
|
184
196
|
const runner = options.commandRunner ?? new NodeClaudeCommandRunner();
|
|
197
|
+
const warnings: string[] = [];
|
|
185
198
|
|
|
186
199
|
try {
|
|
187
200
|
if (options.pluginMarketplaceSource !== undefined) {
|
|
@@ -204,10 +217,73 @@ async function installClaudeCodePlugin(
|
|
|
204
217
|
`Claude Code marketplace add exited with code ${marketplaceResult.exitCode}`,
|
|
205
218
|
);
|
|
206
219
|
}
|
|
220
|
+
|
|
221
|
+
const marketplaceName = extractMarketplaceNameFromSelector(selector);
|
|
222
|
+
if (marketplaceName === undefined) {
|
|
223
|
+
warnings.push(
|
|
224
|
+
`Skipped Claude Code marketplace update because selector does not include a marketplace name: ${selector}`,
|
|
225
|
+
);
|
|
226
|
+
} else {
|
|
227
|
+
const updateResult = await runner.run(command, [
|
|
228
|
+
"plugin",
|
|
229
|
+
"marketplace",
|
|
230
|
+
"update",
|
|
231
|
+
marketplaceName,
|
|
232
|
+
]);
|
|
233
|
+
if (updateResult.exitCode !== 0) {
|
|
234
|
+
return createPluginInstallResult(
|
|
235
|
+
input.targetPlugin,
|
|
236
|
+
"failed",
|
|
237
|
+
command,
|
|
238
|
+
args,
|
|
239
|
+
updateResult.stderr ??
|
|
240
|
+
updateResult.stdout ??
|
|
241
|
+
`Claude Code marketplace update exited with code ${updateResult.exitCode}`,
|
|
242
|
+
);
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
if (input.force === true) {
|
|
248
|
+
const uninstallArgs = [
|
|
249
|
+
"plugin",
|
|
250
|
+
"uninstall",
|
|
251
|
+
selector,
|
|
252
|
+
"--scope",
|
|
253
|
+
scope,
|
|
254
|
+
"--keep-data",
|
|
255
|
+
"-y",
|
|
256
|
+
];
|
|
257
|
+
const uninstallResult = await runner.run(command, uninstallArgs);
|
|
258
|
+
const uninstallMessage = uninstallResult.stderr ?? uninstallResult.stdout;
|
|
259
|
+
if (uninstallResult.exitCode !== 0 && !isNotInstalledMessage(uninstallMessage)) {
|
|
260
|
+
warnings.push(
|
|
261
|
+
`Claude Code plugin refresh uninstall did not complete: ${
|
|
262
|
+
uninstallMessage ??
|
|
263
|
+
`Claude Code plugin uninstall exited with code ${uninstallResult.exitCode}`
|
|
264
|
+
}`,
|
|
265
|
+
);
|
|
266
|
+
}
|
|
207
267
|
}
|
|
208
268
|
|
|
209
269
|
const result = await runner.run(command, args);
|
|
210
270
|
if (result.exitCode === 0) {
|
|
271
|
+
const hydration = await hydrateClaudeWorkspaceCoreDependency({
|
|
272
|
+
paths: options.paths,
|
|
273
|
+
selector,
|
|
274
|
+
});
|
|
275
|
+
warnings.push(...hydration.warnings);
|
|
276
|
+
if (hydration.errors.length > 0) {
|
|
277
|
+
return createPluginInstallResult(
|
|
278
|
+
input.targetPlugin,
|
|
279
|
+
"failed",
|
|
280
|
+
command,
|
|
281
|
+
args,
|
|
282
|
+
hydration.errors.join("\n"),
|
|
283
|
+
warnings,
|
|
284
|
+
);
|
|
285
|
+
}
|
|
286
|
+
|
|
211
287
|
if (isAlreadyInstalledMessage(result.stdout ?? result.stderr)) {
|
|
212
288
|
return createPluginInstallResult(
|
|
213
289
|
input.targetPlugin,
|
|
@@ -216,7 +292,10 @@ async function installClaudeCodePlugin(
|
|
|
216
292
|
args,
|
|
217
293
|
result.stdout,
|
|
218
294
|
[
|
|
219
|
-
|
|
295
|
+
...warnings,
|
|
296
|
+
input.force === true
|
|
297
|
+
? "Claude Code reported the plugin is already installed after forced refresh; check the local plugin cache if changes are not visible."
|
|
298
|
+
: "Claude Code reported the plugin is already installed; if local plugin files changed without a version bump, Claude may keep the existing cached copy. Bump the plugin version or uninstall and reinstall to refresh the cache.",
|
|
220
299
|
],
|
|
221
300
|
);
|
|
222
301
|
}
|
|
@@ -227,6 +306,7 @@ async function installClaudeCodePlugin(
|
|
|
227
306
|
command,
|
|
228
307
|
args,
|
|
229
308
|
result.stdout,
|
|
309
|
+
warnings,
|
|
230
310
|
);
|
|
231
311
|
}
|
|
232
312
|
|
|
@@ -254,6 +334,61 @@ function isAlreadyInstalledMessage(message: string | undefined): boolean {
|
|
|
254
334
|
return message !== undefined && /already\s+installed/i.test(message);
|
|
255
335
|
}
|
|
256
336
|
|
|
337
|
+
function isNotInstalledMessage(message: string | undefined): boolean {
|
|
338
|
+
return message !== undefined && /(not\s+installed|not\s+found|no\s+installed)/i.test(message);
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
function extractMarketplaceNameFromSelector(selector: string): string | undefined {
|
|
342
|
+
const separator = selector.lastIndexOf("@");
|
|
343
|
+
if (separator <= 0 || separator === selector.length - 1) return undefined;
|
|
344
|
+
return selector.slice(separator + 1);
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
function extractPluginNameFromSelector(selector: string): string | undefined {
|
|
348
|
+
const separator = selector.lastIndexOf("@");
|
|
349
|
+
const pluginName = separator <= 0 ? selector : selector.slice(0, separator);
|
|
350
|
+
return pluginName.trim() === "" ? undefined : pluginName;
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
async function hydrateClaudeWorkspaceCoreDependency(input: {
|
|
354
|
+
paths?: ClaudePaths;
|
|
355
|
+
selector: string;
|
|
356
|
+
}): Promise<WorkspaceCoreHydrationResult> {
|
|
357
|
+
const warnings: string[] = [];
|
|
358
|
+
const errors: string[] = [];
|
|
359
|
+
|
|
360
|
+
if (input.paths === undefined) {
|
|
361
|
+
return { warnings, errors };
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
const marketplaceName = extractMarketplaceNameFromSelector(input.selector);
|
|
365
|
+
const pluginName = extractPluginNameFromSelector(input.selector);
|
|
366
|
+
if (marketplaceName === undefined || pluginName === undefined) {
|
|
367
|
+
warnings.push(
|
|
368
|
+
`Skipped @evo-dev/core hydration because Claude plugin selector is not marketplace-qualified: ${input.selector}`,
|
|
369
|
+
);
|
|
370
|
+
return { warnings, errors };
|
|
371
|
+
}
|
|
372
|
+
if (!isSafePluginCacheSegment(marketplaceName) || !isSafePluginCacheSegment(pluginName)) {
|
|
373
|
+
warnings.push(
|
|
374
|
+
`Skipped @evo-dev/core hydration because Claude plugin selector contains an unsafe cache segment: ${input.selector}`,
|
|
375
|
+
);
|
|
376
|
+
return { warnings, errors };
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
const cachePluginRoot = `${input.paths.claudeDir}/plugins/cache/${marketplaceName}/${pluginName}`;
|
|
380
|
+
const marketplaceCoreRoot = `${input.paths.claudeDir}/plugins/marketplaces/${marketplaceName}/packages/core`;
|
|
381
|
+
const hydration = await hydrateWorkspaceCoreDependency({
|
|
382
|
+
sourceCoreRoot: marketplaceCoreRoot,
|
|
383
|
+
cachePluginRoot,
|
|
384
|
+
runtimeLabel: "Claude",
|
|
385
|
+
});
|
|
386
|
+
return {
|
|
387
|
+
warnings: [...warnings, ...hydration.warnings],
|
|
388
|
+
errors: [...errors, ...hydration.errors],
|
|
389
|
+
};
|
|
390
|
+
}
|
|
391
|
+
|
|
257
392
|
class NodeClaudeCommandRunner implements ClaudeCommandRunner {
|
|
258
393
|
async run(
|
|
259
394
|
command: string,
|
|
@@ -384,8 +519,9 @@ async function syncClaudeSkills(
|
|
|
384
519
|
const source = await fileSystem.readFile(asset.entryPath);
|
|
385
520
|
const transformed = transformClaudeSkill({ asset, source });
|
|
386
521
|
const targetPath = resolveClaudeSkillTargetPath(transformed.name, paths);
|
|
522
|
+
const targetExists = await fileSystem.fileExists(targetPath);
|
|
387
523
|
|
|
388
|
-
if (
|
|
524
|
+
if (targetExists && input.force !== true) {
|
|
389
525
|
result.skipped.push(asset.registryKey);
|
|
390
526
|
result.warnings.push(`Skipped existing Claude skill target: ${targetPath}`);
|
|
391
527
|
continue;
|
|
@@ -393,9 +529,16 @@ async function syncClaudeSkills(
|
|
|
393
529
|
|
|
394
530
|
if (!input.dryRun) {
|
|
395
531
|
await fileSystem.ensureDir(dirname(targetPath));
|
|
396
|
-
await fileSystem.writeFile(targetPath, ensureTrailingNewline(transformed.content)
|
|
532
|
+
await fileSystem.writeFile(targetPath, ensureTrailingNewline(transformed.content), {
|
|
533
|
+
overwrite: input.force === true,
|
|
534
|
+
});
|
|
397
535
|
}
|
|
398
536
|
|
|
537
|
+
if (targetExists) {
|
|
538
|
+
result.warnings.push(
|
|
539
|
+
`${input.dryRun ? "Would update" : "Updated"} existing Claude skill target: ${targetPath}`,
|
|
540
|
+
);
|
|
541
|
+
}
|
|
399
542
|
result.syncedSkills.push(asset.registryKey);
|
|
400
543
|
} catch (error) {
|
|
401
544
|
result.errors.push(
|
|
@@ -424,8 +567,9 @@ async function syncClaudeAgents(
|
|
|
424
567
|
const source = await fileSystem.readFile(asset.entryPath);
|
|
425
568
|
const transformed = transformClaudeAgent({ asset, source });
|
|
426
569
|
const targetPath = resolveClaudeAgentTargetPath(transformed.name, paths);
|
|
570
|
+
const targetExists = await fileSystem.fileExists(targetPath);
|
|
427
571
|
|
|
428
|
-
if (
|
|
572
|
+
if (targetExists && input.force !== true) {
|
|
429
573
|
result.skipped.push(asset.registryKey);
|
|
430
574
|
result.warnings.push(`Skipped existing Claude agent target: ${targetPath}`);
|
|
431
575
|
continue;
|
|
@@ -433,9 +577,16 @@ async function syncClaudeAgents(
|
|
|
433
577
|
|
|
434
578
|
if (!input.dryRun) {
|
|
435
579
|
await fileSystem.ensureDir(dirname(targetPath));
|
|
436
|
-
await fileSystem.writeFile(targetPath, ensureTrailingNewline(transformed.content)
|
|
580
|
+
await fileSystem.writeFile(targetPath, ensureTrailingNewline(transformed.content), {
|
|
581
|
+
overwrite: input.force === true,
|
|
582
|
+
});
|
|
437
583
|
}
|
|
438
584
|
|
|
585
|
+
if (targetExists) {
|
|
586
|
+
result.warnings.push(
|
|
587
|
+
`${input.dryRun ? "Would update" : "Updated"} existing Claude agent target: ${targetPath}`,
|
|
588
|
+
);
|
|
589
|
+
}
|
|
439
590
|
result.syncedAgents.push(asset.registryKey);
|
|
440
591
|
} catch (error) {
|
|
441
592
|
result.errors.push(
|
|
@@ -1,57 +1,122 @@
|
|
|
1
1
|
#!/usr/bin/env bun
|
|
2
2
|
|
|
3
|
-
import {
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
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
|
+
}
|
|
10
12
|
|
|
11
13
|
export interface HookRuntimeCliOptions {
|
|
12
14
|
argv?: string[];
|
|
15
|
+
environment?: Record<string, string | undefined>;
|
|
13
16
|
homeDir?: string;
|
|
17
|
+
loadRuntimeApi?: () => Promise<HookRuntimeApi>;
|
|
14
18
|
stdin?: string | (() => Promise<string>);
|
|
15
19
|
write?: (message: string) => void;
|
|
16
20
|
}
|
|
17
21
|
|
|
18
22
|
export async function runHookRuntimeCli(options: HookRuntimeCliOptions = {}): Promise<number> {
|
|
19
23
|
const argv = options.argv ?? process.argv.slice(2);
|
|
24
|
+
const environment = options.environment ?? process.env;
|
|
20
25
|
const write = options.write ?? process.stdout.write.bind(process.stdout);
|
|
26
|
+
const startedAt = new Date();
|
|
27
|
+
const loadRuntimeApi = options.loadRuntimeApi ?? loadDefaultHookRuntimeApi;
|
|
28
|
+
let homeDir = options.homeDir ?? process.env.HOME ?? "~";
|
|
29
|
+
let runtimeApi: HookRuntimeApi | null = null;
|
|
30
|
+
let target = "unknown";
|
|
31
|
+
let payload: Record<string, unknown> | null = null;
|
|
32
|
+
let stdinBytes: number | null = null;
|
|
21
33
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
34
|
+
try {
|
|
35
|
+
if (argv[0] !== "hook" || argv[1] !== "runtime") {
|
|
36
|
+
throw new Error(`Unknown plugin command: ${argv.join(" ")}`.trim());
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const flags = parseHookRuntimeFlags(argv.slice(2));
|
|
40
|
+
target = flags.target;
|
|
41
|
+
if (flags.target !== "claude" && flags.target !== "codex") {
|
|
42
|
+
throw new Error(`Unsupported hook target: ${flags.target}`);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
homeDir = options.homeDir ?? process.env.HOME ?? "~";
|
|
46
|
+
const stdin = await readHookRuntimeStdin(options.stdin);
|
|
47
|
+
stdinBytes = Buffer.byteLength(stdin, "utf8");
|
|
48
|
+
runtimeApi = await loadRuntimeApi();
|
|
49
|
+
payload = JSON.parse(stdin) as Record<string, unknown>;
|
|
50
|
+
await appendExecutionEventSafely({
|
|
51
|
+
core: runtimeApi.core,
|
|
52
|
+
homeDir,
|
|
53
|
+
phase: "started",
|
|
54
|
+
target,
|
|
55
|
+
argv,
|
|
56
|
+
payload,
|
|
57
|
+
stdinBytes,
|
|
58
|
+
environment,
|
|
59
|
+
});
|
|
25
60
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
61
|
+
const settings = await readHookSettings(runtimeApi.core, homeDir);
|
|
62
|
+
const event =
|
|
63
|
+
flags.target === "codex"
|
|
64
|
+
? runtimeApi.normalizers.normalizeCodexRuntimeHookPayload({
|
|
65
|
+
payload,
|
|
66
|
+
receivedAt: new Date().toISOString(),
|
|
67
|
+
})
|
|
68
|
+
: runtimeApi.normalizers.normalizeClaudeRuntimeHookPayload({
|
|
69
|
+
payload,
|
|
70
|
+
receivedAt: new Date().toISOString(),
|
|
71
|
+
});
|
|
72
|
+
const result = await runtimeApi.core.handleHookRuntime({
|
|
73
|
+
target: flags.target,
|
|
74
|
+
homeDir,
|
|
75
|
+
settings: settings.hooks,
|
|
76
|
+
teamRuntimeDisplayMode: settings.teamRuntime.displayMode,
|
|
77
|
+
event,
|
|
78
|
+
rawPayload: payload,
|
|
79
|
+
receivedAt: event.time.receivedAt,
|
|
80
|
+
environment,
|
|
81
|
+
runtimeInjectionEnabled: settings.memory.runtimeInjection,
|
|
82
|
+
sessionMemoryPolicy: settings.memory.sessionMemory,
|
|
83
|
+
});
|
|
84
|
+
const formatted = runtimeApi.core.formatHookRuntimeOutput(result);
|
|
85
|
+
await appendExecutionEventSafely({
|
|
86
|
+
core: runtimeApi.core,
|
|
87
|
+
homeDir,
|
|
88
|
+
phase: "completed",
|
|
89
|
+
target,
|
|
90
|
+
argv,
|
|
91
|
+
payload,
|
|
92
|
+
stdinBytes,
|
|
93
|
+
event,
|
|
94
|
+
result,
|
|
95
|
+
environment,
|
|
96
|
+
durationMs: Date.now() - startedAt.getTime(),
|
|
97
|
+
});
|
|
98
|
+
if (formatted.length > 0) write(formatted);
|
|
99
|
+
return 0;
|
|
100
|
+
} catch (error) {
|
|
101
|
+
await appendExecutionEventSafely({
|
|
102
|
+
core: runtimeApi?.core,
|
|
103
|
+
homeDir,
|
|
104
|
+
phase: "failed",
|
|
105
|
+
target,
|
|
106
|
+
argv,
|
|
107
|
+
payload,
|
|
108
|
+
stdinBytes,
|
|
109
|
+
error,
|
|
110
|
+
environment,
|
|
111
|
+
durationMs: Date.now() - startedAt.getTime(),
|
|
112
|
+
});
|
|
113
|
+
return 0;
|
|
29
114
|
}
|
|
115
|
+
}
|
|
30
116
|
|
|
31
|
-
|
|
32
|
-
const
|
|
33
|
-
|
|
34
|
-
const event =
|
|
35
|
-
flags.target === "codex"
|
|
36
|
-
? normalizeCodexRuntimeHookPayload({
|
|
37
|
-
payload,
|
|
38
|
-
receivedAt: new Date().toISOString(),
|
|
39
|
-
})
|
|
40
|
-
: normalizeClaudeRuntimeHookPayload({
|
|
41
|
-
payload,
|
|
42
|
-
receivedAt: new Date().toISOString(),
|
|
43
|
-
});
|
|
44
|
-
const result = await handleHookRuntime({
|
|
45
|
-
target: flags.target,
|
|
46
|
-
homeDir,
|
|
47
|
-
settings: settings.hooks,
|
|
48
|
-
event,
|
|
49
|
-
rawPayload: payload,
|
|
50
|
-
receivedAt: event.time.receivedAt,
|
|
51
|
-
});
|
|
52
|
-
const formatted = formatHookRuntimeOutput(result);
|
|
53
|
-
if (formatted.length > 0) write(formatted);
|
|
54
|
-
return 0;
|
|
117
|
+
async function loadDefaultHookRuntimeApi(): Promise<HookRuntimeApi> {
|
|
118
|
+
const [core, normalizers] = await Promise.all([import("@evo-dev/core"), import("./hooks.ts")]);
|
|
119
|
+
return { core, normalizers };
|
|
55
120
|
}
|
|
56
121
|
|
|
57
122
|
function parseHookRuntimeFlags(argv: string[]): { target: string } {
|
|
@@ -84,17 +149,144 @@ async function readHookRuntimeStdin(stdin: HookRuntimeCliOptions["stdin"]): Prom
|
|
|
84
149
|
return Buffer.concat(chunks).toString("utf8");
|
|
85
150
|
}
|
|
86
151
|
|
|
87
|
-
async function readHookSettings(homeDir: string) {
|
|
152
|
+
async function readHookSettings(core: HookRuntimeCoreApi, homeDir: string) {
|
|
88
153
|
try {
|
|
89
|
-
return await createCoreConfigStore(homeDir).readSettings();
|
|
154
|
+
return await core.createCoreConfigStore(homeDir).readSettings();
|
|
90
155
|
} catch (error) {
|
|
91
156
|
if (isNotFoundError(error)) {
|
|
92
|
-
return createDefaultSettings();
|
|
157
|
+
return core.createDefaultSettings();
|
|
93
158
|
}
|
|
94
|
-
|
|
159
|
+
return core.createDefaultSettings();
|
|
95
160
|
}
|
|
96
161
|
}
|
|
97
162
|
|
|
163
|
+
async function appendExecutionEventSafely(input: {
|
|
164
|
+
core?: HookRuntimeCoreApi;
|
|
165
|
+
homeDir: string;
|
|
166
|
+
phase: "started" | "completed" | "failed";
|
|
167
|
+
target: string;
|
|
168
|
+
argv: string[];
|
|
169
|
+
payload: Record<string, unknown> | null;
|
|
170
|
+
stdinBytes: number | null;
|
|
171
|
+
event?: HookEventV1;
|
|
172
|
+
result?: HookRuntimeResult;
|
|
173
|
+
durationMs?: number | null;
|
|
174
|
+
error?: unknown;
|
|
175
|
+
environment?: Record<string, string | undefined>;
|
|
176
|
+
}): Promise<void> {
|
|
177
|
+
if (input.core === undefined) return;
|
|
178
|
+
|
|
179
|
+
try {
|
|
180
|
+
const sessionKey =
|
|
181
|
+
input.payload === null ? "session-local" : input.core.resolveTraceSessionKey(input.payload);
|
|
182
|
+
const team = input.core.resolveTraceTeamContext({
|
|
183
|
+
homeDir: input.homeDir,
|
|
184
|
+
environment: input.environment,
|
|
185
|
+
payload: input.payload,
|
|
186
|
+
});
|
|
187
|
+
const cwd =
|
|
188
|
+
input.payload !== null && typeof input.payload.cwd === "string" ? input.payload.cwd : null;
|
|
189
|
+
const workspace =
|
|
190
|
+
team === null && cwd !== null
|
|
191
|
+
? await input.core.resolveProjectWorkspaceFromCwd({ homeDir: input.homeDir, cwd })
|
|
192
|
+
: null;
|
|
193
|
+
const traceRefId = await recordTraceRefSafely(input);
|
|
194
|
+
await input.core.appendEvoDevExecutionEvent(
|
|
195
|
+
input.homeDir,
|
|
196
|
+
input.core.createEvoDevExecutionEvent({
|
|
197
|
+
eventId: input.event?.eventId,
|
|
198
|
+
target: input.target,
|
|
199
|
+
eventType: input.event?.type ?? runtimePhaseEventType(input.phase),
|
|
200
|
+
sessionKey,
|
|
201
|
+
projectKey: team?.projectKey ?? workspace?.projectKey ?? null,
|
|
202
|
+
runId: team?.runId ?? null,
|
|
203
|
+
roleId: team?.roleId ?? null,
|
|
204
|
+
taskId: input.event?.scope.taskId ?? null,
|
|
205
|
+
summary: summarizeExecutionEvent(input),
|
|
206
|
+
metadata: createExecutionEventMetadata(input),
|
|
207
|
+
decision: input.event?.decision ?? null,
|
|
208
|
+
codeAgentTraceRefId: traceRefId,
|
|
209
|
+
}),
|
|
210
|
+
);
|
|
211
|
+
} catch {
|
|
212
|
+
// Execution event logging must never change hook runtime behavior.
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
async function recordTraceRefSafely(input: {
|
|
217
|
+
core?: HookRuntimeCoreApi;
|
|
218
|
+
homeDir: string;
|
|
219
|
+
target: string;
|
|
220
|
+
payload: Record<string, unknown> | null;
|
|
221
|
+
event?: HookEventV1;
|
|
222
|
+
environment?: Record<string, string | undefined>;
|
|
223
|
+
}): Promise<string | null> {
|
|
224
|
+
if (
|
|
225
|
+
input.core === undefined ||
|
|
226
|
+
input.payload === null ||
|
|
227
|
+
(input.target !== "claude" && input.target !== "codex")
|
|
228
|
+
) {
|
|
229
|
+
return null;
|
|
230
|
+
}
|
|
231
|
+
try {
|
|
232
|
+
const record = await input.core.recordCodeAgentTraceRefFromHook({
|
|
233
|
+
homeDir: input.homeDir,
|
|
234
|
+
target: input.target,
|
|
235
|
+
payload: input.payload,
|
|
236
|
+
environment: input.environment,
|
|
237
|
+
now:
|
|
238
|
+
input.event?.time.receivedAt === undefined || input.event.time.receivedAt === "dry-run"
|
|
239
|
+
? undefined
|
|
240
|
+
: input.event.time.receivedAt,
|
|
241
|
+
});
|
|
242
|
+
return record?.ref.id ?? null;
|
|
243
|
+
} catch {
|
|
244
|
+
return null;
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
function runtimePhaseEventType(phase: "started" | "completed" | "failed"): string {
|
|
249
|
+
if (phase === "started") return "HookRuntimeStarted";
|
|
250
|
+
if (phase === "completed") return "HookRuntimeCompleted";
|
|
251
|
+
return "HookRuntimeFailed";
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
function summarizeExecutionEvent(input: {
|
|
255
|
+
phase: "started" | "completed" | "failed";
|
|
256
|
+
event?: HookEventV1;
|
|
257
|
+
result?: HookRuntimeResult;
|
|
258
|
+
error?: unknown;
|
|
259
|
+
}): string {
|
|
260
|
+
if (input.phase === "failed") return "Hook runtime failed before completion.";
|
|
261
|
+
if (input.result !== undefined) return input.result.summary;
|
|
262
|
+
if (input.event !== undefined) return input.event.payload.summary;
|
|
263
|
+
return "Hook runtime started.";
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
function createExecutionEventMetadata(input: {
|
|
267
|
+
phase: "started" | "completed" | "failed";
|
|
268
|
+
stdinBytes: number | null;
|
|
269
|
+
event?: HookEventV1;
|
|
270
|
+
result?: HookRuntimeResult;
|
|
271
|
+
durationMs?: number | null;
|
|
272
|
+
}): Record<string, unknown> {
|
|
273
|
+
return {
|
|
274
|
+
phase: input.phase,
|
|
275
|
+
runtimeSurface: "plugin",
|
|
276
|
+
stdinBytes: input.stdinBytes,
|
|
277
|
+
durationMs: input.durationMs ?? null,
|
|
278
|
+
enabled: input.result?.enabled,
|
|
279
|
+
stateWriteCount: input.result?.stateWrites.length,
|
|
280
|
+
warningCount: input.result?.warnings.length,
|
|
281
|
+
commandClass: input.event?.payload.metadata.commandClass,
|
|
282
|
+
exitCode: input.event?.payload.metadata.exitCode,
|
|
283
|
+
status: input.event?.payload.metadata.status ?? input.phase,
|
|
284
|
+
redactionCount: input.event?.payload.redactionCount,
|
|
285
|
+
redactionLabels: input.event?.payload.redactions,
|
|
286
|
+
normalizedEventType: input.event?.type,
|
|
287
|
+
};
|
|
288
|
+
}
|
|
289
|
+
|
|
98
290
|
function isNotFoundError(error: unknown): boolean {
|
|
99
291
|
return (
|
|
100
292
|
error instanceof Error &&
|
|
@@ -107,7 +299,6 @@ if (import.meta.main) {
|
|
|
107
299
|
try {
|
|
108
300
|
process.exitCode = await runHookRuntimeCli();
|
|
109
301
|
} catch (error) {
|
|
110
|
-
|
|
111
|
-
process.exitCode = 1;
|
|
302
|
+
process.exitCode = 0;
|
|
112
303
|
}
|
|
113
304
|
}
|
|
@@ -10,6 +10,16 @@ export interface ClaudeAgentTransformResult {
|
|
|
10
10
|
content: string;
|
|
11
11
|
}
|
|
12
12
|
|
|
13
|
+
export interface CodexAgentTransformInput {
|
|
14
|
+
asset: ScannedAsset<AgentManifest>;
|
|
15
|
+
source: string;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export interface CodexAgentTransformResult {
|
|
19
|
+
name: string;
|
|
20
|
+
content: string;
|
|
21
|
+
}
|
|
22
|
+
|
|
13
23
|
export function transformClaudeAgent(input: ClaudeAgentTransformInput): ClaudeAgentTransformResult {
|
|
14
24
|
const { manifest } = input.asset;
|
|
15
25
|
const frontmatter = [
|
|
@@ -25,6 +35,26 @@ export function transformClaudeAgent(input: ClaudeAgentTransformInput): ClaudeAg
|
|
|
25
35
|
};
|
|
26
36
|
}
|
|
27
37
|
|
|
38
|
+
export function transformCodexAgent(input: CodexAgentTransformInput): CodexAgentTransformResult {
|
|
39
|
+
const { manifest } = input.asset;
|
|
40
|
+
const lines = [
|
|
41
|
+
`# Generated by EvoDev from ${input.asset.registryKey}`,
|
|
42
|
+
`name = ${tomlString(manifest.id)}`,
|
|
43
|
+
`description = ${tomlString(manifest.description)}`,
|
|
44
|
+
`developer_instructions = ${tomlString(input.source)}`,
|
|
45
|
+
"",
|
|
46
|
+
];
|
|
47
|
+
|
|
48
|
+
return {
|
|
49
|
+
name: manifest.id,
|
|
50
|
+
content: lines.join("\n"),
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
|
|
28
54
|
function escapeFrontmatterValue(value: string): string {
|
|
29
55
|
return value.replaceAll("\n", " ");
|
|
30
56
|
}
|
|
57
|
+
|
|
58
|
+
function tomlString(value: string): string {
|
|
59
|
+
return JSON.stringify(value);
|
|
60
|
+
}
|