@hawkeyexl/manni 4.2.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.
- package/README.md +2 -2
- package/dist/batch-7JJWXWSF.js +18 -0
- package/dist/batch-7JJWXWSF.js.map +1 -0
- package/dist/calibrate-IA2ARFMH.js +13 -0
- package/dist/calibrate-IA2ARFMH.js.map +1 -0
- package/dist/capture-C32JXIFN.js +15 -0
- package/dist/capture-C32JXIFN.js.map +1 -0
- package/dist/chunk-3R23C5DT.js +120 -0
- package/dist/chunk-3R23C5DT.js.map +1 -0
- package/dist/chunk-CI2GOU3A.js +4447 -0
- package/dist/chunk-CI2GOU3A.js.map +1 -0
- package/dist/{chunk-TH565O2N.js → chunk-DP2ZA3WH.js} +898 -278
- package/dist/chunk-DP2ZA3WH.js.map +1 -0
- package/dist/chunk-ESJ5W7EV.js +297 -0
- package/dist/chunk-ESJ5W7EV.js.map +1 -0
- package/dist/chunk-IOYXPBCL.js +37 -0
- package/dist/chunk-IOYXPBCL.js.map +1 -0
- package/dist/chunk-KTSJ2ZHA.js +40 -0
- package/dist/chunk-KTSJ2ZHA.js.map +1 -0
- package/dist/{chunk-BNHQWIAE.js → chunk-MFDD3YGU.js} +2 -2
- package/dist/chunk-MOQXWXSG.js +1941 -0
- package/dist/chunk-MOQXWXSG.js.map +1 -0
- package/dist/{chunk-UYDRDFYF.js → chunk-O6RVQKK6.js} +1943 -909
- package/dist/chunk-O6RVQKK6.js.map +1 -0
- package/dist/chunk-QROCFW5S.js +174 -0
- package/dist/chunk-QROCFW5S.js.map +1 -0
- package/dist/chunk-RVVCA4WL.js +379 -0
- package/dist/chunk-RVVCA4WL.js.map +1 -0
- package/dist/cli.js +422 -42
- package/dist/cli.js.map +1 -1
- package/dist/docmeta.js +3 -2
- package/dist/docmeta.js.map +1 -1
- package/dist/index.d.ts +3873 -795
- package/dist/index.js +292 -4
- package/dist/index.js.map +1 -1
- package/dist/picker-6HRMETE7.js +12 -0
- package/dist/picker-6HRMETE7.js.map +1 -0
- package/package.json +3 -1
- package/plugin/tracevals/.claude-plugin/plugin.json +9 -0
- package/plugin/tracevals/hooks/hooks.json +17 -0
- package/dist/chunk-TH565O2N.js.map +0 -1
- package/dist/chunk-UYDRDFYF.js.map +0 -1
- /package/dist/{chunk-BNHQWIAE.js.map → chunk-MFDD3YGU.js.map} +0 -0
|
@@ -0,0 +1,297 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
discoverArtifacts
|
|
4
|
+
} from "./chunk-QROCFW5S.js";
|
|
5
|
+
import {
|
|
6
|
+
MANIFEST_VERSION,
|
|
7
|
+
hashFile,
|
|
8
|
+
loadConfig,
|
|
9
|
+
makeRedactor,
|
|
10
|
+
manifestPathFor,
|
|
11
|
+
relPosix,
|
|
12
|
+
sha256Hex,
|
|
13
|
+
writeManifest
|
|
14
|
+
} from "./chunk-MOQXWXSG.js";
|
|
15
|
+
import {
|
|
16
|
+
TracevalsError
|
|
17
|
+
} from "./chunk-3R23C5DT.js";
|
|
18
|
+
|
|
19
|
+
// src/tracevals/commands/capture.ts
|
|
20
|
+
import { resolve as resolve2 } from "node:path";
|
|
21
|
+
|
|
22
|
+
// src/tracevals/capture/build.ts
|
|
23
|
+
import { spawn } from "node:child_process";
|
|
24
|
+
import { hostname, platform, userInfo } from "node:os";
|
|
25
|
+
import { resolve } from "node:path";
|
|
26
|
+
async function buildManifest(options) {
|
|
27
|
+
const root = resolve(options.root);
|
|
28
|
+
const redact = makeRedactor(options.redact ?? []);
|
|
29
|
+
const discovered = await discoverArtifacts({ root });
|
|
30
|
+
const artifacts = [];
|
|
31
|
+
for (const found of discovered.artifacts) {
|
|
32
|
+
const rel = relPosix(root, found.artifact.path);
|
|
33
|
+
if (rel === null) continue;
|
|
34
|
+
const digest = await hashFile(found.artifact.path);
|
|
35
|
+
if (digest === null) continue;
|
|
36
|
+
artifacts.push({
|
|
37
|
+
name: found.artifact.name,
|
|
38
|
+
type: found.artifact.type,
|
|
39
|
+
path: rel,
|
|
40
|
+
sha256: digest,
|
|
41
|
+
bytes: Buffer.byteLength(found.artifact.content, "utf-8")
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
artifacts.sort((a, b) => a.path < b.path ? -1 : a.path > b.path ? 1 : 0);
|
|
45
|
+
const git2 = options.git === false ? void 0 : options.git ?? await readGit(root);
|
|
46
|
+
return {
|
|
47
|
+
version: MANIFEST_VERSION,
|
|
48
|
+
sessionId: options.sessionId,
|
|
49
|
+
capturedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
50
|
+
hookEvent: options.hookEvent ?? "manual",
|
|
51
|
+
...options.reason !== void 0 ? { reason: redact(options.reason) } : {},
|
|
52
|
+
...options.transcriptPath !== void 0 ? { transcriptPath: redact(options.transcriptPath) } : {},
|
|
53
|
+
root: redact(root),
|
|
54
|
+
...git2 !== void 0 ? {
|
|
55
|
+
git: {
|
|
56
|
+
sha: git2.sha,
|
|
57
|
+
...git2.branch !== void 0 ? { branch: redact(git2.branch) } : {},
|
|
58
|
+
dirty: git2.dirty
|
|
59
|
+
}
|
|
60
|
+
} : {},
|
|
61
|
+
device: { id: deviceId(), platform: platform() },
|
|
62
|
+
tool: { name: "manni-tracevals", version: options.version ?? "unknown" },
|
|
63
|
+
artifacts,
|
|
64
|
+
config: redactDeep(options.config ?? {}, redact)
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
function deviceId() {
|
|
68
|
+
let user = "";
|
|
69
|
+
try {
|
|
70
|
+
user = userInfo().username;
|
|
71
|
+
} catch {
|
|
72
|
+
}
|
|
73
|
+
return sha256Hex(`${hostname()} ${user} ${platform()}`).slice(0, 16);
|
|
74
|
+
}
|
|
75
|
+
function redactDeep(value, redact) {
|
|
76
|
+
if (typeof value === "string") return redact(value);
|
|
77
|
+
if (Array.isArray(value)) return value.map((v) => redactDeep(v, redact));
|
|
78
|
+
if (value !== null && typeof value === "object") {
|
|
79
|
+
const out = {};
|
|
80
|
+
for (const [key, member] of Object.entries(value)) {
|
|
81
|
+
out[key] = redactDeep(member, redact);
|
|
82
|
+
}
|
|
83
|
+
return out;
|
|
84
|
+
}
|
|
85
|
+
return value;
|
|
86
|
+
}
|
|
87
|
+
async function readGit(root) {
|
|
88
|
+
const sha = await git(["rev-parse", "HEAD"], root);
|
|
89
|
+
if (sha === null || !/^[0-9a-f]{40}$/.test(sha)) return void 0;
|
|
90
|
+
const branch = await git(["rev-parse", "--abbrev-ref", "HEAD"], root);
|
|
91
|
+
const status = await git(
|
|
92
|
+
["status", "--porcelain", "--untracked-files=no"],
|
|
93
|
+
root
|
|
94
|
+
);
|
|
95
|
+
return {
|
|
96
|
+
sha,
|
|
97
|
+
// Detached HEAD reports the literal "HEAD", which is not a branch name.
|
|
98
|
+
...branch !== null && branch !== "HEAD" ? { branch } : {},
|
|
99
|
+
dirty: status !== null && status.length > 0
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
function git(args, cwd, timeoutMs = 5e3) {
|
|
103
|
+
return new Promise((settle) => {
|
|
104
|
+
let done = false;
|
|
105
|
+
let child;
|
|
106
|
+
try {
|
|
107
|
+
child = spawn("git", args, { cwd, shell: false, windowsHide: true });
|
|
108
|
+
} catch {
|
|
109
|
+
settle(null);
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
const timer = setTimeout(() => {
|
|
113
|
+
child.kill();
|
|
114
|
+
finish(null);
|
|
115
|
+
}, timeoutMs);
|
|
116
|
+
const finish = (value) => {
|
|
117
|
+
if (done) return;
|
|
118
|
+
done = true;
|
|
119
|
+
clearTimeout(timer);
|
|
120
|
+
settle(value);
|
|
121
|
+
};
|
|
122
|
+
let out = "";
|
|
123
|
+
child.stdout.on("data", (d) => {
|
|
124
|
+
out += d.toString("utf-8");
|
|
125
|
+
});
|
|
126
|
+
child.stderr.resume();
|
|
127
|
+
child.on("error", () => {
|
|
128
|
+
finish(null);
|
|
129
|
+
});
|
|
130
|
+
child.on("close", (code) => {
|
|
131
|
+
finish(code === 0 ? out.trim() : null);
|
|
132
|
+
});
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// src/tracevals/capture/hook.ts
|
|
137
|
+
var REASON_KEYS = ["how", "why", "source", "reason"];
|
|
138
|
+
function str(record, key) {
|
|
139
|
+
const value = record[key];
|
|
140
|
+
return typeof value === "string" && value.length > 0 ? value : void 0;
|
|
141
|
+
}
|
|
142
|
+
function parseHookPayload(raw) {
|
|
143
|
+
let parsed;
|
|
144
|
+
try {
|
|
145
|
+
parsed = JSON.parse(raw);
|
|
146
|
+
} catch (err) {
|
|
147
|
+
throw new TracevalsError(
|
|
148
|
+
`hook payload is not JSON: ${err instanceof Error ? err.message : String(err)}`
|
|
149
|
+
);
|
|
150
|
+
}
|
|
151
|
+
if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
152
|
+
throw new TracevalsError(
|
|
153
|
+
"hook payload must be a JSON object; Claude Code writes one object on stdin"
|
|
154
|
+
);
|
|
155
|
+
}
|
|
156
|
+
const record = parsed;
|
|
157
|
+
let reason;
|
|
158
|
+
for (const key of REASON_KEYS) {
|
|
159
|
+
reason = str(record, key);
|
|
160
|
+
if (reason !== void 0) break;
|
|
161
|
+
}
|
|
162
|
+
return {
|
|
163
|
+
...str(record, "session_id") !== void 0 ? { sessionId: str(record, "session_id") } : {},
|
|
164
|
+
...str(record, "transcript_path") !== void 0 ? { transcriptPath: str(record, "transcript_path") } : {},
|
|
165
|
+
...str(record, "cwd") !== void 0 ? { cwd: str(record, "cwd") } : {},
|
|
166
|
+
...str(record, "hook_event_name") !== void 0 ? { hookEvent: str(record, "hook_event_name") } : {},
|
|
167
|
+
...reason !== void 0 ? { reason } : {}
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
function readStdin(stream = process.stdin, options = {}) {
|
|
171
|
+
const maxBytes = options.maxBytes ?? 1e6;
|
|
172
|
+
const timeoutMs = options.timeoutMs ?? 1500;
|
|
173
|
+
if (stream.isTTY) return Promise.resolve("");
|
|
174
|
+
return new Promise((settle, fail) => {
|
|
175
|
+
const chunks = [];
|
|
176
|
+
let total = 0;
|
|
177
|
+
let done = false;
|
|
178
|
+
const stop = () => {
|
|
179
|
+
clearTimeout(timer);
|
|
180
|
+
stream.off("data", onData);
|
|
181
|
+
stream.off("end", onEnd);
|
|
182
|
+
stream.off("error", onError);
|
|
183
|
+
stream.pause();
|
|
184
|
+
};
|
|
185
|
+
const finish = () => {
|
|
186
|
+
if (done) return;
|
|
187
|
+
done = true;
|
|
188
|
+
stop();
|
|
189
|
+
settle(Buffer.concat(chunks).toString("utf-8"));
|
|
190
|
+
};
|
|
191
|
+
const abort = (err) => {
|
|
192
|
+
if (done) return;
|
|
193
|
+
done = true;
|
|
194
|
+
stop();
|
|
195
|
+
fail(err);
|
|
196
|
+
};
|
|
197
|
+
const onData = (chunk) => {
|
|
198
|
+
const buf = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
|
|
199
|
+
total += buf.length;
|
|
200
|
+
if (total > maxBytes) {
|
|
201
|
+
abort(
|
|
202
|
+
new TracevalsError(
|
|
203
|
+
`hook payload exceeded ${maxBytes} bytes; this is not a Claude Code hook envelope`
|
|
204
|
+
)
|
|
205
|
+
);
|
|
206
|
+
return;
|
|
207
|
+
}
|
|
208
|
+
chunks.push(buf);
|
|
209
|
+
};
|
|
210
|
+
const onEnd = () => {
|
|
211
|
+
finish();
|
|
212
|
+
};
|
|
213
|
+
const onError = () => {
|
|
214
|
+
finish();
|
|
215
|
+
};
|
|
216
|
+
const timer = setTimeout(finish, timeoutMs);
|
|
217
|
+
stream.on("data", onData);
|
|
218
|
+
stream.on("end", onEnd);
|
|
219
|
+
stream.on("error", onError);
|
|
220
|
+
stream.resume();
|
|
221
|
+
});
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
// src/tracevals/commands/capture.ts
|
|
225
|
+
async function runCapture(options = {}) {
|
|
226
|
+
const raw = options.stdin ?? await readStdin();
|
|
227
|
+
const hookMode = raw.trim().length > 0;
|
|
228
|
+
const payload = hookMode ? parseHookPayload(raw) : {};
|
|
229
|
+
const sessionId = options.sessionId ?? payload.sessionId;
|
|
230
|
+
if (sessionId === void 0) {
|
|
231
|
+
throw new TracevalsError(
|
|
232
|
+
"no session id: pipe a Claude Code hook payload on stdin, or pass --session-id"
|
|
233
|
+
);
|
|
234
|
+
}
|
|
235
|
+
const root = resolve2(options.project ?? payload.cwd ?? process.cwd());
|
|
236
|
+
const config = await loadConfig(options.configDir ?? root, {
|
|
237
|
+
...options.config === void 0 ? {} : { configPath: options.config },
|
|
238
|
+
...options.noConfig === void 0 ? {} : { noConfig: options.noConfig }
|
|
239
|
+
});
|
|
240
|
+
const manifest = await buildManifest({
|
|
241
|
+
sessionId,
|
|
242
|
+
root,
|
|
243
|
+
hookEvent: payload.hookEvent ?? "manual",
|
|
244
|
+
...payload.reason !== void 0 ? { reason: payload.reason } : {},
|
|
245
|
+
...payload.transcriptPath !== void 0 ? { transcriptPath: payload.transcriptPath } : {},
|
|
246
|
+
config,
|
|
247
|
+
redact: config.judge.redact,
|
|
248
|
+
...options.version !== void 0 ? { version: options.version } : {}
|
|
249
|
+
});
|
|
250
|
+
const path = options.out ?? manifestPathFor(resolve2(root, config.capture.dir), sessionId);
|
|
251
|
+
try {
|
|
252
|
+
await writeManifest(path, manifest);
|
|
253
|
+
} catch (err) {
|
|
254
|
+
throw new TracevalsError(
|
|
255
|
+
`could not write ${path}: ${err instanceof Error ? err.message : String(err)}`
|
|
256
|
+
);
|
|
257
|
+
}
|
|
258
|
+
const rendered = options.format === "json" ? JSON.stringify({ path, manifest }, null, 2) : renderCapture(path, manifest);
|
|
259
|
+
return {
|
|
260
|
+
manifest,
|
|
261
|
+
path,
|
|
262
|
+
rendered,
|
|
263
|
+
// The whole point of the split: a SessionStart hook's stdout becomes model
|
|
264
|
+
// context, so hook mode writes none.
|
|
265
|
+
stdout: hookMode ? "" : rendered,
|
|
266
|
+
stderr: hookMode ? rendered : "",
|
|
267
|
+
exitCode: 0
|
|
268
|
+
};
|
|
269
|
+
}
|
|
270
|
+
function renderCapture(path, manifest) {
|
|
271
|
+
const lines = [
|
|
272
|
+
`Captured session ${manifest.sessionId}`,
|
|
273
|
+
` manifest ${path}`,
|
|
274
|
+
` artifacts ${manifest.artifacts.length} hashed`
|
|
275
|
+
];
|
|
276
|
+
if (manifest.git !== void 0) {
|
|
277
|
+
lines.push(
|
|
278
|
+
` commit ${manifest.git.sha}${manifest.git.branch ? ` (${manifest.git.branch})` : ""}${manifest.git.dirty ? " \u2014 working tree dirty" : ""}`
|
|
279
|
+
);
|
|
280
|
+
} else {
|
|
281
|
+
lines.push(" commit not a git repository, or git is unavailable");
|
|
282
|
+
}
|
|
283
|
+
lines.push(` device ${manifest.device.id} (${manifest.device.platform})`);
|
|
284
|
+
return lines.join("\n");
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
export {
|
|
288
|
+
buildManifest,
|
|
289
|
+
deviceId,
|
|
290
|
+
redactDeep,
|
|
291
|
+
readGit,
|
|
292
|
+
parseHookPayload,
|
|
293
|
+
readStdin,
|
|
294
|
+
runCapture,
|
|
295
|
+
renderCapture
|
|
296
|
+
};
|
|
297
|
+
//# sourceMappingURL=chunk-ESJ5W7EV.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/tracevals/commands/capture.ts","../src/tracevals/capture/build.ts","../src/tracevals/capture/hook.ts"],"sourcesContent":["/**\n * `manni tracevals capture` — record what the project's instruction artifacts\n * said, and at which commit, at the moment a session started (ADR 01024).\n *\n * **This is the one write path `run` never takes.** CLAUDE.md states that\n * evaluation is read-only, so the manifest is written by a separate,\n * explicitly-invoked command — the same shape as `fill`. `run` only ever reads\n * a manifest, and degrades cleanly when there is none.\n *\n * Designed to be wired to a `SessionStart` hook, which constrains it in two\n * ways that are not obvious:\n *\n * - **Nothing goes to stdout in hook mode.** Claude Code adds a\n * SessionStart hook's stdout to the model's context, so a report printed\n * there would be a side effect on the very session being observed. In hook\n * mode the report goes to stderr, where an exit-0 hook's output is a debug\n * line and nothing more.\n * - **The transcript is never read.** `transcript_path` is recorded as a\n * correlation key only. At SessionStart the file barely exists, and the\n * reference warns that it lags the conversation in any case.\n */\nimport { resolve } from \"node:path\";\nimport { loadConfig } from \"../core/config.js\";\nimport { buildManifest } from \"../capture/build.js\";\nimport { manifestPathFor, writeManifest } from \"../capture/manifest.js\";\nimport type { SessionManifest } from \"../capture/types.js\";\nimport { parseHookPayload, readStdin } from \"../capture/hook.js\";\nimport type { SummaryFormat } from \"../reporters/index.js\";\nimport { TracevalsError } from \"../types.js\";\n\nexport interface CaptureCommandOptions {\n /**\n * The hook payload. Undefined means \"read stdin\"; an empty string means\n * \"there was none\", which is the hand-run path.\n */\n stdin?: string;\n /** Overrides the payload's `cwd` as the project root to scan. */\n project?: string;\n /** Overrides the payload's `session_id`. */\n sessionId?: string;\n /** Write here instead of `<project>/<capture.dir>/<session-id>.json`. */\n out?: string;\n /** Directory holding manni.config.yaml; defaults to the project root. */\n configDir?: string;\n /** `-c/--config`: read this file instead of discovering one. */\n config?: string;\n /** `--no-config`: skip discovery and run on the built-in defaults. */\n noConfig?: boolean;\n format?: SummaryFormat;\n /**\n * Version recorded in the manifest. Passed in rather than read from\n * package.json here: this module is bundled into an unpredictable chunk\n * path, so any relative require of it resolves differently in dist than in\n * src. The CLI, which is not bundled away, supplies it.\n */\n version?: string;\n}\n\nexport interface CaptureCommandResult {\n manifest: SessionManifest;\n /** Where the manifest was written. */\n path: string;\n /** The report, wherever it is destined for. */\n rendered: string;\n /** What the caller should print to stdout — empty in hook mode. */\n stdout: string;\n /** What the caller should print to stderr — the report, in hook mode. */\n stderr: string;\n exitCode: number;\n}\n\nexport async function runCapture(\n options: CaptureCommandOptions = {},\n): Promise<CaptureCommandResult> {\n const raw = options.stdin ?? (await readStdin());\n const hookMode = raw.trim().length > 0;\n const payload = hookMode ? parseHookPayload(raw) : {};\n\n const sessionId = options.sessionId ?? payload.sessionId;\n if (sessionId === undefined) {\n // Without an id the manifest has no name and no join key, and writing one\n // under a guessed name would produce evidence nothing could ever find.\n throw new TracevalsError(\n \"no session id: pipe a Claude Code hook payload on stdin, or pass --session-id\",\n );\n }\n\n const root = resolve(options.project ?? payload.cwd ?? process.cwd());\n const config = await loadConfig(options.configDir ?? root, {\n ...(options.config === undefined ? {} : { configPath: options.config }),\n ...(options.noConfig === undefined ? {} : { noConfig: options.noConfig }),\n });\n\n const manifest = await buildManifest({\n sessionId,\n root,\n hookEvent: payload.hookEvent ?? \"manual\",\n ...(payload.reason !== undefined ? { reason: payload.reason } : {}),\n ...(payload.transcriptPath !== undefined\n ? { transcriptPath: payload.transcriptPath }\n : {}),\n config,\n redact: config.judge.redact,\n ...(options.version !== undefined ? { version: options.version } : {}),\n });\n\n const path =\n options.out ??\n manifestPathFor(resolve(root, config.capture.dir), sessionId);\n try {\n await writeManifest(path, manifest);\n } catch (err) {\n throw new TracevalsError(\n `could not write ${path}: ${err instanceof Error ? err.message : String(err)}`,\n );\n }\n\n const rendered =\n options.format === \"json\"\n ? JSON.stringify({ path, manifest }, null, 2)\n : renderCapture(path, manifest);\n\n return {\n manifest,\n path,\n rendered,\n // The whole point of the split: a SessionStart hook's stdout becomes model\n // context, so hook mode writes none.\n stdout: hookMode ? \"\" : rendered,\n stderr: hookMode ? rendered : \"\",\n exitCode: 0,\n };\n}\n\nexport function renderCapture(\n path: string,\n manifest: SessionManifest,\n): string {\n const lines = [\n `Captured session ${manifest.sessionId}`,\n ` manifest ${path}`,\n ` artifacts ${manifest.artifacts.length} hashed`,\n ];\n if (manifest.git !== undefined) {\n lines.push(\n ` commit ${manifest.git.sha}${manifest.git.branch ? ` (${manifest.git.branch})` : \"\"}${\n manifest.git.dirty ? \" — working tree dirty\" : \"\"\n }`,\n );\n } else {\n lines.push(\" commit not a git repository, or git is unavailable\");\n }\n lines.push(` device ${manifest.device.id} (${manifest.device.platform})`);\n return lines.join(\"\\n\");\n}\n","/**\n * Building a session manifest (ADR 01024) — the producing half.\n *\n * Separate from `capture/manifest.ts` because this side reaches for\n * `discoverArtifacts`, which imports `artifacts/resolve.ts`, which consumes a\n * manifest. Producing and consuming therefore cannot share a module.\n */\nimport { spawn } from \"node:child_process\";\nimport { hostname, platform, userInfo } from \"node:os\";\nimport { resolve } from \"node:path\";\nimport { discoverArtifacts } from \"../artifacts/discover.js\";\nimport { makeRedactor, type Redactor } from \"../judge/redact.js\";\nimport { hashFile, relPosix, sha256Hex } from \"./manifest.js\";\nimport {\n MANIFEST_VERSION,\n type ManifestArtifact,\n type ManifestGit,\n type SessionManifest,\n} from \"./types.js\";\n\nexport interface BuildManifestOptions {\n sessionId: string;\n /** Project root to scan, and the base every artifact path is relative to. */\n root: string;\n hookEvent?: string;\n reason?: string;\n transcriptPath?: string;\n /** The resolved config section, recorded as provenance. */\n config?: unknown;\n /** `judge.redact` sources, applied to the manifest's free text (ADR 01020). */\n redact?: readonly string[];\n version?: string;\n /** Test seam: `false` skips shelling out to git. */\n git?: ManifestGit | false;\n}\n\n/**\n * Hash every instruction artifact the project holds, at the commit it holds\n * them at.\n *\n * The population is `discoverArtifacts`' — the same one `fill` authors against\n * — so capture and authoring never disagree about what an instruction artifact\n * is. That scope is **project-only** by its own design note, so a user-level or\n * plugin artifact is simply absent from the manifest, and its hash check at run\n * time degrades to the mtime heuristic rather than to a wrong answer.\n *\n * **Redaction covers the free text and never a join key.** `root`,\n * `transcriptPath`, `git.branch`, `reason` and the recorded `config` go through\n * the `judge.redact` redactor; `sessionId`, an artifact's `name` and `path`,\n * and every `sha256` do not. A redactor applied to a join key would turn an\n * exact check into a silent `skipped` — the one failure mode this whole feature\n * exists to remove — and those keys are the project's own vocabulary and its\n * repository-relative paths, not somewhere a credential lives. A secret in an\n * artifact path is a problem to fix in the path, the same call ADR 01020 makes\n * about a secret in a `SKILL.md`.\n */\nexport async function buildManifest(\n options: BuildManifestOptions,\n): Promise<SessionManifest> {\n const root = resolve(options.root);\n const redact = makeRedactor(options.redact ?? []);\n\n const discovered = await discoverArtifacts({ root });\n const artifacts: ManifestArtifact[] = [];\n for (const found of discovered.artifacts) {\n const rel = relPosix(root, found.artifact.path);\n if (rel === null) continue;\n const digest = await hashFile(found.artifact.path);\n // An artifact that cannot be read is one the manifest says nothing about,\n // which degrades to the mtime heuristic at run time. Never a crash.\n if (digest === null) continue;\n artifacts.push({\n name: found.artifact.name,\n type: found.artifact.type,\n path: rel,\n sha256: digest,\n bytes: Buffer.byteLength(found.artifact.content, \"utf-8\"),\n });\n }\n // Stable order, so two captures of an unchanged tree produce the same bytes.\n artifacts.sort((a, b) => (a.path < b.path ? -1 : a.path > b.path ? 1 : 0));\n\n const git =\n options.git === false ? undefined : (options.git ?? (await readGit(root)));\n\n return {\n version: MANIFEST_VERSION,\n sessionId: options.sessionId,\n capturedAt: new Date().toISOString(),\n hookEvent: options.hookEvent ?? \"manual\",\n ...(options.reason !== undefined ? { reason: redact(options.reason) } : {}),\n ...(options.transcriptPath !== undefined\n ? { transcriptPath: redact(options.transcriptPath) }\n : {}),\n root: redact(root),\n ...(git !== undefined\n ? {\n git: {\n sha: git.sha,\n ...(git.branch !== undefined ? { branch: redact(git.branch) } : {}),\n dirty: git.dirty,\n },\n }\n : {}),\n device: { id: deviceId(), platform: platform() },\n tool: { name: \"manni-tracevals\", version: options.version ?? \"unknown\" },\n artifacts,\n config: redactDeep(options.config ?? {}, redact),\n };\n}\n\n/**\n * A stable, opaque per-machine id. Not the hostname: \"was this captured here?\"\n * is the whole question, and answering it does not require shipping the name of\n * the machine into a file that travels with the repository.\n */\nexport function deviceId(): string {\n let user = \"\";\n try {\n user = userInfo().username;\n } catch {\n // A container with no passwd entry throws; the digest is still stable.\n }\n return sha256Hex(`${hostname()} ${user} ${platform()}`).slice(0, 16);\n}\n\n/**\n * Redact every string in a JSON-shaped value. Used for the recorded config\n * only — never for a join key.\n */\nexport function redactDeep(value: unknown, redact: Redactor): unknown {\n if (typeof value === \"string\") return redact(value);\n if (Array.isArray(value)) return value.map((v) => redactDeep(v, redact));\n if (value !== null && typeof value === \"object\") {\n const out: Record<string, unknown> = {};\n for (const [key, member] of Object.entries(value)) {\n out[key] = redactDeep(member, redact);\n }\n return out;\n }\n return value;\n}\n\n/**\n * What `git` says about `root`, or nothing. Every failure mode — git absent,\n * not a repository, a timeout — degrades to an absent block, because a manifest\n * without a SHA is still a manifest with hashes in it.\n */\nexport async function readGit(root: string): Promise<ManifestGit | undefined> {\n const sha = await git([\"rev-parse\", \"HEAD\"], root);\n if (sha === null || !/^[0-9a-f]{40}$/.test(sha)) return undefined;\n const branch = await git([\"rev-parse\", \"--abbrev-ref\", \"HEAD\"], root);\n const status = await git(\n [\"status\", \"--porcelain\", \"--untracked-files=no\"],\n root,\n );\n return {\n sha,\n // Detached HEAD reports the literal \"HEAD\", which is not a branch name.\n ...(branch !== null && branch !== \"HEAD\" ? { branch } : {}),\n dirty: status !== null && status.length > 0,\n };\n}\n\n/** One `git` invocation, argv-only and bounded — the shape ADR 01011 uses. */\nfunction git(\n args: string[],\n cwd: string,\n timeoutMs = 5000,\n): Promise<string | null> {\n return new Promise((settle) => {\n let done = false;\n let child;\n try {\n // shell:false — nothing here is ever parsed as shell syntax.\n child = spawn(\"git\", args, { cwd, shell: false, windowsHide: true });\n } catch {\n settle(null);\n return;\n }\n // `finish` is declared below the timer because it clears it; the timer\n // callback only runs after both exist.\n const timer = setTimeout(() => {\n child.kill();\n finish(null);\n }, timeoutMs);\n const finish = (value: string | null): void => {\n if (done) return;\n done = true;\n clearTimeout(timer);\n settle(value);\n };\n let out = \"\";\n child.stdout.on(\"data\", (d: Buffer) => {\n out += d.toString(\"utf-8\");\n });\n child.stderr.resume();\n child.on(\"error\", () => { finish(null); });\n child.on(\"close\", (code) => { finish(code === 0 ? out.trim() : null); });\n });\n}\n","/**\n * The Claude Code hook envelope, read from stdin (ADR 01024).\n *\n * Claude Code writes a hook command **one JSON object on stdin** and closes it.\n * Four members are documented as common to every hook event and are the only\n * ones `capture` depends on:\n *\n * | Member | Meaning |\n * |---|---|\n * | `session_id` | the session identifier — the manifest's filename and its join key |\n * | `transcript_path` | where the session file will be written; a correlation key only |\n * | `cwd` | the working directory the hook fired in — the project root to scan |\n * | `hook_event_name` | `SessionStart`, `SessionEnd`, … |\n *\n * The member naming *why* a session started or ended is **not** depended on.\n * The reference describes it as \"how the session started\" / \"why the session\n * ended\" and it has been published as `source`/`reason` and as `how`/`why`\n * across versions, so every spelling is accepted and the value is recorded as\n * provenance rather than used as a key. This is the same tolerance\n * `trace/detect.ts` already applies to `sessionId` versus `session_id`.\n *\n * Everything is optional and every member is type-checked before it is\n * believed: this payload is produced by another program, and a member of the\n * wrong shape must be ignored rather than propagated into a manifest.\n */\nimport { TracevalsError } from \"../types.js\";\n\nexport interface HookPayload {\n sessionId?: string;\n transcriptPath?: string;\n cwd?: string;\n hookEvent?: string;\n /** Why the session started or ended, whichever spelling arrived. */\n reason?: string;\n}\n\n/** Spellings of the start/end reason, most recent first. */\nconst REASON_KEYS = [\"how\", \"why\", \"source\", \"reason\"] as const;\n\nfunction str(record: Record<string, unknown>, key: string): string | undefined {\n const value = record[key];\n return typeof value === \"string\" && value.length > 0 ? value : undefined;\n}\n\n/**\n * Parse one hook payload. Throws only when the text is not a JSON object at\n * all — an object missing members is an ordinary, tolerable payload, while\n * text that is not one means `capture` was wired to something that is not a\n * Claude Code hook and saying so is more useful than writing an empty manifest.\n */\nexport function parseHookPayload(raw: string): HookPayload {\n let parsed: unknown;\n try {\n parsed = JSON.parse(raw);\n } catch (err) {\n throw new TracevalsError(\n `hook payload is not JSON: ${err instanceof Error ? err.message : String(err)}`,\n );\n }\n if (parsed === null || typeof parsed !== \"object\" || Array.isArray(parsed)) {\n throw new TracevalsError(\n \"hook payload must be a JSON object; Claude Code writes one object on stdin\",\n );\n }\n const record = parsed as Record<string, unknown>;\n let reason: string | undefined;\n for (const key of REASON_KEYS) {\n reason = str(record, key);\n if (reason !== undefined) break;\n }\n return {\n ...(str(record, \"session_id\") !== undefined\n ? { sessionId: str(record, \"session_id\") }\n : {}),\n ...(str(record, \"transcript_path\") !== undefined\n ? { transcriptPath: str(record, \"transcript_path\") }\n : {}),\n ...(str(record, \"cwd\") !== undefined\n ? { cwd: str(record, \"cwd\") }\n : {}),\n ...(str(record, \"hook_event_name\") !== undefined\n ? { hookEvent: str(record, \"hook_event_name\") }\n : {}),\n ...(reason !== undefined ? { reason } : {}),\n };\n}\n\nexport interface ReadStdinOptions {\n /** Cap on the payload. A hook envelope is a handful of short strings. */\n maxBytes?: number;\n /**\n * How long to wait for a payload that never arrives. **Not optional\n * politeness** — an inherited stdin that is a pipe nobody ever writes to or\n * closes is a hang, and any script or CI runner that does not redirect stdin\n * hands `capture` exactly that. A hook writes and closes immediately, so this\n * only ever elapses when there was no payload to begin with.\n */\n timeoutMs?: number;\n}\n\n/**\n * Read a hook payload from stdin, or return \"\" when there is none.\n *\n * Never rejects on an idle stream: \"nothing arrived\" is the ordinary hand-run\n * case, and it is answered by an empty string.\n */\nexport function readStdin(\n stream: NodeJS.ReadStream = process.stdin,\n options: ReadStdinOptions = {},\n): Promise<string> {\n const maxBytes = options.maxBytes ?? 1_000_000;\n const timeoutMs = options.timeoutMs ?? 1500;\n // A TTY means a person is at the keyboard, not a hook. Reading would block\n // on input nobody intends to type.\n if (stream.isTTY) return Promise.resolve(\"\");\n\n return new Promise((settle, fail) => {\n const chunks: Buffer[] = [];\n let total = 0;\n let done = false;\n\n const stop = (): void => {\n clearTimeout(timer);\n stream.off(\"data\", onData);\n stream.off(\"end\", onEnd);\n stream.off(\"error\", onError);\n stream.pause();\n };\n const finish = (): void => {\n if (done) return;\n done = true;\n stop();\n settle(Buffer.concat(chunks).toString(\"utf-8\"));\n };\n const abort = (err: Error): void => {\n if (done) return;\n done = true;\n stop();\n fail(err);\n };\n\n const onData = (chunk: Buffer | string): void => {\n const buf = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);\n total += buf.length;\n if (total > maxBytes) {\n abort(\n new TracevalsError(\n `hook payload exceeded ${maxBytes} bytes; this is not a Claude Code hook envelope`,\n ),\n );\n return;\n }\n chunks.push(buf);\n };\n const onEnd = (): void => { finish(); };\n // A closed or unreadable stdin is \"no payload\", not a failure: `capture`\n // has to survive being wired up in an environment nobody anticipated.\n const onError = (): void => { finish(); };\n\n const timer = setTimeout(finish, timeoutMs);\n stream.on(\"data\", onData);\n stream.on(\"end\", onEnd);\n stream.on(\"error\", onError);\n stream.resume();\n });\n}\n"],"mappings":"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","names":["resolve","git","resolve"]}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __export = (target, all) => {
|
|
4
|
+
for (var name in all)
|
|
5
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
6
|
+
};
|
|
7
|
+
|
|
8
|
+
// src/shared/errors.ts
|
|
9
|
+
var ToolError = class extends Error {
|
|
10
|
+
/** clig.dev: 2 is operational/usage; 1 is reserved for findings. */
|
|
11
|
+
exitCode = 2;
|
|
12
|
+
constructor(message) {
|
|
13
|
+
super(message);
|
|
14
|
+
this.name = "ToolError";
|
|
15
|
+
}
|
|
16
|
+
};
|
|
17
|
+
function errorMessage(err) {
|
|
18
|
+
if (err instanceof Error) return err.message;
|
|
19
|
+
if (typeof err === "string") return err;
|
|
20
|
+
if (typeof err === "number" || typeof err === "bigint") return String(err);
|
|
21
|
+
if (typeof err === "object" && err !== null && "message" in err && typeof err.message === "string") {
|
|
22
|
+
return err.message;
|
|
23
|
+
}
|
|
24
|
+
try {
|
|
25
|
+
const json = JSON.stringify(err);
|
|
26
|
+
if (json !== void 0) return json;
|
|
27
|
+
} catch {
|
|
28
|
+
}
|
|
29
|
+
return String(err);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export {
|
|
33
|
+
__export,
|
|
34
|
+
ToolError,
|
|
35
|
+
errorMessage
|
|
36
|
+
};
|
|
37
|
+
//# sourceMappingURL=chunk-IOYXPBCL.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/shared/errors.ts"],"sourcesContent":["/**\n * The error every tool under the umbrella throws for an operational failure.\n *\n * Each tool keeps its own subclass (`DocmetaError`, …) so its tests and\n * library callers can match on it. The base is what the shared bin runner\n * matches on: a `ToolError` is reported as one line and exit 2, anything else\n * is a bug and keeps its stack trace.\n */\nexport class ToolError extends Error {\n /** clig.dev: 2 is operational/usage; 1 is reserved for findings. */\n readonly exitCode: number = 2;\n\n constructor(message: string) {\n super(message);\n this.name = \"ToolError\";\n }\n}\n\n/**\n * The message a caught value carries, whatever was thrown.\n *\n * JavaScript can throw anything, so `(err as Error).message` is `undefined`\n * for a thrown string, number or `null`, and \"undefined\" is what the user\n * reads. An Error, or anything with a string `message`, gives that message.\n * A string is its own message, and so is a number or a bigint, as `String()`\n * writes it: JSON would turn `NaN` and the infinities into `null`. Anything\n * else gives its JSON text, or its `String()` form when it will not\n * serialise.\n */\nexport function errorMessage(err: unknown): string {\n if (err instanceof Error) return err.message;\n if (typeof err === \"string\") return err;\n // Before the JSON step, which writes NaN and the infinities as \"null\" and\n // throws on a bigint.\n if (typeof err === \"number\" || typeof err === \"bigint\") return String(err);\n if (\n typeof err === \"object\" &&\n err !== null &&\n \"message\" in err &&\n typeof err.message === \"string\"\n ) {\n return err.message;\n }\n try {\n // Typed as returning a string, but `undefined`, a function or a symbol\n // gives `undefined`.\n const json = JSON.stringify(err) as string | undefined;\n if (json !== undefined) return json;\n } catch {\n // A cycle, or a bigint inside an object: fall through to the one form\n // that always exists.\n }\n return String(err);\n}\n"],"mappings":";;;;;;;;AAQO,IAAM,YAAN,cAAwB,MAAM;AAAA;AAAA,EAE1B,WAAmB;AAAA,EAE5B,YAAY,SAAiB;AAC3B,UAAM,OAAO;AACb,SAAK,OAAO;AAAA,EACd;AACF;AAaO,SAAS,aAAa,KAAsB;AACjD,MAAI,eAAe,MAAO,QAAO,IAAI;AACrC,MAAI,OAAO,QAAQ,SAAU,QAAO;AAGpC,MAAI,OAAO,QAAQ,YAAY,OAAO,QAAQ,SAAU,QAAO,OAAO,GAAG;AACzE,MACE,OAAO,QAAQ,YACf,QAAQ,QACR,aAAa,OACb,OAAO,IAAI,YAAY,UACvB;AACA,WAAO,IAAI;AAAA,EACb;AACA,MAAI;AAGF,UAAM,OAAO,KAAK,UAAU,GAAG;AAC/B,QAAI,SAAS,OAAW,QAAO;AAAA,EACjC,QAAQ;AAAA,EAGR;AACA,SAAO,OAAO,GAAG;AACnB;","names":[]}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
TracevalsError,
|
|
4
|
+
discoverTraces
|
|
5
|
+
} from "./chunk-3R23C5DT.js";
|
|
6
|
+
|
|
7
|
+
// src/tracevals/trace/picker.ts
|
|
8
|
+
async function inquirerPrompt(params) {
|
|
9
|
+
const { select } = await import("@inquirer/prompts");
|
|
10
|
+
return select(params);
|
|
11
|
+
}
|
|
12
|
+
function choiceFor(listing) {
|
|
13
|
+
const when = new Date(listing.mtimeMs).toISOString().slice(0, 16).replace("T", " ");
|
|
14
|
+
return {
|
|
15
|
+
name: `${when} ${listing.firstPrompt ?? "(no prompt)"}`,
|
|
16
|
+
value: listing.file,
|
|
17
|
+
description: `${listing.project ?? "unknown project"} \xB7 ${listing.sessionId ?? "?"}`
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
async function pickTrace(options = {}, prompt = inquirerPrompt) {
|
|
21
|
+
let listings = await discoverTraces({ limit: 25, ...options });
|
|
22
|
+
if (listings.length === 0 && !options.allProjects) {
|
|
23
|
+
listings = await discoverTraces({ limit: 25, ...options, allProjects: true });
|
|
24
|
+
}
|
|
25
|
+
if (listings.length === 0) {
|
|
26
|
+
throw new TracevalsError(
|
|
27
|
+
"no traces found in the session store; pass a trace file instead"
|
|
28
|
+
);
|
|
29
|
+
}
|
|
30
|
+
return prompt({
|
|
31
|
+
message: "Pick a trace to evaluate",
|
|
32
|
+
choices: listings.map(choiceFor)
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export {
|
|
37
|
+
choiceFor,
|
|
38
|
+
pickTrace
|
|
39
|
+
};
|
|
40
|
+
//# sourceMappingURL=chunk-KTSJ2ZHA.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/tracevals/trace/picker.ts"],"sourcesContent":["/**\n * Interactive trace picker (TTY only). The prompt function is injectable so\n * tests never touch a real terminal.\n */\nimport { TracevalsError } from \"../types.js\";\nimport {\n discoverTraces,\n type DiscoverOptions,\n type TraceListing,\n} from \"./discover.js\";\n\nexport interface PickerChoice {\n name: string;\n value: string;\n description?: string;\n}\n\nexport type PromptFn = (params: {\n message: string;\n choices: PickerChoice[];\n}) => Promise<string>;\n\nasync function inquirerPrompt(params: {\n message: string;\n choices: PickerChoice[];\n}): Promise<string> {\n const { select } = await import(\"@inquirer/prompts\");\n return select<string>(params);\n}\n\nexport function choiceFor(listing: TraceListing): PickerChoice {\n const when = new Date(listing.mtimeMs)\n .toISOString()\n .slice(0, 16)\n .replace(\"T\", \" \");\n return {\n name: `${when} ${listing.firstPrompt ?? \"(no prompt)\"}`,\n value: listing.file,\n description: `${listing.project ?? \"unknown project\"} · ${listing.sessionId ?? \"?\"}`,\n };\n}\n\nexport async function pickTrace(\n options: DiscoverOptions = {},\n prompt: PromptFn = inquirerPrompt,\n): Promise<string> {\n let listings = await discoverTraces({ limit: 25, ...options });\n if (listings.length === 0 && !options.allProjects) {\n // Nothing for this project — offer everything rather than a dead end.\n listings = await discoverTraces({ limit: 25, ...options, allProjects: true });\n }\n if (listings.length === 0) {\n throw new TracevalsError(\n \"no traces found in the session store; pass a trace file instead\",\n );\n }\n return prompt({\n message: \"Pick a trace to evaluate\",\n choices: listings.map(choiceFor),\n });\n}\n"],"mappings":";;;;;;;AAsBA,eAAe,eAAe,QAGV;AAClB,QAAM,EAAE,OAAO,IAAI,MAAM,OAAO,mBAAmB;AACnD,SAAO,OAAe,MAAM;AAC9B;AAEO,SAAS,UAAU,SAAqC;AAC7D,QAAM,OAAO,IAAI,KAAK,QAAQ,OAAO,EAClC,YAAY,EACZ,MAAM,GAAG,EAAE,EACX,QAAQ,KAAK,GAAG;AACnB,SAAO;AAAA,IACL,MAAM,GAAG,IAAI,KAAK,QAAQ,eAAe,aAAa;AAAA,IACtD,OAAO,QAAQ;AAAA,IACf,aAAa,GAAG,QAAQ,WAAW,iBAAiB,SAAM,QAAQ,aAAa,GAAG;AAAA,EACpF;AACF;AAEA,eAAsB,UACpB,UAA2B,CAAC,GAC5B,SAAmB,gBACF;AACjB,MAAI,WAAW,MAAM,eAAe,EAAE,OAAO,IAAI,GAAG,QAAQ,CAAC;AAC7D,MAAI,SAAS,WAAW,KAAK,CAAC,QAAQ,aAAa;AAEjD,eAAW,MAAM,eAAe,EAAE,OAAO,IAAI,GAAG,SAAS,aAAa,KAAK,CAAC;AAAA,EAC9E;AACA,MAAI,SAAS,WAAW,GAAG;AACzB,UAAM,IAAI;AAAA,MACR;AAAA,IACF;AAAA,EACF;AACA,SAAO,OAAO;AAAA,IACZ,SAAS;AAAA,IACT,SAAS,SAAS,IAAI,SAAS;AAAA,EACjC,CAAC;AACH;","names":[]}
|
|
@@ -70,7 +70,7 @@ import {
|
|
|
70
70
|
terminalConfirm,
|
|
71
71
|
toJsonText,
|
|
72
72
|
warn
|
|
73
|
-
} from "./chunk-
|
|
73
|
+
} from "./chunk-DP2ZA3WH.js";
|
|
74
74
|
|
|
75
75
|
// src/meta/cli.ts
|
|
76
76
|
import { existsSync } from "node:fs";
|
|
@@ -1502,4 +1502,4 @@ function buildProgram() {
|
|
|
1502
1502
|
export {
|
|
1503
1503
|
buildProgram
|
|
1504
1504
|
};
|
|
1505
|
-
//# sourceMappingURL=chunk-
|
|
1505
|
+
//# sourceMappingURL=chunk-MFDD3YGU.js.map
|