@evo-dev/core 0.0.1-alpha.5 → 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.
@@ -14,13 +14,8 @@ import {
14
14
  enqueueEvolutionTrigger,
15
15
  resolveEvolutionTriggerDecision,
16
16
  } from "../evolution/triggers/index.ts";
17
- import { resolveTraceTeamContext } from "../runtime-logs/index.ts";
18
- import {
19
- type TaskContract,
20
- createTaskContract,
21
- routeTaskContract,
22
- writeTaskContract,
23
- } from "../task/index.ts";
17
+ import { recordDiscoveredProject } from "../projects/index.ts";
18
+ import { resolveTraceSessionKey, resolveTraceTeamContext } from "../runtime-logs/index.ts";
24
19
  import {
25
20
  type TeamMessageRecord,
26
21
  createTeamRoleRuntimeContext,
@@ -159,10 +154,7 @@ export interface HookRuntimeSessionBinding {
159
154
  version: 1;
160
155
  target: CodeAgentHookTarget;
161
156
  sessionKey: string;
162
- taskId: string | null;
163
- contractPath: string | null;
164
157
  cwd: string | null;
165
- route: TaskContract["route"] | null;
166
158
  teamRuntimeContextDeliveredAt?: string | null;
167
159
  updatedAt: string;
168
160
  }
@@ -424,12 +416,11 @@ export function formatHookEventDryRun(event: HookEventV1): string {
424
416
  export function resolveHookRuntimeSessionPaths(input: {
425
417
  homeDir: string;
426
418
  sessionKey: string;
427
- }): { sessionDir: string; bindingPath: string; contractPath: string } {
419
+ }): { sessionDir: string; bindingPath: string } {
428
420
  const sessionDir = join(input.homeDir, ".evodev", "STATE", "hooks", "sessions", input.sessionKey);
429
421
  return {
430
422
  sessionDir,
431
423
  bindingPath: join(sessionDir, "binding.json"),
432
- contractPath: join(sessionDir, "contract.json"),
433
424
  };
434
425
  }
435
426
 
@@ -448,6 +439,7 @@ export async function handleHookRuntime(input: HandleHookRuntimeInput): Promise<
448
439
  }
449
440
 
450
441
  const diagnostics = await recordTeamNativeSessionFromHook(input);
442
+ const projectDiagnostics = await recordDiscoveredProjectFromHook(input);
451
443
  let result: HookRuntimeResult | undefined;
452
444
  if (input.event.type === "SessionStart") result = await handleSessionStart(input);
453
445
  else if (input.event.type === "UserPromptSubmit") result = await handleUserPromptSubmit(input);
@@ -485,12 +477,15 @@ export async function handleHookRuntime(input: HandleHookRuntimeInput): Promise<
485
477
  const messageDiagnostics = await deliverPendingTeamMessagesFromHook(input, result);
486
478
  const scopedContextDiagnostics = await injectScopedKnowledgeContextFromHook(input, result);
487
479
  const teamStateDiagnostics = await recordTeamAgentHookStateFromHook(input);
488
- return appendRuntimeDiagnostics(
480
+ const completed = appendRuntimeDiagnostics(
489
481
  appendRuntimeDiagnostics(
490
482
  appendRuntimeDiagnostics(
491
483
  appendRuntimeDiagnostics(
492
484
  appendRuntimeDiagnostics(
493
- appendRuntimeDiagnostics(result, diagnostics),
485
+ appendRuntimeDiagnostics(
486
+ appendRuntimeDiagnostics(result, diagnostics),
487
+ projectDiagnostics,
488
+ ),
494
489
  sessionMemoryDiagnostics,
495
490
  ),
496
491
  evolutionDiagnostics,
@@ -501,6 +496,7 @@ export async function handleHookRuntime(input: HandleHookRuntimeInput): Promise<
501
496
  ),
502
497
  teamStateDiagnostics,
503
498
  );
499
+ return appendDevelopmentHookDiagnostics(input, completed);
504
500
  }
505
501
 
506
502
  export function formatHookRuntimeOutput(result: HookRuntimeResult): string {
@@ -524,6 +520,41 @@ interface HookRuntimeDiagnostics {
524
520
  warnings: string[];
525
521
  }
526
522
 
523
+ async function recordDiscoveredProjectFromHook(
524
+ input: HandleHookRuntimeInput,
525
+ ): Promise<HookRuntimeDiagnostics> {
526
+ if (
527
+ input.event.type !== "SessionStart" &&
528
+ input.event.type !== "UserPromptSubmit" &&
529
+ input.event.type !== "CwdChanged"
530
+ ) {
531
+ return { stateWrites: [], warnings: [] };
532
+ }
533
+ const cwd = optionalPayloadString(input.rawPayload.cwd);
534
+ if (cwd === null) return { stateWrites: [], warnings: [] };
535
+
536
+ try {
537
+ const result = await recordDiscoveredProject({
538
+ homeDir: input.homeDir,
539
+ cwd,
540
+ target: input.target,
541
+ sessionKey: resolveTraceSessionKey(input.rawPayload),
542
+ now:
543
+ input.receivedAt === undefined || input.receivedAt === "dry-run"
544
+ ? undefined
545
+ : input.receivedAt,
546
+ });
547
+ return result === null
548
+ ? { stateWrites: [], warnings: [] }
549
+ : { stateWrites: [result.path], warnings: [] };
550
+ } catch {
551
+ return {
552
+ stateWrites: [],
553
+ warnings: ["Local project discovery could not be updated at this hook safe point."],
554
+ };
555
+ }
556
+ }
557
+
527
558
  async function recordTeamNativeSessionFromHook(
528
559
  input: HandleHookRuntimeInput,
529
560
  ): Promise<HookRuntimeDiagnostics> {
@@ -825,17 +856,14 @@ async function handleSessionStart(input: HandleHookRuntimeInput): Promise<HookRu
825
856
  }
826
857
 
827
858
  async function handleUserPromptSubmit(input: HandleHookRuntimeInput): Promise<HookRuntimeResult> {
828
- const classification = classifyUserPrompt(input.rawPayload.prompt ?? input.rawPayload.userPrompt);
829
859
  const sessionKey = resolveHookSessionKey(input.rawPayload);
830
860
  const paths = resolveHookRuntimeSessionPaths({ homeDir: input.homeDir, sessionKey });
831
- const contract = routeTaskContract(createHookTaskContract(input, classification));
832
861
  const previousBinding = await readSessionBinding(input.homeDir, input.rawPayload);
833
862
  const teamRuntimeContext =
834
863
  previousBinding?.teamRuntimeContextDeliveredAt === undefined ||
835
864
  previousBinding.teamRuntimeContextDeliveredAt === null
836
865
  ? await createTeamRuntimeContextForUserPrompt(input)
837
866
  : null;
838
- const shouldShowDiagnostics = input.teamRuntimeDisplayMode === "development";
839
867
  const teamRuntimeContextDeliveredAt =
840
868
  teamRuntimeContext !== null
841
869
  ? (input.receivedAt ?? new Date().toISOString())
@@ -844,35 +872,21 @@ async function handleUserPromptSubmit(input: HandleHookRuntimeInput): Promise<Ho
844
872
  version: 1,
845
873
  target: input.target,
846
874
  sessionKey,
847
- taskId: contract.taskId,
848
- contractPath: paths.contractPath,
849
875
  cwd: optionalPayloadString(input.rawPayload.cwd),
850
- route: contract.route,
851
876
  teamRuntimeContextDeliveredAt,
852
877
  updatedAt: input.receivedAt ?? new Date().toISOString(),
853
878
  };
854
879
 
855
- await writeTaskContract(paths.contractPath, contract, { overwrite: true });
856
880
  await writeJsonFile(paths.bindingPath, binding);
857
881
 
858
- const visibleContext = createUserPromptVisibleContext({
859
- classification,
860
- contract,
861
- contractPath: paths.contractPath,
862
- });
863
- let output =
864
- visibleContext === null || !shouldShowDiagnostics
865
- ? null
866
- : hookOutput(input.event.type, {
867
- additionalContext: visibleContext,
868
- });
882
+ let output: Record<string, unknown> | null = null;
869
883
  if (teamRuntimeContext !== null) {
870
884
  output = appendAdditionalContext(output, input.event.type, teamRuntimeContext);
871
885
  }
872
886
 
873
887
  return createRuntimeResult(input, output, {
874
- summary: "User prompt observed; Task Contract prepared.",
875
- stateWrites: [paths.contractPath, paths.bindingPath],
888
+ summary: "User prompt observed; session binding updated.",
889
+ stateWrites: [paths.bindingPath],
876
890
  });
877
891
  }
878
892
 
@@ -909,46 +923,14 @@ async function handlePreToolUse(input: HandleHookRuntimeInput): Promise<HookRunt
909
923
  }
910
924
 
911
925
  async function handlePostToolUse(input: HandleHookRuntimeInput): Promise<HookRuntimeResult> {
912
- const contract = await readActiveContract(input.homeDir, input.rawPayload);
913
- const binding = await readSessionBinding(input.homeDir, input.rawPayload);
914
- if (contract === null || binding?.contractPath === null || binding?.contractPath === undefined) {
915
- return handleAdditionalContext(input, input.event.type);
916
- }
917
-
918
- const status = optionalPayloadNumber(input.rawPayload.exit_code ?? input.rawPayload.exitCode);
919
- const nextContract: TaskContract = {
920
- ...contract,
921
- evidence: {
922
- metadataOnly: true,
923
- items: [
924
- ...contract.evidence.items,
925
- {
926
- type: "command-result",
927
- id: input.event.eventId,
928
- status: status === 0 ? "pass" : status === null ? "unknown" : "fail",
929
- summary: input.event.payload.summary,
930
- rawOutputStored: false,
931
- sourceContentStored: false,
932
- },
933
- ],
934
- },
935
- };
936
- await writeTaskContract(binding.contractPath, nextContract, { overwrite: true });
937
-
938
- return createRuntimeResult(input, null, {
939
- summary: "Post-tool metadata evidence recorded.",
940
- stateWrites: [binding.contractPath],
941
- });
926
+ return handleAdditionalContext(input, input.event.type);
942
927
  }
943
928
 
944
929
  async function handleCompletionObservation(
945
930
  input: HandleHookRuntimeInput,
946
931
  ): Promise<HookRuntimeResult> {
947
- const contract = await readActiveContract(input.homeDir, input.rawPayload);
948
- const suffix =
949
- contract === null ? "without an active Task Contract" : `for Task Contract ${contract.taskId}`;
950
932
  return createRuntimeResult(input, null, {
951
- summary: `${input.event.type} observed ${suffix}; no completion control enforced.`,
933
+ summary: `${input.event.type} observed as metadata-only hook context.`,
952
934
  });
953
935
  }
954
936
 
@@ -1038,6 +1020,47 @@ function appendAdditionalContext(
1038
1020
  };
1039
1021
  }
1040
1022
 
1023
+ function appendDevelopmentHookDiagnostics(
1024
+ input: HandleHookRuntimeInput,
1025
+ result: HookRuntimeResult,
1026
+ ): HookRuntimeResult {
1027
+ if (
1028
+ input.teamRuntimeDisplayMode !== "development" ||
1029
+ !canDeliverTeamMessagesFromHook(input.target, input.event.type)
1030
+ ) {
1031
+ return result;
1032
+ }
1033
+ return {
1034
+ ...result,
1035
+ output: appendAdditionalContext(
1036
+ result.output,
1037
+ input.event.type,
1038
+ formatDevelopmentHookDiagnostics(input),
1039
+ ),
1040
+ };
1041
+ }
1042
+
1043
+ function formatDevelopmentHookDiagnostics(input: HandleHookRuntimeInput): string {
1044
+ const metadata = Object.entries(input.event.payload.metadata);
1045
+ const inputFields = Object.keys(input.rawPayload)
1046
+ .map((field) => field.replace(/[^A-Za-z0-9._-]/g, "-").slice(0, 80))
1047
+ .filter((field) => field.length > 0)
1048
+ .slice(0, 30);
1049
+ return [
1050
+ "EvoDev hook development diagnostics",
1051
+ `Target: ${input.target}`,
1052
+ `Event: ${input.event.type}`,
1053
+ `Summary: ${input.event.payload.summary}`,
1054
+ `Input fields: ${inputFields.join(", ") || "none"}`,
1055
+ "Normalized metadata:",
1056
+ ...(metadata.length === 0
1057
+ ? ["- none"]
1058
+ : metadata.map(([key, value]) => `- ${key}: ${formatMetadataValue(value)}`)),
1059
+ `Redactions: ${input.event.payload.redactions.join(", ") || "none"}`,
1060
+ "Raw payload included: false",
1061
+ ].join("\n");
1062
+ }
1063
+
1041
1064
  function formatTeamInboxContext(messages: TeamMessageRecord[]): string {
1042
1065
  const blocks = messages.map((message) =>
1043
1066
  [
@@ -1061,113 +1084,6 @@ function truncateTeamMessageBody(value: string): string {
1061
1084
  return `${value.slice(0, 4000)}...[truncated:${value.length - 4000}]`;
1062
1085
  }
1063
1086
 
1064
- function createUserPromptVisibleContext(input: {
1065
- classification: ReturnType<typeof classifyUserPrompt>;
1066
- contract: TaskContract;
1067
- contractPath: string;
1068
- }): string | null {
1069
- if (!input.classification.needsClarification) return null;
1070
- return [
1071
- "EvoDev advisory: clarification may be needed before broad changes.",
1072
- `Task Contract: ${input.contractPath}`,
1073
- `Suggested mode: ${input.contract.route.mode ?? "unknown"}`,
1074
- `Suggested workflow: ${input.contract.route.workflowId ?? "none"}`,
1075
- `Reason: ${input.contract.route.rationale}`,
1076
- ].join(" ");
1077
- }
1078
-
1079
- function createHookTaskContract(
1080
- input: HandleHookRuntimeInput,
1081
- classification: ReturnType<typeof classifyUserPrompt>,
1082
- ): TaskContract {
1083
- const sessionKey = resolveHookSessionKey(input.rawPayload);
1084
- const targetName = formatHookTargetName(input.target);
1085
- const contract = createTaskContract({
1086
- title: `${targetName} hook task ${sessionKey}`,
1087
- summary: `${targetName} prompt classified as ${classification.kind}; raw prompt is not stored by EvoDev.`,
1088
- projectId: null,
1089
- });
1090
-
1091
- return {
1092
- ...contract,
1093
- currentState: {
1094
- summary: `UserPromptSubmit received through ${targetName} hooks; raw prompt omitted from Task Contract.`,
1095
- evidenceRefs: [],
1096
- },
1097
- targetState: {
1098
- summary: `Complete the ${classification.kind} task through EvoDev-controlled workflow.`,
1099
- nonGoals: ["Do not store raw prompts, transcripts, source content, secrets, or raw output."],
1100
- constraints: [
1101
- `prompt-kind:${classification.kind}`,
1102
- ...classification.riskTerms.map((term) => `risk:${term}`),
1103
- ],
1104
- },
1105
- scope: {
1106
- ...contract.scope,
1107
- requiresUserConfirmation: classification.riskTerms,
1108
- },
1109
- context: {
1110
- ...contract.context,
1111
- assumptions: [
1112
- `hook-session:${sessionKey}`,
1113
- `cwd:${optionalPayloadString(input.rawPayload.cwd) ?? "unknown"}`,
1114
- ],
1115
- openQuestions: classification.needsClarification
1116
- ? ["User request may need clarification before broad changes."]
1117
- : [],
1118
- },
1119
- };
1120
- }
1121
-
1122
- function formatHookTargetName(target: CodeAgentHookTarget): string {
1123
- return target === "codex" ? "Codex" : "Claude";
1124
- }
1125
-
1126
- function classifyUserPrompt(value: unknown): {
1127
- kind: string;
1128
- riskTerms: string[];
1129
- needsClarification: boolean;
1130
- } {
1131
- const text = typeof value === "string" ? value.toLowerCase() : "";
1132
- const riskTerms = [
1133
- "security",
1134
- "release",
1135
- "publish",
1136
- "hook",
1137
- "memory",
1138
- "learning",
1139
- "secret",
1140
- "privacy",
1141
- ].filter((term) => text.includes(term));
1142
- let kind = "feature";
1143
- if (/\bbug|fix|error|failed|failure\b/.test(text)) kind = "bugfix";
1144
- if (/\brefactor|migration|migrate\b/.test(text)) kind = "refactor";
1145
- if (/\breview|audit\b/.test(text)) kind = "review";
1146
- if (/\btest|coverage\b/.test(text)) kind = "test";
1147
- if (/\bdoc|readme|guide\b/.test(text)) kind = "docs";
1148
- if (riskTerms.includes("security") || riskTerms.includes("privacy")) kind = "security";
1149
- if (riskTerms.includes("release") || riskTerms.includes("publish")) kind = "release";
1150
- return {
1151
- kind,
1152
- riskTerms,
1153
- needsClarification: text.trim().length < 12 || /\bmaybe|unclear|not sure\b/.test(text),
1154
- };
1155
- }
1156
-
1157
- async function readActiveContract(
1158
- homeDir: string,
1159
- payload: Record<string, unknown>,
1160
- ): Promise<TaskContract | null> {
1161
- const binding = await readSessionBinding(homeDir, payload);
1162
- if (binding?.contractPath === null || binding?.contractPath === undefined) return null;
1163
- try {
1164
- return JSON.parse(await readFile(binding.contractPath, "utf8")) as TaskContract;
1165
- } catch (error) {
1166
- if (isNotFoundError(error)) return null;
1167
- throw error;
1168
- }
1169
- }
1170
-
1171
1087
  async function readSessionBinding(
1172
1088
  homeDir: string,
1173
1089
  payload: Record<string, unknown>,
@@ -1202,10 +1118,6 @@ function safeDiagnosticId(value: string): string {
1202
1118
  return value.replace(/[^A-Za-z0-9._-]/g, "-").slice(0, 120) || "unknown";
1203
1119
  }
1204
1120
 
1205
- function optionalPayloadNumber(value: unknown): number | null {
1206
- return typeof value === "number" && Number.isFinite(value) ? value : null;
1207
- }
1208
-
1209
1121
  function isNotFoundError(error: unknown): boolean {
1210
1122
  return (
1211
1123
  error instanceof Error && "code" in error && (error as NodeJS.ErrnoException).code === "ENOENT"
package/src/index.ts CHANGED
@@ -18,11 +18,10 @@ export * from "./hooks/index.ts";
18
18
  export * from "./observability/index.ts";
19
19
  export * from "./pack/index.ts";
20
20
  export * from "./plugins/index.ts";
21
- export * from "./project/index.ts";
22
21
  export * from "./protected-zones/index.ts";
22
+ export * from "./projects/index.ts";
23
23
  export * from "./runtime-logs/index.ts";
24
24
  export * from "./sync/index.ts";
25
- export * from "./task/index.ts";
26
25
  export * from "./team/index.ts";
27
26
  export * from "./team/mcp.ts";
28
27
  export * from "./workflow/index.ts";