@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
@@ -1,12 +1,15 @@
1
1
  import { createHash } from "node:crypto";
2
2
  import { mkdir, readFile, writeFile } from "node:fs/promises";
3
3
  import { dirname, join } from "node:path";
4
+ import { enqueueEvolutionTrigger, resolveEvolutionTriggerDecision } from "../evolution/index.ts";
5
+ import { resolveTraceTeamContext } from "../runtime-logs/index.ts";
4
6
  import {
5
7
  type TaskContract,
6
8
  createTaskContract,
7
9
  routeTaskContract,
8
10
  writeTaskContract,
9
11
  } from "../task/index.ts";
12
+ import { recordTeamAgentNativeSession, resolveTeamRunPaths } from "../team/index.ts";
10
13
 
11
14
  export const CANONICAL_HOOK_EVENT_TYPES = [
12
15
  "SessionStart",
@@ -49,15 +52,15 @@ export type CommandRiskClass =
49
52
  | "credential-sensitive"
50
53
  | "path-escaping"
51
54
  | "unknown";
52
- export type HookDecisionAction = "allow" | "warn" | "ask-user" | "block";
55
+ export type HookDecisionAction = "allow" | "warn";
53
56
 
54
57
  export interface HookSettings {
55
58
  enabled: boolean;
56
59
  targets: Record<CodeAgentHookTarget, HookTargetSettings>;
57
60
  observability: {
58
- metadataOnly: true;
59
- rawPayloadStorage: false;
60
- appendEvents: false;
61
+ metadataOnly: boolean;
62
+ rawPayloadStorage: boolean;
63
+ appendEvents: boolean;
61
64
  };
62
65
  learning: {
63
66
  emitCandidates: false;
@@ -78,7 +81,7 @@ export interface HookEventV1 {
78
81
  pluginId: string;
79
82
  agent: string;
80
83
  sessionIdHash: string | null;
81
- rawPayloadStored: false;
84
+ rawPayloadStored: boolean;
82
85
  };
83
86
  time: {
84
87
  occurredAt: string | null;
@@ -87,7 +90,7 @@ export interface HookEventV1 {
87
90
  scope: {
88
91
  taskId: string | null;
89
92
  projectId: string | null;
90
- cwdPolicy: "metadata-only";
93
+ cwdPolicy: "metadata-only" | "raw-local";
91
94
  projectContextOptedIn: false;
92
95
  };
93
96
  payload: {
@@ -95,11 +98,11 @@ export interface HookEventV1 {
95
98
  metadata: Record<string, string | number | boolean | string[]>;
96
99
  redactions: string[];
97
100
  redactionCount: number;
98
- rawContentIncluded: false;
101
+ rawContentIncluded: boolean;
99
102
  };
100
103
  policy: {
101
104
  classification: "local-private";
102
- allowedUses: Array<"observability" | "safety-check" | "workflow-suggestion">;
105
+ allowedUses: Array<"observability" | "workflow-suggestion">;
103
106
  learningAllowed: false;
104
107
  externalUploadAllowed: false;
105
108
  };
@@ -128,7 +131,7 @@ export interface HookInstallDryRunPlan {
128
131
  reason: string;
129
132
  }>;
130
133
  warnings: string[];
131
- blockers: string[];
134
+ advisories: string[];
132
135
  }
133
136
 
134
137
  export interface HookRuntimeSessionBinding {
@@ -148,6 +151,7 @@ export interface HookRuntimeResult {
148
151
  enabled: boolean;
149
152
  output: Record<string, unknown> | null;
150
153
  stateWrites: string[];
154
+ warnings: string[];
151
155
  summary: string;
152
156
  }
153
157
 
@@ -158,33 +162,34 @@ export interface HandleHookRuntimeInput {
158
162
  event: HookEventV1;
159
163
  rawPayload: Record<string, unknown>;
160
164
  receivedAt?: string;
165
+ environment?: Record<string, string | undefined>;
161
166
  }
162
167
 
163
168
  const DEFAULT_EVENT_SETTINGS: Record<CanonicalHookEventType, boolean> = {
164
- SessionStart: false,
165
- UserPromptSubmit: false,
166
- UserPromptExpansion: false,
167
- PreToolUse: false,
168
- PermissionRequest: false,
169
- PostToolUse: false,
170
- PostToolUseFailure: false,
171
- PostToolBatch: false,
172
- PermissionDenied: false,
173
- SubagentStart: false,
174
- Stop: false,
175
- StopFailure: false,
176
- TeammateIdle: false,
177
- SubagentStop: false,
178
- TaskCreated: false,
179
- TaskCompleted: false,
180
- PreCompact: false,
181
- PostCompact: false,
182
- SessionEnd: false,
183
- ConfigChange: false,
184
- CwdChanged: false,
185
- FileChanged: false,
186
- WorktreeCreate: false,
187
- WorktreeRemove: false,
169
+ SessionStart: true,
170
+ UserPromptSubmit: true,
171
+ UserPromptExpansion: true,
172
+ PreToolUse: true,
173
+ PermissionRequest: true,
174
+ PostToolUse: true,
175
+ PostToolUseFailure: true,
176
+ PostToolBatch: true,
177
+ PermissionDenied: true,
178
+ SubagentStart: true,
179
+ Stop: true,
180
+ StopFailure: true,
181
+ TeammateIdle: true,
182
+ SubagentStop: true,
183
+ TaskCreated: true,
184
+ TaskCompleted: true,
185
+ PreCompact: true,
186
+ PostCompact: true,
187
+ SessionEnd: true,
188
+ ConfigChange: true,
189
+ CwdChanged: true,
190
+ FileChanged: true,
191
+ WorktreeCreate: true,
192
+ WorktreeRemove: true,
188
193
  };
189
194
 
190
195
  const SENSITIVE_TEXT_PATTERN =
@@ -193,20 +198,20 @@ const SOURCE_LIKE_PATTERN = /\b(function|class|import|export|const|let|var)\b.*[
193
198
 
194
199
  export function createDefaultHookSettings(): HookSettings {
195
200
  return {
196
- enabled: false,
201
+ enabled: true,
197
202
  targets: {
198
203
  claude: {
199
- enabled: false,
204
+ enabled: true,
200
205
  events: { ...DEFAULT_EVENT_SETTINGS },
201
206
  },
202
207
  codex: {
203
- enabled: false,
208
+ enabled: true,
204
209
  events: { ...DEFAULT_EVENT_SETTINGS },
205
210
  },
206
211
  },
207
212
  observability: {
208
- metadataOnly: true,
209
- rawPayloadStorage: false,
213
+ metadataOnly: false,
214
+ rawPayloadStorage: true,
210
215
  appendEvents: false,
211
216
  },
212
217
  learning: {
@@ -220,6 +225,17 @@ export function parseHookSettings(value: unknown): HookSettings {
220
225
  const defaults = createDefaultHookSettings();
221
226
  if (value === undefined || value === null) return defaults;
222
227
  if (!isRecord(value)) throw new Error("Invalid hooks settings; expected object.");
228
+ const observability = isRecord(value.observability) ? value.observability : undefined;
229
+ optionalBoolean(
230
+ observability?.metadataOnly,
231
+ defaults.observability.metadataOnly,
232
+ "hooks.observability.metadataOnly",
233
+ );
234
+ optionalBoolean(
235
+ observability?.rawPayloadStorage,
236
+ defaults.observability.rawPayloadStorage,
237
+ "hooks.observability.rawPayloadStorage",
238
+ );
223
239
 
224
240
  return {
225
241
  enabled: optionalBoolean(value.enabled, defaults.enabled, "hooks.enabled"),
@@ -228,9 +244,13 @@ export function parseHookSettings(value: unknown): HookSettings {
228
244
  codex: parseHookTargetSettings(value.targets, defaults.targets.codex, "codex"),
229
245
  },
230
246
  observability: {
231
- metadataOnly: true,
232
- rawPayloadStorage: false,
233
- appendEvents: false,
247
+ metadataOnly: false,
248
+ rawPayloadStorage: true,
249
+ appendEvents: optionalBoolean(
250
+ observability?.appendEvents,
251
+ defaults.observability.appendEvents,
252
+ "hooks.observability.appendEvents",
253
+ ),
234
254
  },
235
255
  learning: {
236
256
  emitCandidates: false,
@@ -257,7 +277,7 @@ export function normalizeHookEvent(input: NormalizeHookEventInput): HookEventV1
257
277
  pluginId: sanitizeScalar(input.pluginId, redactions),
258
278
  agent: sanitizeScalar(input.agent ?? "unknown", redactions),
259
279
  sessionIdHash,
260
- rawPayloadStored: false,
280
+ rawPayloadStored: true,
261
281
  },
262
282
  time: {
263
283
  occurredAt: optionalSanitizedString(
@@ -269,7 +289,7 @@ export function normalizeHookEvent(input: NormalizeHookEventInput): HookEventV1
269
289
  scope: {
270
290
  taskId: optionalSanitizedString(input.payload.taskId, redactions),
271
291
  projectId: optionalSanitizedString(input.payload.projectId, redactions),
272
- cwdPolicy: "metadata-only",
292
+ cwdPolicy: "raw-local",
273
293
  projectContextOptedIn: false,
274
294
  },
275
295
  payload: {
@@ -277,11 +297,11 @@ export function normalizeHookEvent(input: NormalizeHookEventInput): HookEventV1
277
297
  metadata,
278
298
  redactions,
279
299
  redactionCount: redactions.length,
280
- rawContentIncluded: false,
300
+ rawContentIncluded: true,
281
301
  },
282
302
  policy: {
283
303
  classification: "local-private",
284
- allowedUses: ["observability", "safety-check", "workflow-suggestion"],
304
+ allowedUses: ["observability", "workflow-suggestion"],
285
305
  learningAllowed: false,
286
306
  externalUploadAllowed: false,
287
307
  },
@@ -318,7 +338,8 @@ export function formatHookInstallDryRun(plan: HookInstallDryRunPlan): string {
318
338
  (eventType) => ` - ${eventType}: ${plan.settings.targets[plan.target].events[eventType]}`,
319
339
  ),
320
340
  "Boundaries:",
321
- " - observability: metadata-only, appendEvents=false, rawPayloadStorage=false",
341
+ " - trace: local raw payload storage=required, external upload=false",
342
+ " - derived evidence/knowledge/observability surfaces: metadata-only until review/export",
322
343
  " - learning: emitCandidates=false, writeMemory=false",
323
344
  " - external upload: false",
324
345
  " - protected project writes: .claude/.codex/CLAUDE.md/AGENTS.md not targeted",
@@ -331,10 +352,10 @@ export function formatHookInstallDryRun(plan: HookInstallDryRunPlan): string {
331
352
  ...(plan.warnings.length === 0
332
353
  ? [" - none"]
333
354
  : plan.warnings.map((warning) => ` - ${warning}`)),
334
- "Blockers:",
335
- ...(plan.blockers.length === 0
355
+ "Advisories:",
356
+ ...(plan.advisories.length === 0
336
357
  ? [" - none"]
337
- : plan.blockers.map((blocker) => ` - ${blocker}`)),
358
+ : plan.advisories.map((advisory) => ` - ${advisory}`)),
338
359
  ].join("\n");
339
360
  }
340
361
 
@@ -382,35 +403,45 @@ export async function handleHookRuntime(input: HandleHookRuntimeInput): Promise<
382
403
  enabled,
383
404
  output: null,
384
405
  stateWrites: [],
406
+ warnings: [],
385
407
  summary: `Hook ${input.event.type} ignored because EvoDev hooks are disabled.`,
386
408
  };
387
409
  }
388
410
 
389
- if (input.event.type === "SessionStart") return handleSessionStart(input);
390
- if (input.event.type === "UserPromptSubmit") return handleUserPromptSubmit(input);
391
- if (input.event.type === "PreToolUse") return handlePreToolUse(input);
411
+ const diagnostics = await recordTeamNativeSessionFromHook(input);
412
+ let result: HookRuntimeResult | undefined;
413
+ if (input.event.type === "SessionStart") result = await handleSessionStart(input);
414
+ else if (input.event.type === "UserPromptSubmit") result = await handleUserPromptSubmit(input);
415
+ else if (input.event.type === "PreToolUse") result = await handlePreToolUse(input);
392
416
  if (input.event.type === "PostToolUse" || input.event.type === "PostToolUseFailure") {
393
- return handlePostToolUse(input);
394
- }
395
- if (input.event.type === "PostToolBatch") return handleAdditionalContext(input, "PostToolBatch");
396
- if (
417
+ result = await handlePostToolUse(input);
418
+ } else if (input.event.type === "PostToolBatch") {
419
+ result = await handleAdditionalContext(input, "PostToolBatch");
420
+ } else if (
397
421
  input.event.type === "SubagentStart" ||
398
422
  input.event.type === "TaskCreated" ||
399
423
  input.event.type === "PermissionRequest"
400
424
  ) {
401
- return handlePreToolUse(input);
402
- }
403
- if (
425
+ result = await handlePreToolUse(input);
426
+ } else if (
404
427
  input.event.type === "Stop" ||
405
428
  input.event.type === "SubagentStop" ||
406
429
  input.event.type === "TaskCompleted" ||
407
430
  input.event.type === "TeammateIdle"
408
431
  ) {
409
- return handleCompletionGate(input);
432
+ result = await handleCompletionObservation(input);
433
+ } else if (input.event.type === "PreCompact") {
434
+ result = await handlePreCompact(input);
435
+ } else if (input.event.type === "SessionEnd") {
436
+ result = await handleSessionEnd(input);
437
+ } else if (result === undefined) {
438
+ result = await handleAdditionalContext(input, input.event.type);
410
439
  }
411
- if (input.event.type === "PreCompact") return handlePreCompact(input);
412
- if (input.event.type === "SessionEnd") return handleSessionEnd(input);
413
- return handleAdditionalContext(input, input.event.type);
440
+ const evolutionDiagnostics = await recordEvolutionTriggerFromHook(input);
441
+ return appendRuntimeDiagnostics(
442
+ appendRuntimeDiagnostics(result, diagnostics),
443
+ evolutionDiagnostics,
444
+ );
414
445
  }
415
446
 
416
447
  export function formatHookRuntimeOutput(result: HookRuntimeResult): string {
@@ -429,6 +460,128 @@ function isHookEventEnabled(
429
460
  );
430
461
  }
431
462
 
463
+ interface HookRuntimeDiagnostics {
464
+ stateWrites: string[];
465
+ warnings: string[];
466
+ }
467
+
468
+ async function recordTeamNativeSessionFromHook(
469
+ input: HandleHookRuntimeInput,
470
+ ): Promise<HookRuntimeDiagnostics> {
471
+ const environment = input.environment ?? {};
472
+ const runId = optionalPayloadString(environment.EVODEV_TEAM_RUN_ID);
473
+ const roleId = optionalPayloadString(environment.EVODEV_TEAM_ROLE_ID);
474
+ const sessionId = optionalPayloadString(
475
+ input.rawPayload.session_id ?? input.rawPayload.sessionId,
476
+ );
477
+ if (runId === null || roleId === null || sessionId === null) {
478
+ return { stateWrites: [], warnings: [] };
479
+ }
480
+
481
+ try {
482
+ await recordTeamAgentNativeSession({
483
+ homeDir: input.homeDir,
484
+ runId,
485
+ roleId,
486
+ sessionId,
487
+ now:
488
+ input.receivedAt === undefined || input.receivedAt === "dry-run"
489
+ ? undefined
490
+ : new Date(input.receivedAt),
491
+ });
492
+ return {
493
+ stateWrites: [resolveTeamRunPaths(input.homeDir).agentPath(runId, roleId)],
494
+ warnings: [],
495
+ };
496
+ } catch {
497
+ return {
498
+ stateWrites: [],
499
+ warnings: [
500
+ `Team native session metadata could not be recorded for run ${safeDiagnosticId(runId)} role ${safeDiagnosticId(roleId)}.`,
501
+ ],
502
+ };
503
+ }
504
+ }
505
+
506
+ async function recordEvolutionTriggerFromHook(
507
+ input: HandleHookRuntimeInput,
508
+ ): Promise<HookRuntimeDiagnostics> {
509
+ const decision = resolveEvolutionTriggerDecision({
510
+ eventType: input.event.type,
511
+ summary: input.event.payload.summary,
512
+ });
513
+ if (!decision.shouldQueue) return { stateWrites: [], warnings: [] };
514
+
515
+ const team = resolveTraceTeamContext({
516
+ homeDir: input.homeDir,
517
+ environment: input.environment,
518
+ payload: input.rawPayload,
519
+ });
520
+ if (team === null) {
521
+ return {
522
+ stateWrites: [],
523
+ warnings:
524
+ decision.strength === "strong"
525
+ ? [
526
+ `Evolution trigger ${input.event.type} was not queued because team context is missing.`,
527
+ ]
528
+ : [],
529
+ };
530
+ }
531
+
532
+ try {
533
+ const trigger = await enqueueEvolutionTrigger({
534
+ homeDir: input.homeDir,
535
+ projectKey: team.projectKey,
536
+ runId: team.runId,
537
+ roleId: team.roleId,
538
+ taskId: input.event.scope.taskId,
539
+ eventType: input.event.type,
540
+ eventId: input.event.eventId,
541
+ summary: input.event.payload.summary,
542
+ now:
543
+ input.receivedAt === undefined || input.receivedAt === "dry-run"
544
+ ? undefined
545
+ : input.receivedAt,
546
+ });
547
+ if (trigger === null) return { stateWrites: [], warnings: [] };
548
+ return {
549
+ stateWrites: [
550
+ join(
551
+ input.homeDir,
552
+ ".evodev",
553
+ "state",
554
+ "evolution",
555
+ trigger.projectKey,
556
+ trigger.runId,
557
+ "triggers",
558
+ `${trigger.id}.json`,
559
+ ),
560
+ ],
561
+ warnings: [],
562
+ };
563
+ } catch {
564
+ return {
565
+ stateWrites: [],
566
+ warnings: [`Evolution trigger ${input.event.type} could not be queued.`],
567
+ };
568
+ }
569
+ }
570
+
571
+ function appendRuntimeDiagnostics(
572
+ result: HookRuntimeResult | undefined,
573
+ diagnostics: HookRuntimeDiagnostics,
574
+ ): HookRuntimeResult {
575
+ if (result === undefined) {
576
+ throw new Error("Hook runtime did not produce a result.");
577
+ }
578
+ return {
579
+ ...result,
580
+ stateWrites: [...new Set([...diagnostics.stateWrites, ...result.stateWrites])],
581
+ warnings: [...new Set([...diagnostics.warnings, ...result.warnings])],
582
+ };
583
+ }
584
+
432
585
  async function handleSessionStart(input: HandleHookRuntimeInput): Promise<HookRuntimeResult> {
433
586
  const binding = await readSessionBinding(input.homeDir, input.rawPayload);
434
587
  const context = [
@@ -463,88 +616,30 @@ async function handleUserPromptSubmit(input: HandleHookRuntimeInput): Promise<Ho
463
616
  await writeJsonFile(paths.bindingPath, binding);
464
617
 
465
618
  const context = [
466
- `EvoDev routed this request before ${formatHookTargetName(input.target)} execution.`,
619
+ `EvoDev observed this request before ${formatHookTargetName(input.target)} execution.`,
620
+ "Advisory only: EvoDev does not grant, deny, or stop model/tool execution.",
467
621
  `Task Contract: ${paths.contractPath}`,
468
- `Mode: ${contract.route.mode ?? "unknown"}`,
469
- `Workflow: ${contract.route.workflowId ?? "none"}`,
622
+ `Suggested mode: ${contract.route.mode ?? "unknown"}`,
623
+ `Suggested workflow: ${contract.route.workflowId ?? "none"}`,
470
624
  `Reason: ${contract.route.rationale}`,
471
- "Follow the contract scope, anti-criteria, and verification plan before finishing.",
625
+ "Use the suggestion only if it fits the user request.",
472
626
  ].join(" ");
473
627
 
474
628
  return createRuntimeResult(input, hookOutput(input.event.type, { additionalContext: context }), {
475
- summary: "User prompt routed through Task Contract.",
629
+ summary: "User prompt observed and advisory context prepared.",
476
630
  stateWrites: [paths.contractPath, paths.bindingPath],
477
631
  });
478
632
  }
479
633
 
480
634
  async function handlePreToolUse(input: HandleHookRuntimeInput): Promise<HookRuntimeResult> {
481
- const contract = await readActiveContract(input.homeDir, input.rawPayload);
482
635
  const commandClass =
483
636
  typeof input.event.payload.metadata.commandClass === "string"
484
637
  ? input.event.payload.metadata.commandClass
485
638
  : undefined;
486
- const protectedPath = findProtectedPath(input.rawPayload);
487
-
488
- if (contract === null) {
489
- return createRuntimeResult(
490
- input,
491
- hookOutput(input.event.type, {
492
- permissionDecision: "ask",
493
- permissionDecisionReason: "EvoDev requires an active Task Contract before tool use.",
494
- additionalContext:
495
- "Submit the user prompt through UserPromptSubmit to create the Task Contract first.",
496
- }),
497
- { summary: "Tool use requires user confirmation because no active contract exists." },
498
- );
499
- }
500
-
501
- if (protectedPath !== null) {
502
- return createRuntimeResult(
503
- input,
504
- hookOutput(input.event.type, {
505
- permissionDecision: "deny",
506
- permissionDecisionReason: `EvoDev blocked access to protected project asset: ${protectedPath}`,
507
- }),
508
- { summary: "Protected path access denied." },
509
- );
510
- }
511
-
512
- if (
513
- commandClass === "delete" ||
514
- commandClass === "publish" ||
515
- commandClass === "credential-sensitive" ||
516
- commandClass === "path-escaping"
517
- ) {
518
- return createRuntimeResult(
519
- input,
520
- hookOutput(input.event.type, {
521
- permissionDecision: "deny",
522
- permissionDecisionReason: `EvoDev blocked high-risk tool action: ${commandClass}`,
523
- }),
524
- { summary: "High-risk tool action denied." },
525
- );
526
- }
527
-
528
- if (commandClass === "network" || commandClass === "unknown" || commandClass === "write") {
529
- return createRuntimeResult(
530
- input,
531
- hookOutput(input.event.type, {
532
- permissionDecision: "ask",
533
- permissionDecisionReason: `EvoDev requires confirmation for ${commandClass} action under ${contract.route.mode ?? "unrouted"} mode.`,
534
- additionalContext: `Active Task Contract: ${contract.taskId}. Confirm scope before continuing.`,
535
- }),
536
- { summary: "Tool use requires user confirmation." },
537
- );
538
- }
539
639
 
540
- return createRuntimeResult(
541
- input,
542
- hookOutput(input.event.type, {
543
- permissionDecision: "allow",
544
- permissionDecisionReason: `EvoDev allowed ${commandClass ?? "metadata-only"} action under Task Contract ${contract.taskId}.`,
545
- }),
546
- { summary: "Tool use allowed by EvoDev policy." },
547
- );
640
+ return createRuntimeResult(input, null, {
641
+ summary: `${input.event.type} observed (${commandClass ?? "metadata-only"}); no permission decision emitted.`,
642
+ });
548
643
  }
549
644
 
550
645
  async function handlePostToolUse(input: HandleHookRuntimeInput): Promise<HookRuntimeResult> {
@@ -574,62 +669,25 @@ async function handlePostToolUse(input: HandleHookRuntimeInput): Promise<HookRun
574
669
  };
575
670
  await writeTaskContract(binding.contractPath, nextContract, { overwrite: true });
576
671
 
577
- return createRuntimeResult(
578
- input,
579
- hookOutput(input.event.type, {
580
- additionalContext: `EvoDev recorded metadata-only evidence for Task Contract ${contract.taskId}. Raw output was not stored.`,
581
- }),
582
- {
583
- summary: "Post-tool metadata evidence recorded.",
584
- stateWrites: [binding.contractPath],
585
- },
586
- );
672
+ return createRuntimeResult(input, null, {
673
+ summary: "Post-tool metadata evidence recorded.",
674
+ stateWrites: [binding.contractPath],
675
+ });
587
676
  }
588
677
 
589
- async function handleCompletionGate(input: HandleHookRuntimeInput): Promise<HookRuntimeResult> {
678
+ async function handleCompletionObservation(
679
+ input: HandleHookRuntimeInput,
680
+ ): Promise<HookRuntimeResult> {
590
681
  const contract = await readActiveContract(input.homeDir, input.rawPayload);
591
- if (contract === null) return handleAdditionalContext(input, input.event.type);
592
-
593
- const stopHookActive = input.rawPayload.stop_hook_active === true;
594
- if (
595
- !stopHookActive &&
596
- contract.route.mode === "rigorous" &&
597
- contract.evidence.items.length === 0
598
- ) {
599
- return createRuntimeResult(
600
- input,
601
- {
602
- decision: "block",
603
- reason: "EvoDev rigorous mode requires metadata-only evidence before stopping.",
604
- ...hookOutput(input.event.type, {
605
- additionalContext:
606
- "Run the required verification or record metadata-only evidence before finishing.",
607
- }),
608
- },
609
- { summary: "Completion blocked until evidence exists." },
610
- );
611
- }
612
-
613
- return createRuntimeResult(
614
- input,
615
- hookOutput(input.event.type, {
616
- additionalContext: `EvoDev completion gate checked Task Contract ${contract.taskId}. Evidence items: ${contract.evidence.items.length}.`,
617
- }),
618
- { summary: "Completion gate checked." },
619
- );
682
+ const suffix =
683
+ contract === null ? "without an active Task Contract" : `for Task Contract ${contract.taskId}`;
684
+ return createRuntimeResult(input, null, {
685
+ summary: `${input.event.type} observed ${suffix}; no completion control enforced.`,
686
+ });
620
687
  }
621
688
 
622
689
  async function handlePreCompact(input: HandleHookRuntimeInput): Promise<HookRuntimeResult> {
623
- const binding = await readSessionBinding(input.homeDir, input.rawPayload);
624
- return createRuntimeResult(
625
- input,
626
- hookOutput(input.event.type, {
627
- additionalContext: binding?.contractPath
628
- ? `Preserve EvoDev Task Contract reference across compaction: ${binding.contractPath}`
629
- : "No EvoDev Task Contract is active for this session.",
630
- }),
631
- { summary: "PreCompact context prepared." },
632
- );
690
+ return createRuntimeResult(input, null, { summary: "PreCompact observed." });
633
691
  }
634
692
 
635
693
  async function handleSessionEnd(input: HandleHookRuntimeInput): Promise<HookRuntimeResult> {
@@ -656,20 +714,16 @@ function handleAdditionalContext(
656
714
  eventName: CanonicalHookEventType,
657
715
  ): Promise<HookRuntimeResult> {
658
716
  return Promise.resolve(
659
- createRuntimeResult(
660
- input,
661
- hookOutput(eventName, {
662
- additionalContext: `EvoDev processed ${eventName} as metadata-only hook context.`,
663
- }),
664
- { summary: `${eventName} processed as metadata-only context.` },
665
- ),
717
+ createRuntimeResult(input, null, {
718
+ summary: `${eventName} observed as metadata-only hook context.`,
719
+ }),
666
720
  );
667
721
  }
668
722
 
669
723
  function createRuntimeResult(
670
724
  input: HandleHookRuntimeInput,
671
725
  output: Record<string, unknown> | null,
672
- options: { summary: string; stateWrites?: string[] },
726
+ options: { summary: string; stateWrites?: string[]; warnings?: string[] },
673
727
  ): HookRuntimeResult {
674
728
  return {
675
729
  target: input.target,
@@ -677,6 +731,7 @@ function createRuntimeResult(
677
731
  enabled: true,
678
732
  output,
679
733
  stateWrites: options.stateWrites ?? [],
734
+ warnings: options.warnings ?? [],
680
735
  summary: options.summary,
681
736
  };
682
737
  }
@@ -701,14 +756,14 @@ function createHookTaskContract(
701
756
  const targetName = formatHookTargetName(input.target);
702
757
  const contract = createTaskContract({
703
758
  title: `${targetName} hook task ${sessionKey}`,
704
- summary: `${targetName} prompt classified as ${classification.kind}; raw prompt not stored.`,
759
+ summary: `${targetName} prompt classified as ${classification.kind}; raw prompt is local trace only.`,
705
760
  projectId: null,
706
761
  });
707
762
 
708
763
  return {
709
764
  ...contract,
710
765
  currentState: {
711
- summary: `UserPromptSubmit received through ${targetName} hooks; raw prompt omitted.`,
766
+ summary: `UserPromptSubmit received through ${targetName} hooks; raw prompt omitted from Task Contract.`,
712
767
  evidenceRefs: [],
713
768
  },
714
769
  targetState: {
@@ -811,34 +866,14 @@ async function writeJsonFile(path: string, value: unknown): Promise<void> {
811
866
  await writeFile(path, `${JSON.stringify(value, null, 2)}\n`, "utf8");
812
867
  }
813
868
 
814
- function findProtectedPath(payload: Record<string, unknown>): string | null {
815
- const candidates = collectStringValues(payload).filter((value) => value.length < 500);
816
- for (const candidate of candidates) {
817
- if (
818
- candidate === "CLAUDE.md" ||
819
- candidate === "AGENTS.md" ||
820
- candidate.includes("/CLAUDE.md") ||
821
- candidate.includes("/AGENTS.md") ||
822
- candidate.includes(".claude/") ||
823
- candidate.includes(".codex/")
824
- ) {
825
- return candidate;
826
- }
827
- }
828
- return null;
829
- }
830
-
831
- function collectStringValues(value: unknown): string[] {
832
- if (typeof value === "string") return [value];
833
- if (Array.isArray(value)) return value.flatMap((item) => collectStringValues(item));
834
- if (isRecord(value)) return Object.values(value).flatMap((item) => collectStringValues(item));
835
- return [];
836
- }
837
-
838
869
  function optionalPayloadString(value: unknown): string | null {
839
870
  return typeof value === "string" && value.length > 0 ? value.slice(0, 300) : null;
840
871
  }
841
872
 
873
+ function safeDiagnosticId(value: string): string {
874
+ return value.replace(/[^A-Za-z0-9._-]/g, "-").slice(0, 120) || "unknown";
875
+ }
876
+
842
877
  function optionalPayloadNumber(value: unknown): number | null {
843
878
  return typeof value === "number" && Number.isFinite(value) ? value : null;
844
879
  }
@@ -893,7 +928,7 @@ function extractMetadata(
893
928
  redactions: string[],
894
929
  ): Record<string, string | number | boolean | string[]> {
895
930
  const metadata: Record<string, string | number | boolean | string[]> = {
896
- rawContentIncluded: false,
931
+ rawContentIncluded: true,
897
932
  };
898
933
  const toolInput = isRecord(payload.tool_input)
899
934
  ? payload.tool_input
@@ -932,30 +967,12 @@ function extractMetadata(
932
967
  }
933
968
 
934
969
  function decideHookPolicy(commandClass: string | undefined): HookEventV1["decision"] {
935
- if (
936
- commandClass === "delete" ||
937
- commandClass === "network" ||
938
- commandClass === "publish" ||
939
- commandClass === "credential-sensitive" ||
940
- commandClass === "path-escaping" ||
941
- commandClass === "unknown"
942
- ) {
943
- return {
944
- action: "ask-user",
945
- reason: `Command risk requires explicit confirmation: ${commandClass}`,
946
- requiresUserConfirmation: true,
947
- };
948
- }
949
- if (commandClass === "write") {
950
- return {
951
- action: "warn",
952
- reason: "Write-like command requires scope review.",
953
- requiresUserConfirmation: true,
954
- };
955
- }
956
970
  return {
957
971
  action: "allow",
958
- reason: "No policy violation detected.",
972
+ reason:
973
+ commandClass === undefined
974
+ ? "Observed metadata only; EvoDev does not stop hook execution."
975
+ : `Observed ${commandClass} tool action; EvoDev does not stop hook execution.`,
959
976
  requiresUserConfirmation: false,
960
977
  };
961
978
  }
@@ -966,7 +983,7 @@ function summarizeEvent(
966
983
  ): string {
967
984
  if (type === "PreToolUse") return `Tool use requested (${metadata.commandClass ?? "unknown"}).`;
968
985
  if (type === "PostToolUse") return "Tool use completed with metadata-only result.";
969
- if (type === "UserPromptSubmit") return "User prompt submitted; raw prompt redacted.";
986
+ if (type === "UserPromptSubmit") return "User prompt submitted.";
970
987
  if (type === "SubagentStop") return "Subagent stopped; transcript omitted.";
971
988
  return `${type} received; metadata-only dry-run.`;
972
989
  }