@mutagent/evaluator 0.1.0-alpha.2

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 (126) hide show
  1. package/.claude/skills/mutagent-evaluator/SKILL.md +538 -0
  2. package/.claude/skills/mutagent-evaluator/assets/agents/audit-executor.md +169 -0
  3. package/.claude/skills/mutagent-evaluator/assets/agents/dataset-builder.md +160 -0
  4. package/.claude/skills/mutagent-evaluator/assets/agents/discovery.md +425 -0
  5. package/.claude/skills/mutagent-evaluator/assets/agents/evaluator.md +1044 -0
  6. package/.claude/skills/mutagent-evaluator/assets/brand/theme.css +213 -0
  7. package/.claude/skills/mutagent-evaluator/assets/brand/wordmark.html +9 -0
  8. package/.claude/skills/mutagent-evaluator/lenses/context-flow-lens.md +85 -0
  9. package/.claude/skills/mutagent-evaluator/lenses/data-lens.md +47 -0
  10. package/.claude/skills/mutagent-evaluator/lenses/decision-lens.md +48 -0
  11. package/.claude/skills/mutagent-evaluator/lenses/methodology-critic-lens.md +53 -0
  12. package/.claude/skills/mutagent-evaluator/lenses/trajectory-lens.md +44 -0
  13. package/.claude/skills/mutagent-evaluator/references/build-review-interface.md +83 -0
  14. package/.claude/skills/mutagent-evaluator/references/edd-loop.md +134 -0
  15. package/.claude/skills/mutagent-evaluator/references/error-analysis.md +113 -0
  16. package/.claude/skills/mutagent-evaluator/references/eval-audit.md +154 -0
  17. package/.claude/skills/mutagent-evaluator/references/eval-stage.md +168 -0
  18. package/.claude/skills/mutagent-evaluator/references/generate-synthetic-data.md +81 -0
  19. package/.claude/skills/mutagent-evaluator/references/grounded-adjudication.md +221 -0
  20. package/.claude/skills/mutagent-evaluator/references/memory-format.md +65 -0
  21. package/.claude/skills/mutagent-evaluator/references/methodology.md +201 -0
  22. package/.claude/skills/mutagent-evaluator/references/operation-inventory.md +196 -0
  23. package/.claude/skills/mutagent-evaluator/references/validate-evaluator.md +125 -0
  24. package/.claude/skills/mutagent-evaluator/references/workflows/orchestrator-protocol.md +287 -0
  25. package/.claude/skills/mutagent-evaluator/references/write-judge-prompt.md +123 -0
  26. package/.claude/skills/mutagent-evaluator/schemas/behavior-tree.schema.yaml +73 -0
  27. package/.claude/skills/mutagent-evaluator/schemas/dataset.schema.yaml +66 -0
  28. package/.claude/skills/mutagent-evaluator/schemas/edd-change-request.schema.yaml +114 -0
  29. package/.claude/skills/mutagent-evaluator/schemas/eval-matrix.schema.yaml +74 -0
  30. package/.claude/skills/mutagent-evaluator/schemas/flow-graph.schema.yaml +69 -0
  31. package/.claude/skills/mutagent-evaluator/schemas/flow-profile.schema.yaml +49 -0
  32. package/.claude/skills/mutagent-evaluator/schemas/methodology-review.schema.yaml +40 -0
  33. package/.claude/skills/mutagent-evaluator/schemas/scorecard.schema.yaml +85 -0
  34. package/.claude/skills/mutagent-evaluator/scripts/agent-dispatch.ts +0 -0
  35. package/.claude/skills/mutagent-evaluator/scripts/aggregate-discover.ts +543 -0
  36. package/.claude/skills/mutagent-evaluator/scripts/artifact-paths.ts +99 -0
  37. package/.claude/skills/mutagent-evaluator/scripts/assemble-scorecard.ts +172 -0
  38. package/.claude/skills/mutagent-evaluator/scripts/build-dataset.ts +186 -0
  39. package/.claude/skills/mutagent-evaluator/scripts/build-evals.ts +93 -0
  40. package/.claude/skills/mutagent-evaluator/scripts/build-review-ui.ts +393 -0
  41. package/.claude/skills/mutagent-evaluator/scripts/check-method-router.ts +170 -0
  42. package/.claude/skills/mutagent-evaluator/scripts/cli/aggregate.ts +112 -0
  43. package/.claude/skills/mutagent-evaluator/scripts/cli/audit-run.ts +175 -0
  44. package/.claude/skills/mutagent-evaluator/scripts/cli/doctor.ts +211 -0
  45. package/.claude/skills/mutagent-evaluator/scripts/cli/dogfood.ts +133 -0
  46. package/.claude/skills/mutagent-evaluator/scripts/cli/init.ts +601 -0
  47. package/.claude/skills/mutagent-evaluator/scripts/cli/methodology-review.ts +122 -0
  48. package/.claude/skills/mutagent-evaluator/scripts/cli/prep.ts +279 -0
  49. package/.claude/skills/mutagent-evaluator/scripts/cli/profile-subject.ts +165 -0
  50. package/.claude/skills/mutagent-evaluator/scripts/cli/run.sh +56 -0
  51. package/.claude/skills/mutagent-evaluator/scripts/cli/variance-check.ts +105 -0
  52. package/.claude/skills/mutagent-evaluator/scripts/code-eval.ts +248 -0
  53. package/.claude/skills/mutagent-evaluator/scripts/codegen-evals.ts +173 -0
  54. package/.claude/skills/mutagent-evaluator/scripts/cold-start-project.ts +151 -0
  55. package/.claude/skills/mutagent-evaluator/scripts/cold-start-sampler.ts +267 -0
  56. package/.claude/skills/mutagent-evaluator/scripts/config/load.ts +307 -0
  57. package/.claude/skills/mutagent-evaluator/scripts/config/schema.ts +325 -0
  58. package/.claude/skills/mutagent-evaluator/scripts/contracts/agentspec-evals.ts +85 -0
  59. package/.claude/skills/mutagent-evaluator/scripts/contracts/dataset.ts +149 -0
  60. package/.claude/skills/mutagent-evaluator/scripts/contracts/eval-engine.ts +118 -0
  61. package/.claude/skills/mutagent-evaluator/scripts/contracts/eval-matrix.ts +577 -0
  62. package/.claude/skills/mutagent-evaluator/scripts/contracts/eval-types.ts +1074 -0
  63. package/.claude/skills/mutagent-evaluator/scripts/contracts/flow-graph.ts +194 -0
  64. package/.claude/skills/mutagent-evaluator/scripts/contracts/types.ts +303 -0
  65. package/.claude/skills/mutagent-evaluator/scripts/contracts/validation.ts +193 -0
  66. package/.claude/skills/mutagent-evaluator/scripts/derive-dataset.ts +152 -0
  67. package/.claude/skills/mutagent-evaluator/scripts/determine-outcome.ts +219 -0
  68. package/.claude/skills/mutagent-evaluator/scripts/diff-discriminate.ts +160 -0
  69. package/.claude/skills/mutagent-evaluator/scripts/discover-criteria.ts +348 -0
  70. package/.claude/skills/mutagent-evaluator/scripts/edd/change-request.ts +232 -0
  71. package/.claude/skills/mutagent-evaluator/scripts/edd/edd-types.ts +210 -0
  72. package/.claude/skills/mutagent-evaluator/scripts/edd/variance-gate.ts +186 -0
  73. package/.claude/skills/mutagent-evaluator/scripts/emit-completeness.ts +111 -0
  74. package/.claude/skills/mutagent-evaluator/scripts/eval-engine.ts +160 -0
  75. package/.claude/skills/mutagent-evaluator/scripts/evaluate.ts +333 -0
  76. package/.claude/skills/mutagent-evaluator/scripts/flow-graph.ts +230 -0
  77. package/.claude/skills/mutagent-evaluator/scripts/judge-prompt-template.ts +253 -0
  78. package/.claude/skills/mutagent-evaluator/scripts/judge-provider.ts +135 -0
  79. package/.claude/skills/mutagent-evaluator/scripts/lint-grounding.ts +229 -0
  80. package/.claude/skills/mutagent-evaluator/scripts/lint-uniformity.ts +168 -0
  81. package/.claude/skills/mutagent-evaluator/scripts/living-suite.ts +109 -0
  82. package/.claude/skills/mutagent-evaluator/scripts/load-bundle.ts +203 -0
  83. package/.claude/skills/mutagent-evaluator/scripts/load-profile-vocab.ts +64 -0
  84. package/.claude/skills/mutagent-evaluator/scripts/load-profile.ts +106 -0
  85. package/.claude/skills/mutagent-evaluator/scripts/mask.ts +138 -0
  86. package/.claude/skills/mutagent-evaluator/scripts/materialize-dataset.ts +113 -0
  87. package/.claude/skills/mutagent-evaluator/scripts/matrix-judge.ts +750 -0
  88. package/.claude/skills/mutagent-evaluator/scripts/memory/append.ts +215 -0
  89. package/.claude/skills/mutagent-evaluator/scripts/memory/read.ts +168 -0
  90. package/.claude/skills/mutagent-evaluator/scripts/prep-tasks.ts +125 -0
  91. package/.claude/skills/mutagent-evaluator/scripts/profile-subject.ts +310 -0
  92. package/.claude/skills/mutagent-evaluator/scripts/publish-report.ts +131 -0
  93. package/.claude/skills/mutagent-evaluator/scripts/read-unitf-traces.ts +81 -0
  94. package/.claude/skills/mutagent-evaluator/scripts/render-build-cards.ts +195 -0
  95. package/.claude/skills/mutagent-evaluator/scripts/render-discover-report.ts +1640 -0
  96. package/.claude/skills/mutagent-evaluator/scripts/render-eval-report.ts +3823 -0
  97. package/.claude/skills/mutagent-evaluator/scripts/render-report.ts +212 -0
  98. package/.claude/skills/mutagent-evaluator/scripts/resolve-credential.ts +110 -0
  99. package/.claude/skills/mutagent-evaluator/scripts/resolve-ref.ts +98 -0
  100. package/.claude/skills/mutagent-evaluator/scripts/result-verify.ts +129 -0
  101. package/.claude/skills/mutagent-evaluator/scripts/route-failures.ts +320 -0
  102. package/.claude/skills/mutagent-evaluator/scripts/run-deterministic.ts +271 -0
  103. package/.claude/skills/mutagent-evaluator/scripts/run-evaluate.ts +715 -0
  104. package/.claude/skills/mutagent-evaluator/scripts/run-judge.ts +155 -0
  105. package/.claude/skills/mutagent-evaluator/scripts/run-pipeline.ts +175 -0
  106. package/.claude/skills/mutagent-evaluator/scripts/sample-traces.ts +210 -0
  107. package/.claude/skills/mutagent-evaluator/scripts/self-audit.ts +387 -0
  108. package/.claude/skills/mutagent-evaluator/scripts/source-map.ts +106 -0
  109. package/.claude/skills/mutagent-evaluator/scripts/subject-profile.ts +134 -0
  110. package/.claude/skills/mutagent-evaluator/scripts/substrate.ts +162 -0
  111. package/.claude/skills/mutagent-evaluator/scripts/ui-slots.ts +119 -0
  112. package/.claude/skills/mutagent-evaluator/scripts/unitf-to-evaltrace.ts +284 -0
  113. package/.claude/skills/mutagent-evaluator/scripts/validate-judge.ts +358 -0
  114. package/.claude/skills/mutagent-evaluator/scripts/variance-compare.ts +177 -0
  115. package/.claude/skills/mutagent-evaluator/subjects/mutagent-diagnostics/behavior-tree.yaml +140 -0
  116. package/.claude/skills/mutagent-evaluator/subjects/mutagent-diagnostics/eval-matrix.yaml +1270 -0
  117. package/.claude/skills/mutagent-evaluator/subjects/mutagent-diagnostics/methodology-review.yaml +105 -0
  118. package/.claude/skills/mutagent-evaluator/workflows/audit.workflow.js +82 -0
  119. package/.claude/skills/mutagent-evaluator/workflows/data-leak.workflow.js +236 -0
  120. package/.claude/skills/mutagent-evaluator/workflows/variance.workflow.js +163 -0
  121. package/LICENSE +201 -0
  122. package/NOTICE +23 -0
  123. package/README.md +90 -0
  124. package/bin/mutagent-cli.mjs +9263 -0
  125. package/bin/mutagent-evaluator.mjs +96 -0
  126. package/package.json +52 -0
@@ -0,0 +1,215 @@
1
+ /**
2
+ * scripts/memory/append.ts — project-level AutoMemory: APPEND (dated + classified).
3
+ * ---------------------------------------------------------------------------
4
+ * Append an operator-feedback entry to the project-level AutoMemory store
5
+ * (`.mutagent/memory/`) in the Claude-Code AutoMemory format
6
+ * (`references/memory-format.md`). The SUBJECT is the TOOL + operator preferences
7
+ * — NOT the evaluated agent (subject findings go to the living-suite, never here).
8
+ *
9
+ * The store: one FACT per `<slug>.md` file + an index `MEMORY.md`
10
+ * (`- [Title](<slug>.md) — <hook>` one line/entry). Every entry is DATED
11
+ * (`metadata.created`) + lifecycle-tagged. DEDUPE on append: an entry with the
12
+ * same slug is UPDATED (body + refreshed date), never duplicated.
13
+ *
14
+ * Determinism (coding-rules): the CORES are PURE — `now` (the date) is INJECTED,
15
+ * never read from a clock; fs is done only in the thin writer at the bottom. Tests
16
+ * exercise the pure cores with a fixed `now`. Standalone: no cross-skill import
17
+ * (mirrors the diagnostics library-store APPEND DISCIPLINE, not its per-entity
18
+ * keying — this store is flat + tool-scoped).
19
+ */
20
+ import { existsSync, mkdirSync, readdirSync, readFileSync, writeFileSync } from "node:fs";
21
+ import { join } from "node:path";
22
+
23
+ // ── Categorical constants (no magic strings) ─────────────────────────────────
24
+
25
+ /** Claude Code's four memory types (the classification target). */
26
+ export const MemoryType = {
27
+ User: "user",
28
+ Feedback: "feedback",
29
+ Project: "project",
30
+ Reference: "reference",
31
+ } as const;
32
+ export type MemoryTypeValue = (typeof MemoryType)[keyof typeof MemoryType];
33
+
34
+ /** The ADL lifecycle tag (the recall filter axis). */
35
+ export const Lifecycle = {
36
+ Spec: "spec",
37
+ Build: "build",
38
+ Evaluate: "evaluate",
39
+ Diagnose: "diagnose",
40
+ Improve: "improve",
41
+ General: "general",
42
+ } as const;
43
+ export type LifecycleValue = (typeof Lifecycle)[keyof typeof Lifecycle];
44
+
45
+ // ── Classification (the memory-format.md rubric — first match wins) ───────────
46
+
47
+ /** The raw operator feedback to classify + persist. */
48
+ export interface FeedbackInput {
49
+ /** the fact to store (verbatim operator text or a distilled summary). */
50
+ text: string;
51
+ /** optional caller hint (a URL / path) — strengthens the `reference` signal. */
52
+ resourceHint?: string;
53
+ }
54
+
55
+ /**
56
+ * Classify feedback into one of the four Claude-Code types by the memory-format.md
57
+ * decision order (FIRST match wins): reference → user → feedback → project. PURE,
58
+ * deterministic — regex + explicit hint, no I/O, no LLM. A caller may OVERRIDE the
59
+ * classification explicitly (an operator who knows the type); this is the default.
60
+ */
61
+ export function classifyFeedback(fb: FeedbackInput): MemoryTypeValue {
62
+ const t = fb.text.trim();
63
+ // 1 — external resource (URL / dashboard / ticket / dataset path).
64
+ if (
65
+ fb.resourceHint !== undefined ||
66
+ /\bhttps?:\/\/\S+/i.test(t) ||
67
+ /\b(?:dashboard|ticket|dataset|\S+\.(?:ndjson|jsonl|csv|parquet)(?:\.gz)?)\b/i.test(t)
68
+ ) {
69
+ return MemoryType.Reference;
70
+ }
71
+ // 2 — who the operator is (identity / standing preference about themselves).
72
+ if (/\bI['’]?m\b|\bI am\b|\bmy (?:role|team|preference)\b|\bas the (?:lead|operator|owner)\b/i.test(t)) {
73
+ return MemoryType.User;
74
+ }
75
+ // 3 — how the TOOL should behave (behavioral steer).
76
+ if (/\b(?:stop|don['’]?t|do not|too verbose|next time|always|never|prefer)\b/i.test(t)) {
77
+ return MemoryType.Feedback;
78
+ }
79
+ // 4 — ongoing work / goals / constraints (default).
80
+ return MemoryType.Project;
81
+ }
82
+
83
+ // ── Slug + entry formatting ───────────────────────────────────────────────────
84
+
85
+ /** Kebab-case a title into a stable slug (deterministic — no random). */
86
+ export function slugify(title: string): string {
87
+ const s = title
88
+ .toLowerCase()
89
+ .replace(/['’"]/g, "")
90
+ .replace(/[^a-z0-9]+/g, "-")
91
+ .replace(/^-+|-+$/g, "");
92
+ return s === "" ? "untitled" : s;
93
+ }
94
+
95
+ export interface MemoryEntry {
96
+ /** kebab-case slug (the file name stem + `name:` frontmatter). */
97
+ slug: string;
98
+ /** one-line description (recall relevance). */
99
+ description: string;
100
+ type: MemoryTypeValue;
101
+ lifecycle: LifecycleValue;
102
+ /** YYYY-MM-DD (INJECTED — never a live clock). */
103
+ created: string;
104
+ /** the fact body (feedback/project carry **Why:** + **How to apply:** lines). */
105
+ body: string;
106
+ }
107
+
108
+ const DATE_RE = /^\d{4}-\d{2}-\d{2}$/;
109
+
110
+ /** Render a MemoryEntry to its `<slug>.md` file text (Claude-Code AutoMemory format). */
111
+ export function renderEntryMarkdown(entry: MemoryEntry): string {
112
+ if (!DATE_RE.test(entry.created)) {
113
+ throw new Error(
114
+ `renderEntryMarkdown: created must be YYYY-MM-DD (got '${entry.created}'). Every memory is DATED — ` +
115
+ "inject `now`, never read a clock.",
116
+ );
117
+ }
118
+ return (
119
+ "---\n" +
120
+ `name: ${entry.slug}\n` +
121
+ `description: ${entry.description}\n` +
122
+ "metadata:\n" +
123
+ ` type: ${entry.type}\n` +
124
+ ` lifecycle: ${entry.lifecycle}\n` +
125
+ ` created: ${entry.created}\n` +
126
+ "---\n\n" +
127
+ entry.body.trimEnd() +
128
+ "\n"
129
+ );
130
+ }
131
+
132
+ /** The one index line for an entry (`- [Title](<slug>.md) — <hook>`). */
133
+ export function indexLine(entry: MemoryEntry): string {
134
+ return `- [${entry.slug}](${entry.slug}.md) — ${entry.description}`;
135
+ }
136
+
137
+ /**
138
+ * Fold an entry's index line into an existing `MEMORY.md`, DEDUPED by slug (an
139
+ * existing line for the same slug is REPLACED in place; a new slug is appended).
140
+ * PURE — string in, string out. Preserves a leading title/header block.
141
+ */
142
+ export function upsertIndex(existingIndex: string, entry: MemoryEntry): string {
143
+ const newLine = indexLine(entry);
144
+ const lineRe = new RegExp(`^- \\[${entry.slug}\\]\\(${entry.slug}\\.md\\).*$`);
145
+ const lines = existingIndex.split("\n");
146
+ let replaced = false;
147
+ const out = lines.map((l) => {
148
+ if (lineRe.test(l)) {
149
+ replaced = true;
150
+ return newLine;
151
+ }
152
+ return l;
153
+ });
154
+ if (replaced) return out.join("\n");
155
+ // Append (ensure a header exists + trailing newline hygiene).
156
+ const base =
157
+ existingIndex.trim() === ""
158
+ ? "# AutoMemory — index\n\n> Project-level operator-feedback + tool self-learning. One line per entry.\n"
159
+ : existingIndex.replace(/\n*$/, "\n");
160
+ return base + newLine + "\n";
161
+ }
162
+
163
+ // ── Filesystem writer (the ONLY impure edge) ──────────────────────────────────
164
+
165
+ export interface AppendResult {
166
+ slug: string;
167
+ entryFile: string;
168
+ indexFile: string;
169
+ /** true when an existing entry was UPDATED (dedupe hit) rather than created. */
170
+ updated: boolean;
171
+ }
172
+
173
+ /**
174
+ * Append (or UPDATE) an AutoMemory entry under `<memoryDir>/`. Writes the
175
+ * `<slug>.md` file + upserts the `MEMORY.md` index line. DEDUPE: an existing
176
+ * `<slug>.md` is overwritten (not duplicated) and its index line replaced.
177
+ * `now` (YYYY-MM-DD) is INJECTED. The pure cores above do the formatting; this is
178
+ * the thin fs edge.
179
+ */
180
+ export function appendMemory(
181
+ memoryDir: string,
182
+ input: { title: string; description: string; lifecycle: LifecycleValue; feedback: FeedbackInput; type?: MemoryTypeValue },
183
+ now: string,
184
+ ): AppendResult {
185
+ const slug = slugify(input.title);
186
+ const type = input.type ?? classifyFeedback(input.feedback);
187
+ const entry: MemoryEntry = {
188
+ slug,
189
+ description: input.description,
190
+ type,
191
+ lifecycle: input.lifecycle,
192
+ created: now,
193
+ body: input.feedback.text.trim(),
194
+ };
195
+
196
+ mkdirSync(memoryDir, { recursive: true });
197
+ const entryFile = join(memoryDir, `${slug}.md`);
198
+ const updated = existsSync(entryFile);
199
+ writeFileSync(entryFile, renderEntryMarkdown(entry));
200
+
201
+ const indexFile = join(memoryDir, "MEMORY.md");
202
+ const existingIndex = existsSync(indexFile) ? readFileSync(indexFile, "utf8") : "";
203
+ writeFileSync(indexFile, upsertIndex(existingIndex, entry));
204
+
205
+ return { slug, entryFile, indexFile, updated };
206
+ }
207
+
208
+ /** List the entry slugs currently in a memory dir (PURE-ish fs read; [] if absent). */
209
+ export function listEntrySlugs(memoryDir: string): string[] {
210
+ if (!existsSync(memoryDir)) return [];
211
+ return readdirSync(memoryDir)
212
+ .filter((f) => f.endsWith(".md") && f !== "MEMORY.md")
213
+ .map((f) => f.replace(/\.md$/, ""))
214
+ .sort();
215
+ }
@@ -0,0 +1,168 @@
1
+ /**
2
+ * scripts/memory/read.ts — project-level AutoMemory: READ (filter by lifecycle).
3
+ * ---------------------------------------------------------------------------
4
+ * Read the project-level AutoMemory store (`.mutagent/memory/`) at run START and
5
+ * return the entries relevant to THIS stage. The evaluator reads entries whose
6
+ * `lifecycle ∈ {evaluate, general}` (the run-start injection surface). The subject
7
+ * is the TOOL + operator preferences — NOT the evaluated agent.
8
+ *
9
+ * Determinism (coding-rules): the PARSE + FILTER cores are PURE (string in, data
10
+ * out); fs is confined to `readMemoryDir`. Tests exercise the pure cores against
11
+ * committed fixture text. Standalone — no cross-skill import.
12
+ *
13
+ * Format: one FACT per `<slug>.md`, Claude-Code AutoMemory frontmatter
14
+ * (`references/memory-format.md`). A malformed / partial entry is SKIPPED (never
15
+ * throws) so one bad file can't blind a run.
16
+ */
17
+ import { existsSync, readdirSync, readFileSync } from "node:fs";
18
+ import { join } from "node:path";
19
+ import {
20
+ Lifecycle,
21
+ MemoryType,
22
+ type LifecycleValue,
23
+ type MemoryTypeValue,
24
+ } from "./append.ts";
25
+
26
+ export interface ParsedMemoryEntry {
27
+ slug: string;
28
+ description: string;
29
+ type: MemoryTypeValue;
30
+ lifecycle: LifecycleValue;
31
+ created: string;
32
+ body: string;
33
+ }
34
+
35
+ const LIFECYCLE_VALUES = new Set<string>(Object.values(Lifecycle));
36
+ const TYPE_VALUES = new Set<string>(Object.values(MemoryType));
37
+
38
+ /**
39
+ * Parse ONE `<slug>.md` entry (frontmatter + body). Returns null on a malformed /
40
+ * partial entry (missing frontmatter, unknown type/lifecycle, absent date) — a bad
41
+ * file is SKIPPED, never fatal. PURE — string in, entry out. `fallbackSlug` is the
42
+ * file stem, used when the `name:` field is absent.
43
+ */
44
+ export function parseMemoryEntry(text: string, fallbackSlug: string): ParsedMemoryEntry | null {
45
+ const m = /^---\n([\s\S]*?)\n---\n?([\s\S]*)$/.exec(text);
46
+ if (m === null) return null;
47
+ const front = m[1] ?? "";
48
+ const body = (m[2] ?? "").trim();
49
+
50
+ const scalar = (key: string): string | undefined => {
51
+ // top-level `key: value` OR nested ` key: value` (metadata block).
52
+ const re = new RegExp(`^\\s*${key}:\\s*(.+?)\\s*$`, "m");
53
+ const mm = re.exec(front);
54
+ return mm ? (mm[1] as string).trim() : undefined;
55
+ };
56
+
57
+ const slug = scalar("name") ?? fallbackSlug;
58
+ const description = scalar("description") ?? "";
59
+ const type = scalar("type");
60
+ const lifecycle = scalar("lifecycle");
61
+ const created = scalar("created");
62
+
63
+ if (type === undefined || !TYPE_VALUES.has(type)) return null;
64
+ if (lifecycle === undefined || !LIFECYCLE_VALUES.has(lifecycle)) return null;
65
+ if (created === undefined || !/^\d{4}-\d{2}-\d{2}$/.test(created)) return null;
66
+
67
+ return {
68
+ slug,
69
+ description,
70
+ type: type as MemoryTypeValue,
71
+ lifecycle: lifecycle as LifecycleValue,
72
+ created,
73
+ body,
74
+ };
75
+ }
76
+
77
+ /**
78
+ * Keep only entries whose `lifecycle` is in `stages` (the recall filter). PURE.
79
+ * For the evaluator run start: `stages = [Evaluate, General]`.
80
+ */
81
+ export function filterByLifecycle(
82
+ entries: ParsedMemoryEntry[],
83
+ stages: LifecycleValue[],
84
+ ): ParsedMemoryEntry[] {
85
+ const want = new Set<string>(stages);
86
+ return entries.filter((e) => want.has(e.lifecycle));
87
+ }
88
+
89
+ /** The default recall filter for an EVALUATE run: {evaluate, general}. */
90
+ export const EVALUATE_RECALL: LifecycleValue[] = [Lifecycle.Evaluate, Lifecycle.General];
91
+
92
+ export interface ReadMemoryResult {
93
+ exists: boolean;
94
+ /** entries matching the lifecycle filter, sorted by slug (deterministic). */
95
+ entries: ParsedMemoryEntry[];
96
+ /** files that failed to parse (skipped) — surfaced for honesty, never fatal. */
97
+ skipped: string[];
98
+ }
99
+
100
+ /**
101
+ * Read `<memoryDir>/*.md` (excluding the `MEMORY.md` index), parse each, and
102
+ * return the entries matching `stages` (default = EVALUATE_RECALL). A missing dir
103
+ * → `{ exists:false, entries:[] }`. A malformed file is SKIPPED (listed in
104
+ * `skipped`). Deterministic: entries sorted by slug. The ONLY impure edge.
105
+ */
106
+ export function readMemoryDir(
107
+ memoryDir: string,
108
+ stages: LifecycleValue[] = EVALUATE_RECALL,
109
+ ): ReadMemoryResult {
110
+ if (!existsSync(memoryDir)) return { exists: false, entries: [], skipped: [] };
111
+
112
+ const files = readdirSync(memoryDir)
113
+ .filter((f) => f.endsWith(".md") && f !== "MEMORY.md")
114
+ .sort();
115
+
116
+ const parsed: ParsedMemoryEntry[] = [];
117
+ const skipped: string[] = [];
118
+ for (const f of files) {
119
+ const stem = f.replace(/\.md$/, "");
120
+ let text: string;
121
+ try {
122
+ text = readFileSync(join(memoryDir, f), "utf8");
123
+ } catch {
124
+ skipped.push(f);
125
+ continue;
126
+ }
127
+ const entry = parseMemoryEntry(text, stem);
128
+ if (entry === null) {
129
+ skipped.push(f);
130
+ continue;
131
+ }
132
+ parsed.push(entry);
133
+ }
134
+
135
+ return { exists: true, entries: filterByLifecycle(parsed, stages), skipped };
136
+ }
137
+
138
+ /**
139
+ * Render the recalled entries as a compact operator-preference context block for
140
+ * injection at run start (one bullet per entry: type · slug · description). PURE.
141
+ * EMPTY string when nothing recalled (a run with no AutoMemory is byte-identical
142
+ * to the legacy no-memory path).
143
+ */
144
+ export function renderRecallContext(entries: ParsedMemoryEntry[]): string {
145
+ if (entries.length === 0) return "";
146
+ const bullets = entries
147
+ .map((e) => `- [${e.type}] ${e.slug} — ${e.description}`)
148
+ .join("\n");
149
+ return `AutoMemory (operator preferences · evaluate + general):\n${bullets}`;
150
+ }
151
+
152
+ // ── CLI — thin wrapper (print the recalled entries for a project root) ─────────
153
+ // bun scripts/memory/read.ts [project-root]
154
+
155
+ function memoryDirFor(projectRoot: string): string {
156
+ return join(projectRoot, ".mutagent", "memory");
157
+ }
158
+
159
+ const isMain =
160
+ typeof import.meta !== "undefined" &&
161
+ (import.meta as unknown as { main?: boolean }).main === true;
162
+ if (isMain) {
163
+ const argv = typeof Bun !== "undefined" ? Bun.argv : process.argv;
164
+ const projectRoot = argv[2] ?? process.cwd();
165
+ const result = readMemoryDir(memoryDirFor(projectRoot));
166
+ process.stdout.write(JSON.stringify(result, null, 2) + "\n");
167
+ process.exit(0);
168
+ }
@@ -0,0 +1,125 @@
1
+ /**
2
+ * scripts/prep-tasks.ts — the deterministic PREP half of the agent-dispatch engine.
3
+ * ---------------------------------------------------------------------------
4
+ * The agent-dispatch substrate splits judging into PREP (this file) + DISPATCH
5
+ * (the parent session, per references/workflows/orchestrator-protocol.md) +
6
+ * AGGREGATE (run-pipeline.ts reading the verdict files). PREP emits one
7
+ * task-spec file per judging unit — the EXACT (system, user) prompt a dispatched
8
+ * leaf subagent must reason over, keyed by a content-hash so AGGREGATE re-derives
9
+ * the same key from the prompt alone.
10
+ *
11
+ * There is a HARD STAGE BARRIER (the same dependency diagnostics has between its
12
+ * deep-read and its analyzer fan-out): the determiner labels (EV-042) must be
13
+ * REAL before the *build-evals judge prompts can be built, because the judge
14
+ * few-shot block is drawn from the determiner-labeled TRAIN split. So PREP runs
15
+ * in two stages:
16
+ *
17
+ * Stage A — prepDeterminerTasks (no label dependency; pure per-trace prompt).
18
+ * → dispatch error-analyst → collect determiner verdict files.
19
+ * Stage B — prepJudgeTasks (REQUIRES stage-A verdicts collected; replays
20
+ * the pipeline with a capturing judge so the emitted judge prompts
21
+ * are byte-identical to what AGGREGATE will build).
22
+ *
23
+ * PURE except for fs (writes task-spec files; reads stage-A verdict files). No
24
+ * clock / random / network. Model intent SACRED: the pinned envelope is carried
25
+ * on every task spec for the host runtime to honor.
26
+ */
27
+ import { existsSync, readFileSync } from "node:fs";
28
+ import { join } from "node:path";
29
+ import { extractOutcomeSignals, type JudgeInvoke } from "./determine-outcome.ts";
30
+ import { buildOutcomePrompt } from "./judge-prompt-template.ts";
31
+ import {
32
+ writeJudgeTask,
33
+ verdictFileName,
34
+ type JudgeTaskSpec,
35
+ type PinnedEnvelope,
36
+ } from "./agent-dispatch.ts";
37
+ import { runEvalPipeline } from "./run-pipeline.ts";
38
+ import type { PipelineOptions } from "./run-pipeline.ts";
39
+ import type { EvalTrace, SubjectVocab } from "./contracts/eval-types.ts";
40
+
41
+ /** A PREP placeholder verdict — lets the pipeline complete during stage-B capture; never reported. */
42
+ const PREP_PLACEHOLDER = JSON.stringify({
43
+ critique: "PREP placeholder — awaiting the dispatched subagent's verdict file",
44
+ result: "pass",
45
+ confidence: 0,
46
+ });
47
+
48
+ /**
49
+ * Stage A PREP — emit one determiner (EV-042) task-spec per trace. The
50
+ * determiner prompt has NO label dependency (it reads the trace's own event +
51
+ * trajectory + terminal state), so these can be emitted + dispatched first.
52
+ * The dispatched error-analyst writes each verdict file; AGGREGATE-A reads them.
53
+ */
54
+ export function prepDeterminerTasks(
55
+ traces: EvalTrace[],
56
+ opts: { dir: string; pin: PinnedEnvelope; vocab: SubjectVocab },
57
+ ): JudgeTaskSpec[] {
58
+ return traces.map((trace) => {
59
+ const { system, user } = buildOutcomePrompt(
60
+ trace,
61
+ extractOutcomeSignals(trace, opts.vocab),
62
+ opts.vocab,
63
+ );
64
+ return writeJudgeTask(opts.dir, {
65
+ unit: { kind: "discover", traceId: trace.id },
66
+ system,
67
+ user,
68
+ pin: opts.pin,
69
+ });
70
+ });
71
+ }
72
+
73
+ /**
74
+ * A capturing JudgeInvoke: when a prompt's verdict file is already present
75
+ * (stage-A determiner verdicts), READ it; otherwise CAPTURE the prompt as a
76
+ * judge task-spec and return a deterministic placeholder so the pipeline runs to
77
+ * completion. Replaying the real pipeline with this judge emits the *build-evals
78
+ * judge prompts using the REAL determiner labels — guaranteeing they are
79
+ * byte-identical to the prompts AGGREGATE will build (same code, same labels).
80
+ */
81
+ export function createCapturingJudge(opts: {
82
+ verdictDir: string;
83
+ taskDir: string;
84
+ pin: PinnedEnvelope;
85
+ }): { judge: JudgeInvoke; captured: JudgeTaskSpec[] } {
86
+ const captured: JudgeTaskSpec[] = [];
87
+ const judge: JudgeInvoke = (system, user) => {
88
+ const vpath = join(opts.verdictDir, verdictFileName(system, user));
89
+ if (existsSync(vpath)) return Promise.resolve(readFileSync(vpath, "utf8"));
90
+ captured.push(
91
+ // D4 — the criterion judging axis (*build-evals / Step-2b). `kind` now matches the
92
+ // command (was mis-tagged "evaluate" pre-merge). ENVELOPE-ONLY retag: `unit` is not
93
+ // part of promptHash(system, user), so the rendered prompt + its key (8b58d9ca) are
94
+ // invariant — only the task.json envelope carries the new {kind, axis}.
95
+ writeJudgeTask(opts.taskDir, {
96
+ unit: { kind: "build-evals", axis: "criterion" },
97
+ system,
98
+ user,
99
+ pin: opts.pin,
100
+ }),
101
+ );
102
+ return Promise.resolve(PREP_PLACEHOLDER);
103
+ };
104
+ return { judge, captured };
105
+ }
106
+
107
+ /**
108
+ * Stage B PREP — emit the *build-evals / *evaluate judge (EV-043/048) task-specs.
109
+ * REQUIRES the stage-A determiner verdict files to already be collected in
110
+ * `verdictDir` (else the determiner prompts would themselves be captured with
111
+ * placeholder labels, corrupting the judge few-shot). Replays runEvalPipeline
112
+ * with the capturing judge and returns the captured judge tasks for dispatch.
113
+ */
114
+ export async function prepJudgeTasks(
115
+ traces: EvalTrace[],
116
+ opts: { verdictDir: string; taskDir: string; pin: PinnedEnvelope; pipeline: PipelineOptions },
117
+ ): Promise<JudgeTaskSpec[]> {
118
+ const { judge, captured } = createCapturingJudge({
119
+ verdictDir: opts.verdictDir,
120
+ taskDir: opts.taskDir,
121
+ pin: opts.pin,
122
+ });
123
+ await runEvalPipeline(traces, judge, opts.pipeline);
124
+ return captured;
125
+ }