@hawkeyexl/manni 4.1.0 → 4.3.0

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 (51) hide show
  1. package/README.md +2 -2
  2. package/dist/batch-7JJWXWSF.js +18 -0
  3. package/dist/batch-7JJWXWSF.js.map +1 -0
  4. package/dist/calibrate-IA2ARFMH.js +13 -0
  5. package/dist/calibrate-IA2ARFMH.js.map +1 -0
  6. package/dist/capture-C32JXIFN.js +15 -0
  7. package/dist/capture-C32JXIFN.js.map +1 -0
  8. package/dist/chunk-3R23C5DT.js +120 -0
  9. package/dist/chunk-3R23C5DT.js.map +1 -0
  10. package/dist/chunk-CI2GOU3A.js +4447 -0
  11. package/dist/chunk-CI2GOU3A.js.map +1 -0
  12. package/dist/{chunk-I57KBF45.js → chunk-DP2ZA3WH.js} +1203 -142
  13. package/dist/chunk-DP2ZA3WH.js.map +1 -0
  14. package/dist/chunk-ESJ5W7EV.js +297 -0
  15. package/dist/chunk-ESJ5W7EV.js.map +1 -0
  16. package/dist/chunk-IOYXPBCL.js +37 -0
  17. package/dist/chunk-IOYXPBCL.js.map +1 -0
  18. package/dist/chunk-KTSJ2ZHA.js +40 -0
  19. package/dist/chunk-KTSJ2ZHA.js.map +1 -0
  20. package/dist/{chunk-JODRT4NF.js → chunk-MFDD3YGU.js} +8 -37
  21. package/dist/chunk-MFDD3YGU.js.map +1 -0
  22. package/dist/chunk-MOQXWXSG.js +1941 -0
  23. package/dist/chunk-MOQXWXSG.js.map +1 -0
  24. package/dist/{chunk-S3NDEIOW.js → chunk-O6RVQKK6.js} +10502 -3017
  25. package/dist/chunk-O6RVQKK6.js.map +1 -0
  26. package/dist/chunk-QROCFW5S.js +174 -0
  27. package/dist/chunk-QROCFW5S.js.map +1 -0
  28. package/dist/chunk-RVVCA4WL.js +379 -0
  29. package/dist/chunk-RVVCA4WL.js.map +1 -0
  30. package/dist/cli.js +1293 -140
  31. package/dist/cli.js.map +1 -1
  32. package/dist/docmeta.js +3 -2
  33. package/dist/docmeta.js.map +1 -1
  34. package/dist/graph/embed.d.ts +154 -0
  35. package/dist/graph/embed.js +137 -0
  36. package/dist/graph/embed.js.map +1 -0
  37. package/dist/graph/runtime.d.ts +751 -0
  38. package/dist/graph/runtime.js +3067 -0
  39. package/dist/graph/runtime.js.map +1 -0
  40. package/dist/index.d.ts +6149 -799
  41. package/dist/index.js +682 -7
  42. package/dist/index.js.map +1 -1
  43. package/dist/picker-6HRMETE7.js +12 -0
  44. package/dist/picker-6HRMETE7.js.map +1 -0
  45. package/package.json +32 -4
  46. package/plugin/tracevals/.claude-plugin/plugin.json +9 -0
  47. package/plugin/tracevals/hooks/hooks.json +17 -0
  48. package/shapes/graph/shapes-1.0.0.ttl +689 -0
  49. package/dist/chunk-I57KBF45.js.map +0 -1
  50. package/dist/chunk-JODRT4NF.js.map +0 -1
  51. package/dist/chunk-S3NDEIOW.js.map +0 -1
@@ -0,0 +1,1941 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ DocmetaError,
4
+ FAMILY_CONFIG_NAMES,
5
+ META_CONTEXT,
6
+ __default4 as __default,
7
+ assertKnownProvider,
8
+ decryptValue,
9
+ encryptValue,
10
+ escapePointerSegment,
11
+ extractFrontmatter,
12
+ extractorByName,
13
+ extractorForExtension,
14
+ findConfigFileSync,
15
+ isEncryptedValue,
16
+ keyHome,
17
+ lazyKey,
18
+ loadExternalMetadata,
19
+ loadMetaSection,
20
+ marksValidator,
21
+ memberOf,
22
+ mergeWithMarks,
23
+ metaProvenanceEntries,
24
+ pageMarks,
25
+ pageSchemaSet,
26
+ readConfigFileSync,
27
+ rebaseConfigSchemaRefs,
28
+ schemaTrustRoot,
29
+ supportedExtensions,
30
+ withValueAt
31
+ } from "./chunk-DP2ZA3WH.js";
32
+ import {
33
+ TracevalsError,
34
+ configDir
35
+ } from "./chunk-3R23C5DT.js";
36
+ import {
37
+ errorMessage
38
+ } from "./chunk-IOYXPBCL.js";
39
+
40
+ // src/meta/core/reencrypt.ts
41
+ import { extname } from "node:path";
42
+ var CITATIONS_KEY = "citations";
43
+ var NOT_OURS = "does not decrypt under the current key";
44
+ var READ_ONLY = "this format cannot be written";
45
+ function reencryptMetadata(page, opts) {
46
+ const extractor = page.format !== void 0 ? extractorByName(page.format) : extractorForExtension(extname(page.file));
47
+ if (!extractor) {
48
+ throw new DocmetaError(
49
+ page.format !== void 0 ? `Unknown format "${page.format}" for "${page.file}".` : `Unsupported file type "${extname(page.file)}" for "${page.file}". Supported: ${supportedExtensions().join(", ")}.`
50
+ );
51
+ }
52
+ return reencryptWith(extractor, page, opts);
53
+ }
54
+ function tokensIn(data) {
55
+ const found = [];
56
+ const walk = (node, pointer, top) => {
57
+ if (isEncryptedValue(node)) {
58
+ found.push({ pointer, top, token: node });
59
+ return;
60
+ }
61
+ if (Array.isArray(node)) {
62
+ node.forEach((item, i) => {
63
+ walk(item, `${pointer}/${String(i)}`, top);
64
+ });
65
+ return;
66
+ }
67
+ if (typeof node === "object" && node !== null && !(node instanceof Date)) {
68
+ for (const [k, v] of Object.entries(node)) {
69
+ walk(v, `${pointer}/${escapePointerSegment(k)}`, top);
70
+ }
71
+ }
72
+ };
73
+ for (const [key, value] of Object.entries(data)) {
74
+ if (key === CITATIONS_KEY) continue;
75
+ walk(value, `/${escapePointerSegment(key)}`, key);
76
+ }
77
+ return found;
78
+ }
79
+ function reencryptData(data, opts) {
80
+ const writable = opts.writable ?? true;
81
+ const rewritten = [];
82
+ const skipped = [];
83
+ const found = tokensIn(data);
84
+ for (const { pointer, token } of found) {
85
+ const opened = decryptValue(token, opts.fromKey, META_CONTEXT);
86
+ if (opened.ok) {
87
+ if (!writable) {
88
+ skipped.push({ pointer, message: READ_ONLY });
89
+ continue;
90
+ }
91
+ rewritten.push({
92
+ pointer,
93
+ from: token,
94
+ to: encryptValue(opened.value, opts.toKey, META_CONTEXT)
95
+ });
96
+ continue;
97
+ }
98
+ if (decryptValue(token, opts.toKey, META_CONTEXT).ok) continue;
99
+ skipped.push({ pointer, message: NOT_OURS });
100
+ }
101
+ if (rewritten.length === 0) return { data, rewritten, skipped, keys: [] };
102
+ let next = data;
103
+ for (const r of rewritten) next = withValueAt(next, r.pointer, r.to);
104
+ const done = new Set(rewritten.map((r) => r.pointer));
105
+ const keys = [...new Set(found.filter((f) => done.has(f.pointer)).map((f) => f.top))];
106
+ return { data: next, rewritten, skipped, keys };
107
+ }
108
+ function reencryptWith(extractor, page, opts) {
109
+ const read = (content2) => {
110
+ try {
111
+ return extractor.extract(content2, page.file).data;
112
+ } catch (err) {
113
+ if (err instanceof DocmetaError) throw err;
114
+ throw new DocmetaError(`${page.file}: ${errorMessage(err)}`);
115
+ }
116
+ };
117
+ const data = read(page.content);
118
+ const apply = extractor.apply;
119
+ const plan = reencryptData(data, { ...opts, writable: apply !== void 0 });
120
+ const { rewritten, skipped } = plan;
121
+ if (rewritten.length === 0 || apply === void 0) {
122
+ return { content: page.content, rewritten: [], skipped };
123
+ }
124
+ const patch = {};
125
+ for (const top of plan.keys) patch[top] = plan.data[top];
126
+ const content = apply(page.content, patch, { filePath: page.file });
127
+ const reread = tokensIn(read(content));
128
+ for (const r of rewritten) {
129
+ if (!reread.some((f) => f.pointer === r.pointer && f.token === r.to)) {
130
+ throw new DocmetaError(
131
+ `${page.file}: the ${extractor.name} writer did not keep ${r.pointer}; nothing was re-encrypted.`
132
+ );
133
+ }
134
+ }
135
+ return { content, rewritten, skipped };
136
+ }
137
+
138
+ // src/tracevals/calibrate/labels.ts
139
+ import { readFile } from "node:fs/promises";
140
+ import { dirname, resolve } from "node:path";
141
+ import { Ajv2020 } from "ajv/dist/2020.js";
142
+ import { parse as parseYaml } from "yaml";
143
+
144
+ // src/tracevals/calibrate/labels-schema.json
145
+ var labels_schema_default = {
146
+ $schema: "https://json-schema.org/draft/2020-12/schema",
147
+ $id: "manni:tracevals:calibration-labels",
148
+ title: "manni tracevals calibration labels",
149
+ description: "Ground truth for `manni tracevals calibrate`: what a human decided a given eval should have produced on a given trace. A sidecar rather than an eval field, because a label belongs to a corpus and a reviewer, not to the artifact \u2014 and because the eval vocabulary is docmeta's, vendored byte-identical, with `inlineEval` closed to additions (ADR 01010).",
150
+ type: "object",
151
+ additionalProperties: false,
152
+ required: ["version", "labels"],
153
+ properties: {
154
+ version: {
155
+ const: 1,
156
+ description: "Format version. Pinned so a future shape change is an explicit migration rather than a silently-reinterpreted file."
157
+ },
158
+ labels: {
159
+ type: "array",
160
+ minItems: 1,
161
+ description: "One entry per (trace, artifact, eval) you have judged by hand.",
162
+ items: {
163
+ type: "object",
164
+ additionalProperties: false,
165
+ required: ["trace", "artifact", "eval", "expected"],
166
+ properties: {
167
+ trace: {
168
+ type: "string",
169
+ minLength: 1,
170
+ description: "Path to the trace file, resolved against this file's own directory \u2014 not the working directory."
171
+ },
172
+ artifact: {
173
+ type: "string",
174
+ minLength: 1,
175
+ description: "Artifact name as the report shows it: a skill name, a subagent_type, or a project-rules filename such as CLAUDE.md."
176
+ },
177
+ type: {
178
+ enum: ["skill", "agent", "project-rules", "slash-command"],
179
+ description: "Optional disambiguator, needed when one name resolves to two kinds of artifact \u2014 a skill and an agent, or a skill and the slash command that types it. Every `ArtifactType` the resolver can produce belongs here: a label whose type this rejects has no way to name the artifact it means."
180
+ },
181
+ eval: {
182
+ type: "string",
183
+ minLength: 1,
184
+ description: "The eval's id. Shorthand entries with no declared id get a positional one (eval-1, eval-2, ...), so labelling those is only as stable as the order they are written in."
185
+ },
186
+ expected: {
187
+ enum: ["pass", "fail", "needs-review"],
188
+ description: "What you decided. `needs-review` means you found it genuinely ambiguous and want the tool to defer too."
189
+ },
190
+ note: {
191
+ type: "string",
192
+ description: "Why you decided that. Carried into the disagreement report, where it is the whole reason a disagreement is actionable."
193
+ }
194
+ }
195
+ }
196
+ }
197
+ }
198
+ };
199
+
200
+ // src/tracevals/calibrate/labels.ts
201
+ var labelsSchema = labels_schema_default;
202
+ var DEFAULT_LABELS_FILE = "tracevals/labels.yaml";
203
+ var ajv = new Ajv2020({ allErrors: true });
204
+ var validate = ajv.compile(labelsSchema);
205
+ var labelKey = (traceFile, artifact, evalName, type) => `${traceFile}\0${type ?? ""}\0${artifact}\0${evalName}`;
206
+ function parseLabels(text, file) {
207
+ let raw;
208
+ try {
209
+ raw = parseYaml(text);
210
+ } catch (err) {
211
+ throw new TracevalsError(
212
+ `could not parse ${file}: ${errorMessage(err)}`
213
+ );
214
+ }
215
+ if (raw === null || raw === void 0) {
216
+ throw new TracevalsError(`invalid labels file ${file}: it is empty`);
217
+ }
218
+ if (!validate(raw)) {
219
+ const detail = (validate.errors ?? []).map((e) => `${e.instancePath || "/"} ${e.message ?? "invalid"}`).join("; ");
220
+ const empty2 = (validate.errors ?? []).some(
221
+ (e) => e.instancePath === "/labels" && e.keyword === "minItems"
222
+ );
223
+ throw new TracevalsError(
224
+ empty2 ? `invalid labels file ${file}: it must carry at least one label \u2014 an empty one measures nothing` : `invalid labels file ${file}: ${detail}`
225
+ );
226
+ }
227
+ const base = dirname(resolve(file));
228
+ const labels = [];
229
+ const seen = /* @__PURE__ */ new Set();
230
+ for (const entry of raw.labels) {
231
+ const traceFile = resolve(base, entry.trace);
232
+ const type = entry.type;
233
+ const key = labelKey(
234
+ traceFile,
235
+ entry.artifact,
236
+ entry.eval,
237
+ type
238
+ );
239
+ if (seen.has(key)) {
240
+ throw new TracevalsError(
241
+ `invalid labels file ${file}: duplicate label for ${entry.artifact ?? "?"} \u203A ${entry.eval ?? "?"} on ${entry.trace ?? "?"}`
242
+ );
243
+ }
244
+ seen.add(key);
245
+ labels.push({
246
+ trace: entry.trace,
247
+ traceFile,
248
+ artifact: entry.artifact,
249
+ ...type !== void 0 ? { type } : {},
250
+ eval: entry.eval,
251
+ expected: entry.expected,
252
+ ...entry.note !== void 0 ? { note: entry.note } : {}
253
+ });
254
+ }
255
+ return labels;
256
+ }
257
+ async function loadLabels(file) {
258
+ let text;
259
+ try {
260
+ text = await readFile(file, "utf-8");
261
+ } catch (err) {
262
+ throw new TracevalsError(
263
+ `could not read labels file ${file}: ${errorMessage(err)}`
264
+ );
265
+ }
266
+ return parseLabels(text, file);
267
+ }
268
+
269
+ // src/tracevals/core/config.ts
270
+ import { Ajv2020 as Ajv20202 } from "ajv/dist/2020.js";
271
+
272
+ // src/tracevals/core/config-schema.json
273
+ var config_schema_default = {
274
+ $schema: "https://json-schema.org/draft/2020-12/schema",
275
+ $id: "manni:tracevals:config",
276
+ title: "tracevals section of manni.config.yaml",
277
+ description: "Settings for manni tracevals, held under the `tracevals` key of the config file shared by the manni family of tools. Sibling keys belong to other tools and are not validated here.",
278
+ type: "object",
279
+ additionalProperties: false,
280
+ properties: {
281
+ provider: {
282
+ type: "string",
283
+ minLength: 1,
284
+ description: "Which provider judges: auto, anthropic, openai, claude-cli, or llama-cpp. Overrides the family's top-level `providers.provider` for tracevals only. Connection settings \u2014 an endpoint, an API key's environment variable, the Claude CLI command \u2014 live in that top-level `providers:` map, never here."
285
+ },
286
+ model: {
287
+ type: "string",
288
+ minLength: 1,
289
+ description: "The model within `provider`, which must be named at this level. Unset, the inference library's default for the winning provider is used; manni pins no model id of its own."
290
+ },
291
+ judge: {
292
+ type: "object",
293
+ additionalProperties: false,
294
+ properties: {
295
+ ensembleRuns: { type: "integer", minimum: 1 },
296
+ temperature: { type: "number", minimum: 0, maximum: 2 },
297
+ zones: {
298
+ type: "object",
299
+ additionalProperties: false,
300
+ properties: {
301
+ autoPass: { type: "number", minimum: 0, maximum: 1 },
302
+ autoFail: { type: "number", minimum: 0, maximum: 1 }
303
+ }
304
+ },
305
+ cacheDir: { type: "string" },
306
+ maxTurns: { type: "integer", minimum: 1, description: "Stop after this many ensemble runs. A cached ensemble spends none. Unset means unbounded; the remaining evals report `skipped` with the reason `turn budget` rather than aborting the run." },
307
+ redact: {
308
+ type: "array",
309
+ description: "Extra regular expressions scrubbed from the session digest before it is sent to a judge provider. Applied on top of the built-in secret shapes, never instead of them; each source is compiled with the `g` flag, and a match is replaced with `[redacted]`. Pattern matching is best-effort \u2014 --deterministic-only is the only setting that guarantees nothing leaves the machine.",
310
+ items: { type: "string", minLength: 1 }
311
+ }
312
+ }
313
+ },
314
+ execution: {
315
+ type: "object",
316
+ additionalProperties: false,
317
+ description: "Operator grant for executing content-authored code.",
318
+ properties: {
319
+ allow: {
320
+ type: "array",
321
+ items: { enum: ["frontmatter-commands"] },
322
+ default: ["frontmatter-commands"],
323
+ description: "Which content-authored code a run may execute. Absent, every grant holds; list fewer to narrow it, or write [] to run none. frontmatter-commands lets `command` evals declared in an artifact's front matter run; without it they report `skipped` with a reason, never `pass`."
324
+ }
325
+ }
326
+ },
327
+ render: {
328
+ type: "object",
329
+ additionalProperties: false,
330
+ properties: {
331
+ maxBlockChars: { type: "integer", minimum: 100 },
332
+ maxTotalChars: {
333
+ type: "integer",
334
+ minimum: 1e3,
335
+ description: "Upper bound on the rendered digest, in characters. The timeline always keeps at least 200 characters, so a very long header can push the digest past this bound by up to 200."
336
+ }
337
+ }
338
+ },
339
+ history: {
340
+ type: "object",
341
+ additionalProperties: false,
342
+ properties: {
343
+ file: { type: "string" }
344
+ }
345
+ },
346
+ capture: {
347
+ type: "object",
348
+ additionalProperties: false,
349
+ description: "Session manifests \u2014 sha256 of every instruction artifact plus the git SHA, recorded at session start by `manni tracevals capture` and read back by `run` to make staleness exact instead of an mtime guess. See ADR 01024.",
350
+ properties: {
351
+ dir: {
352
+ type: "string",
353
+ minLength: 1,
354
+ description: "Directory manifests are written to and looked for, resolved against the project root. `run` also accepts one placed beside the trace as `<trace>.manifest.json`, and --manifest names one outright."
355
+ }
356
+ }
357
+ },
358
+ fill: {
359
+ type: "object",
360
+ additionalProperties: false,
361
+ properties: {
362
+ confidenceThreshold: { type: "number", minimum: 0, maximum: 1 },
363
+ maxEvalsPerArtifact: { type: "integer", minimum: 1 },
364
+ temperature: { type: "number", minimum: 0, maximum: 2 },
365
+ cacheDir: { type: "string" },
366
+ maxTurns: { type: "integer", minimum: 1, description: "Stop after this many inference calls, one per artifact filled. A cached artifact spends none. Unset means unbounded." }
367
+ }
368
+ },
369
+ calibrate: {
370
+ type: "object",
371
+ additionalProperties: false,
372
+ description: "Settings for `manni tracevals calibrate` \u2014 measuring the tool's verdicts against a human's answers. See ADR 01022.",
373
+ properties: {
374
+ labels: {
375
+ type: "string",
376
+ minLength: 1,
377
+ description: "Default labels sidecar, resolved against this config file's directory. Overridden by --labels."
378
+ },
379
+ maxFalsePass: {
380
+ type: "integer",
381
+ minimum: 0,
382
+ description: "Fail the calibration run (exit 1) above this many false passes \u2014 labelled fail, judged pass. Unset by default: a calibration run is a measurement, not a gate."
383
+ },
384
+ maxFalseFail: {
385
+ type: "integer",
386
+ minimum: 0,
387
+ description: "Fail the calibration run above this many false fails \u2014 labelled pass, judged fail."
388
+ },
389
+ maxReview: {
390
+ type: "integer",
391
+ minimum: 0,
392
+ description: "Fail the calibration run above this many needs-review outcomes across the whole corpus."
393
+ },
394
+ sweep: {
395
+ type: "object",
396
+ additionalProperties: false,
397
+ description: "Values --sweep tries on each axis, one axis at a time with the others held at their configured value. The corpus is judged once at the largest ensembleRuns here and every cell is re-scored from those cached runs, so the grid costs nothing beyond that one run.",
398
+ properties: {
399
+ ensembleRuns: {
400
+ type: "array",
401
+ minItems: 1,
402
+ items: { type: "integer", minimum: 1 }
403
+ },
404
+ autoPass: {
405
+ type: "array",
406
+ minItems: 1,
407
+ items: { type: "number", minimum: 0, maximum: 1 }
408
+ },
409
+ autoFail: {
410
+ type: "array",
411
+ minItems: 1,
412
+ items: { type: "number", minimum: 0, maximum: 1 }
413
+ }
414
+ }
415
+ }
416
+ }
417
+ },
418
+ failOnReview: { type: "boolean" },
419
+ plugins: {
420
+ type: "array",
421
+ description: "Modules imported before evals are planned, so one calling registerGrader() can add a grader kind the built-ins do not cover. A relative or absolute path resolves against this file's directory, not the working directory; a bare package name resolves the way an import from this file's directory would. Load order is significant: a later entry wins a colliding kind.",
422
+ items: { type: "string", minLength: 1 }
423
+ },
424
+ reportUnusedArtifacts: {
425
+ type: "boolean",
426
+ description: "List every skill and agent the session was offered and never used, rather than only counting them. Off by default: a real roster runs to hundreds of skills."
427
+ }
428
+ }
429
+ };
430
+
431
+ // src/tracevals/judge/redact.ts
432
+ var SECRET_NAME = "(?:(?:[A-Za-z0-9]+[_\\-.])*(?:api[_\\-.]?key|access[_\\-.]?key|secret[_\\-.]?key|auth[_\\-.]?token|access[_\\-.]?token|refresh[_\\-.]?token|client[_\\-.]?secret|session[_\\-.]?token|key|token|secret|password|passwd|passphrase|credentials?|authorization)|AWS_[A-Za-z0-9_]+)";
433
+ var REDACTION_PATTERNS = [
434
+ {
435
+ label: "private-key",
436
+ pattern: /-----BEGIN [A-Z0-9 ]*PRIVATE KEY-----[\s\S]*?-----END [A-Z0-9 ]*PRIVATE KEY-----/g,
437
+ replacement: "[redacted:private-key]"
438
+ },
439
+ {
440
+ // `sk-`, `sk-ant-…`, `pk-`, `rk-`: the OpenAI/Anthropic/Stripe family.
441
+ label: "api-key",
442
+ pattern: /\b[sprk]k-(?:[A-Za-z0-9]+-)*[A-Za-z0-9_-]{16,}/g,
443
+ replacement: "[redacted:api-key]"
444
+ },
445
+ {
446
+ label: "aws-access-key-id",
447
+ pattern: /\b(?:AKIA|ASIA|AROA|AIDA)[0-9A-Z]{16}\b/g,
448
+ replacement: "[redacted:aws-access-key-id]"
449
+ },
450
+ {
451
+ label: "github-token",
452
+ pattern: /\b(?:gh[pousr]_[A-Za-z0-9]{20,}|github_pat_[A-Za-z0-9_]{20,})\b/g,
453
+ replacement: "[redacted:github-token]"
454
+ },
455
+ {
456
+ label: "slack-token",
457
+ pattern: /\bxox[abeoprs]-[A-Za-z0-9-]{10,}/g,
458
+ replacement: "[redacted:slack-token]"
459
+ },
460
+ {
461
+ label: "google-api-key",
462
+ pattern: /\bAIza[0-9A-Za-z_-]{35}\b/g,
463
+ replacement: "[redacted:google-api-key]"
464
+ },
465
+ {
466
+ label: "jwt",
467
+ pattern: /\beyJ[A-Za-z0-9_-]{8,}\.[A-Za-z0-9_-]{8,}\.[A-Za-z0-9_-]{8,}/g,
468
+ replacement: "[redacted:jwt]"
469
+ },
470
+ {
471
+ // `Authorization: Bearer …`, `Basic …`, `Token …`. The scheme is kept so
472
+ // the judge can still see that authentication happened.
473
+ //
474
+ // Deliberately *not* case-insensitive as a whole: `basic` and `token` are
475
+ // ordinary English words, and `/i` would redact "basic configuration" as an
476
+ // auth header. `bearer` is not, so it gets both spellings.
477
+ label: "auth-token",
478
+ pattern: /\b([Bb]earer|Basic|Token)\s+[A-Za-z0-9\-._~+/]{12,}={0,2}/g,
479
+ replacement: "$1 [redacted:auth-token]"
480
+ },
481
+ {
482
+ // JSON member — the shape a tool input renders as.
483
+ label: "secret-member",
484
+ pattern: new RegExp(`("${SECRET_NAME}"\\s*:\\s*)"[^"]*"`, "gi"),
485
+ replacement: '$1"[redacted:secret-value]"'
486
+ },
487
+ {
488
+ // Shell / `.env` assignment. The variable name is kept: knowing that a
489
+ // secret was set, and which one, is often the adherence question.
490
+ label: "secret-assignment",
491
+ pattern: new RegExp(
492
+ `\\b(${SECRET_NAME})(\\s*=\\s*)(?:"[^"\\n]*"|'[^'\\n]*'|[^\\s"'\\n]+)`,
493
+ "gi"
494
+ ),
495
+ replacement: "$1$2[redacted:secret-value]"
496
+ }
497
+ ];
498
+ var CUSTOM_PLACEHOLDER = "[redacted]";
499
+ function makeRedactor(extra = []) {
500
+ const custom = compileRedactPatterns(extra).map(
501
+ (pattern) => [pattern, CUSTOM_PLACEHOLDER]
502
+ );
503
+ const all = [
504
+ ...REDACTION_PATTERNS.map(
505
+ ({ pattern, replacement }) => [pattern, replacement]
506
+ ),
507
+ ...custom
508
+ ];
509
+ return (text) => {
510
+ let out = text;
511
+ for (const [pattern, replacement] of all) {
512
+ out = out.replace(pattern, replacement);
513
+ }
514
+ return out;
515
+ };
516
+ }
517
+ function compileRedactPatterns(sources) {
518
+ return sources.map((source) => {
519
+ try {
520
+ return new RegExp(source, "g");
521
+ } catch (err) {
522
+ throw new TracevalsError(
523
+ `invalid config: judge.redact pattern ${JSON.stringify(source)} is not a valid regular expression: ${err instanceof Error ? err.message : String(err)}`
524
+ );
525
+ }
526
+ });
527
+ }
528
+
529
+ // src/tracevals/capture/types.ts
530
+ var MANIFEST_VERSION = 1;
531
+ var DEFAULT_CAPTURE_DIR = ".manni/tracevals/sessions";
532
+
533
+ // src/shared/execution.ts
534
+ var EXECUTION_GRANTS = [
535
+ "frontmatter-commands"
536
+ ];
537
+ function isExecutionGrant(value) {
538
+ return EXECUTION_GRANTS.includes(value);
539
+ }
540
+ function unknownGrantsMessage(unknown) {
541
+ return `unknown execution grant${unknown.length > 1 ? "s" : ""} ${unknown.map((u) => `"${u}"`).join(", ")}; expected one of ${EXECUTION_GRANTS.join(" | ")}`;
542
+ }
543
+ function allowExecutionMessage(value) {
544
+ return `--allow-execution must be one of ${EXECUTION_GRANTS.join(" | ")}, got "${value}"`;
545
+ }
546
+ var NOT_GRANTED_REASON = "frontmatter commands not granted (execution.allow: [frontmatter-commands])";
547
+ function grantsFor(configured, flags = {}) {
548
+ if (flags.execution === false) return /* @__PURE__ */ new Set();
549
+ const named = flags.allowExecution;
550
+ return new Set(
551
+ named === void 0 ? configured : configured.filter((g) => named.includes(g))
552
+ );
553
+ }
554
+ function configuredGrants(section) {
555
+ if (!section || typeof section !== "object") return [];
556
+ const execution = section.execution;
557
+ if (!execution || typeof execution !== "object") return [];
558
+ const allow = execution.allow;
559
+ return Array.isArray(allow) ? allow.filter((g) => typeof g === "string") : [];
560
+ }
561
+
562
+ // src/tracevals/core/config.ts
563
+ var configSchema = config_schema_default;
564
+ var DEFAULT_CONFIG_FILENAME = FAMILY_CONFIG_NAMES[0] ?? "manni.config.yaml";
565
+ var CONFIG_SECTION_KEY = "tracevals";
566
+ var CONFIG_SECTION_KEY_PATH = `/${CONFIG_SECTION_KEY}`;
567
+ var CONFIG_FILE = {
568
+ section: CONFIG_SECTION_KEY,
569
+ legacyNames: [],
570
+ toError: (message) => new TracevalsError(message)
571
+ };
572
+ var ajv2 = new Ajv20202({ allErrors: true, verbose: true });
573
+ var validate2 = ajv2.compile(configSchema);
574
+ function camelCaseHint(key, parentSchema) {
575
+ const camel = key.replace(/-([a-z0-9])/g, (_, c) => c.toUpperCase());
576
+ if (camel === key || parentSchema === null || typeof parentSchema !== "object") {
577
+ return "";
578
+ }
579
+ const properties = parentSchema.properties;
580
+ return properties !== null && typeof properties === "object" && Object.hasOwn(properties, camel) ? `; did you mean "${camel}"?` : "";
581
+ }
582
+ function movedProviderHint(instancePath, keyword) {
583
+ return instancePath === "/provider" && keyword === "type" ? `; "provider" is now a provider name; per-provider settings moved to the top-level providers: map` : "";
584
+ }
585
+ function parseConfig2(raw, file = {}) {
586
+ const source = file.source ?? DEFAULT_CONFIG_FILENAME;
587
+ const unknownGrants = configuredGrants(raw).filter((g) => !isExecutionGrant(g));
588
+ if (unknownGrants.length > 0) {
589
+ throw new TracevalsError(`${source}: ${unknownGrantsMessage(unknownGrants)}`);
590
+ }
591
+ if (!validate2(raw)) {
592
+ const detail = (validate2.errors ?? []).map((e) => {
593
+ const extra = e.keyword === "additionalProperties" ? e.params.additionalProperty : void 0;
594
+ return extra !== void 0 ? `unknown key "${extra}"${camelCaseHint(extra, e.parentSchema)}` : `${CONFIG_SECTION_KEY_PATH}${e.instancePath} ${e.message ?? "is invalid"}` + movedProviderHint(e.instancePath, e.keyword);
595
+ }).join("; ");
596
+ throw new TracevalsError(`${source}: ${detail}`);
597
+ }
598
+ const r = raw;
599
+ const provider = r.provider ?? null;
600
+ if (provider !== null) {
601
+ assertKnownProvider(provider, (message) => new TracevalsError(message));
602
+ }
603
+ const config = {
604
+ provider,
605
+ model: r.model ?? null,
606
+ providers: file.providers ?? {},
607
+ configSource: source,
608
+ judge: {
609
+ ensembleRuns: r.judge?.ensembleRuns ?? 3,
610
+ temperature: r.judge?.temperature ?? 0,
611
+ zones: {
612
+ autoPass: r.judge?.zones?.autoPass ?? 0.8,
613
+ autoFail: r.judge?.zones?.autoFail ?? 0.8
614
+ },
615
+ cacheDir: r.judge?.cacheDir ?? ".manni/tracevals/cache",
616
+ // `null`, not a number: unbounded is a state, and any number here would
617
+ // be a ceiling nobody asked for.
618
+ maxTurns: r.judge?.maxTurns ?? null,
619
+ // Always a list: the render site concatenates nothing onto it, but a
620
+ // hole here would be a special case in every consumer.
621
+ redact: [...r.judge?.redact ?? []]
622
+ },
623
+ render: {
624
+ maxBlockChars: r.render?.maxBlockChars ?? 2e3,
625
+ maxTotalChars: r.render?.maxTotalChars ?? 15e4
626
+ },
627
+ execution: { allow: [...r.execution?.allow ?? EXECUTION_GRANTS] },
628
+ history: {
629
+ file: r.history?.file ?? ".manni/tracevals/history.jsonl"
630
+ },
631
+ capture: {
632
+ // Beside the judge cache, under the state directory that already travels
633
+ // with the project — which is what puts a manifest next to its trace in
634
+ // the CI patterns the docs describe.
635
+ dir: r.capture?.dir ?? DEFAULT_CAPTURE_DIR
636
+ },
637
+ fill: {
638
+ // 0.7 matches the manuscript's calibration bar for judged agreement.
639
+ confidenceThreshold: r.fill?.confidenceThreshold ?? 0.7,
640
+ maxEvalsPerArtifact: r.fill?.maxEvalsPerArtifact ?? 8,
641
+ temperature: r.fill?.temperature ?? 0,
642
+ // Separate from the judge cache: different key scheme and value shape.
643
+ cacheDir: r.fill?.cacheDir ?? ".manni/tracevals/cache/fill",
644
+ maxTurns: r.fill?.maxTurns ?? null
645
+ },
646
+ calibrate: {
647
+ labels: r.calibrate?.labels ?? DEFAULT_LABELS_FILE,
648
+ sweep: {
649
+ // Spans the range rather than filling it: the first sweep judges the
650
+ // corpus at the largest value here, so a wide grid is the one part of
651
+ // calibration that does cost money.
652
+ ensembleRuns: [...r.calibrate?.sweep?.ensembleRuns ?? [1, 3, 5]],
653
+ autoPass: [
654
+ ...r.calibrate?.sweep?.autoPass ?? [0.5, 0.6, 0.7, 0.8, 0.9, 0.95]
655
+ ],
656
+ autoFail: [
657
+ ...r.calibrate?.sweep?.autoFail ?? [0.5, 0.6, 0.7, 0.8, 0.9, 0.95]
658
+ ]
659
+ }
660
+ },
661
+ failOnReview: r.failOnReview ?? true,
662
+ // Always a list, never undefined: the read site concatenates `--require`
663
+ // onto it, and a hole there would be a special case in every caller.
664
+ plugins: [...r.plugins ?? []],
665
+ // An observation, not a gate: listing it is opt-in because a real roster
666
+ // runs to hundreds of skills (ADR 01016).
667
+ reportUnusedArtifacts: r.reportUnusedArtifacts ?? false
668
+ };
669
+ compileRedactPatterns(config.judge.redact);
670
+ for (const key of ["maxFalsePass", "maxFalseFail", "maxReview"]) {
671
+ if (typeof r.calibrate?.[key] === "number") {
672
+ config.calibrate[key] = r.calibrate[key];
673
+ }
674
+ }
675
+ return config;
676
+ }
677
+ async function discoverConfig(dir = process.cwd(), lookup = {}) {
678
+ if (lookup.noConfig === true) {
679
+ return { config: parseConfig2({}), dir, collections: [] };
680
+ }
681
+ const file = await Promise.resolve().then(
682
+ () => lookup.configPath === void 0 ? findConfigFileSync(dir, CONFIG_FILE) : readConfigFileSync(lookup.configPath, dir, CONFIG_FILE)
683
+ );
684
+ if (file === null) return { config: parseConfig2({}), dir, collections: [] };
685
+ return {
686
+ config: parseConfig2(file.value ?? {}, {
687
+ source: file.source,
688
+ ...file.providers !== void 0 ? { providers: file.providers } : {}
689
+ }),
690
+ dir: file.dir,
691
+ collections: file.collections,
692
+ path: file.path
693
+ };
694
+ }
695
+ async function loadConfig2(dir = process.cwd(), lookup = {}) {
696
+ return (await discoverConfig(dir, lookup)).config;
697
+ }
698
+
699
+ // src/tracevals/capture/manifest.ts
700
+ import { createHash } from "node:crypto";
701
+ import { mkdir, readFile as readFile2, writeFile } from "node:fs/promises";
702
+ import {
703
+ dirname as dirname2,
704
+ extname as extname2,
705
+ isAbsolute,
706
+ join,
707
+ relative,
708
+ resolve as resolve2,
709
+ sep
710
+ } from "node:path";
711
+ function sha256Hex(data) {
712
+ return createHash("sha256").update(data).digest("hex");
713
+ }
714
+ async function hashFile(path) {
715
+ try {
716
+ return sha256Hex(await readFile2(path));
717
+ } catch {
718
+ return null;
719
+ }
720
+ }
721
+ function relPosix(root, path) {
722
+ const rel = relative(resolve2(root), resolve2(path));
723
+ if (rel === "" || rel.startsWith("..") || isAbsolute(rel)) return null;
724
+ return rel.split(sep).join("/");
725
+ }
726
+ function safeSessionId(sessionId) {
727
+ const cleaned = sessionId.replace(/[^A-Za-z0-9._-]/g, "-").replace(/^\.+/, "");
728
+ return cleaned.length > 0 ? cleaned.slice(0, 128) : "session";
729
+ }
730
+ function manifestPathFor(dir, sessionId) {
731
+ return join(dir, `${safeSessionId(sessionId)}.json`);
732
+ }
733
+ function siblingManifestPath(tracePath) {
734
+ const ext = extname2(tracePath);
735
+ const stem = ext === "" ? tracePath : tracePath.slice(0, -ext.length);
736
+ return `${stem}.manifest.json`;
737
+ }
738
+ async function writeManifest(path, manifest) {
739
+ await mkdir(dirname2(resolve2(path)), { recursive: true });
740
+ await writeFile(path, `${JSON.stringify(manifest, null, 2)}
741
+ `, "utf-8");
742
+ }
743
+ function isManifestArtifact(value) {
744
+ if (value === null || typeof value !== "object" || Array.isArray(value)) {
745
+ return false;
746
+ }
747
+ const entry = value;
748
+ return typeof entry.path === "string" && entry.path.length > 0 && typeof entry.sha256 === "string" && entry.sha256.length > 0 && typeof entry.name === "string" && typeof entry.type === "string" && typeof entry.bytes === "number";
749
+ }
750
+ async function readManifest(path) {
751
+ let raw;
752
+ try {
753
+ raw = await readFile2(path, "utf-8");
754
+ } catch {
755
+ return null;
756
+ }
757
+ let parsed;
758
+ try {
759
+ parsed = JSON.parse(raw);
760
+ } catch {
761
+ return null;
762
+ }
763
+ if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
764
+ return null;
765
+ }
766
+ const record = parsed;
767
+ if (typeof record.version !== "number" || record.version > MANIFEST_VERSION) {
768
+ return null;
769
+ }
770
+ if (typeof record.sessionId !== "string") return null;
771
+ if (typeof record.capturedAt !== "string") return null;
772
+ if (typeof record.root !== "string") return null;
773
+ if (!Array.isArray(record.artifacts)) return null;
774
+ if (!record.artifacts.every(isManifestArtifact)) return null;
775
+ return parsed;
776
+ }
777
+ async function findManifest(options) {
778
+ const explicit = options.explicit !== void 0;
779
+ const candidates = [];
780
+ if (options.explicit !== void 0) {
781
+ candidates.push(options.explicit);
782
+ } else {
783
+ candidates.push(siblingManifestPath(options.tracePath));
784
+ if (options.sessionId !== void 0) {
785
+ candidates.push(
786
+ manifestPathFor(
787
+ resolve2(dirname2(options.tracePath), options.captureDir),
788
+ options.sessionId
789
+ ),
790
+ manifestPathFor(
791
+ resolve2(options.projectDir, options.captureDir),
792
+ options.sessionId
793
+ )
794
+ );
795
+ }
796
+ }
797
+ for (const path of candidates) {
798
+ const manifest = await readManifest(path);
799
+ if (manifest === null) continue;
800
+ const refusal = refuse(manifest, options.sessionId, explicit);
801
+ if (refusal !== null) {
802
+ options.onRefused?.(path, refusal);
803
+ continue;
804
+ }
805
+ return { path, manifest };
806
+ }
807
+ return null;
808
+ }
809
+ function refuse(manifest, sessionId, explicit) {
810
+ if (sessionId === void 0) {
811
+ if (explicit) return null;
812
+ return `the trace records no session id, so a manifest found by convention cannot be verified as belonging to it (it records "${manifest.sessionId}"). Pass --manifest to use it anyway.`;
813
+ }
814
+ if (manifest.sessionId !== sessionId) {
815
+ return `it was recorded for a different session ("${manifest.sessionId}", not "${sessionId}")`;
816
+ }
817
+ return null;
818
+ }
819
+ function checkContent(manifest, actual) {
820
+ const recorded = new Map(manifest.artifacts.map((a) => [a.path, a.sha256]));
821
+ const missing = [];
822
+ let matched = 0;
823
+ let agreed;
824
+ for (const [path, digest] of actual) {
825
+ const expected = recorded.get(path);
826
+ if (expected === void 0) {
827
+ missing.push(path);
828
+ continue;
829
+ }
830
+ if (expected !== digest) {
831
+ return { status: "mismatch", expected, actual: digest };
832
+ }
833
+ agreed ??= { expected, actual: digest };
834
+ matched += 1;
835
+ }
836
+ if (matched === 0) {
837
+ return {
838
+ status: "skipped",
839
+ reason: actual.size === 0 ? "no file to compare" : "not recorded in the session manifest"
840
+ };
841
+ }
842
+ if (missing.length > 0) {
843
+ return {
844
+ status: "skipped",
845
+ reason: `not recorded in the session manifest: ${missing.join(", ")}`
846
+ };
847
+ }
848
+ return {
849
+ status: "match",
850
+ ...actual.size === 1 && agreed !== void 0 ? agreed : {}
851
+ };
852
+ }
853
+
854
+ // src/meta/core/page-view.ts
855
+ async function metaPageView(opts) {
856
+ const { cwd } = opts;
857
+ const loaded = opts.noConfig === true ? null : await loadMetaSection(opts.configPath, cwd);
858
+ const config = loaded ? rebaseConfigSchemaRefs(loaded.config, loaded.dir, cwd) : null;
859
+ const configDir2 = loaded?.dir ?? cwd;
860
+ const collections = loaded?.collections ?? [];
861
+ const validator = marksValidator({
862
+ config,
863
+ cwd,
864
+ ...loaded ? { configDir: loaded.dir } : {}
865
+ })();
866
+ const marksOptions = {
867
+ validator,
868
+ config,
869
+ cwd,
870
+ trustRoot: schemaTrustRoot(cwd, loaded?.dir)
871
+ };
872
+ const membersOf = (label) => memberOf(collections, configDir2, cwd, label);
873
+ const index = await loadExternalMetadata(collections, {
874
+ configDir: configDir2,
875
+ base: cwd,
876
+ offline: config?.offline ?? false,
877
+ ...opts.pages === void 0 ? {} : { pages: opts.pages }
878
+ });
879
+ const joinKey = lazyKey(loaded?.configFile, void 0);
880
+ const marks = pageMarks(marksOptions);
881
+ const ctx = {
882
+ cwd,
883
+ base: cwd,
884
+ noConfig: loaded === null,
885
+ config,
886
+ ...loaded ? { configDir: loaded.dir, configPath: loaded.path } : {},
887
+ ...loaded?.configFile !== void 0 ? { configFile: loaded.configFile } : {},
888
+ collections,
889
+ declaredCollections: collections,
890
+ targets: [],
891
+ validator
892
+ };
893
+ return {
894
+ validator,
895
+ refsFor: (label, data) => pageSchemaSet(marksOptions, label, data, membersOf(label)),
896
+ merge: (label, extracted) => mergeWithMarks(label, extracted, index, membersOf(label), cwd, {
897
+ encryptionKey: joinKey,
898
+ marks
899
+ }),
900
+ home: (label, data, key) => keyHome(ctx, label, data, key)
901
+ };
902
+ }
903
+
904
+ // src/meta/core/declared-at.ts
905
+ function declaredAt(label, pointer, metadata) {
906
+ const at = metadata.locate?.(pointer);
907
+ if (at !== void 0) {
908
+ return { file: at.file, ...at.line === void 0 ? {} : { line: at.line } };
909
+ }
910
+ if (!metadata.extracted.present) return { file: label };
911
+ const line = metadata.extracted.lineFor(pointer);
912
+ return { file: label, ...line === void 0 ? {} : { line } };
913
+ }
914
+
915
+ // src/tracevals/trace/availability.ts
916
+ function newAvailability() {
917
+ return { recorded: false, skills: [], agents: [], tools: [], mcpServers: [] };
918
+ }
919
+ function availableAt(entries, index) {
920
+ return entries.filter(
921
+ (e) => e.offeredAt <= index && (e.withdrawnAt === void 0 || index < e.withdrawnAt)
922
+ );
923
+ }
924
+ function offeredNames(entries) {
925
+ return new Set(entries.map((e) => e.name));
926
+ }
927
+ function joinDescriptions(content, names) {
928
+ const byName = new Map(
929
+ names.map((name) => [name, void 0])
930
+ );
931
+ let current;
932
+ for (const raw of content.split("\n")) {
933
+ const line = raw.trimEnd();
934
+ if (line.startsWith("- ")) {
935
+ const body = line.slice(2);
936
+ let best;
937
+ for (const name of names) {
938
+ if (body !== name && !body.startsWith(`${name}:`)) continue;
939
+ if (best === void 0 || name.length > best.length) best = name;
940
+ }
941
+ current = best;
942
+ if (best === void 0) continue;
943
+ const rest = body.slice(best.length).replace(/^:\s*/, "").trim();
944
+ byName.set(best, rest.length > 0 ? rest : void 0);
945
+ continue;
946
+ }
947
+ if (current === void 0) continue;
948
+ const continued = line.trim();
949
+ if (continued.length === 0) continue;
950
+ const previous = byName.get(current);
951
+ byName.set(current, previous ? `${previous}
952
+ ${continued}` : continued);
953
+ }
954
+ return byName;
955
+ }
956
+ function describedLines(names, lines) {
957
+ const byName = /* @__PURE__ */ new Map();
958
+ names.forEach((name, i) => {
959
+ const line = lines[i];
960
+ if (typeof line !== "string") {
961
+ byName.set(name, void 0);
962
+ return;
963
+ }
964
+ const body = line.startsWith("- ") ? line.slice(2) : line;
965
+ const rest = body.startsWith(`${name}:`) ? body.slice(name.length + 1).trim() : body === name ? "" : body.trim();
966
+ byName.set(name, rest.length > 0 ? rest : void 0);
967
+ });
968
+ return byName;
969
+ }
970
+ var asStrings = (value) => Array.isArray(value) ? value.filter((v) => typeof v === "string") : [];
971
+ function newReplay() {
972
+ return {
973
+ roster: newAvailability(),
974
+ open: /* @__PURE__ */ new Map([
975
+ ["skill", /* @__PURE__ */ new Map()],
976
+ ["agent", /* @__PURE__ */ new Map()],
977
+ ["tool", /* @__PURE__ */ new Map()],
978
+ ["mcp-server", /* @__PURE__ */ new Map()]
979
+ ])
980
+ };
981
+ }
982
+ function listFor(roster, kind) {
983
+ if (kind === "skill") return roster.skills;
984
+ if (kind === "agent") return roster.agents;
985
+ if (kind === "tool") return roster.tools;
986
+ return roster.mcpServers;
987
+ }
988
+ function offer(replay, kind, name, index, description, status) {
989
+ const open = replay.open.get(kind);
990
+ const existing = open.get(name);
991
+ if (existing !== void 0) {
992
+ if (existing.description === void 0 && description !== void 0) {
993
+ existing.description = description;
994
+ }
995
+ if (status !== void 0) existing.status = status;
996
+ return;
997
+ }
998
+ const entry = { kind, name, offeredAt: index };
999
+ if (description !== void 0) entry.description = description;
1000
+ if (status !== void 0) entry.status = status;
1001
+ open.set(name, entry);
1002
+ listFor(replay.roster, kind).push(entry);
1003
+ }
1004
+ function withdraw(replay, kind, name, index) {
1005
+ const open = replay.open.get(kind);
1006
+ const entry = open.get(name);
1007
+ if (entry === void 0) return;
1008
+ entry.withdrawnAt = index;
1009
+ open.delete(name);
1010
+ }
1011
+ function openNames(replay, kind) {
1012
+ return [...replay.open.get(kind).keys()];
1013
+ }
1014
+ function applyAvailabilityRecord(replay, attachment, index) {
1015
+ if (typeof attachment !== "object" || attachment === null) return false;
1016
+ const record = attachment;
1017
+ const type = record.type;
1018
+ if (type === "skill_listing") {
1019
+ const names = asStrings(record.names);
1020
+ const content = typeof record.content === "string" ? record.content : "";
1021
+ if (record.isInitial === true) {
1022
+ for (const name of openNames(replay, "skill")) {
1023
+ if (!names.includes(name)) withdraw(replay, "skill", name, index);
1024
+ }
1025
+ }
1026
+ const described = joinDescriptions(content, names);
1027
+ for (const name of names) {
1028
+ offer(replay, "skill", name, index, described.get(name));
1029
+ }
1030
+ return true;
1031
+ }
1032
+ if (type === "agent_listing_delta") {
1033
+ const added = asStrings(record.addedTypes);
1034
+ const lines = asStrings(record.addedLines);
1035
+ if (record.isInitial === true) {
1036
+ for (const name of openNames(replay, "agent")) {
1037
+ if (!added.includes(name)) withdraw(replay, "agent", name, index);
1038
+ }
1039
+ }
1040
+ const described = describedLines(added, lines);
1041
+ for (const name of added) {
1042
+ offer(replay, "agent", name, index, described.get(name));
1043
+ }
1044
+ for (const name of asStrings(record.removedTypes)) {
1045
+ withdraw(replay, "agent", name, index);
1046
+ }
1047
+ return true;
1048
+ }
1049
+ if (type === "deferred_tools_delta") {
1050
+ for (const name of asStrings(record.removedNames)) {
1051
+ withdraw(replay, "tool", name, index);
1052
+ }
1053
+ for (const name of [
1054
+ ...asStrings(record.addedNames),
1055
+ ...asStrings(record.readdedNames)
1056
+ ]) {
1057
+ offer(replay, "tool", name, index);
1058
+ }
1059
+ const servers = [
1060
+ ...asStrings(record.pendingMcpServers).map(
1061
+ (n) => [n, "pending"]
1062
+ ),
1063
+ ...asStrings(record.needsAuthMcpServers).map(
1064
+ (n) => [n, "needs-auth"]
1065
+ ),
1066
+ ...asStrings(record.failedMcpServers).map(
1067
+ (n) => [n, "failed"]
1068
+ )
1069
+ ];
1070
+ for (const [name, status] of servers) {
1071
+ offer(replay, "mcp-server", name, index, void 0, status);
1072
+ }
1073
+ return true;
1074
+ }
1075
+ return false;
1076
+ }
1077
+ function remapAvailability(roster, at) {
1078
+ const shift = (entries) => entries.map((entry) => ({
1079
+ ...entry,
1080
+ offeredAt: at(entry.offeredAt),
1081
+ ...entry.withdrawnAt !== void 0 ? { withdrawnAt: at(entry.withdrawnAt) } : {}
1082
+ }));
1083
+ return {
1084
+ recorded: roster.recorded,
1085
+ skills: shift(roster.skills),
1086
+ agents: shift(roster.agents),
1087
+ tools: shift(roster.tools),
1088
+ mcpServers: shift(roster.mcpServers)
1089
+ };
1090
+ }
1091
+ function foldBranchAvailability(roster, branch, branchId, spawnIndex) {
1092
+ if (!branch.recorded) return;
1093
+ roster.recorded = true;
1094
+ const merge = (into, from) => {
1095
+ const known = offeredNames(into);
1096
+ for (const entry of from) {
1097
+ if (known.has(entry.name)) continue;
1098
+ known.add(entry.name);
1099
+ into.push({
1100
+ ...entry,
1101
+ offeredAt: spawnIndex,
1102
+ branchId,
1103
+ ...entry.withdrawnAt !== void 0 ? { withdrawnAt: void 0 } : {}
1104
+ });
1105
+ }
1106
+ };
1107
+ merge(roster.skills, branch.skills);
1108
+ merge(roster.agents, branch.agents);
1109
+ merge(roster.tools, branch.tools);
1110
+ merge(roster.mcpServers, branch.mcpServers);
1111
+ }
1112
+
1113
+ // src/tracevals/artifacts/availability.ts
1114
+ var empty = () => ({ offered: 0, used: 0, unused: 0 });
1115
+ var UNROSTERED = /* @__PURE__ */ new Set([
1116
+ "project-rules",
1117
+ "slash-command"
1118
+ ]);
1119
+ function coverAvailability(trace, coverage, options = {}) {
1120
+ const roster = trace.availability;
1121
+ if (!roster.recorded) {
1122
+ return {
1123
+ report: { recorded: false, skills: empty(), agents: empty(), listed: false },
1124
+ coverage: coverage.map(
1125
+ (entry) => UNROSTERED.has(entry.kind) ? entry : { ...entry, availability: "unknown" }
1126
+ )
1127
+ };
1128
+ }
1129
+ const offeredSkills = offeredNames(roster.skills);
1130
+ const offeredAgents = offeredNames(roster.agents);
1131
+ const offeredFor = (kind) => kind === "skill" ? offeredSkills : offeredAgents;
1132
+ const usedSkills = /* @__PURE__ */ new Set();
1133
+ const usedAgents = /* @__PURE__ */ new Set();
1134
+ const annotated = coverage.map((entry) => {
1135
+ if (UNROSTERED.has(entry.kind)) return entry;
1136
+ const offered = offeredFor(entry.kind).has(entry.ref);
1137
+ if (offered) {
1138
+ (entry.kind === "skill" ? usedSkills : usedAgents).add(entry.ref);
1139
+ }
1140
+ return {
1141
+ ...entry,
1142
+ availability: offered ? "offered-and-used" : "not-offered"
1143
+ };
1144
+ });
1145
+ const listUnused = options.listUnused === true;
1146
+ const unusedRows = [];
1147
+ const collect = (kind, used) => {
1148
+ let unused = 0;
1149
+ const seen = /* @__PURE__ */ new Set();
1150
+ for (const entry of kind === "skill" ? roster.skills : roster.agents) {
1151
+ if (seen.has(entry.name)) continue;
1152
+ seen.add(entry.name);
1153
+ if (used.has(entry.name)) continue;
1154
+ unused += 1;
1155
+ if (!listUnused) continue;
1156
+ unusedRows.push({
1157
+ ref: entry.name,
1158
+ kind,
1159
+ resolved: false,
1160
+ tried: [],
1161
+ note: entry.description === void 0 ? "offered, never used" : `offered, never used \u2014 ${firstLine(entry.description)}`,
1162
+ availability: "offered-not-used"
1163
+ });
1164
+ }
1165
+ return unused;
1166
+ };
1167
+ const skillsUnused = collect("skill", usedSkills);
1168
+ const agentsUnused = collect("agent", usedAgents);
1169
+ return {
1170
+ report: {
1171
+ recorded: true,
1172
+ skills: {
1173
+ offered: offeredSkills.size,
1174
+ used: usedSkills.size,
1175
+ unused: skillsUnused
1176
+ },
1177
+ agents: {
1178
+ offered: offeredAgents.size,
1179
+ used: usedAgents.size,
1180
+ unused: agentsUnused
1181
+ },
1182
+ listed: listUnused
1183
+ },
1184
+ coverage: [...annotated, ...unusedRows]
1185
+ };
1186
+ }
1187
+ function firstLine(description) {
1188
+ const line = description.split("\n")[0] ?? "";
1189
+ return line.length > 160 ? `${line.slice(0, 157)}...` : line;
1190
+ }
1191
+
1192
+ // src/tracevals/artifacts/fs.ts
1193
+ import { access, readdir, readFile as readFile3, stat } from "node:fs/promises";
1194
+ import { dirname as dirname3, join as join2, resolve as resolve3, sep as sep2 } from "node:path";
1195
+ function segments(path) {
1196
+ return path.split(sep2).join("/").split("/");
1197
+ }
1198
+ var PRUNED_DIRS = /* @__PURE__ */ new Set([
1199
+ "node_modules",
1200
+ ".git",
1201
+ "dist",
1202
+ "build",
1203
+ "coverage",
1204
+ ".manni",
1205
+ ".tmp",
1206
+ ".next",
1207
+ ".venv"
1208
+ ]);
1209
+ async function findGitRoot(start) {
1210
+ let current = resolve3(start);
1211
+ for (let depth = 0; depth < 64; depth += 1) {
1212
+ try {
1213
+ await access(join2(current, ".git"));
1214
+ return current;
1215
+ } catch {
1216
+ }
1217
+ const parent = dirname3(current);
1218
+ if (parent === current) return null;
1219
+ current = parent;
1220
+ }
1221
+ return null;
1222
+ }
1223
+ async function safeRead(path) {
1224
+ try {
1225
+ return await readFile3(path, "utf-8");
1226
+ } catch {
1227
+ return null;
1228
+ }
1229
+ }
1230
+ async function safeMtime(path) {
1231
+ try {
1232
+ const info = await stat(path);
1233
+ return info.mtime.toISOString();
1234
+ } catch {
1235
+ return null;
1236
+ }
1237
+ }
1238
+ async function findInTree(root, match) {
1239
+ let entries;
1240
+ try {
1241
+ entries = await readdir(root, { withFileTypes: true, recursive: true });
1242
+ } catch {
1243
+ return null;
1244
+ }
1245
+ for (const entry of entries) {
1246
+ if (!entry.isFile()) continue;
1247
+ const full = join2(entry.parentPath, entry.name);
1248
+ if (match(full)) return full;
1249
+ }
1250
+ return null;
1251
+ }
1252
+ async function listInTree(root, match, options = {}) {
1253
+ const prune = options.prune ?? PRUNED_DIRS;
1254
+ const maxDepth = options.maxDepth ?? 24;
1255
+ const found = [];
1256
+ async function walk(dir, depth) {
1257
+ if (depth > maxDepth) return;
1258
+ let entries;
1259
+ try {
1260
+ entries = await readdir(dir, { withFileTypes: true });
1261
+ } catch {
1262
+ return;
1263
+ }
1264
+ for (const entry of entries) {
1265
+ const full = join2(dir, entry.name);
1266
+ if (entry.isDirectory()) {
1267
+ if (prune.has(entry.name)) continue;
1268
+ await walk(full, depth + 1);
1269
+ } else if (entry.isFile() && match(full)) {
1270
+ found.push(full);
1271
+ }
1272
+ }
1273
+ }
1274
+ await walk(resolve3(root), 0);
1275
+ return found.sort();
1276
+ }
1277
+
1278
+ // src/tracevals/artifacts/resolve.ts
1279
+ import { basename, dirname as dirname4, join as join3, resolve as resolve4 } from "node:path";
1280
+ var PROJECT_RULES_FILENAMES = [
1281
+ "CLAUDE.md",
1282
+ "AGENTS.md",
1283
+ "GEMINI.md"
1284
+ ];
1285
+ var BUILTIN_AGENTS = /* @__PURE__ */ new Set([
1286
+ "general-purpose",
1287
+ "claude",
1288
+ "Explore",
1289
+ "Plan",
1290
+ "statusline-setup",
1291
+ "fork"
1292
+ ]);
1293
+ async function resolveArtifacts(trace, options = {}) {
1294
+ const projectDir = resolve4(options.projectDir ?? trace.cwd);
1295
+ const userDir = configDir(options.env);
1296
+ const projectRoot = resolve4(
1297
+ options.projectRoot ?? await findGitRoot(projectDir) ?? projectDir
1298
+ );
1299
+ const artifacts = [];
1300
+ const coverage = [];
1301
+ const warnings = [];
1302
+ const seenPaths = /* @__PURE__ */ new Set();
1303
+ const seenRefs = /* @__PURE__ */ new Set();
1304
+ const add = (artifact) => {
1305
+ if (seenPaths.has(artifact.path)) return;
1306
+ seenPaths.add(artifact.path);
1307
+ artifacts.push(artifact);
1308
+ };
1309
+ const addSkill = (name, artifact, tried) => {
1310
+ const refKey = `skill:${name}`;
1311
+ if (seenRefs.has(refKey)) return;
1312
+ seenRefs.add(refKey);
1313
+ add(artifact);
1314
+ coverage.push({
1315
+ ref: name,
1316
+ kind: "skill",
1317
+ resolved: true,
1318
+ path: artifact.path,
1319
+ tried
1320
+ });
1321
+ };
1322
+ for (const invocation of trace.skillInvocations) {
1323
+ if (invocation.via === "command-injection") {
1324
+ const refKey2 = `command:${invocation.name}`;
1325
+ if (seenRefs.has(refKey2)) continue;
1326
+ seenRefs.add(refKey2);
1327
+ const command = await resolveSlashCommand(
1328
+ invocation.name,
1329
+ projectDir,
1330
+ userDir
1331
+ );
1332
+ if (command.artifact) {
1333
+ add(command.artifact);
1334
+ coverage.push({
1335
+ ref: invocation.name,
1336
+ kind: "slash-command",
1337
+ resolved: true,
1338
+ path: command.artifact.path,
1339
+ tried: command.tried
1340
+ });
1341
+ continue;
1342
+ }
1343
+ const skill = await resolveSkill(invocation.name, projectDir, userDir);
1344
+ if (skill.artifact) {
1345
+ addSkill(invocation.name, skill.artifact, skill.tried);
1346
+ continue;
1347
+ }
1348
+ coverage.push({
1349
+ ref: invocation.name,
1350
+ kind: "slash-command",
1351
+ resolved: false,
1352
+ tried: [...command.tried, ...skill.tried],
1353
+ note: "built-in slash command, or not installed here (no definition file)"
1354
+ });
1355
+ continue;
1356
+ }
1357
+ const refKey = `skill:${invocation.name}`;
1358
+ if (seenRefs.has(refKey)) continue;
1359
+ const { artifact, tried } = await resolveSkill(
1360
+ invocation.name,
1361
+ projectDir,
1362
+ userDir
1363
+ );
1364
+ if (artifact) {
1365
+ addSkill(invocation.name, artifact, tried);
1366
+ } else {
1367
+ seenRefs.add(refKey);
1368
+ coverage.push({ ref: invocation.name, kind: "skill", resolved: false, tried });
1369
+ warnings.push(
1370
+ `skill "${invocation.name}" was invoked but no SKILL.md was found (${tried.length} location(s) tried)`
1371
+ );
1372
+ }
1373
+ }
1374
+ for (const spawn of trace.agentSpawns) {
1375
+ const refKey = `agent:${spawn.subagentType}`;
1376
+ if (seenRefs.has(refKey)) continue;
1377
+ seenRefs.add(refKey);
1378
+ if (BUILTIN_AGENTS.has(spawn.subagentType)) {
1379
+ coverage.push({
1380
+ ref: spawn.subagentType,
1381
+ kind: "agent",
1382
+ resolved: false,
1383
+ tried: [],
1384
+ note: "built-in agent (no definition file)"
1385
+ });
1386
+ continue;
1387
+ }
1388
+ const { artifact, tried } = await resolveAgent(
1389
+ spawn.subagentType,
1390
+ projectDir,
1391
+ userDir
1392
+ );
1393
+ if (artifact) {
1394
+ add(artifact);
1395
+ coverage.push({
1396
+ ref: spawn.subagentType,
1397
+ kind: "agent",
1398
+ resolved: true,
1399
+ path: artifact.path,
1400
+ tried
1401
+ });
1402
+ } else {
1403
+ coverage.push({
1404
+ ref: spawn.subagentType,
1405
+ kind: "agent",
1406
+ resolved: false,
1407
+ tried
1408
+ });
1409
+ warnings.push(
1410
+ `agent "${spawn.subagentType}" was spawned but no definition file was found (${tried.length} location(s) tried)`
1411
+ );
1412
+ }
1413
+ }
1414
+ const rules = await resolveProjectRules(projectDir, projectRoot);
1415
+ for (const rule of rules.artifacts) add(rule);
1416
+ const rulesEntry = {
1417
+ ref: "project rules",
1418
+ kind: "project-rules",
1419
+ resolved: rules.artifacts.length > 0,
1420
+ tried: rules.tried,
1421
+ ...rules.artifacts.length === 0 ? { note: "no CLAUDE.md or AGENTS.md found" } : {}
1422
+ };
1423
+ coverage.push(rulesEntry);
1424
+ await annotateStaleness(
1425
+ trace,
1426
+ coverage,
1427
+ /* @__PURE__ */ new Map([[rulesEntry, rules.artifacts.map((a) => a.path)]]),
1428
+ warnings,
1429
+ projectRoot,
1430
+ projectDir,
1431
+ options.manifest
1432
+ );
1433
+ const availability = coverAvailability(trace, coverage, {
1434
+ ...options.reportUnusedArtifacts !== void 0 ? { listUnused: options.reportUnusedArtifacts } : {}
1435
+ });
1436
+ return {
1437
+ artifacts,
1438
+ coverage: availability.coverage,
1439
+ availability: availability.report,
1440
+ warnings
1441
+ };
1442
+ }
1443
+ async function annotateStaleness(trace, coverage, extraPaths, warnings, projectRoot, projectDir, manifest) {
1444
+ const endedAt = trace.endedAt !== void 0 ? Date.parse(trace.endedAt) : Number.NaN;
1445
+ const haveEnd = Number.isFinite(endedAt);
1446
+ if (!haveEnd && manifest === void 0) return;
1447
+ const base = manifest === void 0 ? projectRoot : joinBase(manifest, coverage, extraPaths, [
1448
+ projectDir,
1449
+ projectRoot,
1450
+ manifest.root
1451
+ ]);
1452
+ const guessed = [];
1453
+ const changed = [];
1454
+ let joined = false;
1455
+ for (const entry of coverage) {
1456
+ if (!entry.resolved) continue;
1457
+ const paths = extraPaths.get(entry) ?? (entry.path ? [entry.path] : []);
1458
+ if (paths.length === 0) continue;
1459
+ let newest;
1460
+ let outside = false;
1461
+ const digests = /* @__PURE__ */ new Map();
1462
+ for (const path of paths) {
1463
+ const mtime = await safeMtime(path);
1464
+ if (mtime !== null && (newest === void 0 || mtime > newest)) {
1465
+ newest = mtime;
1466
+ }
1467
+ if (manifest === void 0) continue;
1468
+ const rel = relPosix(base, path);
1469
+ if (rel === null) {
1470
+ outside = true;
1471
+ continue;
1472
+ }
1473
+ const digest = await hashFile(path);
1474
+ if (digest !== null) digests.set(rel, digest);
1475
+ }
1476
+ if (newest !== void 0) entry.modifiedAt = newest;
1477
+ const check = manifest === void 0 ? {
1478
+ status: "skipped",
1479
+ reason: "no session manifest for this trace"
1480
+ } : outside && digests.size === 0 ? {
1481
+ status: "skipped",
1482
+ reason: "outside the project root, so no session manifest could record it"
1483
+ } : checkContent(manifest, digests);
1484
+ entry.contentCheck = check;
1485
+ if (check.status !== "skipped") joined = true;
1486
+ if (check.status === "mismatch") {
1487
+ entry.stale = true;
1488
+ changed.push(entry.ref);
1489
+ continue;
1490
+ }
1491
+ if (check.status === "match") {
1492
+ entry.stale = false;
1493
+ continue;
1494
+ }
1495
+ if (!haveEnd || newest === void 0) continue;
1496
+ entry.stale = Date.parse(newest) > endedAt;
1497
+ if (entry.stale) guessed.push(entry.ref);
1498
+ }
1499
+ if (changed.length > 0) {
1500
+ warnings.push(
1501
+ `${changed.length} artifact(s) changed since the session started (${changed.join(", ")}) \u2014 the session manifest recorded a different sha256, so their evals are not the instructions this session followed.`
1502
+ );
1503
+ }
1504
+ if (guessed.length > 0) {
1505
+ warnings.push(
1506
+ `${guessed.length} artifact(s) were modified after the session ended (${guessed.join(", ")}) \u2014 their evals may not be the instructions this session followed. mtime is a heuristic, not content identity.`
1507
+ );
1508
+ }
1509
+ if (manifest !== void 0 && manifest.artifacts.length > 0 && !joined) {
1510
+ warnings.push(
1511
+ `the session manifest matched no artifact in this run \u2014 it recorded ${manifest.artifacts.length} artifact(s) relative to "${manifest.root}", and nothing resolved here lines up with them, so every hash check fell back to the mtime heuristic. Check that --project names the directory the session ran in.`
1512
+ );
1513
+ }
1514
+ }
1515
+ function joinBase(manifest, coverage, extraPaths, candidates) {
1516
+ const recorded = new Set(manifest.artifacts.map((a) => a.path));
1517
+ const paths = coverage.filter((entry) => entry.resolved).flatMap((entry) => extraPaths.get(entry) ?? (entry.path ? [entry.path] : []));
1518
+ let best = candidates[0];
1519
+ let bestScore = -1;
1520
+ for (const candidate of candidates) {
1521
+ if (typeof candidate !== "string" || candidate.length === 0) continue;
1522
+ let score = 0;
1523
+ for (const path of paths) {
1524
+ let rel;
1525
+ try {
1526
+ rel = relPosix(candidate, path);
1527
+ } catch {
1528
+ continue;
1529
+ }
1530
+ if (rel !== null && recorded.has(rel)) score += 1;
1531
+ }
1532
+ if (score > bestScore) {
1533
+ best = candidate;
1534
+ bestScore = score;
1535
+ }
1536
+ }
1537
+ return best;
1538
+ }
1539
+ async function resolveSkill(name, projectDir, userDir) {
1540
+ const tried = [];
1541
+ const [pluginName, shortName] = splitPluginRef(name);
1542
+ const candidates = [];
1543
+ if (pluginName === null) {
1544
+ for (const dir of ["skills", join3("src", "skills"), join3(".claude", "skills")]) {
1545
+ candidates.push({
1546
+ path: join3(projectDir, dir, shortName, "SKILL.md"),
1547
+ origin: "project"
1548
+ });
1549
+ }
1550
+ candidates.push({
1551
+ path: join3(userDir, "skills", shortName, "SKILL.md"),
1552
+ origin: "user"
1553
+ });
1554
+ }
1555
+ for (const candidate of candidates) {
1556
+ tried.push(candidate.path);
1557
+ const content = await safeRead(candidate.path);
1558
+ if (content !== null) {
1559
+ return {
1560
+ artifact: {
1561
+ name,
1562
+ type: "skill",
1563
+ path: candidate.path,
1564
+ content,
1565
+ origin: candidate.origin
1566
+ },
1567
+ tried
1568
+ };
1569
+ }
1570
+ }
1571
+ const pluginRoot = join3(userDir, "plugins");
1572
+ tried.push(join3(pluginRoot, "**", "skills", shortName, "SKILL.md"));
1573
+ const found = await findInTree(
1574
+ pluginRoot,
1575
+ (path) => basename(path) === "SKILL.md" && basename(dirname4(path)) === shortName && (pluginName === null || hasPathSegment(path, pluginName))
1576
+ );
1577
+ if (found) {
1578
+ const content = await safeRead(found);
1579
+ if (content !== null) {
1580
+ return {
1581
+ artifact: { name, type: "skill", path: found, content, origin: "plugin" },
1582
+ tried
1583
+ };
1584
+ }
1585
+ }
1586
+ return { artifact: null, tried };
1587
+ }
1588
+ async function resolveSlashCommand(name, projectDir, userDir) {
1589
+ const tried = [];
1590
+ const [pluginName, shortName] = splitPluginRef(name);
1591
+ const made = (path, content, origin) => ({
1592
+ artifact: { name, type: "slash-command", path, content, origin },
1593
+ tried
1594
+ });
1595
+ if (pluginName === null) {
1596
+ const dirs = [
1597
+ { dir: join3(projectDir, ".claude", "commands"), origin: "project" },
1598
+ { dir: join3(userDir, "commands"), origin: "user" }
1599
+ ];
1600
+ for (const { dir, origin } of dirs) {
1601
+ const path = join3(dir, `${shortName}.md`);
1602
+ tried.push(path);
1603
+ const content = await safeRead(path);
1604
+ if (content !== null) return made(path, content, origin);
1605
+ }
1606
+ for (const { dir, origin } of dirs) {
1607
+ tried.push(join3(dir, "**", `${shortName}.md`));
1608
+ const found2 = await findInTree(
1609
+ dir,
1610
+ (path) => basename(path) === `${shortName}.md`
1611
+ );
1612
+ if (found2 === null) continue;
1613
+ const content = await safeRead(found2);
1614
+ if (content !== null) return made(found2, content, origin);
1615
+ }
1616
+ return { artifact: null, tried };
1617
+ }
1618
+ const pluginRoot = join3(userDir, "plugins");
1619
+ tried.push(join3(pluginRoot, "**", "commands", `${shortName}.md`));
1620
+ const found = await findInTree(
1621
+ pluginRoot,
1622
+ (path) => basename(path) === `${shortName}.md` && segments(path).includes("commands") && hasPathSegment(path, pluginName)
1623
+ );
1624
+ if (found !== null) {
1625
+ const content = await safeRead(found);
1626
+ if (content !== null) return made(found, content, "plugin");
1627
+ }
1628
+ return { artifact: null, tried };
1629
+ }
1630
+ function splitPluginRef(name) {
1631
+ const at = name.indexOf(":");
1632
+ if (at === -1) return [null, name];
1633
+ return [name.slice(0, at), name.slice(at + 1)];
1634
+ }
1635
+ function hasPathSegment(path, pluginName) {
1636
+ return segments(path).includes(pluginName);
1637
+ }
1638
+ async function resolveAgent(subagentType, projectDir, userDir) {
1639
+ const tried = [];
1640
+ const shortName = splitPluginRef(subagentType)[1];
1641
+ const candidates = [
1642
+ { path: join3(projectDir, ".claude", "agents", `${shortName}.md`), origin: "project" },
1643
+ { path: join3(projectDir, "agents", `${shortName}.md`), origin: "project" },
1644
+ { path: join3(userDir, "agents", `${shortName}.md`), origin: "user" }
1645
+ ];
1646
+ for (const candidate of candidates) {
1647
+ tried.push(candidate.path);
1648
+ const content = await safeRead(candidate.path);
1649
+ if (content !== null) {
1650
+ return {
1651
+ artifact: {
1652
+ name: subagentType,
1653
+ type: "agent",
1654
+ path: candidate.path,
1655
+ content,
1656
+ origin: candidate.origin
1657
+ },
1658
+ tried
1659
+ };
1660
+ }
1661
+ }
1662
+ return { artifact: null, tried };
1663
+ }
1664
+ async function resolveProjectRules(projectDir, projectRoot) {
1665
+ const artifacts = [];
1666
+ const tried = [];
1667
+ const dirs = [];
1668
+ let current = projectDir;
1669
+ for (let depth = 0; depth < 32; depth += 1) {
1670
+ dirs.push(current);
1671
+ if (current === projectRoot) break;
1672
+ const parent = dirname4(current);
1673
+ if (parent === current) break;
1674
+ current = parent;
1675
+ }
1676
+ dirs.push(join3(projectRoot, ".claude"));
1677
+ for (const dir of dirs) {
1678
+ for (const filename of PROJECT_RULES_FILENAMES) {
1679
+ const path = join3(dir, filename);
1680
+ tried.push(path);
1681
+ const content = await safeRead(path);
1682
+ if (content !== null) {
1683
+ artifacts.push({
1684
+ name: filename,
1685
+ type: "project-rules",
1686
+ path,
1687
+ content,
1688
+ origin: "project"
1689
+ });
1690
+ }
1691
+ }
1692
+ }
1693
+ return { artifacts, tried };
1694
+ }
1695
+
1696
+ // src/tracevals/evals/schema.ts
1697
+ import { Ajv2020 as Ajv20203 } from "ajv/dist/2020.js";
1698
+ var artifactEvalsSchema = __default;
1699
+ var ARTIFACT_EVALS_SCHEMA_ID = __default.$id;
1700
+ var ajv3 = new Ajv20203({ allErrors: true, allowUnionTypes: true });
1701
+ ajv3.addKeyword({ keyword: "x-manni-location" });
1702
+ var validateArtifact = ajv3.compile(artifactEvalsSchema);
1703
+ function subjectOf(e) {
1704
+ switch (e.keyword) {
1705
+ case "required":
1706
+ return e.params.missingProperty;
1707
+ case "additionalProperties":
1708
+ return e.params.additionalProperty;
1709
+ case "format":
1710
+ return e.params.format;
1711
+ case "type":
1712
+ return e.params.type;
1713
+ default:
1714
+ return void 0;
1715
+ }
1716
+ }
1717
+ function artifactEvalsErrors(data, lineFor) {
1718
+ if (validateArtifact(data)) return [];
1719
+ const errors = [];
1720
+ for (const raw of validateArtifact.errors ?? []) {
1721
+ errors.push(toFieldError(raw, lineFor));
1722
+ }
1723
+ return errors;
1724
+ }
1725
+ function toFieldError(e, lineFor) {
1726
+ const subject = subjectOf(e);
1727
+ const message = subject !== void 0 && (e.keyword === "required" || e.keyword === "additionalProperties") ? `${e.message ?? "is invalid"} ("${subject}")` : e.message ?? "is invalid";
1728
+ const line = lineFor(e.instancePath);
1729
+ return {
1730
+ schema: ARTIFACT_EVALS_SCHEMA_ID,
1731
+ instancePath: e.instancePath,
1732
+ message,
1733
+ keyword: e.keyword,
1734
+ ...subject !== void 0 ? { subject } : {},
1735
+ ...line !== void 0 ? { line } : {}
1736
+ };
1737
+ }
1738
+
1739
+ // src/tracevals/evals/extract.ts
1740
+ var CLAIMED_KEYS = ["evals", "eval-skip", "meta-provenance"];
1741
+ var PREFIX_EXCEPTION = "eval-skip";
1742
+ function isPlainObject(value) {
1743
+ return typeof value === "object" && value !== null && !Array.isArray(value);
1744
+ }
1745
+ function extractEvals(artifact, supplied) {
1746
+ const extracted = supplied ?? extractFrontmatter(artifact.content, "markdown");
1747
+ const metadata = extracted.data.metadata;
1748
+ const bag = isPlainObject(metadata) ? metadata : void 0;
1749
+ const declared = bag !== void 0 && bag.evals !== void 0;
1750
+ if (!extracted.present && metadata === void 0) {
1751
+ return { evals: [], errors: [], declared: false, skip: false, proposedBy: /* @__PURE__ */ new Map() };
1752
+ }
1753
+ const typoErrors = reservedPrefixErrors(bag, extracted.lineFor);
1754
+ if (typoErrors.length > 0) {
1755
+ return { evals: [], errors: typoErrors, declared, skip: false, proposedBy: /* @__PURE__ */ new Map() };
1756
+ }
1757
+ const wellFormed = metadata === void 0 || bag !== void 0;
1758
+ const claimsNothing = bag === void 0 || !CLAIMED_KEYS.some((key) => bag[key] !== void 0);
1759
+ if (wellFormed && claimsNothing) {
1760
+ return {
1761
+ evals: [],
1762
+ errors: [],
1763
+ declared: false,
1764
+ skip: false,
1765
+ proposedBy: /* @__PURE__ */ new Map()
1766
+ };
1767
+ }
1768
+ const errors = artifactEvalsErrors(extracted.data, extracted.lineFor);
1769
+ if (errors.length > 0) {
1770
+ return { evals: [], errors, declared, skip: false, proposedBy: /* @__PURE__ */ new Map() };
1771
+ }
1772
+ const valid = bag ?? {};
1773
+ const skip = valid["eval-skip"] === true;
1774
+ return {
1775
+ evals: normalizeBlock(valid.evals),
1776
+ errors: [],
1777
+ declared,
1778
+ skip,
1779
+ proposedBy: readProvenance(valid["meta-provenance"])
1780
+ };
1781
+ }
1782
+ function readProvenance(raw) {
1783
+ const out = /* @__PURE__ */ new Map();
1784
+ for (const entry of metaProvenanceEntries(raw)) {
1785
+ const by = entry["generated-by"];
1786
+ for (const id of entry.evals) {
1787
+ const list = out.get(id) ?? [];
1788
+ if (!list.includes(by)) list.push(by);
1789
+ out.set(id, list);
1790
+ }
1791
+ }
1792
+ return out;
1793
+ }
1794
+ function reservedPrefixErrors(bag, lineFor) {
1795
+ if (bag === void 0) return [];
1796
+ const errors = [];
1797
+ for (const key of Object.keys(bag)) {
1798
+ if (!/^eval-/i.test(key) || key === PREFIX_EXCEPTION) continue;
1799
+ const line = lineFor(`/metadata/${key}`);
1800
+ errors.push({
1801
+ schema: ARTIFACT_EVALS_SCHEMA_ID,
1802
+ instancePath: `/metadata/${key}`,
1803
+ message: `unrecognized "eval-" key; the only one this vocabulary claims under that prefix is ${PREFIX_EXCEPTION} (its other members are ${CLAIMED_KEYS.filter((k) => k !== PREFIX_EXCEPTION).join(" and ")})`,
1804
+ keyword: "additionalProperties",
1805
+ subject: key,
1806
+ ...line !== void 0 ? { line } : {}
1807
+ });
1808
+ }
1809
+ return errors;
1810
+ }
1811
+ function normalizeBlock(raw) {
1812
+ if (typeof raw === "string") return [normalizeEntry(raw, 0)];
1813
+ if (!Array.isArray(raw)) return [];
1814
+ return raw.map((entry, index) => normalizeEntry(entry, index));
1815
+ }
1816
+ function normalizeEntry(raw, index) {
1817
+ if (typeof raw === "string") {
1818
+ return {
1819
+ id: `eval-${index + 1}`,
1820
+ assertion: raw,
1821
+ type: "regression",
1822
+ grader: "ai",
1823
+ severity: "error"
1824
+ };
1825
+ }
1826
+ const obj = raw;
1827
+ const entry = {
1828
+ id: obj.id,
1829
+ // Optional since artifact-evals proposal.2: a `tool-usage` criterion says
1830
+ // everything in `options`, and demanding a sentence no grader reads was
1831
+ // the one place this vocabulary disagreed with the page side. The schema
1832
+ // still requires it for `ai`, `human`, and a bare entry (which defaults to
1833
+ // `ai`), so anything that actually needs an assertion still has one.
1834
+ ...typeof obj.assertion === "string" ? { assertion: obj.assertion } : {},
1835
+ type: obj.type ?? "regression",
1836
+ grader: typeof obj.grader === "string" ? obj.grader : "ai",
1837
+ severity: obj.severity ?? "error"
1838
+ };
1839
+ if (typeof obj.provider === "string") entry.provider = obj.provider;
1840
+ if (typeof obj.model === "string") entry.model = obj.model;
1841
+ if (typeof obj.runs === "number") entry.runs = obj.runs;
1842
+ if (typeof obj.weight === "number") entry.weight = obj.weight;
1843
+ if (typeof obj.target === "string" || obj.target && typeof obj.target === "object") {
1844
+ entry.target = obj.target;
1845
+ }
1846
+ if (obj.options && typeof obj.options === "object") {
1847
+ entry.options = obj.options;
1848
+ }
1849
+ if (typeof obj.evidence === "string") entry.evidence = obj.evidence;
1850
+ if (obj.examples && typeof obj.examples === "object") {
1851
+ entry.examples = normalizeExamples(obj.examples);
1852
+ }
1853
+ if (obj.skip === true) entry.skip = true;
1854
+ if (obj["severity-map"] && typeof obj["severity-map"] === "object") {
1855
+ entry.severityMap = obj["severity-map"];
1856
+ }
1857
+ if (Array.isArray(obj.command)) entry.command = obj.command;
1858
+ if (Array.isArray(obj["success-exit-codes"])) {
1859
+ entry.successExitCodes = obj["success-exit-codes"];
1860
+ }
1861
+ if (typeof obj["timeout-ms"] === "number") {
1862
+ entry.timeoutMs = obj["timeout-ms"];
1863
+ }
1864
+ if (typeof obj["generated-assertion-hash"] === "string") {
1865
+ entry.generatedAssertionHash = obj["generated-assertion-hash"];
1866
+ }
1867
+ return entry;
1868
+ }
1869
+ function normalizeExamples(raw) {
1870
+ const out = {};
1871
+ const pass = anchorList(raw.pass);
1872
+ const fail = anchorList(raw.fail);
1873
+ if (pass) out.pass = pass;
1874
+ if (fail) out.fail = fail;
1875
+ return out;
1876
+ }
1877
+ function anchorList(raw) {
1878
+ if (typeof raw === "string") return [raw];
1879
+ if (Array.isArray(raw)) return raw.filter((e) => typeof e === "string");
1880
+ return void 0;
1881
+ }
1882
+
1883
+ export {
1884
+ reencryptMetadata,
1885
+ reencryptData,
1886
+ metaPageView,
1887
+ declaredAt,
1888
+ EXECUTION_GRANTS,
1889
+ isExecutionGrant,
1890
+ unknownGrantsMessage,
1891
+ allowExecutionMessage,
1892
+ NOT_GRANTED_REASON,
1893
+ grantsFor,
1894
+ configuredGrants,
1895
+ REDACTION_PATTERNS,
1896
+ CUSTOM_PLACEHOLDER,
1897
+ makeRedactor,
1898
+ compileRedactPatterns,
1899
+ MANIFEST_VERSION,
1900
+ DEFAULT_CAPTURE_DIR,
1901
+ DEFAULT_LABELS_FILE,
1902
+ labelKey,
1903
+ parseLabels,
1904
+ loadLabels,
1905
+ DEFAULT_CONFIG_FILENAME,
1906
+ CONFIG_SECTION_KEY,
1907
+ parseConfig2 as parseConfig,
1908
+ discoverConfig,
1909
+ loadConfig2 as loadConfig,
1910
+ newAvailability,
1911
+ availableAt,
1912
+ offeredNames,
1913
+ joinDescriptions,
1914
+ newReplay,
1915
+ applyAvailabilityRecord,
1916
+ remapAvailability,
1917
+ foldBranchAvailability,
1918
+ coverAvailability,
1919
+ segments,
1920
+ PRUNED_DIRS,
1921
+ findGitRoot,
1922
+ safeRead,
1923
+ safeMtime,
1924
+ findInTree,
1925
+ listInTree,
1926
+ sha256Hex,
1927
+ hashFile,
1928
+ relPosix,
1929
+ safeSessionId,
1930
+ manifestPathFor,
1931
+ siblingManifestPath,
1932
+ writeManifest,
1933
+ readManifest,
1934
+ findManifest,
1935
+ checkContent,
1936
+ PROJECT_RULES_FILENAMES,
1937
+ resolveArtifacts,
1938
+ ARTIFACT_EVALS_SCHEMA_ID,
1939
+ extractEvals
1940
+ };
1941
+ //# sourceMappingURL=chunk-MOQXWXSG.js.map