@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.
- package/.claude/skills/mutagent-evaluator/SKILL.md +538 -0
- package/.claude/skills/mutagent-evaluator/assets/agents/audit-executor.md +169 -0
- package/.claude/skills/mutagent-evaluator/assets/agents/dataset-builder.md +160 -0
- package/.claude/skills/mutagent-evaluator/assets/agents/discovery.md +425 -0
- package/.claude/skills/mutagent-evaluator/assets/agents/evaluator.md +1044 -0
- package/.claude/skills/mutagent-evaluator/assets/brand/theme.css +213 -0
- package/.claude/skills/mutagent-evaluator/assets/brand/wordmark.html +9 -0
- package/.claude/skills/mutagent-evaluator/lenses/context-flow-lens.md +85 -0
- package/.claude/skills/mutagent-evaluator/lenses/data-lens.md +47 -0
- package/.claude/skills/mutagent-evaluator/lenses/decision-lens.md +48 -0
- package/.claude/skills/mutagent-evaluator/lenses/methodology-critic-lens.md +53 -0
- package/.claude/skills/mutagent-evaluator/lenses/trajectory-lens.md +44 -0
- package/.claude/skills/mutagent-evaluator/references/build-review-interface.md +83 -0
- package/.claude/skills/mutagent-evaluator/references/edd-loop.md +134 -0
- package/.claude/skills/mutagent-evaluator/references/error-analysis.md +113 -0
- package/.claude/skills/mutagent-evaluator/references/eval-audit.md +154 -0
- package/.claude/skills/mutagent-evaluator/references/eval-stage.md +168 -0
- package/.claude/skills/mutagent-evaluator/references/generate-synthetic-data.md +81 -0
- package/.claude/skills/mutagent-evaluator/references/grounded-adjudication.md +221 -0
- package/.claude/skills/mutagent-evaluator/references/memory-format.md +65 -0
- package/.claude/skills/mutagent-evaluator/references/methodology.md +201 -0
- package/.claude/skills/mutagent-evaluator/references/operation-inventory.md +196 -0
- package/.claude/skills/mutagent-evaluator/references/validate-evaluator.md +125 -0
- package/.claude/skills/mutagent-evaluator/references/workflows/orchestrator-protocol.md +287 -0
- package/.claude/skills/mutagent-evaluator/references/write-judge-prompt.md +123 -0
- package/.claude/skills/mutagent-evaluator/schemas/behavior-tree.schema.yaml +73 -0
- package/.claude/skills/mutagent-evaluator/schemas/dataset.schema.yaml +66 -0
- package/.claude/skills/mutagent-evaluator/schemas/edd-change-request.schema.yaml +114 -0
- package/.claude/skills/mutagent-evaluator/schemas/eval-matrix.schema.yaml +74 -0
- package/.claude/skills/mutagent-evaluator/schemas/flow-graph.schema.yaml +69 -0
- package/.claude/skills/mutagent-evaluator/schemas/flow-profile.schema.yaml +49 -0
- package/.claude/skills/mutagent-evaluator/schemas/methodology-review.schema.yaml +40 -0
- package/.claude/skills/mutagent-evaluator/schemas/scorecard.schema.yaml +85 -0
- package/.claude/skills/mutagent-evaluator/scripts/agent-dispatch.ts +0 -0
- package/.claude/skills/mutagent-evaluator/scripts/aggregate-discover.ts +543 -0
- package/.claude/skills/mutagent-evaluator/scripts/artifact-paths.ts +99 -0
- package/.claude/skills/mutagent-evaluator/scripts/assemble-scorecard.ts +172 -0
- package/.claude/skills/mutagent-evaluator/scripts/build-dataset.ts +186 -0
- package/.claude/skills/mutagent-evaluator/scripts/build-evals.ts +93 -0
- package/.claude/skills/mutagent-evaluator/scripts/build-review-ui.ts +393 -0
- package/.claude/skills/mutagent-evaluator/scripts/check-method-router.ts +170 -0
- package/.claude/skills/mutagent-evaluator/scripts/cli/aggregate.ts +112 -0
- package/.claude/skills/mutagent-evaluator/scripts/cli/audit-run.ts +175 -0
- package/.claude/skills/mutagent-evaluator/scripts/cli/doctor.ts +211 -0
- package/.claude/skills/mutagent-evaluator/scripts/cli/dogfood.ts +133 -0
- package/.claude/skills/mutagent-evaluator/scripts/cli/init.ts +601 -0
- package/.claude/skills/mutagent-evaluator/scripts/cli/methodology-review.ts +122 -0
- package/.claude/skills/mutagent-evaluator/scripts/cli/prep.ts +279 -0
- package/.claude/skills/mutagent-evaluator/scripts/cli/profile-subject.ts +165 -0
- package/.claude/skills/mutagent-evaluator/scripts/cli/run.sh +56 -0
- package/.claude/skills/mutagent-evaluator/scripts/cli/variance-check.ts +105 -0
- package/.claude/skills/mutagent-evaluator/scripts/code-eval.ts +248 -0
- package/.claude/skills/mutagent-evaluator/scripts/codegen-evals.ts +173 -0
- package/.claude/skills/mutagent-evaluator/scripts/cold-start-project.ts +151 -0
- package/.claude/skills/mutagent-evaluator/scripts/cold-start-sampler.ts +267 -0
- package/.claude/skills/mutagent-evaluator/scripts/config/load.ts +307 -0
- package/.claude/skills/mutagent-evaluator/scripts/config/schema.ts +325 -0
- package/.claude/skills/mutagent-evaluator/scripts/contracts/agentspec-evals.ts +85 -0
- package/.claude/skills/mutagent-evaluator/scripts/contracts/dataset.ts +149 -0
- package/.claude/skills/mutagent-evaluator/scripts/contracts/eval-engine.ts +118 -0
- package/.claude/skills/mutagent-evaluator/scripts/contracts/eval-matrix.ts +577 -0
- package/.claude/skills/mutagent-evaluator/scripts/contracts/eval-types.ts +1074 -0
- package/.claude/skills/mutagent-evaluator/scripts/contracts/flow-graph.ts +194 -0
- package/.claude/skills/mutagent-evaluator/scripts/contracts/types.ts +303 -0
- package/.claude/skills/mutagent-evaluator/scripts/contracts/validation.ts +193 -0
- package/.claude/skills/mutagent-evaluator/scripts/derive-dataset.ts +152 -0
- package/.claude/skills/mutagent-evaluator/scripts/determine-outcome.ts +219 -0
- package/.claude/skills/mutagent-evaluator/scripts/diff-discriminate.ts +160 -0
- package/.claude/skills/mutagent-evaluator/scripts/discover-criteria.ts +348 -0
- package/.claude/skills/mutagent-evaluator/scripts/edd/change-request.ts +232 -0
- package/.claude/skills/mutagent-evaluator/scripts/edd/edd-types.ts +210 -0
- package/.claude/skills/mutagent-evaluator/scripts/edd/variance-gate.ts +186 -0
- package/.claude/skills/mutagent-evaluator/scripts/emit-completeness.ts +111 -0
- package/.claude/skills/mutagent-evaluator/scripts/eval-engine.ts +160 -0
- package/.claude/skills/mutagent-evaluator/scripts/evaluate.ts +333 -0
- package/.claude/skills/mutagent-evaluator/scripts/flow-graph.ts +230 -0
- package/.claude/skills/mutagent-evaluator/scripts/judge-prompt-template.ts +253 -0
- package/.claude/skills/mutagent-evaluator/scripts/judge-provider.ts +135 -0
- package/.claude/skills/mutagent-evaluator/scripts/lint-grounding.ts +229 -0
- package/.claude/skills/mutagent-evaluator/scripts/lint-uniformity.ts +168 -0
- package/.claude/skills/mutagent-evaluator/scripts/living-suite.ts +109 -0
- package/.claude/skills/mutagent-evaluator/scripts/load-bundle.ts +203 -0
- package/.claude/skills/mutagent-evaluator/scripts/load-profile-vocab.ts +64 -0
- package/.claude/skills/mutagent-evaluator/scripts/load-profile.ts +106 -0
- package/.claude/skills/mutagent-evaluator/scripts/mask.ts +138 -0
- package/.claude/skills/mutagent-evaluator/scripts/materialize-dataset.ts +113 -0
- package/.claude/skills/mutagent-evaluator/scripts/matrix-judge.ts +750 -0
- package/.claude/skills/mutagent-evaluator/scripts/memory/append.ts +215 -0
- package/.claude/skills/mutagent-evaluator/scripts/memory/read.ts +168 -0
- package/.claude/skills/mutagent-evaluator/scripts/prep-tasks.ts +125 -0
- package/.claude/skills/mutagent-evaluator/scripts/profile-subject.ts +310 -0
- package/.claude/skills/mutagent-evaluator/scripts/publish-report.ts +131 -0
- package/.claude/skills/mutagent-evaluator/scripts/read-unitf-traces.ts +81 -0
- package/.claude/skills/mutagent-evaluator/scripts/render-build-cards.ts +195 -0
- package/.claude/skills/mutagent-evaluator/scripts/render-discover-report.ts +1640 -0
- package/.claude/skills/mutagent-evaluator/scripts/render-eval-report.ts +3823 -0
- package/.claude/skills/mutagent-evaluator/scripts/render-report.ts +212 -0
- package/.claude/skills/mutagent-evaluator/scripts/resolve-credential.ts +110 -0
- package/.claude/skills/mutagent-evaluator/scripts/resolve-ref.ts +98 -0
- package/.claude/skills/mutagent-evaluator/scripts/result-verify.ts +129 -0
- package/.claude/skills/mutagent-evaluator/scripts/route-failures.ts +320 -0
- package/.claude/skills/mutagent-evaluator/scripts/run-deterministic.ts +271 -0
- package/.claude/skills/mutagent-evaluator/scripts/run-evaluate.ts +715 -0
- package/.claude/skills/mutagent-evaluator/scripts/run-judge.ts +155 -0
- package/.claude/skills/mutagent-evaluator/scripts/run-pipeline.ts +175 -0
- package/.claude/skills/mutagent-evaluator/scripts/sample-traces.ts +210 -0
- package/.claude/skills/mutagent-evaluator/scripts/self-audit.ts +387 -0
- package/.claude/skills/mutagent-evaluator/scripts/source-map.ts +106 -0
- package/.claude/skills/mutagent-evaluator/scripts/subject-profile.ts +134 -0
- package/.claude/skills/mutagent-evaluator/scripts/substrate.ts +162 -0
- package/.claude/skills/mutagent-evaluator/scripts/ui-slots.ts +119 -0
- package/.claude/skills/mutagent-evaluator/scripts/unitf-to-evaltrace.ts +284 -0
- package/.claude/skills/mutagent-evaluator/scripts/validate-judge.ts +358 -0
- package/.claude/skills/mutagent-evaluator/scripts/variance-compare.ts +177 -0
- package/.claude/skills/mutagent-evaluator/subjects/mutagent-diagnostics/behavior-tree.yaml +140 -0
- package/.claude/skills/mutagent-evaluator/subjects/mutagent-diagnostics/eval-matrix.yaml +1270 -0
- package/.claude/skills/mutagent-evaluator/subjects/mutagent-diagnostics/methodology-review.yaml +105 -0
- package/.claude/skills/mutagent-evaluator/workflows/audit.workflow.js +82 -0
- package/.claude/skills/mutagent-evaluator/workflows/data-leak.workflow.js +236 -0
- package/.claude/skills/mutagent-evaluator/workflows/variance.workflow.js +163 -0
- package/LICENSE +201 -0
- package/NOTICE +23 -0
- package/README.md +90 -0
- package/bin/mutagent-cli.mjs +9263 -0
- package/bin/mutagent-evaluator.mjs +96 -0
- 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
|
+
}
|