@evo-dev/core 0.0.1-alpha.1 → 0.0.1-alpha.11

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 (66) hide show
  1. package/assets/skills/coding/knowledge-distillation/SKILL.md +117 -114
  2. package/assets/skills/coding/knowledge-distillation/references/knowledge-distillation-methods.md +11 -7
  3. package/assets/team/agents/code-reviewer.md +48 -0
  4. package/assets/team/agents/docs-maintainer.md +51 -0
  5. package/assets/team/agents/implementation-engineer.md +51 -0
  6. package/assets/team/agents/product-scope-analyst.md +58 -0
  7. package/assets/team/agents/release-engineer.md +55 -0
  8. package/assets/team/agents/security-boundary-reviewer.md +50 -0
  9. package/assets/team/agents/solution-architect.md +51 -0
  10. package/assets/team/agents/verification-engineer.md +51 -0
  11. package/assets/team/team.md +102 -0
  12. package/dist/config/index.js +925 -97
  13. package/dist/index.js +13107 -5618
  14. package/package.json +5 -1
  15. package/src/agents/index.ts +56 -264
  16. package/src/code-agent-traces/index.ts +520 -0
  17. package/src/config/index.ts +5 -0
  18. package/src/config/paths.ts +1 -1
  19. package/src/config/settings.ts +149 -0
  20. package/src/config/store.ts +2 -0
  21. package/src/daemon/index.ts +99 -50
  22. package/src/evolution/candidates/index.ts +564 -0
  23. package/src/evolution/control/index.ts +20 -0
  24. package/src/evolution/evidence/analysis.ts +533 -0
  25. package/src/evolution/evidence/index.ts +3 -0
  26. package/src/evolution/evidence/session-memory/analysis.ts +281 -0
  27. package/src/evolution/evidence/session-memory/constants.ts +9 -0
  28. package/src/evolution/evidence/session-memory/index.ts +7 -0
  29. package/src/evolution/evidence/session-memory/paths.ts +29 -0
  30. package/src/evolution/evidence/session-memory/policy.ts +39 -0
  31. package/src/evolution/evidence/session-memory/segment.ts +202 -0
  32. package/src/evolution/evidence/session-memory/sensitivity.ts +335 -0
  33. package/src/evolution/evidence/session-memory/state-machine.ts +249 -0
  34. package/src/evolution/evidence/session-memory/storage.ts +379 -0
  35. package/src/evolution/evidence/session-memory/types.ts +221 -0
  36. package/src/evolution/evidence/session-memory/updater.ts +191 -0
  37. package/src/evolution/formatters.ts +169 -0
  38. package/src/evolution/index.ts +16 -2356
  39. package/src/evolution/knowledge/index.ts +5427 -0
  40. package/src/evolution/paths.ts +44 -0
  41. package/src/evolution/processor/distillation.ts +518 -0
  42. package/src/evolution/processor/index.ts +3 -0
  43. package/src/evolution/processor/process.ts +528 -0
  44. package/src/{learning → evolution/review}/index.ts +10 -14
  45. package/src/evolution/schema.ts +568 -0
  46. package/src/evolution/shared.ts +758 -0
  47. package/src/evolution/triggers/classification.ts +102 -0
  48. package/src/evolution/triggers/index.ts +295 -0
  49. package/src/hooks/index.ts +438 -179
  50. package/src/index.ts +12 -3
  51. package/src/projects/index.ts +453 -0
  52. package/src/runtime-logs/index.ts +490 -24
  53. package/src/team/index.ts +1429 -185
  54. package/src/team/mcp.ts +9 -5
  55. package/src/team/prompts.ts +141 -0
  56. package/src/utils/errors.ts +13 -0
  57. package/src/utils/fs.ts +40 -0
  58. package/src/utils/hash.ts +9 -0
  59. package/src/utils/ids.ts +12 -0
  60. package/src/utils/index.ts +7 -0
  61. package/src/utils/parsing.ts +11 -0
  62. package/src/utils/text.ts +18 -0
  63. package/src/utils/time.ts +5 -0
  64. package/src/workflow/index.ts +3 -21
  65. package/src/project/index.ts +0 -507
  66. package/src/task/index.ts +0 -840
package/src/task/index.ts DELETED
@@ -1,840 +0,0 @@
1
- import { lstat, mkdir, readFile, realpath, stat, writeFile } from "node:fs/promises";
2
- import { basename, dirname, join, resolve } from "node:path";
3
-
4
- export type TaskContractStatus = "draft" | "routed" | "verified" | "failed";
5
- export type TaskExecutionMode = "minimal" | "standard" | "rigorous";
6
- export type VerificationStatus =
7
- | "pass"
8
- | "fail"
9
- | "not-run"
10
- | "not-applicable"
11
- | "unknown"
12
- | "triggered"
13
- | "clear";
14
-
15
- export interface TaskContract {
16
- version: 1;
17
- taskId: string;
18
- status: TaskContractStatus;
19
- classification: "local-private";
20
- source: {
21
- summary: string;
22
- rawPromptStored: false;
23
- };
24
- context: {
25
- projectId: string | null;
26
- relatedFiles: Array<{ path: string; reason: string; contentCaptured: false }>;
27
- assumptions: string[];
28
- openQuestions: string[];
29
- };
30
- currentState: { summary: string; evidenceRefs: string[] };
31
- targetState: { summary: string; nonGoals: string[]; constraints: string[] };
32
- scope: {
33
- allowedPaths: string[];
34
- forbiddenPaths: string[];
35
- allowedOperations: string[];
36
- requiresUserConfirmation: string[];
37
- };
38
- acceptanceCriteria: Array<{
39
- id: string;
40
- category: "product" | "engineering" | "safety" | "documentation";
41
- statement: string;
42
- requiredEvidence: string[];
43
- status: VerificationStatus;
44
- }>;
45
- antiCriteria: Array<{
46
- id: string;
47
- category: "privacy" | "scope" | "safety" | "release";
48
- statement: string;
49
- status: VerificationStatus;
50
- }>;
51
- route: {
52
- mode: TaskExecutionMode | null;
53
- workflowId: string | null;
54
- rationale: string;
55
- requiredReview: string[];
56
- requiredVerification: string[];
57
- };
58
- verification: {
59
- policy: "advisory";
60
- commands: VerificationCommandResult[];
61
- acceptanceResults: VerificationCriterionResult[];
62
- antiCriteriaResults: VerificationCriterionResult[];
63
- status: "pass" | "fail" | "not-run";
64
- summary: string;
65
- };
66
- evidence: {
67
- metadataOnly: true;
68
- items: VerificationEvidenceItem[];
69
- };
70
- learningCandidates: [];
71
- }
72
-
73
- export interface VerificationCommandResult {
74
- id: string;
75
- status: "pass" | "fail" | "not-run";
76
- exitCode?: number;
77
- summary: string;
78
- rawOutputStored: false;
79
- }
80
-
81
- export type AcceptanceResultStatus = "pass" | "fail" | "not-run" | "not-applicable";
82
- export type AntiCriteriaResultStatus = "clear" | "triggered" | "unknown";
83
-
84
- export interface VerificationCriterionResult {
85
- id: string;
86
- status: VerificationStatus;
87
- summary: string;
88
- }
89
-
90
- export interface VerificationEvidenceItem {
91
- type: "command-result" | "manual-check" | "review";
92
- id: string;
93
- status: VerificationStatus;
94
- summary: string;
95
- rawOutputStored: false;
96
- sourceContentStored: false;
97
- }
98
-
99
- export interface TaskInitInput {
100
- title: string;
101
- summary?: string;
102
- projectId?: string | null;
103
- allowedPaths?: string[];
104
- allowedOperations?: string[];
105
- requiresUserConfirmation?: string[];
106
- requiredVerification?: string[];
107
- acceptanceCriteria?: Array<{
108
- id: string;
109
- category: "product" | "engineering" | "safety" | "documentation";
110
- statement: string;
111
- requiredEvidence?: string[];
112
- }>;
113
- antiCriteria?: Array<{
114
- id: string;
115
- category: "privacy" | "scope" | "safety" | "release";
116
- statement: string;
117
- }>;
118
- }
119
-
120
- export interface TaskVerificationInput {
121
- commands?: Array<{
122
- id: string;
123
- status: "pass" | "fail" | "not-run";
124
- exitCode?: number;
125
- summary?: string;
126
- }>;
127
- acceptanceResults?: Array<{
128
- id: string;
129
- status: AcceptanceResultStatus;
130
- summary?: string;
131
- }>;
132
- antiCriteriaResults?: Array<{
133
- id: string;
134
- status: AntiCriteriaResultStatus;
135
- summary?: string;
136
- }>;
137
- evidence?: Array<{
138
- type: "command-result" | "manual-check" | "review";
139
- id: string;
140
- status: VerificationStatus;
141
- summary?: string;
142
- }>;
143
- }
144
-
145
- const FORBIDDEN_TASK_PATHS = ["CLAUDE.md", "AGENTS.md", ".claude/**", ".codex/**"];
146
- const FORBIDDEN_TASK_WRITE_SEGMENTS = new Set([".claude", ".codex"]);
147
- const FORBIDDEN_PROJECT_ASSET_SEGMENTS = new Set(["packages", "src"]);
148
- const FORBIDDEN_TASK_WRITE_FILES = new Set(["agents.md", "claude.md", "package.json", "readme.md"]);
149
- const FORBIDDEN_RAW_KEYS = new Set([
150
- "rawoutput",
151
- "raw_output",
152
- "stdout",
153
- "stderr",
154
- "source",
155
- "sourcecontent",
156
- "source_content",
157
- "sourcetext",
158
- "source_text",
159
- "prompt",
160
- "prompttext",
161
- "prompt_text",
162
- "transcript",
163
- "transcripttext",
164
- "transcript_text",
165
- "secret",
166
- "secretvalue",
167
- "secret_value",
168
- ]);
169
- const ALLOWED_VERIFICATION_KEYS = new Set([
170
- "acceptanceResults",
171
- "antiCriteriaResults",
172
- "commands",
173
- "evidence",
174
- "exitCode",
175
- "id",
176
- "status",
177
- "summary",
178
- "type",
179
- ]);
180
- const SENSITIVE_TEXT_PATTERN =
181
- /https?:\/\/\S+|\b(secret|token|password|passwd|private|internal|api[_-]?key|apikey|credential|credentials|raw log|raw source|raw prompt)\b/gi;
182
-
183
- export function createTaskContract(input: TaskInitInput): TaskContract {
184
- const taskId = createTaskId(input.title);
185
- const summary = input.summary ?? input.title;
186
- const acceptanceCriteria =
187
- input.acceptanceCriteria === undefined || input.acceptanceCriteria.length === 0
188
- ? [
189
- {
190
- id: "AC1",
191
- category: "engineering" as const,
192
- statement: "Task outcome satisfies the requested target state.",
193
- requiredEvidence: ["verification-summary"],
194
- status: "not-run" as const,
195
- },
196
- ]
197
- : input.acceptanceCriteria.map((criterion) => ({
198
- id: sanitizeId(criterion.id),
199
- category: criterion.category,
200
- statement: sanitizeText(criterion.statement),
201
- requiredEvidence: uniqueSanitizedIds(criterion.requiredEvidence ?? []),
202
- status: "not-run" as const,
203
- }));
204
- const defaultAntiCriteria = [
205
- {
206
- id: "ANTI1",
207
- category: "privacy" as const,
208
- statement:
209
- "Do not collect source code, prompts, raw command output, secrets, or internal links.",
210
- status: "unknown" as const,
211
- },
212
- {
213
- id: "ANTI2",
214
- category: "scope" as const,
215
- statement: "Do not write outside approved task storage or approved implementation scope.",
216
- status: "unknown" as const,
217
- },
218
- ];
219
-
220
- return {
221
- version: 1,
222
- taskId,
223
- status: "draft",
224
- classification: "local-private",
225
- source: { summary: sanitizeText(summary), rawPromptStored: false },
226
- context: {
227
- projectId:
228
- input.projectId === undefined || input.projectId === null
229
- ? null
230
- : sanitizeText(input.projectId),
231
- relatedFiles: [],
232
- assumptions: [],
233
- openQuestions: [],
234
- },
235
- currentState: { summary: "To be completed by the task owner.", evidenceRefs: [] },
236
- targetState: { summary: sanitizeText(input.title), nonGoals: [], constraints: [] },
237
- scope: {
238
- allowedPaths: sanitizeTextList(input.allowedPaths ?? []),
239
- forbiddenPaths: FORBIDDEN_TASK_PATHS,
240
- allowedOperations:
241
- input.allowedOperations === undefined || input.allowedOperations.length === 0
242
- ? ["read", "edit-approved-files", "run-local-tests"]
243
- : sanitizeTextList(input.allowedOperations),
244
- requiresUserConfirmation: sanitizeTextList(input.requiresUserConfirmation ?? []),
245
- },
246
- acceptanceCriteria,
247
- antiCriteria: [
248
- ...defaultAntiCriteria,
249
- ...(input.antiCriteria ?? []).map((criterion) => ({
250
- id: sanitizeId(criterion.id),
251
- category: criterion.category,
252
- statement: sanitizeText(criterion.statement),
253
- status: "unknown" as const,
254
- })),
255
- ],
256
- route: {
257
- mode: null,
258
- workflowId: null,
259
- rationale: "Not routed yet.",
260
- requiredReview: [],
261
- requiredVerification: uniqueSanitizedIds(input.requiredVerification ?? []),
262
- },
263
- verification: {
264
- policy: "advisory",
265
- commands: [],
266
- acceptanceResults: [],
267
- antiCriteriaResults: [],
268
- status: "not-run",
269
- summary: "Verification has not run.",
270
- },
271
- evidence: { metadataOnly: true, items: [] },
272
- learningCandidates: [],
273
- };
274
- }
275
-
276
- export function routeTaskContract(contract: TaskContract): TaskContract {
277
- const mode = selectMode(contract);
278
- const requiredVerification = uniqueSanitizedIds([
279
- ...(contract.route.requiredVerification ?? []),
280
- "verification-summary",
281
- ]);
282
- return {
283
- ...contract,
284
- status: "routed",
285
- route: {
286
- mode,
287
- workflowId: selectWorkflowId(contract, mode),
288
- rationale: createRouteRationale(contract, mode),
289
- requiredReview: mode === "rigorous" ? ["security-boundary", "verification"] : [],
290
- requiredVerification,
291
- },
292
- };
293
- }
294
-
295
- export function verifyTaskContract(
296
- contract: TaskContract,
297
- input: TaskVerificationInput,
298
- ): { contract: TaskContract; ok: boolean; summary: string } {
299
- assertMetadataOnly(input);
300
-
301
- const commands = (input.commands ?? []).map((command) => ({
302
- id: sanitizeId(command.id),
303
- status: assertVerificationCommandStatus(command.status, `command ${command.id}`),
304
- exitCode: command.exitCode,
305
- summary: sanitizeText(command.summary ?? `${command.id}: ${command.status}`),
306
- rawOutputStored: false as const,
307
- }));
308
- const acceptanceResults = (input.acceptanceResults ?? []).map((result) => ({
309
- id: sanitizeId(result.id),
310
- status: assertAcceptanceResultStatus(result.status, `acceptance ${result.id}`),
311
- summary: sanitizeText(result.summary ?? `${result.id}: ${result.status}`),
312
- }));
313
- const antiCriteriaResults = (input.antiCriteriaResults ?? []).map((result) => ({
314
- id: sanitizeId(result.id),
315
- status: assertAntiCriteriaResultStatus(result.status, `anti-criteria ${result.id}`),
316
- summary: sanitizeText(result.summary ?? `${result.id}: ${result.status}`),
317
- }));
318
- const evidence = (input.evidence ?? []).map((item) => ({
319
- type: assertEvidenceType(item.type, `evidence ${item.id}`),
320
- id: sanitizeId(item.id),
321
- status: assertVerificationStatus(item.status, `evidence ${item.id}`),
322
- summary: sanitizeText(item.summary ?? `${item.id}: ${item.status}`),
323
- rawOutputStored: false as const,
324
- sourceContentStored: false as const,
325
- }));
326
-
327
- const failures = collectVerificationFailures(
328
- contract,
329
- commands,
330
- acceptanceResults,
331
- antiCriteriaResults,
332
- evidence,
333
- );
334
- const ok = failures.length === 0;
335
- const summary = ok ? "Verification passed." : `Verification failed: ${failures.join("; ")}`;
336
-
337
- return {
338
- ok,
339
- summary,
340
- contract: {
341
- ...contract,
342
- status: ok ? "verified" : "failed",
343
- acceptanceCriteria: contract.acceptanceCriteria.map((criterion) => ({
344
- ...criterion,
345
- status:
346
- acceptanceResults.find((result) => result.id === criterion.id)?.status ??
347
- criterion.status,
348
- })),
349
- antiCriteria: contract.antiCriteria.map((criterion) => ({
350
- ...criterion,
351
- status:
352
- antiCriteriaResults.find((result) => result.id === criterion.id)?.status ??
353
- criterion.status,
354
- })),
355
- verification: {
356
- policy: "advisory",
357
- commands,
358
- acceptanceResults,
359
- antiCriteriaResults,
360
- status: ok ? "pass" : "fail",
361
- summary,
362
- },
363
- evidence: {
364
- metadataOnly: true,
365
- items: evidence,
366
- },
367
- },
368
- };
369
- }
370
-
371
- export async function readTaskContract(path: string): Promise<TaskContract> {
372
- return parseTaskContract(JSON.parse(await readFile(path, "utf8")));
373
- }
374
-
375
- export async function writeTaskContract(
376
- path: string,
377
- contract: TaskContract,
378
- options: { overwrite?: boolean } = {},
379
- ): Promise<void> {
380
- await assertTaskContractWritePathAllowed(path);
381
- await mkdir(dirname(path), { recursive: true });
382
- await writeFile(path, `${JSON.stringify(contract, null, 2)}\n`, {
383
- encoding: "utf8",
384
- flag: options.overwrite === true ? "w" : "wx",
385
- });
386
- }
387
-
388
- export async function resolveTaskContractOutputPath(input: {
389
- taskId: string;
390
- outputDir?: string;
391
- projectDir?: string;
392
- }): Promise<string> {
393
- if (input.outputDir !== undefined) {
394
- const outputPath = join(input.outputDir, input.taskId, "contract.json");
395
- await assertTaskContractWritePathAllowed(outputPath);
396
- return outputPath;
397
- }
398
-
399
- if (input.projectDir !== undefined) {
400
- const projectContextPath = join(input.projectDir, ".evodev", "project.json");
401
- if (!(await pathExists(projectContextPath))) {
402
- throw new Error(
403
- "Project mode requires existing .evodev/project.json; use --output-dir instead.",
404
- );
405
- }
406
- const outputPath = join(input.projectDir, ".evodev", "tasks", input.taskId, "contract.json");
407
- await assertTaskContractWritePathAllowed(outputPath);
408
- return outputPath;
409
- }
410
-
411
- throw new Error(
412
- "Task writes require --output-dir or --project-dir with existing project context.",
413
- );
414
- }
415
-
416
- export function formatTaskContract(contract: TaskContract): string {
417
- return [
418
- "EvoDev task contract",
419
- "",
420
- `Task id: ${contract.taskId}`,
421
- `Status: ${contract.status}`,
422
- `Summary: ${contract.source.summary}`,
423
- `Mode: ${contract.route.mode ?? "not routed"}`,
424
- `Workflow: ${contract.route.workflowId ?? "not routed"}`,
425
- `Required verification: ${formatList(contract.route.requiredVerification)}`,
426
- `Required review: ${formatList(contract.route.requiredReview)}`,
427
- `Route rationale: ${contract.route.rationale}`,
428
- `Verification: ${contract.verification.status}`,
429
- `Verification summary: ${contract.verification.summary}`,
430
- ].join("\n");
431
- }
432
-
433
- function selectMode(contract: TaskContract): TaskExecutionMode {
434
- if (selectRigorousTrigger(contract) !== null) {
435
- return "rigorous";
436
- }
437
- const allowedPaths = contract.scope.allowedPaths ?? [];
438
- if (allowedPaths.length <= 1 && contract.acceptanceCriteria.length <= 1) {
439
- return "minimal";
440
- }
441
- return "standard";
442
- }
443
-
444
- function selectWorkflowId(contract: TaskContract, mode: TaskExecutionMode): string {
445
- const text = collectRouteText(contract);
446
- if (text.includes("release") || text.includes("publish")) return "rd-release-readiness";
447
- if (hasSecurityBoundaryTerms(text) || mode === "rigorous") {
448
- return "rd-security-boundary-review";
449
- }
450
- if (text.includes("bug")) return "rd-bug-fix";
451
- if (text.includes("refactor")) return "rd-refactor";
452
- if (text.includes("review")) return "rd-code-review";
453
- if (text.includes("doc")) return "rd-docs-update";
454
- if (text.includes("test")) return "rd-test-generation";
455
- if (mode === "minimal") return "rd-docs-update";
456
- return "rd-feature-implementation";
457
- }
458
-
459
- function collectRouteText(contract: TaskContract): string {
460
- return [
461
- contract.taskId,
462
- contract.source.summary,
463
- contract.currentState.summary,
464
- ...contract.currentState.evidenceRefs,
465
- contract.targetState.summary,
466
- ...contract.targetState.nonGoals,
467
- ...contract.targetState.constraints,
468
- ...contract.context.relatedFiles.map((file) => `${file.path} ${file.reason}`),
469
- ...contract.context.assumptions,
470
- ...contract.context.openQuestions,
471
- ...(contract.scope.allowedPaths ?? []),
472
- ...(contract.scope.allowedOperations ?? []),
473
- ...(contract.scope.requiresUserConfirmation ?? []),
474
- ...contract.acceptanceCriteria.map((criterion) => criterion.statement),
475
- ...contract.acceptanceCriteria.flatMap((criterion) => criterion.requiredEvidence),
476
- ...(contract.route.requiredVerification ?? []),
477
- ]
478
- .join(" ")
479
- .toLowerCase();
480
- }
481
-
482
- function createRouteRationale(contract: TaskContract, mode: TaskExecutionMode): string {
483
- const trigger = selectRigorousTrigger(contract);
484
- if (mode === "rigorous") return `Selected rigorous due to ${trigger ?? "high-risk"} trigger.`;
485
- if (mode === "minimal")
486
- return "Selected minimal for narrow scope and simple acceptance criteria.";
487
- return "Selected standard for bounded engineering work requiring Task Contract verification.";
488
- }
489
-
490
- function selectRigorousTrigger(contract: TaskContract): string | null {
491
- const text = collectRouteText(contract);
492
- if (text.includes("[redacted]") || hasSecurityBoundaryTerms(text)) return "privacy/security";
493
- if (/\b(release|publish|publishing|package distribution|npm publish)\b/.test(text)) {
494
- return "release/publish";
495
- }
496
- if (/\b(hook|hooks|learning|memory|telemetry|observability)\b/.test(text)) {
497
- return "hook/learning/telemetry";
498
- }
499
- if (
500
- /\b(migration|migrate|hard to rollback|hard-to-rollback|irreversible|destructive)\b/.test(text)
501
- ) {
502
- return "hard-to-rollback";
503
- }
504
- if (hasHighRiskOperationTerms(text)) {
505
- return "high-risk operation";
506
- }
507
- if ((contract.scope.requiresUserConfirmation ?? []).length > 0) {
508
- return "explicit user confirmation";
509
- }
510
- for (const path of contract.scope.allowedPaths ?? []) {
511
- const trigger = selectRigorousPathTrigger(path);
512
- if (trigger !== null) return trigger;
513
- }
514
- for (const operation of contract.scope.allowedOperations ?? []) {
515
- if (hasHighRiskOperationTerms(operation)) return "high-risk operation";
516
- }
517
- return null;
518
- }
519
-
520
- function hasSecurityBoundaryTerms(text: string): boolean {
521
- return /\b(security|privacy|private data|auth|authentication|authorization|credential|credentials|secret|secrets|token|tokens|api key|api-key|apikey|password|passwd)\b/.test(
522
- text,
523
- );
524
- }
525
-
526
- function selectRigorousPathTrigger(path: string): string | null {
527
- const normalized = path.trim().replace(/\\/g, "/").toLowerCase();
528
- if (/^~\/\.(evodev|claude|codex)(\/|$)/.test(normalized)) {
529
- return "user-level Code Agent/EvoDev path";
530
- }
531
- if (
532
- normalized === ".evodev" ||
533
- normalized.startsWith(".evodev/") ||
534
- normalized.includes("/.evodev/")
535
- ) {
536
- return "project .evodev path";
537
- }
538
- if (
539
- normalized === ".claude" ||
540
- normalized.startsWith(".claude/") ||
541
- normalized.includes("/.claude/") ||
542
- normalized === ".codex" ||
543
- normalized.startsWith(".codex/") ||
544
- normalized.includes("/.codex/")
545
- ) {
546
- return "project Code Agent config path";
547
- }
548
- const fileName = basename(normalized);
549
- if (fileName === "claude.md" || fileName === "agents.md") return "agent instruction file";
550
- return null;
551
- }
552
-
553
- function hasHighRiskOperationTerms(text: string): boolean {
554
- const normalized = text.toLowerCase().replace(/[_-]+/g, " ");
555
- return /\b(delete|remove|publish|release|install|uninstall|network|external|external service|external api|write user config|user config|write project context|project context|hook|learning|memory|telemetry)\b/.test(
556
- normalized,
557
- );
558
- }
559
-
560
- function collectVerificationFailures(
561
- contract: TaskContract,
562
- commands: VerificationCommandResult[],
563
- acceptanceResults: VerificationCriterionResult[],
564
- antiCriteriaResults: VerificationCriterionResult[],
565
- evidence: VerificationEvidenceItem[],
566
- ): string[] {
567
- const failures: string[] = [];
568
-
569
- if (!isVerificationReady(contract)) {
570
- failures.push("task contract must be routed before verification");
571
- }
572
-
573
- failures.push(...collectDuplicateIdFailures("command", commands));
574
- failures.push(...collectDuplicateIdFailures("evidence", evidence));
575
- failures.push(...collectDuplicateIdFailures("acceptance result", acceptanceResults));
576
- failures.push(...collectDuplicateIdFailures("anti-criteria result", antiCriteriaResults));
577
-
578
- for (const requiredId of contract.route.requiredVerification ?? []) {
579
- const matches = [
580
- ...commands.filter((command) => command.id === requiredId),
581
- ...evidence.filter((item) => item.id === requiredId),
582
- ];
583
- if (matches.length === 0) {
584
- failures.push(`missing required verification: ${requiredId}`);
585
- continue;
586
- }
587
- if (matches.some((item) => !isSatisfyingVerificationItem(item))) {
588
- failures.push(`required verification ${requiredId} is not satisfied`);
589
- }
590
- }
591
-
592
- for (const criterion of contract.acceptanceCriteria) {
593
- const result = acceptanceResults.find((candidate) => candidate.id === criterion.id);
594
- if (result === undefined || (result.status !== "pass" && result.status !== "not-applicable")) {
595
- failures.push(`acceptance ${criterion.id} is not satisfied`);
596
- }
597
- for (const evidenceId of criterion.requiredEvidence) {
598
- const matches = evidence.filter((item) => item.id === evidenceId);
599
- if (matches.length === 0) {
600
- failures.push(`missing evidence for ${criterion.id}: ${evidenceId}`);
601
- continue;
602
- }
603
- if (matches.some((item) => !isSatisfyingEvidenceItem(item))) {
604
- failures.push(`evidence for ${criterion.id} is not satisfied: ${evidenceId}`);
605
- }
606
- }
607
- }
608
-
609
- for (const criterion of contract.antiCriteria) {
610
- const result = antiCriteriaResults.find((candidate) => candidate.id === criterion.id);
611
- if (result === undefined || result.status === "unknown" || result.status === "triggered") {
612
- failures.push(`anti-criteria ${criterion.id} is not clear`);
613
- }
614
- }
615
-
616
- return failures;
617
- }
618
-
619
- function collectDuplicateIdFailures(label: string, items: Array<{ id: string }>): string[] {
620
- const seen = new Set<string>();
621
- const duplicates = new Set<string>();
622
- for (const item of items) {
623
- if (seen.has(item.id)) duplicates.add(item.id);
624
- seen.add(item.id);
625
- }
626
- return Array.from(duplicates).map((id) => `duplicate ${label} id: ${id}`);
627
- }
628
-
629
- function isSatisfyingVerificationItem(
630
- item: VerificationCommandResult | VerificationEvidenceItem,
631
- ): boolean {
632
- if ("rawOutputStored" in item && "type" in item) return isSatisfyingEvidenceItem(item);
633
- return item.status === "pass";
634
- }
635
-
636
- function isSatisfyingEvidenceItem(item: VerificationEvidenceItem): boolean {
637
- if (item.type === "command-result") return item.status === "pass";
638
- return item.status === "pass" || item.status === "clear";
639
- }
640
-
641
- function isVerificationReady(contract: TaskContract): boolean {
642
- return (
643
- contract.status !== "draft" &&
644
- contract.route.mode !== null &&
645
- contract.route.workflowId !== null &&
646
- (contract.route.requiredVerification ?? []).length > 0
647
- );
648
- }
649
-
650
- function parseTaskContract(value: unknown): TaskContract {
651
- if (!isRecord(value) || value.version !== 1 || typeof value.taskId !== "string") {
652
- throw new Error("Invalid Task Contract JSON.");
653
- }
654
- return value as unknown as TaskContract;
655
- }
656
-
657
- function assertMetadataOnly(value: unknown): void {
658
- if (Array.isArray(value)) {
659
- for (const item of value) assertMetadataOnly(item);
660
- return;
661
- }
662
- if (!isRecord(value)) {
663
- if (typeof value === "string" && containsSensitiveText(value)) {
664
- throw new Error("Verification input contains sensitive content in metadata field.");
665
- }
666
- return;
667
- }
668
-
669
- for (const [key, child] of Object.entries(value)) {
670
- const normalizedKey = key.toLowerCase().replace(/[^a-z0-9]/g, "");
671
- if (FORBIDDEN_RAW_KEYS.has(key) || FORBIDDEN_RAW_KEYS.has(normalizedKey)) {
672
- throw new Error(`Verification input contains forbidden raw field: ${key}`);
673
- }
674
- if (!ALLOWED_VERIFICATION_KEYS.has(key)) {
675
- throw new Error(`Verification input contains unsupported field: ${key}`);
676
- }
677
- assertMetadataOnly(child);
678
- }
679
- }
680
-
681
- function assertVerificationCommandStatus(
682
- value: unknown,
683
- label: string,
684
- ): VerificationCommandResult["status"] {
685
- if (value === "pass" || value === "fail" || value === "not-run") return value;
686
- throw new Error(`Invalid verification command status for ${label}: ${String(value)}`);
687
- }
688
-
689
- function assertAcceptanceResultStatus(value: unknown, label: string): AcceptanceResultStatus {
690
- if (value === "pass" || value === "fail" || value === "not-run" || value === "not-applicable") {
691
- return value;
692
- }
693
- throw new Error(`Invalid acceptance result status for ${label}: ${String(value)}`);
694
- }
695
-
696
- function assertAntiCriteriaResultStatus(value: unknown, label: string): AntiCriteriaResultStatus {
697
- if (value === "clear" || value === "triggered" || value === "unknown") return value;
698
- throw new Error(`Invalid anti-criteria result status for ${label}: ${String(value)}`);
699
- }
700
-
701
- function assertVerificationStatus(value: unknown, label: string): VerificationStatus {
702
- if (
703
- value === "pass" ||
704
- value === "fail" ||
705
- value === "not-run" ||
706
- value === "not-applicable" ||
707
- value === "unknown" ||
708
- value === "triggered" ||
709
- value === "clear"
710
- ) {
711
- return value;
712
- }
713
- throw new Error(`Invalid verification status for ${label}: ${String(value)}`);
714
- }
715
-
716
- function assertEvidenceType(value: unknown, label: string): VerificationEvidenceItem["type"] {
717
- if (value === "command-result" || value === "manual-check" || value === "review") return value;
718
- throw new Error(`Invalid evidence type for ${label}: ${String(value)}`);
719
- }
720
-
721
- function sanitizeText(value: string): string {
722
- return value.replace(SENSITIVE_TEXT_PATTERN, "[redacted]").slice(0, 500);
723
- }
724
-
725
- function containsSensitiveText(value: string): boolean {
726
- SENSITIVE_TEXT_PATTERN.lastIndex = 0;
727
- return SENSITIVE_TEXT_PATTERN.test(value);
728
- }
729
-
730
- async function assertTaskContractWritePathAllowed(path: string): Promise<void> {
731
- assertTaskContractWritePathSegmentsAllowed(resolve(path));
732
- assertTaskContractWritePathSegmentsAllowed(await resolveTaskWriteRealPath(path));
733
- }
734
-
735
- function assertTaskContractWritePathSegmentsAllowed(path: string): void {
736
- const segments = path
737
- .replace(/\\/g, "/")
738
- .split("/")
739
- .filter((segment) => segment.length > 0)
740
- .map((segment) => segment.toLowerCase());
741
- const protectedSegment = segments.find((segment) => FORBIDDEN_TASK_WRITE_SEGMENTS.has(segment));
742
- if (protectedSegment !== undefined) {
743
- throw new Error(`Task contract write path is protected: ${protectedSegment}`);
744
- }
745
- if (!isTaskStoragePath(segments)) {
746
- const protectedProjectAssetSegment = segments.find((segment) =>
747
- FORBIDDEN_PROJECT_ASSET_SEGMENTS.has(segment),
748
- );
749
- if (protectedProjectAssetSegment !== undefined) {
750
- throw new Error(`Task contract write path is protected: ${protectedProjectAssetSegment}`);
751
- }
752
- }
753
- const protectedFile = segments.find((segment) => FORBIDDEN_TASK_WRITE_FILES.has(segment));
754
- if (protectedFile !== undefined) {
755
- throw new Error(`Task contract write path is protected: ${protectedFile}`);
756
- }
757
- }
758
-
759
- async function resolveTaskWriteRealPath(path: string): Promise<string> {
760
- let currentPath = resolve(path);
761
- const missingSegments: string[] = [];
762
-
763
- while (true) {
764
- try {
765
- await lstat(currentPath);
766
- return join(await realpath(currentPath), ...missingSegments.reverse());
767
- } catch (error) {
768
- if (!isMissingPathError(error)) throw error;
769
- const parentPath = dirname(currentPath);
770
- if (parentPath === currentPath) return resolve(path);
771
- missingSegments.push(basename(currentPath));
772
- currentPath = parentPath;
773
- }
774
- }
775
- }
776
-
777
- function isTaskStoragePath(segments: string[]): boolean {
778
- return segments.some(
779
- (segment, index) =>
780
- segment === ".evodev" &&
781
- (segments[index + 1] === "tasks" ||
782
- (segments[index + 1] === "state" && segments[index + 2] === "tasks")),
783
- );
784
- }
785
-
786
- function sanitizeTextList(values: string[]): string[] {
787
- return values.map((value) => sanitizeText(value)).filter((value) => value.length > 0);
788
- }
789
-
790
- function sanitizeId(value: string): string {
791
- const sanitized = sanitizeText(value)
792
- .replace(/\[redacted\]/gi, "redacted")
793
- .replace(/[^a-zA-Z0-9._-]/g, "-")
794
- .replace(/-+/g, "-")
795
- .replace(/^-+|-+$/g, "")
796
- .slice(0, 80);
797
- return sanitized || "item";
798
- }
799
-
800
- function uniqueSanitizedIds(values: string[]): string[] {
801
- return Array.from(new Set(values.map((value) => sanitizeId(value))));
802
- }
803
-
804
- function formatList(values: string[]): string {
805
- return values.length === 0 ? "none" : values.join(", ");
806
- }
807
-
808
- function createTaskId(title: string): string {
809
- const slug = sanitizeId(title.toLowerCase()) || "task";
810
- return `task-${slug}`.slice(0, 80);
811
- }
812
-
813
- async function pathExists(path: string): Promise<boolean> {
814
- try {
815
- await stat(path);
816
- return true;
817
- } catch (error) {
818
- if (
819
- error instanceof Error &&
820
- "code" in error &&
821
- (error as NodeJS.ErrnoException).code === "ENOENT"
822
- ) {
823
- return false;
824
- }
825
- throw error;
826
- }
827
- }
828
-
829
- function isMissingPathError(error: unknown): boolean {
830
- return (
831
- error instanceof Error &&
832
- "code" in error &&
833
- ((error as NodeJS.ErrnoException).code === "ENOENT" ||
834
- (error as NodeJS.ErrnoException).code === "ENOTDIR")
835
- );
836
- }
837
-
838
- function isRecord(value: unknown): value is Record<string, unknown> {
839
- return typeof value === "object" && value !== null && !Array.isArray(value);
840
- }