@evo-dev/evodev 0.0.1-alpha → 0.0.1-alpha.1
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 +248 -0
- package/dist/assets/skills/coding/knowledge-distillation/manifest.json +10 -0
- package/dist/assets/skills/coding/knowledge-distillation/references/knowledge-distillation-methods.md +122 -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 +13114 -6432
- 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 +393 -34
- package/dist/plugins/evodev/hooks/hooks.json +18 -18
- package/dist/plugins/evodev/hooks/hooks.ts +268 -10
- 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 +146 -31
- package/dist/plugins/evodev/hooks/transform-agent.ts +30 -0
- package/dist/plugins/evodev/hooks/workspace-core.ts +181 -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 +248 -0
- package/dist/plugins/evodev/skills/knowledge-distillation/references/knowledge-distillation-methods.md +122 -0
- package/package.json +3 -6
|
@@ -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,15 +1,21 @@
|
|
|
1
1
|
#!/usr/bin/env bun
|
|
2
2
|
|
|
3
3
|
import {
|
|
4
|
+
type HookEventV1,
|
|
5
|
+
type HookRuntimeResult,
|
|
6
|
+
appendTraceLogEntry,
|
|
4
7
|
createCoreConfigStore,
|
|
5
8
|
createDefaultSettings,
|
|
9
|
+
createTraceLogEntry,
|
|
6
10
|
formatHookRuntimeOutput,
|
|
7
11
|
handleHookRuntime,
|
|
12
|
+
resolveTraceTeamContext,
|
|
8
13
|
} from "@evo-dev/core";
|
|
9
14
|
import { normalizeClaudeRuntimeHookPayload, normalizeCodexRuntimeHookPayload } from "./hooks.ts";
|
|
10
15
|
|
|
11
16
|
export interface HookRuntimeCliOptions {
|
|
12
17
|
argv?: string[];
|
|
18
|
+
environment?: Record<string, string | undefined>;
|
|
13
19
|
homeDir?: string;
|
|
14
20
|
stdin?: string | (() => Promise<string>);
|
|
15
21
|
write?: (message: string) => void;
|
|
@@ -17,41 +23,89 @@ export interface HookRuntimeCliOptions {
|
|
|
17
23
|
|
|
18
24
|
export async function runHookRuntimeCli(options: HookRuntimeCliOptions = {}): Promise<number> {
|
|
19
25
|
const argv = options.argv ?? process.argv.slice(2);
|
|
26
|
+
const environment = options.environment ?? process.env;
|
|
20
27
|
const write = options.write ?? process.stdout.write.bind(process.stdout);
|
|
28
|
+
const startedAt = new Date();
|
|
29
|
+
let homeDir = options.homeDir ?? process.env.HOME ?? "~";
|
|
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
|
+
}
|
|
25
38
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
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
|
+
payload = JSON.parse(stdin) as Record<string, unknown>;
|
|
49
|
+
await appendTraceLogSafely({
|
|
50
|
+
homeDir,
|
|
51
|
+
phase: "started",
|
|
52
|
+
target,
|
|
53
|
+
argv,
|
|
54
|
+
payload,
|
|
55
|
+
stdinBytes,
|
|
56
|
+
environment,
|
|
57
|
+
});
|
|
30
58
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
59
|
+
const settings = await readHookSettings(homeDir);
|
|
60
|
+
const event =
|
|
61
|
+
flags.target === "codex"
|
|
62
|
+
? normalizeCodexRuntimeHookPayload({
|
|
63
|
+
payload,
|
|
64
|
+
receivedAt: new Date().toISOString(),
|
|
65
|
+
})
|
|
66
|
+
: normalizeClaudeRuntimeHookPayload({
|
|
67
|
+
payload,
|
|
68
|
+
receivedAt: new Date().toISOString(),
|
|
69
|
+
});
|
|
70
|
+
const result = await handleHookRuntime({
|
|
71
|
+
target: flags.target,
|
|
72
|
+
homeDir,
|
|
73
|
+
settings: settings.hooks,
|
|
74
|
+
event,
|
|
75
|
+
rawPayload: payload,
|
|
76
|
+
receivedAt: event.time.receivedAt,
|
|
77
|
+
environment,
|
|
78
|
+
});
|
|
79
|
+
const formatted = formatHookRuntimeOutput(result);
|
|
80
|
+
await appendTraceLogSafely({
|
|
81
|
+
homeDir,
|
|
82
|
+
phase: "completed",
|
|
83
|
+
target,
|
|
84
|
+
argv,
|
|
85
|
+
payload,
|
|
86
|
+
stdinBytes,
|
|
87
|
+
event,
|
|
88
|
+
result,
|
|
89
|
+
formatted,
|
|
90
|
+
environment,
|
|
91
|
+
durationMs: Date.now() - startedAt.getTime(),
|
|
92
|
+
});
|
|
93
|
+
if (formatted.length > 0) write(formatted);
|
|
94
|
+
return 0;
|
|
95
|
+
} catch (error) {
|
|
96
|
+
await appendTraceLogSafely({
|
|
97
|
+
homeDir,
|
|
98
|
+
phase: "failed",
|
|
99
|
+
target,
|
|
100
|
+
argv,
|
|
101
|
+
payload,
|
|
102
|
+
stdinBytes,
|
|
103
|
+
error,
|
|
104
|
+
environment,
|
|
105
|
+
durationMs: Date.now() - startedAt.getTime(),
|
|
106
|
+
});
|
|
107
|
+
throw error;
|
|
108
|
+
}
|
|
55
109
|
}
|
|
56
110
|
|
|
57
111
|
function parseHookRuntimeFlags(argv: string[]): { target: string } {
|
|
@@ -95,6 +149,67 @@ async function readHookSettings(homeDir: string) {
|
|
|
95
149
|
}
|
|
96
150
|
}
|
|
97
151
|
|
|
152
|
+
async function appendTraceLogSafely(input: {
|
|
153
|
+
homeDir: string;
|
|
154
|
+
phase: "started" | "completed" | "failed";
|
|
155
|
+
target: string;
|
|
156
|
+
argv: string[];
|
|
157
|
+
payload: Record<string, unknown> | null;
|
|
158
|
+
stdinBytes: number | null;
|
|
159
|
+
event?: HookEventV1;
|
|
160
|
+
result?: HookRuntimeResult;
|
|
161
|
+
formatted?: string;
|
|
162
|
+
durationMs?: number | null;
|
|
163
|
+
error?: unknown;
|
|
164
|
+
environment?: Record<string, string | undefined>;
|
|
165
|
+
}): Promise<void> {
|
|
166
|
+
try {
|
|
167
|
+
await appendTraceLogEntry(
|
|
168
|
+
input.homeDir,
|
|
169
|
+
createTraceLogEntry({
|
|
170
|
+
phase: input.phase,
|
|
171
|
+
target: input.target,
|
|
172
|
+
runtime: {
|
|
173
|
+
surface: "plugin",
|
|
174
|
+
argv: input.argv,
|
|
175
|
+
runtimeFile: import.meta.url,
|
|
176
|
+
},
|
|
177
|
+
payload: input.payload,
|
|
178
|
+
stdinBytes: input.stdinBytes,
|
|
179
|
+
team: resolveTraceTeamContext({
|
|
180
|
+
homeDir: input.homeDir,
|
|
181
|
+
environment: input.environment,
|
|
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,
|
|
206
|
+
}),
|
|
207
|
+
);
|
|
208
|
+
} catch {
|
|
209
|
+
// Trace logging must never change hook runtime behavior.
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
|
|
98
213
|
function isNotFoundError(error: unknown): boolean {
|
|
99
214
|
return (
|
|
100
215
|
error instanceof Error &&
|
|
@@ -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
|
+
}
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
import type { Dirent } from "node:fs";
|
|
2
|
+
import { cp, mkdir, readFile, readdir, rm, stat, writeFile } from "node:fs/promises";
|
|
3
|
+
import { dirname } from "node:path";
|
|
4
|
+
|
|
5
|
+
export interface WorkspaceCoreHydrationResult {
|
|
6
|
+
warnings: string[];
|
|
7
|
+
errors: string[];
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export async function hydrateWorkspaceCoreDependency(input: {
|
|
11
|
+
sourceCoreRoot: string;
|
|
12
|
+
cachePluginRoot: string;
|
|
13
|
+
runtimeLabel: string;
|
|
14
|
+
}): Promise<WorkspaceCoreHydrationResult> {
|
|
15
|
+
const warnings: string[] = [];
|
|
16
|
+
const errors: string[] = [];
|
|
17
|
+
|
|
18
|
+
const installedPluginRoot = await findInstalledPluginRoot(input.cachePluginRoot);
|
|
19
|
+
if (installedPluginRoot === null) {
|
|
20
|
+
warnings.push(
|
|
21
|
+
`Skipped @evo-dev/core hydration because the ${input.runtimeLabel} plugin cache was not found: ${input.cachePluginRoot}`,
|
|
22
|
+
);
|
|
23
|
+
return { warnings, errors };
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
if (!(await isDirectory(input.sourceCoreRoot))) {
|
|
27
|
+
errors.push(
|
|
28
|
+
`Cannot hydrate @evo-dev/core for ${input.runtimeLabel} plugin runtime: marketplace core package not found at ${input.sourceCoreRoot}`,
|
|
29
|
+
);
|
|
30
|
+
return { warnings, errors };
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
try {
|
|
34
|
+
await writeRuntimeCorePackage({
|
|
35
|
+
sourceCoreRoot: input.sourceCoreRoot,
|
|
36
|
+
targetCoreRoot: `${installedPluginRoot}/node_modules/@evo-dev/core`,
|
|
37
|
+
});
|
|
38
|
+
} catch (error) {
|
|
39
|
+
errors.push(
|
|
40
|
+
`Cannot hydrate @evo-dev/core for ${input.runtimeLabel} plugin runtime: ${describeError(error)}`,
|
|
41
|
+
);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
return { warnings, errors };
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export function isSafePluginCacheSegment(segment: string): boolean {
|
|
48
|
+
return /^[A-Za-z0-9._-]+$/.test(segment) && segment !== "." && segment !== "..";
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
async function findInstalledPluginRoot(cachePluginRoot: string): Promise<string | null> {
|
|
52
|
+
let entries: Dirent[];
|
|
53
|
+
try {
|
|
54
|
+
entries = await readdir(cachePluginRoot, { withFileTypes: true });
|
|
55
|
+
} catch (error) {
|
|
56
|
+
if (isNotFoundError(error)) {
|
|
57
|
+
return null;
|
|
58
|
+
}
|
|
59
|
+
throw error;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const versionDirs = await Promise.all(
|
|
63
|
+
entries
|
|
64
|
+
.filter((entry) => entry.isDirectory())
|
|
65
|
+
.map(async (entry) => {
|
|
66
|
+
const path = `${cachePluginRoot}/${entry.name}`;
|
|
67
|
+
const pathStat = await stat(path);
|
|
68
|
+
return { path, mtimeMs: pathStat.mtimeMs };
|
|
69
|
+
}),
|
|
70
|
+
);
|
|
71
|
+
if (versionDirs.length === 0) {
|
|
72
|
+
return null;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
versionDirs.sort(
|
|
76
|
+
(left, right) => right.mtimeMs - left.mtimeMs || right.path.localeCompare(left.path),
|
|
77
|
+
);
|
|
78
|
+
return versionDirs[0].path;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
async function writeRuntimeCorePackage(input: {
|
|
82
|
+
sourceCoreRoot: string;
|
|
83
|
+
targetCoreRoot: string;
|
|
84
|
+
}): Promise<void> {
|
|
85
|
+
const sourcePackagePath = `${input.sourceCoreRoot}/package.json`;
|
|
86
|
+
if (!(await isFile(sourcePackagePath))) {
|
|
87
|
+
throw new Error(`source package.json not found at ${sourcePackagePath}`);
|
|
88
|
+
}
|
|
89
|
+
if (!(await isDirectory(`${input.sourceCoreRoot}/src`))) {
|
|
90
|
+
throw new Error(`source src directory not found at ${input.sourceCoreRoot}/src`);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
await rm(input.targetCoreRoot, { recursive: true, force: true });
|
|
94
|
+
await mkdir(dirname(input.targetCoreRoot), { recursive: true });
|
|
95
|
+
await mkdir(input.targetCoreRoot, { recursive: true });
|
|
96
|
+
await cp(`${input.sourceCoreRoot}/src`, `${input.targetCoreRoot}/src`, {
|
|
97
|
+
recursive: true,
|
|
98
|
+
});
|
|
99
|
+
if (await isDirectory(`${input.sourceCoreRoot}/assets`)) {
|
|
100
|
+
await cp(`${input.sourceCoreRoot}/assets`, `${input.targetCoreRoot}/assets`, {
|
|
101
|
+
recursive: true,
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
const sourcePackageJson = JSON.parse(await readFile(sourcePackagePath, "utf8")) as Record<
|
|
106
|
+
string,
|
|
107
|
+
unknown
|
|
108
|
+
>;
|
|
109
|
+
const runtimePackageJson = {
|
|
110
|
+
...sourcePackageJson,
|
|
111
|
+
exports: rewritePackageExportsToSource(sourcePackageJson.exports),
|
|
112
|
+
files: ["src", "assets", "package.json"],
|
|
113
|
+
};
|
|
114
|
+
await writeFile(
|
|
115
|
+
`${input.targetCoreRoot}/package.json`,
|
|
116
|
+
`${JSON.stringify(runtimePackageJson, null, 2)}\n`,
|
|
117
|
+
"utf8",
|
|
118
|
+
);
|
|
119
|
+
}
|
|
120
|
+
|
|
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
|
+
async function isDirectory(path: string): Promise<boolean> {
|
|
148
|
+
try {
|
|
149
|
+
return (await stat(path)).isDirectory();
|
|
150
|
+
} catch (error) {
|
|
151
|
+
if (isNotFoundError(error)) {
|
|
152
|
+
return false;
|
|
153
|
+
}
|
|
154
|
+
throw error;
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
async function isFile(path: string): Promise<boolean> {
|
|
159
|
+
try {
|
|
160
|
+
return (await stat(path)).isFile();
|
|
161
|
+
} catch (error) {
|
|
162
|
+
if (isNotFoundError(error)) {
|
|
163
|
+
return false;
|
|
164
|
+
}
|
|
165
|
+
throw error;
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
function isNotFoundError(error: unknown): boolean {
|
|
170
|
+
return (
|
|
171
|
+
error instanceof Error && "code" in error && (error as NodeJS.ErrnoException).code === "ENOENT"
|
|
172
|
+
);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
function describeError(error: unknown): string {
|
|
176
|
+
if (error instanceof Error) {
|
|
177
|
+
return error.message;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
return String(error);
|
|
181
|
+
}
|