@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.
Files changed (49) hide show
  1. package/dist/.claude-plugin/marketplace.json +2 -2
  2. package/dist/assets/agents/review/code-reviewer/examples.md +1 -1
  3. package/dist/assets/agents/review/code-reviewer/prompt.md +1 -1
  4. package/dist/assets/agents/review/code-reviewer/verification.md +1 -1
  5. package/dist/assets/skills/coding/knowledge-distillation/SKILL.md +251 -0
  6. package/dist/assets/skills/coding/knowledge-distillation/manifest.json +10 -0
  7. package/dist/assets/skills/coding/knowledge-distillation/references/knowledge-distillation-methods.md +126 -0
  8. package/dist/assets/team/agents/code-reviewer.md +48 -0
  9. package/dist/assets/team/agents/docs-maintainer.md +51 -0
  10. package/dist/assets/team/agents/implementation-engineer.md +51 -0
  11. package/dist/assets/team/agents/product-scope-analyst.md +58 -0
  12. package/dist/assets/team/agents/release-engineer.md +55 -0
  13. package/dist/assets/team/agents/security-boundary-reviewer.md +50 -0
  14. package/dist/assets/team/agents/solution-architect.md +51 -0
  15. package/dist/assets/team/agents/verification-engineer.md +51 -0
  16. package/dist/assets/team/team.md +102 -0
  17. package/dist/assets/workflows/rd-bug-fix/WORKFLOW.json +1 -1
  18. package/dist/assets/workflows/rd-code-review/WORKFLOW.json +1 -1
  19. package/dist/assets/workflows/rd-docs-update/WORKFLOW.json +1 -1
  20. package/dist/assets/workflows/rd-feature-implementation/WORKFLOW.json +1 -1
  21. package/dist/assets/workflows/rd-refactor/WORKFLOW.json +1 -1
  22. package/dist/assets/workflows/rd-release-readiness/WORKFLOW.json +1 -1
  23. package/dist/assets/workflows/rd-security-boundary-review/WORKFLOW.json +2 -2
  24. package/dist/assets/workflows/rd-test-generation/WORKFLOW.json +1 -1
  25. package/dist/index.js +29774 -9310
  26. package/dist/plugins/evodev/.claude-plugin/plugin.json +2 -2
  27. package/dist/plugins/evodev/.codex-plugin/plugin.json +8 -6
  28. package/dist/plugins/evodev/.mcp.json +6 -0
  29. package/dist/plugins/evodev/hooks/codex-hooks.json +10 -10
  30. package/dist/plugins/evodev/hooks/codex.ts +596 -34
  31. package/dist/plugins/evodev/hooks/hooks.json +18 -18
  32. package/dist/plugins/evodev/hooks/hooks.ts +470 -25
  33. package/dist/plugins/evodev/hooks/index.ts +15 -0
  34. package/dist/plugins/evodev/hooks/paths.ts +44 -1
  35. package/dist/plugins/evodev/hooks/plugin.ts +160 -9
  36. package/dist/plugins/evodev/hooks/runtime.ts +234 -43
  37. package/dist/plugins/evodev/hooks/transform-agent.ts +30 -0
  38. package/dist/plugins/evodev/hooks/workspace-core.ts +154 -0
  39. package/dist/plugins/evodev/package.json +3 -3
  40. package/dist/plugins/evodev/skills/engineering-discipline/SKILL.md +63 -0
  41. package/dist/plugins/evodev/skills/engineering-discipline/anti-patterns.md +21 -0
  42. package/dist/plugins/evodev/skills/engineering-discipline/examples.md +19 -0
  43. package/dist/plugins/evodev/skills/engineering-discipline/verification.md +11 -0
  44. package/dist/plugins/evodev/skills/knowledge-distillation/SKILL.md +251 -0
  45. package/dist/plugins/evodev/skills/knowledge-distillation/references/knowledge-distillation-methods.md +126 -0
  46. package/dist/ui/app.js +9 -0
  47. package/dist/ui/styles.css +2 -0
  48. package/package.json +9 -7
  49. 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(path: string, content: string): Promise<void> {
146
- await writeFile(path, content, { encoding: "utf8", flag: "wx" });
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
- "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.",
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 (await fileSystem.fileExists(targetPath)) {
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 (await fileSystem.fileExists(targetPath)) {
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
- createCoreConfigStore,
5
- createDefaultSettings,
6
- formatHookRuntimeOutput,
7
- handleHookRuntime,
8
- } from "@evo-dev/core";
9
- 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
+ }
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
- if (argv[0] !== "hook" || argv[1] !== "runtime") {
23
- throw new Error(`Unknown plugin command: ${argv.join(" ")}`.trim());
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
- const flags = parseHookRuntimeFlags(argv.slice(2));
27
- if (flags.target !== "claude" && flags.target !== "codex") {
28
- throw new Error(`Unsupported hook target: ${flags.target}`);
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
- const homeDir = options.homeDir ?? process.env.HOME ?? "~";
32
- const payload = JSON.parse(await readHookRuntimeStdin(options.stdin)) as Record<string, unknown>;
33
- const settings = await readHookSettings(homeDir);
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
- throw error;
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
- console.error(error instanceof Error ? error.message : String(error));
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
+ }