@evo-dev/core 0.0.1-alpha.4 → 0.0.1-alpha.6

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@evo-dev/core",
3
- "version": "0.0.1-alpha.4",
3
+ "version": "0.0.1-alpha.6",
4
4
  "type": "module",
5
5
  "repository": {
6
6
  "type": "git",
@@ -4,17 +4,8 @@ import {
4
4
  createScopedKnowledgeContextPack,
5
5
  formatScopedKnowledgePromptBlock,
6
6
  } from "../evolution/knowledge/index.ts";
7
- import type { TaskContract, TaskExecutionMode } from "../task/index.ts";
8
7
 
9
8
  export type AgentPersistence = "named" | "dynamic";
10
- export type AgentLens =
11
- | "review"
12
- | "qa"
13
- | "security"
14
- | "release"
15
- | "architecture"
16
- | "migration"
17
- | "test";
18
9
  export type AgentOutputSchemaId =
19
10
  | "review-findings-v1"
20
11
  | "verification-summary-v1"
@@ -61,34 +52,6 @@ export interface AgentProfile {
61
52
  };
62
53
  }
63
54
 
64
- export interface AgentPlannedInvocation {
65
- profile: AgentProfile;
66
- lens: AgentLens;
67
- reason: string;
68
- plannedOnly: true;
69
- }
70
-
71
- export interface AgentMergePlan {
72
- strategy: "none" | "single-output" | "dedupe-preserve-conflicts";
73
- outputSchema: AgentOutputSchemaId | null;
74
- dedupeBy: string[];
75
- conflictPolicy: string[];
76
- advisoryCategories: string[];
77
- }
78
-
79
- export interface AgentComposeDryRunPlan {
80
- ok: boolean;
81
- taskId: string;
82
- mode: TaskExecutionMode | null;
83
- workflowId: string | null;
84
- agents: AgentPlannedInvocation[];
85
- permissions: AgentPermissions;
86
- mergePlan: AgentMergePlan;
87
- warnings: string[];
88
- advisories: string[];
89
- rationale: string;
90
- }
91
-
92
55
  export interface AgentContextDryRunBundle {
93
56
  profile: AgentProfile;
94
57
  context: {
@@ -138,17 +101,6 @@ const DEFAULT_AGENT_PERMISSIONS: AgentPermissions = {
138
101
  };
139
102
  const FORBIDDEN_INPUTS = ["rawPrompts", "secrets", "rawCommandLogs", "sourceCorpus"];
140
103
  const SAFE_AGENT_ID_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._-]{0,99}$/;
141
- const PRIVACY_ADVISORY_PATTERN =
142
- /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;
143
- const LENS_TO_EXPERTISE: Record<AgentLens, string[]> = {
144
- review: ["code-review"],
145
- qa: ["qa", "verification"],
146
- security: ["security", "privacy"],
147
- release: ["release", "packaging"],
148
- architecture: ["architecture"],
149
- migration: ["migration", "impact-analysis"],
150
- test: ["testing"],
151
- };
152
104
 
153
105
  export function createDefaultAgentPermissions(): AgentPermissions {
154
106
  return { ...DEFAULT_AGENT_PERMISSIONS };
@@ -202,67 +154,6 @@ export async function readAgentProfile(path: string): Promise<AgentProfile> {
202
154
  return parseAgentProfile(JSON.parse(await readFile(path, "utf8")));
203
155
  }
204
156
 
205
- export function composeAgentDryRun(input: {
206
- contract: TaskContract;
207
- workflowId?: string;
208
- lenses?: AgentLens[];
209
- }): AgentComposeDryRunPlan {
210
- const mode = input.contract.route.mode;
211
- const workflowId = input.workflowId ?? input.contract.route.workflowId;
212
- const warnings: string[] = [];
213
- const permissions = createDefaultAgentPermissions();
214
- const requestedLenses = dedupeLenses(input.lenses ?? []);
215
- const advisories = collectPrivacyBoundaryAdvisories(input.contract, requestedLenses, workflowId);
216
-
217
- if (mode === "minimal") {
218
- return {
219
- ok: advisories.length === 0,
220
- taskId: input.contract.taskId,
221
- mode,
222
- workflowId,
223
- agents: [],
224
- permissions,
225
- mergePlan: createMergePlan([]),
226
- warnings,
227
- advisories,
228
- rationale: "Minimal mode does not force dynamic agents by default.",
229
- };
230
- }
231
-
232
- const selectedLenses = selectLenses(
233
- mode,
234
- requestedLenses,
235
- workflowId,
236
- input.contract.route.requiredReview,
237
- );
238
- const agents = selectedLenses.map((lens) => ({
239
- profile: createDynamicProfile(lens, input.contract, permissions),
240
- lens,
241
- reason: createLensReason(lens, mode, workflowId),
242
- plannedOnly: true as const,
243
- }));
244
-
245
- if (mode === "standard" && agents.length > 1) {
246
- warnings.push("Standard mode advisory selected more than one reviewer/triager.");
247
- }
248
-
249
- return {
250
- ok: advisories.length === 0,
251
- taskId: input.contract.taskId,
252
- mode,
253
- workflowId,
254
- agents,
255
- permissions,
256
- mergePlan: createMergePlan(agents),
257
- warnings,
258
- advisories,
259
- rationale:
260
- agents.length === 0
261
- ? "No dynamic agents selected; deterministic verification may be sufficient."
262
- : `Selected ${agents.length} planned-only agent(s) based on mode/workflow/lenses.`,
263
- };
264
- }
265
-
266
157
  export function loadAgentContextDryRun(profile: AgentProfile): AgentContextDryRunBundle {
267
158
  const parsed = parseAgentProfile(profile);
268
159
  return {
@@ -376,47 +267,6 @@ export function mergeReviewFindings(outputs: ReviewFindingOutput[]): {
376
267
  return { acceptedFindings: [...byKey.values()], unresolvedConflicts: conflicts };
377
268
  }
378
269
 
379
- export function formatAgentComposeDryRun(plan: AgentComposeDryRunPlan): string {
380
- return [
381
- "EvoDev agent compose dry-run",
382
- "",
383
- `Task: ${plan.taskId}`,
384
- `Mode: ${plan.mode ?? "not routed"}`,
385
- `Workflow: ${plan.workflowId ?? "none"}`,
386
- `Rationale: ${plan.rationale}`,
387
- "No-write/no-spawn: true",
388
- "Privacy: local-private, metadata-only, raw prompts/source/secrets/logs forbidden",
389
- "Agents:",
390
- ...(plan.agents.length === 0
391
- ? [" - none"]
392
- : plan.agents.map(
393
- (agent) =>
394
- ` - ${agent.profile.id} [${agent.lens}] traits=${agent.profile.traits.expertise.join(",")} schema=${agent.profile.output.schema} plannedOnly=${agent.plannedOnly}`,
395
- )),
396
- "Inputs:",
397
- ...(plan.agents.length === 0
398
- ? [" - none"]
399
- : [
400
- ` - required: ${plan.agents[0].profile.inputs.required.join(",")}`,
401
- ` - optional: ${plan.agents[0].profile.inputs.optional.join(",")}`,
402
- ` - forbidden: ${plan.agents[0].profile.inputs.forbidden.join(",")}`,
403
- ]),
404
- "Permissions:",
405
- ...Object.entries(plan.permissions).map(([key, value]) => ` - ${key}: ${value}`),
406
- "Merge plan:",
407
- ` - strategy: ${plan.mergePlan.strategy}`,
408
- ` - outputSchema: ${plan.mergePlan.outputSchema ?? "none"}`,
409
- "Warnings:",
410
- ...(plan.warnings.length === 0
411
- ? [" - none"]
412
- : plan.warnings.map((warning) => ` - ${warning}`)),
413
- "Advisories:",
414
- ...(plan.advisories.length === 0
415
- ? [" - none"]
416
- : plan.advisories.map((advisory) => ` - ${advisory}`)),
417
- ].join("\n");
418
- }
419
-
420
270
  export function formatAgentContextDryRun(bundle: AgentContextDryRunBundle): string {
421
271
  return [
422
272
  "EvoDev agent load-context dry-run",
@@ -436,116 +286,6 @@ export function formatAgentContextDryRun(bundle: AgentContextDryRunBundle): stri
436
286
  ].join("\n");
437
287
  }
438
288
 
439
- function createDynamicProfile(
440
- lens: AgentLens,
441
- contract: TaskContract,
442
- permissions: AgentPermissions,
443
- ): AgentProfile {
444
- return {
445
- version: 1,
446
- id: `dynamic-${lens}-${contract.taskId}`.slice(0, 100),
447
- name: `Dynamic ${lens} reviewer`,
448
- persistence: "dynamic",
449
- description: `Task-scoped ${lens} profile for metadata-only dry-run planning.`,
450
- traits: {
451
- expertise: LENS_TO_EXPERTISE[lens],
452
- stance: ["skeptical-reviewer"],
453
- approach: ["evidence-first", "advisory"],
454
- domain: ["software-rd"],
455
- },
456
- inputs: {
457
- required: ["taskContract", "scope", "evidenceSummary"],
458
- optional: ["workflowPlan"],
459
- forbidden: FORBIDDEN_INPUTS,
460
- },
461
- permissions,
462
- output: {
463
- schema: "review-findings-v1",
464
- requiredFields: ["summary", "findings", "confidence", "evidenceRefs"],
465
- },
466
- privacy: {
467
- classification: "local-private",
468
- metadataOnly: true,
469
- rawPromptStored: false,
470
- sourceContentStored: false,
471
- },
472
- };
473
- }
474
-
475
- function selectLenses(
476
- mode: TaskExecutionMode | null,
477
- requested: AgentLens[],
478
- workflowId: string | null,
479
- requiredReview: string[],
480
- ): AgentLens[] {
481
- if (requested.length > 0) return mode === "standard" ? requested.slice(0, 1) : requested;
482
- if (mode === "standard") return requiredReview.length > 0 ? ["review"] : [];
483
- if (mode === "rigorous") {
484
- if (workflowId?.includes("security")) return ["security", "review"];
485
- if (workflowId?.includes("release")) return ["release", "security"];
486
- if (workflowId?.includes("migration")) return ["migration", "architecture"];
487
- if (workflowId?.includes("architecture")) return ["architecture", "review"];
488
- return requiredReview.length > 0 ? ["review", "qa"] : [];
489
- }
490
- return [];
491
- }
492
-
493
- function createMergePlan(agents: AgentPlannedInvocation[]): AgentMergePlan {
494
- if (agents.length === 0)
495
- return {
496
- strategy: "none",
497
- outputSchema: null,
498
- dedupeBy: [],
499
- conflictPolicy: [],
500
- advisoryCategories: [],
501
- };
502
- if (agents.length === 1)
503
- return {
504
- strategy: "single-output",
505
- outputSchema: agents[0].profile.output.schema,
506
- dedupeBy: [],
507
- conflictPolicy: [],
508
- advisoryCategories: [],
509
- };
510
- return {
511
- strategy: "dedupe-preserve-conflicts",
512
- outputSchema: "review-findings-v1",
513
- dedupeBy: ["category", "path", "line", "title"],
514
- conflictPolicy: ["preserve dissent", "keep higher severity unless evidence refutes"],
515
- advisoryCategories: ["security", "privacy", "release"],
516
- };
517
- }
518
-
519
- function createLensReason(
520
- lens: AgentLens,
521
- mode: TaskExecutionMode | null,
522
- workflowId: string | null,
523
- ): string {
524
- return `${lens} lens selected for ${mode ?? "unrouted"} mode${workflowId ? ` and workflow ${workflowId}` : ""}.`;
525
- }
526
-
527
- function collectPrivacyBoundaryAdvisories(
528
- contract: TaskContract,
529
- lenses: AgentLens[],
530
- workflowId: string | null,
531
- ): string[] {
532
- const text = [
533
- contract.source.summary,
534
- contract.currentState.summary,
535
- contract.targetState.summary,
536
- contract.route.rationale,
537
- workflowId ?? "",
538
- ...contract.scope.allowedOperations,
539
- ...contract.scope.requiresUserConfirmation,
540
- ...lenses,
541
- ].join(" ");
542
- return PRIVACY_ADVISORY_PATTERN.test(text)
543
- ? [
544
- "Agent planning detected privacy/risky context requiring raw data, writes, commands, network, spawning, or memory.",
545
- ]
546
- : [];
547
- }
548
-
549
289
  function validateRequiredObject(
550
290
  output: unknown,
551
291
  fields: string[],
@@ -569,10 +309,6 @@ function isKnownOutputSchema(value: unknown): value is AgentOutputSchemaId {
569
309
  );
570
310
  }
571
311
 
572
- function dedupeLenses(lenses: AgentLens[]): AgentLens[] {
573
- return [...new Set(lenses)];
574
- }
575
-
576
312
  function severityRank(severity: AgentFindingSeverity): number {
577
313
  return { info: 0, low: 1, medium: 2, high: 3, critical: 4 }[severity];
578
314
  }
@@ -1,6 +1,7 @@
1
1
  import { mkdir, readFile, readdir, stat, writeFile } from "node:fs/promises";
2
2
  import { dirname, isAbsolute, join, normalize, relative } from "node:path";
3
3
  import { resolveEvoDevPaths } from "../config/paths.ts";
4
+ import { resolveProjectWorkspaceFromCwd } from "../projects/index.ts";
4
5
  import { resolveTraceSessionKey, resolveTraceTeamContext } from "../runtime-logs/index.ts";
5
6
  import { normalizeTimestamp, sha256Short } from "../utils/index.ts";
6
7
 
@@ -264,6 +265,11 @@ export async function recordCodeAgentTraceRefFromHook(
264
265
  payload: input.payload,
265
266
  environment,
266
267
  });
268
+ const cwd = optionalString(input.payload.cwd);
269
+ const workspace =
270
+ team === null && cwd !== null
271
+ ? await resolveProjectWorkspaceFromCwd({ homeDir: input.homeDir, cwd })
272
+ : null;
267
273
  const source =
268
274
  sessionIdFromPayload !== null || tracePathFromPayload !== null ? "hook-payload" : "environment";
269
275
 
@@ -271,10 +277,10 @@ export async function recordCodeAgentTraceRefFromHook(
271
277
  target: input.target,
272
278
  sessionKey: resolveTraceSessionKey(input.payload),
273
279
  nativeSessionId,
274
- projectKey: team?.projectKey ?? null,
280
+ projectKey: team?.projectKey ?? workspace?.projectKey ?? null,
275
281
  runId: team?.runId ?? null,
276
282
  roleId: team?.roleId ?? null,
277
- cwd: optionalString(input.payload.cwd),
283
+ cwd,
278
284
  discoveredAt: input.now,
279
285
  source,
280
286
  tracePath: normalizedPath,
@@ -251,8 +251,6 @@ export async function handleDaemonRequest(
251
251
  }
252
252
  if (input.method !== "GET") return notFound(warnings);
253
253
 
254
- if (input.path === "/tasks")
255
- return ok(await collectTaskSummaries(input.homeDir, warnings), warnings);
256
254
  if (input.path === "/observability/events")
257
255
  return ok(await collectObservabilitySummaries(input.homeDir, warnings), warnings);
258
256
  if (input.path === "/memory/candidates")
@@ -407,33 +405,6 @@ function isAllowedLocalOrigin(origin: string | null): boolean {
407
405
  }
408
406
  }
409
407
 
410
- async function collectTaskSummaries(homeDir: string, warnings: string[]): Promise<unknown[]> {
411
- const root = join(homeDir, ".evodev", "STATE", "tasks");
412
- if (!(await pathExists(root))) {
413
- warnings.push("Task store not found; returning empty tasks.");
414
- return [];
415
- }
416
- const contracts = await collectNamedFiles(root, "contract.json");
417
- const summaries: unknown[] = [];
418
- for (const file of contracts) {
419
- try {
420
- const contract = JSON.parse(await readFile(file, "utf8"));
421
- summaries.push(
422
- sanitizeMetadata({
423
- taskId: contract.taskId,
424
- status: contract.status,
425
- mode: contract.route?.mode ?? null,
426
- workflowId: contract.route?.workflowId ?? null,
427
- verificationStatus: contract.verification?.status ?? null,
428
- }),
429
- );
430
- } catch {
431
- warnings.push(`Skipped unreadable task contract: ${file}`);
432
- }
433
- }
434
- return summaries;
435
- }
436
-
437
408
  async function collectObservabilitySummaries(
438
409
  homeDir: string,
439
410
  warnings: string[],
@@ -841,17 +812,6 @@ function describeError(error: unknown): string {
841
812
  return error instanceof Error ? error.message : String(error);
842
813
  }
843
814
 
844
- async function collectNamedFiles(root: string, name: string): Promise<string[]> {
845
- const entries = await readdir(root, { withFileTypes: true });
846
- const files: string[] = [];
847
- for (const entry of entries) {
848
- const path = join(root, entry.name);
849
- if (entry.isDirectory()) files.push(...(await collectNamedFiles(path, name)));
850
- else if (entry.isFile() && entry.name === name) files.push(path);
851
- }
852
- return files;
853
- }
854
-
855
815
  function ok(data: unknown, warnings: string[]): { status: number; body: DaemonResponseBody } {
856
816
  return { status: 200, body: { ok: true, data: sanitizeMetadata(data), warnings } };
857
817
  }