@evo-dev/core 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.
Files changed (38) hide show
  1. package/assets/agents/review/code-reviewer/examples.md +1 -1
  2. package/assets/agents/review/code-reviewer/prompt.md +1 -1
  3. package/assets/agents/review/code-reviewer/verification.md +1 -1
  4. package/assets/skills/coding/knowledge-distillation/SKILL.md +248 -0
  5. package/assets/skills/coding/knowledge-distillation/manifest.json +10 -0
  6. package/assets/skills/coding/knowledge-distillation/references/knowledge-distillation-methods.md +122 -0
  7. package/assets/workflows/rd-bug-fix/WORKFLOW.json +1 -1
  8. package/assets/workflows/rd-code-review/WORKFLOW.json +1 -1
  9. package/assets/workflows/rd-docs-update/WORKFLOW.json +1 -1
  10. package/assets/workflows/rd-feature-implementation/WORKFLOW.json +1 -1
  11. package/assets/workflows/rd-refactor/WORKFLOW.json +1 -1
  12. package/assets/workflows/rd-release-readiness/WORKFLOW.json +1 -1
  13. package/assets/workflows/rd-security-boundary-review/WORKFLOW.json +2 -2
  14. package/assets/workflows/rd-test-generation/WORKFLOW.json +1 -1
  15. package/dist/config/index.js +242 -36
  16. package/dist/index.js +5045 -934
  17. package/dist/plugins/index.js +32 -32
  18. package/package.json +1 -1
  19. package/src/agents/index.ts +28 -49
  20. package/src/config/index.ts +2 -0
  21. package/src/config/paths.ts +30 -0
  22. package/src/config/settings.ts +52 -0
  23. package/src/config/store.ts +150 -0
  24. package/src/daemon/index.ts +376 -3
  25. package/src/evolution/index.ts +2356 -0
  26. package/src/hooks/index.ts +255 -238
  27. package/src/index.ts +4 -0
  28. package/src/pack/index.ts +13 -13
  29. package/src/plugins/capabilities.ts +40 -42
  30. package/src/plugins/index.ts +0 -1
  31. package/src/plugins/types.ts +4 -0
  32. package/src/protected-zones/index.ts +29 -11
  33. package/src/runtime-logs/index.ts +324 -0
  34. package/src/sync/orchestrator.ts +6 -0
  35. package/src/task/index.ts +3 -3
  36. package/src/team/index.ts +2398 -0
  37. package/src/team/mcp.ts +401 -0
  38. package/src/workflow/index.ts +6 -6
@@ -9,37 +9,28 @@ function createUnknownNegotiatedCapabilities(pluginId) {
9
9
  hooks: "unsupported",
10
10
  canPlanWrites: false,
11
11
  warnings: [],
12
- blockers: [`Plugin ${pluginId} capabilities are unknown and unsupported.`]
12
+ advisories: [`Plugin ${pluginId} capabilities are unknown and unsupported.`]
13
13
  };
14
14
  }
15
15
  function isCapabilityUsable(state) {
16
16
  return state === "enabled";
17
17
  }
18
- function assertNoUnverifiedWrites(negotiation) {
19
- if (!negotiation.canPlanWrites) {
20
- throw new Error(`Plugin ${negotiation.pluginId} has no verified enabled write capabilities.`);
21
- }
22
- }
23
18
  function negotiatePluginCapabilities(input) {
24
19
  if (input.pluginId === "codex") {
25
- const declaredSupport = [
26
- input.declared.skills.supported ? "skills" : null,
27
- input.declared.agents.supported ? "agents" : null,
28
- input.declared.hooks.supported ? "hooks" : null
29
- ].filter(Boolean);
30
- const blockers = ["Codex capabilities are unverified; writes are unsupported in I10."];
20
+ const advisories = [];
31
21
  const warnings = [
32
- input.verified ? "Codex readonly verification artifact exists; sync remains no-write until formats are verified." : null,
33
- declaredSupport.length > 0 ? `Codex declares ${declaredSupport.join(", ")} support, but declared support alone cannot authorize writes.` : null
22
+ input.verified ? "Codex metadata-only verification artifact exists; implemented Codex skill/agent distribution remains governed by adapter tests." : null,
23
+ input.declared.skills.supported ? "Codex skills are distributed through the EvoDev Codex plugin bundle, not direct user-directory skill sync." : null,
24
+ input.declared.agents.supported ? "Codex agents can sync to user-level TOML files with no-overwrite semantics." : null
34
25
  ].filter((warning) => warning !== null);
35
26
  return {
36
27
  pluginId: input.pluginId,
37
- skills: input.declared.skills.supported ? "unverified" : "unsupported",
38
- agents: input.declared.agents.supported ? "unverified" : "unsupported",
39
- hooks: input.declared.hooks.supported ? "unverified" : "unsupported",
40
- canPlanWrites: false,
28
+ skills: resolveNonCodexCapabilityState(input.declared.skills.supported, input.enabled),
29
+ agents: resolveNonCodexCapabilityState(input.declared.agents.supported, input.enabled),
30
+ hooks: resolveNonCodexCapabilityState(input.declared.hooks.supported, input.enabled),
31
+ canPlanWrites: input.enabled && input.declared.agents.supported,
41
32
  warnings,
42
- blockers
33
+ advisories
43
34
  };
44
35
  }
45
36
  return {
@@ -49,9 +40,9 @@ function negotiatePluginCapabilities(input) {
49
40
  hooks: resolveNonCodexCapabilityState(input.declared.hooks.supported, input.enabled),
50
41
  canPlanWrites: input.enabled && input.declared.skills.supported && input.declared.agents.supported,
51
42
  warnings: input.enabled ? [] : [
52
- `Plugin ${input.pluginId} has declared capabilities but is not enabled; writes are not authorized.`
43
+ `Plugin ${input.pluginId} has declared capabilities but is not enabled for write planning.`
53
44
  ],
54
- blockers: []
45
+ advisories: []
55
46
  };
56
47
  }
57
48
  function resolveNonCodexCapabilityState(supported, enabled) {
@@ -63,7 +54,9 @@ async function createCodexCapabilityVerificationArtifact(input) {
63
54
  const timestamp = input.createdAt ?? new Date().toISOString();
64
55
  const detectionMessage = sanitizeText(input.detection.message);
65
56
  const diagnostics = [
66
- "Codex remains no-write until concrete user-level paths and formats are verified."
57
+ "Codex skills are packaged in the EvoDev Codex plugin payload.",
58
+ "Codex agents sync to user-level TOML files under ~/.codex/agents.",
59
+ "Codex hooks are supplied by the EvoDev Codex plugin manifest and enabled through Codex plugin hooks."
67
60
  ];
68
61
  return {
69
62
  version: 1,
@@ -80,7 +73,11 @@ async function createCodexCapabilityVerificationArtifact(input) {
80
73
  externalUpload: false,
81
74
  networkUsed: false,
82
75
  toolSummary: { tool: "codex", detectionStatus: input.detection.status, version: null },
83
- capabilities: { skills: "unverified", agents: "unverified", hooks: "unverified" },
76
+ capabilities: {
77
+ skills: "plugin-bundled",
78
+ agents: "user-level-toml-sync",
79
+ hooks: "plugin-manifest-hooks"
80
+ },
84
81
  writeBoundary: {
85
82
  writesAllowed: false,
86
83
  allowedPaths: [],
@@ -98,7 +95,7 @@ async function createCodexCapabilityVerificationArtifact(input) {
98
95
  secretsStored: false,
99
96
  externalUpload: false
100
97
  },
101
- blockers: ["Codex asset capabilities are unverified and unsupported for writes in I10."],
98
+ advisories: [],
102
99
  diagnostics,
103
100
  evidenceRefs: [],
104
101
  evidence: {
@@ -106,7 +103,8 @@ async function createCodexCapabilityVerificationArtifact(input) {
106
103
  detectionMessage,
107
104
  writesAllowed: false,
108
105
  notes: [
109
- "Metadata-only readonly artifact; no Codex paths, formats, hooks, or writes verified."
106
+ "Metadata-only verification artifact; it does not probe or write Code Agent directories.",
107
+ "Implemented Codex asset distribution is covered by adapter tests: plugin-bundled skills and user-level TOML agents."
110
108
  ]
111
109
  }
112
110
  };
@@ -149,12 +147,12 @@ function formatCodexCapabilityVerificationArtifact(artifact, path) {
149
147
  `Status: ${artifact.status}`,
150
148
  `Artifact: ${path ?? "dry-run only"}`,
151
149
  `Verified at: ${artifact.verifiedAt}`,
152
- "Capabilities: skills=unverified agents=unverified hooks=unverified",
153
- "Writes allowed: false",
150
+ `Capabilities: skills=${artifact.capabilities.skills} agents=${artifact.capabilities.agents} hooks=${artifact.capabilities.hooks}`,
151
+ "Verification writes allowed: false",
154
152
  "Allowed paths: none",
155
- "User-level write paths verified: false",
153
+ "User-level agent sync path: ~/.codex/agents",
156
154
  "Project writes allowed: false",
157
- "No-overwrite verified: false",
155
+ "No-overwrite policy: skip existing files unless force refresh is requested",
158
156
  `External upload: ${artifact.externalUpload}`,
159
157
  `Detection: ${artifact.evidence.detectionStatus}`,
160
158
  ...artifact.evidence.notes.map((note) => `Note: ${note}`)
@@ -171,8 +169,11 @@ async function runPluginConformance(plugin) {
171
169
  }
172
170
  if (plugin.id === "codex") {
173
171
  const capabilities = await plugin.getCapabilities();
174
- if (capabilities.skills.supported || capabilities.agents.supported || capabilities.hooks.supported) {
175
- findings.push("Codex must not declare verified runtime support in I10.");
172
+ if (!capabilities.skills.supported || !capabilities.agents.supported) {
173
+ findings.push("Codex must declare plugin-bundled skills and user-level TOML agents.");
174
+ }
175
+ if (!capabilities.hooks.supported || capabilities.hooks.events.length === 0) {
176
+ findings.push("Codex must declare plugin-manifest hook support.");
176
177
  }
177
178
  }
178
179
  return { ok: findings.length === 0, findings };
@@ -259,7 +260,6 @@ export {
259
260
  createUnknownNegotiatedCapabilities,
260
261
  createPluginRegistry,
261
262
  createCodexCapabilityVerificationArtifact,
262
- assertNoUnverifiedWrites,
263
263
  PluginRegistryError,
264
264
  PluginRegistry
265
265
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@evo-dev/core",
3
- "version": "0.0.1-alpha",
3
+ "version": "0.0.1-alpha.1",
4
4
  "type": "module",
5
5
  "repository": {
6
6
  "type": "git",
@@ -14,7 +14,7 @@ export type AgentOutputSchemaId =
14
14
  | "review-findings-v1"
15
15
  | "verification-summary-v1"
16
16
  | "design-options-v1";
17
- export type AgentFindingSeverity = "blocker" | "high" | "medium" | "low" | "info";
17
+ export type AgentFindingSeverity = "critical" | "high" | "medium" | "low" | "info";
18
18
 
19
19
  export interface AgentPermissions {
20
20
  canReadMetadata: boolean;
@@ -64,11 +64,11 @@ export interface AgentPlannedInvocation {
64
64
  }
65
65
 
66
66
  export interface AgentMergePlan {
67
- strategy: "none" | "single-output" | "dedupe-preserve-conflicts-fail-closed";
67
+ strategy: "none" | "single-output" | "dedupe-preserve-conflicts";
68
68
  outputSchema: AgentOutputSchemaId | null;
69
69
  dedupeBy: string[];
70
70
  conflictPolicy: string[];
71
- failClosedCategories: string[];
71
+ advisoryCategories: string[];
72
72
  }
73
73
 
74
74
  export interface AgentComposeDryRunPlan {
@@ -80,7 +80,7 @@ export interface AgentComposeDryRunPlan {
80
80
  permissions: AgentPermissions;
81
81
  mergePlan: AgentMergePlan;
82
82
  warnings: string[];
83
- blockers: string[];
83
+ advisories: string[];
84
84
  rationale: string;
85
85
  }
86
86
 
@@ -93,7 +93,7 @@ export interface AgentContextDryRunBundle {
93
93
  provenance: string[];
94
94
  };
95
95
  warnings: string[];
96
- blockers: string[];
96
+ advisories: string[];
97
97
  }
98
98
 
99
99
  export interface ReviewFindingOutput {
@@ -124,7 +124,7 @@ const DEFAULT_AGENT_PERMISSIONS: AgentPermissions = {
124
124
  };
125
125
  const FORBIDDEN_INPUTS = ["rawPrompts", "secrets", "rawCommandLogs", "sourceCorpus"];
126
126
  const SAFE_AGENT_ID_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._-]{0,99}$/;
127
- const PRIVACY_BLOCKER_PATTERN =
127
+ const PRIVACY_ADVISORY_PATTERN =
128
128
  /raw prompt|rawprompt|source corpus|sourcecorpus|secret|\.env|raw command|raw log|internal url|private url|network|write files|run commands|spawn agents|write memory/i;
129
129
  const LENS_TO_EXPERTISE: Record<AgentLens, string[]> = {
130
130
  review: ["code-review"],
@@ -170,16 +170,6 @@ export function parseAgentProfile(value: unknown): AgentProfile {
170
170
  throw new Error("Agent profile must be metadata-only and must not store raw prompts/source.");
171
171
  }
172
172
  const permissions = normalizeAgentPermissions(profile.permissions);
173
- if (
174
- permissions.canWriteFiles ||
175
- permissions.canRunCommands ||
176
- permissions.canUseNetwork ||
177
- permissions.canSpawnAgents ||
178
- permissions.canWriteMemory ||
179
- permissions.canReadSourceContent
180
- ) {
181
- throw new Error("Agent profile requests permissions outside I8 dry-run boundaries.");
182
- }
183
173
  if (!SAFE_AGENT_ID_PATTERN.test(profile.id)) throw new Error("Agent profile id is unsafe.");
184
174
  assertStringArray(profile.traits?.expertise, "profile.traits.expertise");
185
175
  assertStringArray(profile.traits?.stance, "profile.traits.stance");
@@ -188,11 +178,6 @@ export function parseAgentProfile(value: unknown): AgentProfile {
188
178
  assertStringArray(profile.inputs?.required, "profile.inputs.required");
189
179
  assertStringArray(profile.inputs?.optional, "profile.inputs.optional");
190
180
  assertStringArray(profile.inputs?.forbidden, "profile.inputs.forbidden");
191
- if (!FORBIDDEN_INPUTS.every((item) => profile.inputs.forbidden.includes(item))) {
192
- throw new Error(
193
- "Agent profile must forbid raw prompts, secrets, raw command logs, and source corpus.",
194
- );
195
- }
196
181
  if (!isKnownOutputSchema(profile.output?.schema))
197
182
  throw new Error("Agent profile output schema is unsupported.");
198
183
  assertStringArray(profile.output.requiredFields, "profile.output.requiredFields");
@@ -211,19 +196,13 @@ export function composeAgentDryRun(input: {
211
196
  const mode = input.contract.route.mode;
212
197
  const workflowId = input.workflowId ?? input.contract.route.workflowId;
213
198
  const warnings: string[] = [];
214
- const blockers: string[] = [];
215
199
  const permissions = createDefaultAgentPermissions();
216
200
  const requestedLenses = dedupeLenses(input.lenses ?? []);
217
- const privacyBlockers = collectPrivacyBoundaryBlockers(
218
- input.contract,
219
- requestedLenses,
220
- workflowId,
221
- );
222
- blockers.push(...privacyBlockers);
201
+ const advisories = collectPrivacyBoundaryAdvisories(input.contract, requestedLenses, workflowId);
223
202
 
224
203
  if (mode === "minimal") {
225
204
  return {
226
- ok: blockers.length === 0,
205
+ ok: advisories.length === 0,
227
206
  taskId: input.contract.taskId,
228
207
  mode,
229
208
  workflowId,
@@ -231,7 +210,7 @@ export function composeAgentDryRun(input: {
231
210
  permissions,
232
211
  mergePlan: createMergePlan([]),
233
212
  warnings,
234
- blockers,
213
+ advisories,
235
214
  rationale: "Minimal mode does not force dynamic agents by default.",
236
215
  };
237
216
  }
@@ -250,11 +229,11 @@ export function composeAgentDryRun(input: {
250
229
  }));
251
230
 
252
231
  if (mode === "standard" && agents.length > 1) {
253
- blockers.push("Standard mode allows at most one optional reviewer/triager in I8 dry-run.");
232
+ warnings.push("Standard mode advisory selected more than one reviewer/triager.");
254
233
  }
255
234
 
256
235
  return {
257
- ok: blockers.length === 0,
236
+ ok: advisories.length === 0,
258
237
  taskId: input.contract.taskId,
259
238
  mode,
260
239
  workflowId,
@@ -262,7 +241,7 @@ export function composeAgentDryRun(input: {
262
241
  permissions,
263
242
  mergePlan: createMergePlan(agents),
264
243
  warnings,
265
- blockers,
244
+ advisories,
266
245
  rationale:
267
246
  agents.length === 0
268
247
  ? "No dynamic agents selected; deterministic verification may be sufficient."
@@ -288,7 +267,7 @@ export function loadAgentContextDryRun(profile: AgentProfile): AgentContextDryRu
288
267
  provenance: [`agent-profile:${parsed.id}`],
289
268
  },
290
269
  warnings: [],
291
- blockers: [],
270
+ advisories: [],
292
271
  };
293
272
  }
294
273
 
@@ -375,10 +354,10 @@ export function formatAgentComposeDryRun(plan: AgentComposeDryRunPlan): string {
375
354
  ...(plan.warnings.length === 0
376
355
  ? [" - none"]
377
356
  : plan.warnings.map((warning) => ` - ${warning}`)),
378
- "Blockers:",
379
- ...(plan.blockers.length === 0
357
+ "Advisories:",
358
+ ...(plan.advisories.length === 0
380
359
  ? [" - none"]
381
- : plan.blockers.map((blocker) => ` - ${blocker}`)),
360
+ : plan.advisories.map((advisory) => ` - ${advisory}`)),
382
361
  ].join("\n");
383
362
  }
384
363
 
@@ -394,10 +373,10 @@ export function formatAgentContextDryRun(bundle: AgentContextDryRunBundle): stri
394
373
  ...bundle.context.forbiddenSections.map((section) => ` - ${section}`),
395
374
  "Permissions:",
396
375
  ...Object.entries(bundle.profile.permissions).map(([key, value]) => ` - ${key}: ${value}`),
397
- "Blockers:",
398
- ...(bundle.blockers.length === 0
376
+ "Advisories:",
377
+ ...(bundle.advisories.length === 0
399
378
  ? [" - none"]
400
- : bundle.blockers.map((blocker) => ` - ${blocker}`)),
379
+ : bundle.advisories.map((advisory) => ` - ${advisory}`)),
401
380
  ].join("\n");
402
381
  }
403
382
 
@@ -415,7 +394,7 @@ function createDynamicProfile(
415
394
  traits: {
416
395
  expertise: LENS_TO_EXPERTISE[lens],
417
396
  stance: ["skeptical-reviewer"],
418
- approach: ["evidence-first", "fail-closed"],
397
+ approach: ["evidence-first", "advisory"],
419
398
  domain: ["software-rd"],
420
399
  },
421
400
  inputs: {
@@ -462,7 +441,7 @@ function createMergePlan(agents: AgentPlannedInvocation[]): AgentMergePlan {
462
441
  outputSchema: null,
463
442
  dedupeBy: [],
464
443
  conflictPolicy: [],
465
- failClosedCategories: [],
444
+ advisoryCategories: [],
466
445
  };
467
446
  if (agents.length === 1)
468
447
  return {
@@ -470,14 +449,14 @@ function createMergePlan(agents: AgentPlannedInvocation[]): AgentMergePlan {
470
449
  outputSchema: agents[0].profile.output.schema,
471
450
  dedupeBy: [],
472
451
  conflictPolicy: [],
473
- failClosedCategories: [],
452
+ advisoryCategories: [],
474
453
  };
475
454
  return {
476
- strategy: "dedupe-preserve-conflicts-fail-closed",
455
+ strategy: "dedupe-preserve-conflicts",
477
456
  outputSchema: "review-findings-v1",
478
457
  dedupeBy: ["category", "path", "line", "title"],
479
458
  conflictPolicy: ["preserve dissent", "keep higher severity unless evidence refutes"],
480
- failClosedCategories: ["security", "privacy", "release"],
459
+ advisoryCategories: ["security", "privacy", "release"],
481
460
  };
482
461
  }
483
462
 
@@ -489,7 +468,7 @@ function createLensReason(
489
468
  return `${lens} lens selected for ${mode ?? "unrouted"} mode${workflowId ? ` and workflow ${workflowId}` : ""}.`;
490
469
  }
491
470
 
492
- function collectPrivacyBoundaryBlockers(
471
+ function collectPrivacyBoundaryAdvisories(
493
472
  contract: TaskContract,
494
473
  lenses: AgentLens[],
495
474
  workflowId: string | null,
@@ -504,9 +483,9 @@ function collectPrivacyBoundaryBlockers(
504
483
  ...contract.scope.requiresUserConfirmation,
505
484
  ...lenses,
506
485
  ].join(" ");
507
- return PRIVACY_BLOCKER_PATTERN.test(text)
486
+ return PRIVACY_ADVISORY_PATTERN.test(text)
508
487
  ? [
509
- "Agent planning refuses privacy/risky context requiring raw data, writes, commands, network, spawning, or memory.",
488
+ "Agent planning detected privacy/risky context requiring raw data, writes, commands, network, spawning, or memory.",
510
489
  ]
511
490
  : [];
512
491
  }
@@ -539,7 +518,7 @@ function dedupeLenses(lenses: AgentLens[]): AgentLens[] {
539
518
  }
540
519
 
541
520
  function severityRank(severity: AgentFindingSeverity): number {
542
- return { info: 0, low: 1, medium: 2, high: 3, blocker: 4 }[severity];
521
+ return { info: 0, low: 1, medium: 2, high: 3, critical: 4 }[severity];
543
522
  }
544
523
 
545
524
  function assertString(value: unknown, path: string): void {
@@ -10,6 +10,8 @@ export {
10
10
  type EvoDevSettings,
11
11
  type PluginSettings,
12
12
  type SettingsInput,
13
+ type TeamRuntimeSettings,
14
+ createDefaultTeamRuntimeSettings,
13
15
  createDefaultSettings,
14
16
  mergeSettings,
15
17
  parseSettings,
@@ -4,6 +4,18 @@ export interface EvoDevPaths {
4
4
  settingsPath: string;
5
5
  registryPath: string;
6
6
  stateDir: string;
7
+ logsDir: string;
8
+ knowledgeDir: string;
9
+ knowledgeIndexPath: string;
10
+ evosDir: string;
11
+ evosCasesDir: string;
12
+ evosIndexPath: string;
13
+ roleAgentsDir: string;
14
+ roleAgentsIndexPath: string;
15
+ teamsDir: string;
16
+ teamsIndexPath: string;
17
+ runsDir: string;
18
+ latestRunPath: string;
7
19
  installStatePath: string;
8
20
  syncStatePath: string;
9
21
  }
@@ -12,6 +24,12 @@ export function resolveEvoDevPaths(homeDir: string = getHomeDir()): EvoDevPaths
12
24
  const normalizedHome = stripTrailingSlash(homeDir);
13
25
  const rootDir = `${normalizedHome}/.evodev`;
14
26
  const stateDir = `${rootDir}/state`;
27
+ const logsDir = `${rootDir}/logs`;
28
+ const knowledgeDir = `${rootDir}/knowledge`;
29
+ const evosDir = `${rootDir}/evos`;
30
+ const roleAgentsDir = `${rootDir}/agents/roles`;
31
+ const teamsDir = `${rootDir}/teams`;
32
+ const runsDir = `${rootDir}/runs`;
15
33
 
16
34
  return {
17
35
  homeDir: normalizedHome,
@@ -19,6 +37,18 @@ export function resolveEvoDevPaths(homeDir: string = getHomeDir()): EvoDevPaths
19
37
  settingsPath: `${rootDir}/settings.json`,
20
38
  registryPath: `${rootDir}/registry.json`,
21
39
  stateDir,
40
+ logsDir,
41
+ knowledgeDir,
42
+ knowledgeIndexPath: `${knowledgeDir}/index.json`,
43
+ evosDir,
44
+ evosCasesDir: `${evosDir}/cases`,
45
+ evosIndexPath: `${evosDir}/index.json`,
46
+ roleAgentsDir,
47
+ roleAgentsIndexPath: `${roleAgentsDir}/index.json`,
48
+ teamsDir,
49
+ teamsIndexPath: `${teamsDir}/index.json`,
50
+ runsDir,
51
+ latestRunPath: `${runsDir}/latest.json`,
22
52
  installStatePath: `${stateDir}/install.json`,
23
53
  syncStatePath: `${stateDir}/sync.json`,
24
54
  };
@@ -28,6 +28,7 @@ export interface EvoDevSettings {
28
28
  lastRunAt: string | null;
29
29
  };
30
30
  hooks: HookSettings;
31
+ teamRuntime: TeamRuntimeSettings;
31
32
  }
32
33
 
33
34
  export type SettingsInput = Partial<{
@@ -43,8 +44,16 @@ export type SettingsInput = Partial<{
43
44
  }>;
44
45
  doctor: Partial<EvoDevSettings["doctor"]>;
45
46
  hooks: unknown;
47
+ teamRuntime: Partial<TeamRuntimeSettings>;
46
48
  }>;
47
49
 
50
+ export interface TeamRuntimeSettings {
51
+ defaultRuntime: "codex" | "claude";
52
+ defaultModel: string | null;
53
+ defaultThinkingLevel: string | null;
54
+ recordTranscript: boolean;
55
+ }
56
+
48
57
  export function createDefaultSettings(os: string = process.platform): EvoDevSettings {
49
58
  return {
50
59
  version: 1,
@@ -73,6 +82,16 @@ export function createDefaultSettings(os: string = process.platform): EvoDevSett
73
82
  lastRunAt: null,
74
83
  },
75
84
  hooks: createDefaultHookSettings(),
85
+ teamRuntime: createDefaultTeamRuntimeSettings(),
86
+ };
87
+ }
88
+
89
+ export function createDefaultTeamRuntimeSettings(): TeamRuntimeSettings {
90
+ return {
91
+ defaultRuntime: "codex",
92
+ defaultModel: null,
93
+ defaultThinkingLevel: null,
94
+ recordTranscript: false,
76
95
  };
77
96
  }
78
97
 
@@ -112,6 +131,10 @@ export function mergeSettings(
112
131
  ...existing.doctor,
113
132
  },
114
133
  hooks: existing.hooks ?? defaults.hooks,
134
+ teamRuntime: {
135
+ ...defaults.teamRuntime,
136
+ ...existing.teamRuntime,
137
+ },
115
138
  };
116
139
 
117
140
  return parseSettings(merged);
@@ -157,11 +180,40 @@ export function parseSettings(value: unknown): EvoDevSettings {
157
180
  lastRunAt: expectNullableString(doctor.lastRunAt, "settings.doctor.lastRunAt"),
158
181
  },
159
182
  hooks: parseHookSettings(root.hooks),
183
+ teamRuntime: parseTeamRuntimeSettings(
184
+ root.teamRuntime ?? createDefaultTeamRuntimeSettings(),
185
+ "settings.teamRuntime",
186
+ ),
160
187
  };
161
188
 
162
189
  return parsed;
163
190
  }
164
191
 
192
+ function parseTeamRuntimeSettings(value: unknown, path: string): TeamRuntimeSettings {
193
+ const input = expectRecord(value, path);
194
+ const defaults = createDefaultTeamRuntimeSettings();
195
+ const defaultRuntime = input.defaultRuntime ?? defaults.defaultRuntime;
196
+ if (defaultRuntime !== "codex" && defaultRuntime !== "claude") {
197
+ throw new EvoDevConfigError(`Invalid ${path}.defaultRuntime; expected codex or claude`);
198
+ }
199
+
200
+ return {
201
+ defaultRuntime,
202
+ defaultModel:
203
+ input.defaultModel === undefined
204
+ ? defaults.defaultModel
205
+ : expectNullableString(input.defaultModel, `${path}.defaultModel`),
206
+ defaultThinkingLevel:
207
+ input.defaultThinkingLevel === undefined
208
+ ? defaults.defaultThinkingLevel
209
+ : expectNullableString(input.defaultThinkingLevel, `${path}.defaultThinkingLevel`),
210
+ recordTranscript:
211
+ input.recordTranscript === undefined
212
+ ? defaults.recordTranscript
213
+ : expectBoolean(input.recordTranscript, `${path}.recordTranscript`),
214
+ };
215
+ }
216
+
165
217
  function parsePluginSettings(value: unknown, path: string): PluginSettings {
166
218
  const input = expectRecord(value, path);
167
219
  const parsed: PluginSettings = {