altimate-receipts 0.5.2 → 0.6.1
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/dist/{chunk-EKMFU3ES.js → chunk-543NGQTN.js} +2 -2
- package/dist/{chunk-GOLRNSZT.js → chunk-54J2FEKI.js} +41 -6
- package/dist/chunk-54J2FEKI.js.map +1 -0
- package/dist/{chunk-EYM5WETZ.js → chunk-DBQWQVZZ.js} +139 -19
- package/dist/chunk-DBQWQVZZ.js.map +1 -0
- package/dist/cli.js +284 -121
- package/dist/cli.js.map +1 -1
- package/dist/index.js +2 -2
- package/dist/mcp/server.js +2 -2
- package/package.json +1 -1
- package/dist/chunk-EYM5WETZ.js.map +0 -1
- package/dist/chunk-GOLRNSZT.js.map +0 -1
- /package/dist/{chunk-EKMFU3ES.js.map → chunk-543NGQTN.js.map} +0 -0
package/dist/cli.js
CHANGED
|
@@ -13,13 +13,13 @@ import {
|
|
|
13
13
|
renderShareMarkdown,
|
|
14
14
|
sliceByBranch,
|
|
15
15
|
toDsseEnvelope
|
|
16
|
-
} from "./chunk-
|
|
16
|
+
} from "./chunk-54J2FEKI.js";
|
|
17
17
|
import {
|
|
18
18
|
computeTrends,
|
|
19
19
|
deriveTargets,
|
|
20
20
|
renderTrends,
|
|
21
21
|
upsertTrendsSection
|
|
22
|
-
} from "./chunk-
|
|
22
|
+
} from "./chunk-543NGQTN.js";
|
|
23
23
|
import {
|
|
24
24
|
agentIds,
|
|
25
25
|
anyDetected,
|
|
@@ -43,10 +43,10 @@ import {
|
|
|
43
43
|
selectSummary,
|
|
44
44
|
upsertGuardrailsSection,
|
|
45
45
|
verifyBundle
|
|
46
|
-
} from "./chunk-
|
|
46
|
+
} from "./chunk-DBQWQVZZ.js";
|
|
47
47
|
|
|
48
48
|
// src/cli.ts
|
|
49
|
-
import { spawnSync as
|
|
49
|
+
import { spawnSync as spawnSync5 } from "child_process";
|
|
50
50
|
import {
|
|
51
51
|
existsSync as existsSync4,
|
|
52
52
|
mkdirSync as mkdirSync3,
|
|
@@ -57,7 +57,7 @@ import {
|
|
|
57
57
|
writeFileSync as writeFileSync3
|
|
58
58
|
} from "fs";
|
|
59
59
|
import { homedir } from "os";
|
|
60
|
-
import { join as
|
|
60
|
+
import { join as join7, relative } from "path";
|
|
61
61
|
import { pathToFileURL } from "url";
|
|
62
62
|
|
|
63
63
|
// src/hook/installGitHook.ts
|
|
@@ -174,9 +174,64 @@ function wirePrepareScript(pkgPath) {
|
|
|
174
174
|
}
|
|
175
175
|
|
|
176
176
|
// src/hook/prePush.ts
|
|
177
|
+
import { spawnSync as spawnSync3 } from "child_process";
|
|
178
|
+
import { existsSync as existsSync3 } from "fs";
|
|
179
|
+
import { join as join3 } from "path";
|
|
180
|
+
|
|
181
|
+
// src/report/store.ts
|
|
177
182
|
import { spawnSync as spawnSync2 } from "child_process";
|
|
178
|
-
|
|
179
|
-
|
|
183
|
+
var IDENT = ["-c", "user.name=receipts", "-c", "user.email=receipts@altimate.ai"];
|
|
184
|
+
var RECEIPT_REF_PREFIX = "refs/receipts/";
|
|
185
|
+
var receiptRef = (slug) => `${RECEIPT_REF_PREFIX}${slug}`;
|
|
186
|
+
function git2(args, opts = {}) {
|
|
187
|
+
const r = spawnSync2("git", args, {
|
|
188
|
+
encoding: "utf8",
|
|
189
|
+
cwd: opts.cwd,
|
|
190
|
+
input: opts.input,
|
|
191
|
+
env: opts.env ? { ...process.env, ...opts.env } : process.env
|
|
192
|
+
});
|
|
193
|
+
return { out: (r.stdout ?? "").trim(), ok: r.status === 0, err: (r.stderr ?? "").trim() };
|
|
194
|
+
}
|
|
195
|
+
function writeReceiptRef(slug, branch, json, endedAtMs, cwd) {
|
|
196
|
+
const blob = git2(["hash-object", "-w", "--stdin"], { cwd, input: json });
|
|
197
|
+
if (!blob.ok) {
|
|
198
|
+
return { ok: false, reason: "hash-object failed (not a git repository?)" };
|
|
199
|
+
}
|
|
200
|
+
const tree = git2(["mktree"], { cwd, input: `100644 blob ${blob.out} receipt.json
|
|
201
|
+
` });
|
|
202
|
+
if (!tree.ok) {
|
|
203
|
+
return { ok: false, reason: "mktree failed" };
|
|
204
|
+
}
|
|
205
|
+
const when = `@${Math.max(1, Math.floor((endedAtMs ?? 0) / 1e3))} +0000`;
|
|
206
|
+
const commit = git2([...IDENT, "commit-tree", tree.out, "-m", `receipt: ${branch}`], {
|
|
207
|
+
cwd,
|
|
208
|
+
env: { GIT_AUTHOR_DATE: when, GIT_COMMITTER_DATE: when }
|
|
209
|
+
});
|
|
210
|
+
if (!commit.ok) {
|
|
211
|
+
return { ok: false, reason: `commit-tree failed: ${commit.err}` };
|
|
212
|
+
}
|
|
213
|
+
const ref = receiptRef(slug);
|
|
214
|
+
const upd = git2(["update-ref", ref, commit.out], { cwd });
|
|
215
|
+
if (!upd.ok) {
|
|
216
|
+
return { ok: false, reason: `update-ref ${ref} failed` };
|
|
217
|
+
}
|
|
218
|
+
return { ok: true, ref, commit: commit.out };
|
|
219
|
+
}
|
|
220
|
+
function readReceiptRef(slug, cwd) {
|
|
221
|
+
const r = git2(["cat-file", "blob", `${receiptRef(slug)}:receipt.json`], { cwd });
|
|
222
|
+
return r.ok ? r.out : null;
|
|
223
|
+
}
|
|
224
|
+
function listReceiptRefs(cwd) {
|
|
225
|
+
const r = git2(["for-each-ref", RECEIPT_REF_PREFIX, "--format=%(refname)"], { cwd });
|
|
226
|
+
if (!r.ok || !r.out) {
|
|
227
|
+
return [];
|
|
228
|
+
}
|
|
229
|
+
return r.out.split("\n").filter(Boolean).map((ref) => ({ ref, slug: ref.slice(RECEIPT_REF_PREFIX.length) }));
|
|
230
|
+
}
|
|
231
|
+
function pushReceiptRef(slug, remote = "origin", cwd) {
|
|
232
|
+
const ref = receiptRef(slug);
|
|
233
|
+
return git2(["push", remote, `+${ref}:${ref}`], { cwd }).ok;
|
|
234
|
+
}
|
|
180
235
|
|
|
181
236
|
// src/trace/gitCommand.ts
|
|
182
237
|
var WRAPPERS = /* @__PURE__ */ new Set(["command", "exec", "nohup", "time", "env"]);
|
|
@@ -209,11 +264,63 @@ function gitInvocations(command) {
|
|
|
209
264
|
return out;
|
|
210
265
|
}
|
|
211
266
|
|
|
267
|
+
// src/hook/settingsMerge.ts
|
|
268
|
+
import { existsSync as existsSync2, mkdirSync as mkdirSync2, readFileSync as readFileSync2, writeFileSync as writeFileSync2 } from "fs";
|
|
269
|
+
import { dirname, join as join2 } from "path";
|
|
270
|
+
var HOOK_COMMAND = "npx -y altimate-receipts@latest hook pre-push";
|
|
271
|
+
function settingsStore(root) {
|
|
272
|
+
try {
|
|
273
|
+
const s = JSON.parse(readFileSync2(join2(root, ".claude", "settings.json"), "utf8"));
|
|
274
|
+
const v = s?.env?.RECEIPTS_STORE;
|
|
275
|
+
return typeof v === "string" && v ? v : void 0;
|
|
276
|
+
} catch {
|
|
277
|
+
return void 0;
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
function mergeHookIntoSettings(path, command = HOOK_COMMAND) {
|
|
281
|
+
let settings = {};
|
|
282
|
+
if (existsSync2(path)) {
|
|
283
|
+
let parsed;
|
|
284
|
+
try {
|
|
285
|
+
parsed = JSON.parse(readFileSync2(path, "utf8"));
|
|
286
|
+
} catch {
|
|
287
|
+
return { ok: false, reason: `${path} is not valid JSON \u2014 leaving it untouched` };
|
|
288
|
+
}
|
|
289
|
+
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
|
|
290
|
+
return { ok: false, reason: `${path} is not a JSON object \u2014 leaving it untouched` };
|
|
291
|
+
}
|
|
292
|
+
settings = parsed;
|
|
293
|
+
}
|
|
294
|
+
if (settings.hooks === void 0) {
|
|
295
|
+
settings.hooks = {};
|
|
296
|
+
}
|
|
297
|
+
const hooks = settings.hooks;
|
|
298
|
+
if (typeof hooks !== "object" || hooks === null || Array.isArray(hooks)) {
|
|
299
|
+
return { ok: false, reason: `${path} has a non-object "hooks" key \u2014 leaving it untouched` };
|
|
300
|
+
}
|
|
301
|
+
if (hooks.PreToolUse === void 0) {
|
|
302
|
+
hooks.PreToolUse = [];
|
|
303
|
+
}
|
|
304
|
+
const pre = hooks.PreToolUse;
|
|
305
|
+
if (!Array.isArray(pre)) {
|
|
306
|
+
return { ok: false, reason: `${path} has a non-array hooks.PreToolUse \u2014 leaving it untouched` };
|
|
307
|
+
}
|
|
308
|
+
const present = pre.some((e) => e?.hooks?.some((h) => h?.command === command));
|
|
309
|
+
if (present) {
|
|
310
|
+
return { ok: true, changed: false };
|
|
311
|
+
}
|
|
312
|
+
pre.push({ matcher: "Bash", hooks: [{ type: "command", command }] });
|
|
313
|
+
mkdirSync2(dirname(path), { recursive: true });
|
|
314
|
+
writeFileSync2(path, `${JSON.stringify(settings, null, 2)}
|
|
315
|
+
`);
|
|
316
|
+
return { ok: true, changed: true };
|
|
317
|
+
}
|
|
318
|
+
|
|
212
319
|
// src/hook/prePush.ts
|
|
213
320
|
var REPUSH_EXIT = 42;
|
|
214
321
|
var ATTACH_SUBJECT = (branch) => `chore(receipts): attach agent receipt for ${branch}`;
|
|
215
|
-
function
|
|
216
|
-
const r =
|
|
322
|
+
function git3(args, cwd) {
|
|
323
|
+
const r = spawnSync3("git", args, { encoding: "utf8", cwd });
|
|
217
324
|
return r.status === 0 ? r.stdout.trim() : "";
|
|
218
325
|
}
|
|
219
326
|
var TAG_REFSPEC = /^(refs\/tags\/|v\d+(\.\d+)*$)/;
|
|
@@ -233,25 +340,44 @@ function isGitPush(command) {
|
|
|
233
340
|
}
|
|
234
341
|
return false;
|
|
235
342
|
}
|
|
343
|
+
function isGitCommit(command) {
|
|
344
|
+
for (const { sub, rest } of gitInvocations(command)) {
|
|
345
|
+
if (sub === "commit" && !rest.includes("--dry-run")) {
|
|
346
|
+
return true;
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
return false;
|
|
350
|
+
}
|
|
236
351
|
function gitStdinPushesBranch(stdin) {
|
|
237
352
|
return stdin.split("\n").some((line) => line.trim().startsWith("refs/heads/"));
|
|
238
353
|
}
|
|
239
|
-
var hasMarker = (root) =>
|
|
354
|
+
var hasMarker = (root) => existsSync3(join3(root, ".github", "workflows", "receipts.yml")) || existsSync3(join3(root, ".receipts"));
|
|
240
355
|
function repoOptedIn(repoRoot) {
|
|
241
356
|
if (hasMarker(repoRoot)) {
|
|
242
357
|
return true;
|
|
243
358
|
}
|
|
244
|
-
const
|
|
359
|
+
const primary = primaryCheckout(repoRoot);
|
|
360
|
+
return primary ? hasMarker(primary) : false;
|
|
361
|
+
}
|
|
362
|
+
function primaryCheckout(repoRoot) {
|
|
363
|
+
const common = git3(["rev-parse", "--git-common-dir"], repoRoot);
|
|
245
364
|
if (common.endsWith("/.git")) {
|
|
246
365
|
const primary = common.slice(0, -"/.git".length);
|
|
247
366
|
if (primary && primary !== repoRoot) {
|
|
248
|
-
return
|
|
367
|
+
return primary;
|
|
249
368
|
}
|
|
250
369
|
}
|
|
251
|
-
return
|
|
370
|
+
return null;
|
|
252
371
|
}
|
|
253
372
|
function headIsAttachCommit(branch, cwd) {
|
|
254
|
-
return
|
|
373
|
+
return git3(["log", "-1", "--format=%s"], cwd) === ATTACH_SUBJECT(branch);
|
|
374
|
+
}
|
|
375
|
+
function isDefaultBranch(branch, cwd) {
|
|
376
|
+
const head = git3(["symbolic-ref", "--short", "refs/remotes/origin/HEAD"], cwd);
|
|
377
|
+
if (head) {
|
|
378
|
+
return head === `origin/${branch}`;
|
|
379
|
+
}
|
|
380
|
+
return branch === "main" || branch === "master";
|
|
255
381
|
}
|
|
256
382
|
async function readStdin(stream = process.stdin) {
|
|
257
383
|
let data = "";
|
|
@@ -261,6 +387,7 @@ async function readStdin(stream = process.stdin) {
|
|
|
261
387
|
return data;
|
|
262
388
|
}
|
|
263
389
|
async function runHookPrePush(dialect, stdin, generate) {
|
|
390
|
+
let pendingCommit = false;
|
|
264
391
|
try {
|
|
265
392
|
if (process.env.RECEIPTS_HOOK === "0") {
|
|
266
393
|
return { exit: 0 };
|
|
@@ -278,42 +405,59 @@ async function runHookPrePush(dialect, stdin, generate) {
|
|
|
278
405
|
if (payload.tool_name !== "Bash" || !payload.tool_input?.command) {
|
|
279
406
|
return { exit: 0 };
|
|
280
407
|
}
|
|
281
|
-
if (payload.cwd &&
|
|
408
|
+
if (payload.cwd && existsSync3(payload.cwd)) {
|
|
282
409
|
process.chdir(payload.cwd);
|
|
283
410
|
}
|
|
284
411
|
if (!isGitPush(payload.tool_input.command)) {
|
|
285
412
|
return { exit: 0 };
|
|
286
413
|
}
|
|
414
|
+
pendingCommit = isGitCommit(payload.tool_input.command);
|
|
287
415
|
} else if (!gitStdinPushesBranch(stdin)) {
|
|
288
416
|
return { exit: 0 };
|
|
289
417
|
}
|
|
290
|
-
const repoRoot =
|
|
418
|
+
const repoRoot = git3(["rev-parse", "--show-toplevel"]);
|
|
291
419
|
if (!repoRoot || !repoOptedIn(repoRoot)) {
|
|
292
420
|
return { exit: 0 };
|
|
293
421
|
}
|
|
294
|
-
const branch =
|
|
295
|
-
if (!branch || branch === "HEAD") {
|
|
422
|
+
const branch = git3(["rev-parse", "--abbrev-ref", "HEAD"]);
|
|
423
|
+
if (!branch || branch === "HEAD" || isDefaultBranch(branch)) {
|
|
296
424
|
return { exit: 0 };
|
|
297
425
|
}
|
|
298
|
-
|
|
426
|
+
const store = (process.env.RECEIPTS_STORE || settingsStore(repoRoot) || settingsStore(primaryCheckout(repoRoot) ?? "") || "commit").toLowerCase();
|
|
427
|
+
if (store === "none") {
|
|
428
|
+
return { exit: 0 };
|
|
429
|
+
}
|
|
430
|
+
process.env.RECEIPTS_STORE = store;
|
|
431
|
+
if (store !== "ref" && dialect === "git" && headIsAttachCommit(branch)) {
|
|
299
432
|
return { exit: 0 };
|
|
300
433
|
}
|
|
301
434
|
const write = process.stderr.write.bind(process.stderr);
|
|
302
435
|
process.stderr.write = (() => true);
|
|
303
436
|
let generated;
|
|
304
437
|
try {
|
|
305
|
-
generated = await generate();
|
|
438
|
+
generated = await generate({ pendingCommit });
|
|
306
439
|
} finally {
|
|
307
440
|
process.stderr.write = write;
|
|
308
441
|
}
|
|
309
442
|
if (generated !== 0) {
|
|
310
443
|
return { exit: 0 };
|
|
311
444
|
}
|
|
312
|
-
if (
|
|
445
|
+
if (store === "ref") {
|
|
446
|
+
const slug = branch.replace(/[/\\]/g, "-");
|
|
447
|
+
if (readReceiptRef(slug) == null) {
|
|
448
|
+
return { exit: 0 };
|
|
449
|
+
}
|
|
450
|
+
const pushed = pushReceiptRef(slug);
|
|
451
|
+
return {
|
|
452
|
+
exit: 0,
|
|
453
|
+
message: pushed ? `receipts: receipt at refs/receipts/${slug} \u2014 travels alongside this push.` : `receipts: wrote refs/receipts/${slug}; pushing it failed \u2014 push the ref manually.`
|
|
454
|
+
};
|
|
455
|
+
}
|
|
456
|
+
if (!git3(["status", "--porcelain", ".receipts/"])) {
|
|
313
457
|
return { exit: 0 };
|
|
314
458
|
}
|
|
315
|
-
|
|
316
|
-
const commit =
|
|
459
|
+
spawnSync3("git", ["add", ".receipts/"], { encoding: "utf8" });
|
|
460
|
+
const commit = spawnSync3(
|
|
317
461
|
"git",
|
|
318
462
|
["commit", "--no-verify", "-m", ATTACH_SUBJECT(branch), "--", ".receipts/"],
|
|
319
463
|
{ encoding: "utf8" }
|
|
@@ -336,52 +480,9 @@ async function runHookPrePush(dialect, stdin, generate) {
|
|
|
336
480
|
}
|
|
337
481
|
}
|
|
338
482
|
|
|
339
|
-
// src/hook/settingsMerge.ts
|
|
340
|
-
import { existsSync as existsSync3, mkdirSync as mkdirSync2, readFileSync as readFileSync2, writeFileSync as writeFileSync2 } from "fs";
|
|
341
|
-
import { dirname } from "path";
|
|
342
|
-
var HOOK_COMMAND = "npx -y altimate-receipts@latest hook pre-push";
|
|
343
|
-
function mergeHookIntoSettings(path, command = HOOK_COMMAND) {
|
|
344
|
-
let settings = {};
|
|
345
|
-
if (existsSync3(path)) {
|
|
346
|
-
let parsed;
|
|
347
|
-
try {
|
|
348
|
-
parsed = JSON.parse(readFileSync2(path, "utf8"));
|
|
349
|
-
} catch {
|
|
350
|
-
return { ok: false, reason: `${path} is not valid JSON \u2014 leaving it untouched` };
|
|
351
|
-
}
|
|
352
|
-
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
|
|
353
|
-
return { ok: false, reason: `${path} is not a JSON object \u2014 leaving it untouched` };
|
|
354
|
-
}
|
|
355
|
-
settings = parsed;
|
|
356
|
-
}
|
|
357
|
-
if (settings.hooks === void 0) {
|
|
358
|
-
settings.hooks = {};
|
|
359
|
-
}
|
|
360
|
-
const hooks = settings.hooks;
|
|
361
|
-
if (typeof hooks !== "object" || hooks === null || Array.isArray(hooks)) {
|
|
362
|
-
return { ok: false, reason: `${path} has a non-object "hooks" key \u2014 leaving it untouched` };
|
|
363
|
-
}
|
|
364
|
-
if (hooks.PreToolUse === void 0) {
|
|
365
|
-
hooks.PreToolUse = [];
|
|
366
|
-
}
|
|
367
|
-
const pre = hooks.PreToolUse;
|
|
368
|
-
if (!Array.isArray(pre)) {
|
|
369
|
-
return { ok: false, reason: `${path} has a non-array hooks.PreToolUse \u2014 leaving it untouched` };
|
|
370
|
-
}
|
|
371
|
-
const present = pre.some((e) => e?.hooks?.some((h) => h?.command === command));
|
|
372
|
-
if (present) {
|
|
373
|
-
return { ok: true, changed: false };
|
|
374
|
-
}
|
|
375
|
-
pre.push({ matcher: "Bash", hooks: [{ type: "command", command }] });
|
|
376
|
-
mkdirSync2(dirname(path), { recursive: true });
|
|
377
|
-
writeFileSync2(path, `${JSON.stringify(settings, null, 2)}
|
|
378
|
-
`);
|
|
379
|
-
return { ok: true, changed: true };
|
|
380
|
-
}
|
|
381
|
-
|
|
382
483
|
// src/receipt/assert.ts
|
|
383
484
|
import { readFileSync as readFileSync3 } from "fs";
|
|
384
|
-
import { join as
|
|
485
|
+
import { join as join4 } from "path";
|
|
385
486
|
var OPS = /* @__PURE__ */ new Set([
|
|
386
487
|
"eq",
|
|
387
488
|
"ne",
|
|
@@ -476,7 +577,7 @@ function validateAssertion(raw) {
|
|
|
476
577
|
};
|
|
477
578
|
}
|
|
478
579
|
function loadAsserts(repoRoot) {
|
|
479
|
-
const path =
|
|
580
|
+
const path = join4(repoRoot, ".receipts", "asserts.json");
|
|
480
581
|
let text;
|
|
481
582
|
try {
|
|
482
583
|
text = readFileSync3(path, "utf8");
|
|
@@ -704,7 +805,7 @@ function renderFieldScan(s) {
|
|
|
704
805
|
|
|
705
806
|
// src/report/log.ts
|
|
706
807
|
import { readFileSync as readFileSync4, readdirSync } from "fs";
|
|
707
|
-
import { join as
|
|
808
|
+
import { dirname as dirname2, join as join5 } from "path";
|
|
708
809
|
var SEV_ICON2 = { critical: "\u26D4", high: "\u26A0\uFE0F", medium: "\u{1F50D}", low: "\xB7" };
|
|
709
810
|
var SEV_RANK = { critical: 0, high: 1, medium: 2, low: 3 };
|
|
710
811
|
var NON_RECEIPT = /(?:^|\/)(?:asserts(?:\.example)?|sample)\.json$/i;
|
|
@@ -722,13 +823,27 @@ function loadReceiptHistory(dir) {
|
|
|
722
823
|
try {
|
|
723
824
|
files = readdirSync(dir).filter((f) => f.endsWith(".json") && !NON_RECEIPT.test(f));
|
|
724
825
|
} catch {
|
|
725
|
-
|
|
826
|
+
files = [];
|
|
827
|
+
}
|
|
828
|
+
const sources = files.map((f) => {
|
|
829
|
+
try {
|
|
830
|
+
return { name: f, raw: readFileSync4(join5(dir, f), "utf8") };
|
|
831
|
+
} catch {
|
|
832
|
+
return { name: f, raw: null };
|
|
833
|
+
}
|
|
834
|
+
});
|
|
835
|
+
const repo = dirname2(dir);
|
|
836
|
+
for (const { slug } of listReceiptRefs(repo)) {
|
|
837
|
+
sources.push({ name: `${slug}@ref`, raw: readReceiptRef(slug, repo) });
|
|
726
838
|
}
|
|
727
839
|
const entries = [];
|
|
728
|
-
for (const f of
|
|
840
|
+
for (const { name: f, raw } of sources) {
|
|
841
|
+
if (raw == null) {
|
|
842
|
+
continue;
|
|
843
|
+
}
|
|
729
844
|
let input;
|
|
730
845
|
try {
|
|
731
|
-
input = JSON.parse(
|
|
846
|
+
input = JSON.parse(raw);
|
|
732
847
|
} catch {
|
|
733
848
|
continue;
|
|
734
849
|
}
|
|
@@ -892,7 +1007,7 @@ function toSarif(receipt) {
|
|
|
892
1007
|
|
|
893
1008
|
// src/report/stats.ts
|
|
894
1009
|
import { readFileSync as readFileSync5, readdirSync as readdirSync2 } from "fs";
|
|
895
|
-
import { join as
|
|
1010
|
+
import { join as join6 } from "path";
|
|
896
1011
|
var NON_RECEIPT2 = /(?:^|\/)(?:asserts(?:\.example)?|sample)\.json$/i;
|
|
897
1012
|
function computeStats(dir) {
|
|
898
1013
|
let files;
|
|
@@ -910,7 +1025,7 @@ function computeStats(dir) {
|
|
|
910
1025
|
for (const f of files) {
|
|
911
1026
|
let input;
|
|
912
1027
|
try {
|
|
913
|
-
input = JSON.parse(readFileSync5(
|
|
1028
|
+
input = JSON.parse(readFileSync5(join6(dir, f), "utf8"));
|
|
914
1029
|
} catch {
|
|
915
1030
|
skipped++;
|
|
916
1031
|
continue;
|
|
@@ -1027,13 +1142,13 @@ function renderHandoffMarkdown(h) {
|
|
|
1027
1142
|
}
|
|
1028
1143
|
|
|
1029
1144
|
// src/trace/commitMatch.ts
|
|
1030
|
-
import { spawnSync as
|
|
1145
|
+
import { spawnSync as spawnSync4 } from "child_process";
|
|
1031
1146
|
var SHA_CAP = 200;
|
|
1032
1147
|
function branchShas(base, cwd) {
|
|
1033
1148
|
if (!base) {
|
|
1034
1149
|
return [];
|
|
1035
1150
|
}
|
|
1036
|
-
const r =
|
|
1151
|
+
const r = spawnSync4("git", ["log", `--max-count=${SHA_CAP}`, "--format=%H", `${base}..HEAD`], {
|
|
1037
1152
|
encoding: "utf8",
|
|
1038
1153
|
cwd
|
|
1039
1154
|
});
|
|
@@ -1330,6 +1445,7 @@ async function run(argv) {
|
|
|
1330
1445
|
}
|
|
1331
1446
|
if (args.command === "rederive") {
|
|
1332
1447
|
return runRederive(args.file, {
|
|
1448
|
+
source: args.agent,
|
|
1333
1449
|
branch: args.branchScope,
|
|
1334
1450
|
redact: args.redact,
|
|
1335
1451
|
compact: args.compact
|
|
@@ -1436,8 +1552,8 @@ Run a coding-agent session first, then try again.
|
|
|
1436
1552
|
process.stdout.write(renderCard({ summary, derived, findings }, { color: args.color }));
|
|
1437
1553
|
return 0;
|
|
1438
1554
|
}
|
|
1439
|
-
function
|
|
1440
|
-
const r =
|
|
1555
|
+
function git4(args) {
|
|
1556
|
+
const r = spawnSync5("git", args, { encoding: "utf8" });
|
|
1441
1557
|
return r.status === 0 ? r.stdout.trim() : "";
|
|
1442
1558
|
}
|
|
1443
1559
|
var PR_SELECT_SCAN = 150;
|
|
@@ -1448,7 +1564,7 @@ function branchBirthMs(base) {
|
|
|
1448
1564
|
if (!base) {
|
|
1449
1565
|
return null;
|
|
1450
1566
|
}
|
|
1451
|
-
const out =
|
|
1567
|
+
const out = git4(["log", "--reverse", "--format=%at", `${base}..HEAD`]);
|
|
1452
1568
|
const first = Number(out.split("\n")[0]?.trim());
|
|
1453
1569
|
return Number.isFinite(first) && first > 0 ? first * 1e3 : null;
|
|
1454
1570
|
}
|
|
@@ -1493,13 +1609,13 @@ async function pickForDiff(all, branch, repoRoot, files, birthMs = null, shas =
|
|
|
1493
1609
|
return best ?? primary;
|
|
1494
1610
|
}
|
|
1495
1611
|
async function runPr(opts) {
|
|
1496
|
-
const branch = opts.branch ||
|
|
1497
|
-
const repoRoot =
|
|
1612
|
+
const branch = opts.branch || git4(["rev-parse", "--abbrev-ref", "HEAD"]);
|
|
1613
|
+
const repoRoot = git4(["rev-parse", "--show-toplevel"]);
|
|
1498
1614
|
if (!branch || branch === "HEAD") {
|
|
1499
1615
|
process.stderr.write("receipts pr: not on a git branch (use --branch <name>).\n");
|
|
1500
1616
|
return 1;
|
|
1501
1617
|
}
|
|
1502
|
-
const diff = opts.wholeSession ? null : changedFiles(opts.base);
|
|
1618
|
+
const diff = opts.wholeSession ? null : changedFiles(opts.base, { includeWorkingTree: opts.pendingCommit });
|
|
1503
1619
|
const all = await listSessions();
|
|
1504
1620
|
const picked = diff ? await pickForDiff(
|
|
1505
1621
|
all,
|
|
@@ -1553,7 +1669,7 @@ Build the branch with a coding agent first, or run \`receipts --list\`.
|
|
|
1553
1669
|
const receipt = redactReceipt(await buildReceipt(scopedSession, derived, findings, { scope }));
|
|
1554
1670
|
const json = `${JSON.stringify(receipt, null, 2)}
|
|
1555
1671
|
`;
|
|
1556
|
-
const store = (opts.store || process.env.RECEIPTS_STORE || "commit").toLowerCase();
|
|
1672
|
+
const store = (opts.store || process.env.RECEIPTS_STORE || (repoRoot ? settingsStore(repoRoot) ?? settingsStore(primaryCheckout(repoRoot) ?? "") : void 0) || "commit").toLowerCase();
|
|
1557
1673
|
if (store === "none") {
|
|
1558
1674
|
process.stdout.write(json);
|
|
1559
1675
|
process.stderr.write(
|
|
@@ -1567,21 +1683,33 @@ Build the branch with a coding agent first, or run \`receipts --list\`.
|
|
|
1567
1683
|
`receipts pr: store=${store} not yet implemented (SPEC-0064) \u2014 using \`commit\`.
|
|
1568
1684
|
`
|
|
1569
1685
|
);
|
|
1570
|
-
} else if (store !== "commit") {
|
|
1686
|
+
} else if (store !== "commit" && store !== "ref") {
|
|
1571
1687
|
process.stderr.write(`receipts pr: unknown store=${store} \u2014 using \`commit\`.
|
|
1572
1688
|
`);
|
|
1573
1689
|
}
|
|
1574
1690
|
const safe = branch.replace(/[/\\]/g, "-");
|
|
1575
|
-
|
|
1576
|
-
|
|
1691
|
+
if (store === "ref") {
|
|
1692
|
+
const w = writeReceiptRef(safe, branch, json, summary.endedAt, repoRoot || void 0);
|
|
1693
|
+
if (w.ok) {
|
|
1694
|
+
process.stderr.write(
|
|
1695
|
+
`receipts pr: wrote ${w.ref} (Grade ${receipt.predicate.grade}, ${scopeNote}) from "${summary.title ?? "untitled"}" \u2014 no tree files.
|
|
1696
|
+
`
|
|
1697
|
+
);
|
|
1698
|
+
return 0;
|
|
1699
|
+
}
|
|
1700
|
+
process.stderr.write(`receipts pr: store=ref failed (${w.reason}) \u2014 using \`commit\`.
|
|
1701
|
+
`);
|
|
1702
|
+
}
|
|
1703
|
+
const dir = join7(repoRoot || ".", ".receipts");
|
|
1704
|
+
const out = opts.out ?? join7(dir, `${safe}.json`);
|
|
1577
1705
|
mkdirSync3(dir, { recursive: true });
|
|
1578
|
-
const attrs =
|
|
1706
|
+
const attrs = join7(dir, ".gitattributes");
|
|
1579
1707
|
if (!existsSync4(attrs)) {
|
|
1580
1708
|
writeFileSync3(attrs, "* linguist-generated\n");
|
|
1581
|
-
|
|
1709
|
+
git4(["add", attrs]);
|
|
1582
1710
|
}
|
|
1583
1711
|
writeFileSync3(out, json);
|
|
1584
|
-
|
|
1712
|
+
git4(["add", out]);
|
|
1585
1713
|
const rel = repoRoot ? relative(repoRoot, out) : out;
|
|
1586
1714
|
process.stderr.write(
|
|
1587
1715
|
`receipts pr: wrote ${rel} (Grade ${receipt.predicate.grade}, ${scopeNote}) from "${summary.title ?? "untitled"}".
|
|
@@ -1739,8 +1867,9 @@ function runDiff(fileA, fileB, opts = {}) {
|
|
|
1739
1867
|
);
|
|
1740
1868
|
return 1;
|
|
1741
1869
|
}
|
|
1742
|
-
|
|
1743
|
-
|
|
1870
|
+
const sourceOf = (name) => name.endsWith("@ref") ? name : join7(".receipts", `${name}.json`);
|
|
1871
|
+
pathA = sourceOf(hist[1].name);
|
|
1872
|
+
pathB = sourceOf(hist[0].name);
|
|
1744
1873
|
process.stdout.write(`receipts diff: ${hist[1].name} \u2192 ${hist[0].name} (most recent two)
|
|
1745
1874
|
|
|
1746
1875
|
`);
|
|
@@ -1754,7 +1883,8 @@ function runDiff(fileA, fileB, opts = {}) {
|
|
|
1754
1883
|
const read = (f) => {
|
|
1755
1884
|
let input;
|
|
1756
1885
|
try {
|
|
1757
|
-
|
|
1886
|
+
const raw = f.endsWith("@ref") ? readReceiptRef(f.slice(0, -"@ref".length)) : null;
|
|
1887
|
+
input = JSON.parse(raw ?? readFileSync6(f, "utf8"));
|
|
1758
1888
|
} catch (err) {
|
|
1759
1889
|
process.stderr.write(`Could not read ${f}: ${err instanceof Error ? err.message : err}
|
|
1760
1890
|
`);
|
|
@@ -1813,9 +1943,9 @@ function runStats(dir, opts = {}) {
|
|
|
1813
1943
|
return 0;
|
|
1814
1944
|
}
|
|
1815
1945
|
function runBadge(file, opts = {}) {
|
|
1816
|
-
const repoRoot =
|
|
1817
|
-
const branch =
|
|
1818
|
-
const path = file ?? (branch && branch !== "HEAD" ?
|
|
1946
|
+
const repoRoot = git4(["rev-parse", "--show-toplevel"]) || ".";
|
|
1947
|
+
const branch = git4(["rev-parse", "--abbrev-ref", "HEAD"]);
|
|
1948
|
+
const path = file ?? (branch && branch !== "HEAD" ? join7(repoRoot, ".receipts", `${branch.replace(/[/\\]/g, "-")}.json`) : void 0);
|
|
1819
1949
|
if (!path || !existsSync4(path)) {
|
|
1820
1950
|
process.stderr.write(
|
|
1821
1951
|
`receipts badge: no receipt found${path ? ` at ${path}` : ""}. Run \`receipts pr\` first, or pass a receipt path.
|
|
@@ -1849,9 +1979,9 @@ function runBadge(file, opts = {}) {
|
|
|
1849
1979
|
return 0;
|
|
1850
1980
|
}
|
|
1851
1981
|
function runSarif(file, opts = {}) {
|
|
1852
|
-
const repoRoot =
|
|
1853
|
-
const branch =
|
|
1854
|
-
const path = file ?? (branch && branch !== "HEAD" ?
|
|
1982
|
+
const repoRoot = git4(["rev-parse", "--show-toplevel"]) || ".";
|
|
1983
|
+
const branch = git4(["rev-parse", "--abbrev-ref", "HEAD"]);
|
|
1984
|
+
const path = file ?? (branch && branch !== "HEAD" ? join7(repoRoot, ".receipts", `${branch.replace(/[/\\]/g, "-")}.json`) : void 0);
|
|
1855
1985
|
if (!path || !existsSync4(path)) {
|
|
1856
1986
|
process.stderr.write(
|
|
1857
1987
|
`receipts sarif: no receipt found${path ? ` at ${path}` : ""}. Run \`receipts pr\` first, or pass a receipt path.
|
|
@@ -1885,8 +2015,8 @@ function runSarif(file, opts = {}) {
|
|
|
1885
2015
|
return 0;
|
|
1886
2016
|
}
|
|
1887
2017
|
function runPrune(dir, opts = {}) {
|
|
1888
|
-
const repoRoot =
|
|
1889
|
-
const dpath = dir ??
|
|
2018
|
+
const repoRoot = git4(["rev-parse", "--show-toplevel"]) || ".";
|
|
2019
|
+
const dpath = dir ?? join7(repoRoot, ".receipts");
|
|
1890
2020
|
let files;
|
|
1891
2021
|
try {
|
|
1892
2022
|
files = readdirSync3(dpath).filter((f) => f.endsWith(".json"));
|
|
@@ -1895,7 +2025,7 @@ function runPrune(dir, opts = {}) {
|
|
|
1895
2025
|
`);
|
|
1896
2026
|
return 0;
|
|
1897
2027
|
}
|
|
1898
|
-
const ls =
|
|
2028
|
+
const ls = git4(["ls-remote", "--heads", "origin"]);
|
|
1899
2029
|
if (ls === null) {
|
|
1900
2030
|
process.stderr.write(
|
|
1901
2031
|
"receipts prune: could not list remote branches (offline / no 'origin'?) \u2014 aborting, nothing removed.\n"
|
|
@@ -1907,8 +2037,11 @@ function runPrune(dir, opts = {}) {
|
|
|
1907
2037
|
);
|
|
1908
2038
|
const { keep, remove } = planPrune(files, liveSlugs);
|
|
1909
2039
|
if (remove.length === 0) {
|
|
1910
|
-
|
|
2040
|
+
const prunedRefs = pruneReceiptRefs(liveSlugs, opts.dryRun ?? false);
|
|
2041
|
+
if (prunedRefs === 0) {
|
|
2042
|
+
process.stdout.write(`receipts prune: nothing to prune (${keep.length} receipt(s) kept).
|
|
1911
2043
|
`);
|
|
2044
|
+
}
|
|
1912
2045
|
return 0;
|
|
1913
2046
|
}
|
|
1914
2047
|
if (opts.dryRun) {
|
|
@@ -1920,11 +2053,13 @@ function runPrune(dir, opts = {}) {
|
|
|
1920
2053
|
process.stdout.write(` - ${f}
|
|
1921
2054
|
`);
|
|
1922
2055
|
}
|
|
2056
|
+
pruneReceiptRefs(liveSlugs, true);
|
|
1923
2057
|
return 0;
|
|
1924
2058
|
}
|
|
2059
|
+
pruneReceiptRefs(liveSlugs, false);
|
|
1925
2060
|
for (const f of remove) {
|
|
1926
|
-
const p =
|
|
1927
|
-
if (
|
|
2061
|
+
const p = join7(dpath, f);
|
|
2062
|
+
if (git4(["rm", "-f", "--", p]) === null) {
|
|
1928
2063
|
try {
|
|
1929
2064
|
unlinkSync(p);
|
|
1930
2065
|
} catch {
|
|
@@ -1937,6 +2072,24 @@ function runPrune(dir, opts = {}) {
|
|
|
1937
2072
|
);
|
|
1938
2073
|
return 0;
|
|
1939
2074
|
}
|
|
2075
|
+
function pruneReceiptRefs(liveSlugs, dryRun) {
|
|
2076
|
+
const refs = listReceiptRefs().filter(({ slug }) => !liveSlugs.has(slug));
|
|
2077
|
+
if (refs.length === 0) {
|
|
2078
|
+
return 0;
|
|
2079
|
+
}
|
|
2080
|
+
for (const { ref, slug } of refs) {
|
|
2081
|
+
if (dryRun) {
|
|
2082
|
+
process.stdout.write(` - ${ref}
|
|
2083
|
+
`);
|
|
2084
|
+
continue;
|
|
2085
|
+
}
|
|
2086
|
+
git4(["update-ref", "-d", ref]);
|
|
2087
|
+
git4(["push", "origin", `:${ref}`]);
|
|
2088
|
+
process.stdout.write(`receipts prune: removed ${ref} (branch ${slug} gone).
|
|
2089
|
+
`);
|
|
2090
|
+
}
|
|
2091
|
+
return refs.length;
|
|
2092
|
+
}
|
|
1940
2093
|
async function runEval(opts = {}) {
|
|
1941
2094
|
const limit = opts.limit && opts.limit > 0 ? opts.limit : 200;
|
|
1942
2095
|
const summaries = (await listSessions(opts.agent)).slice(0, limit);
|
|
@@ -1973,7 +2126,7 @@ function runInit(opts = {}) {
|
|
|
1973
2126
|
written.push(path);
|
|
1974
2127
|
lines.push(`receipts init: wrote ${path} (tracking ${tag}, quiet + non-blocking).`);
|
|
1975
2128
|
}
|
|
1976
|
-
const settingsPath =
|
|
2129
|
+
const settingsPath = join7(".claude", "settings.json");
|
|
1977
2130
|
const merged = mergeHookIntoSettings(settingsPath);
|
|
1978
2131
|
if (!merged.ok) {
|
|
1979
2132
|
lines.push(`receipts init: ${merged.reason}.`);
|
|
@@ -1983,14 +2136,14 @@ function runInit(opts = {}) {
|
|
|
1983
2136
|
} else {
|
|
1984
2137
|
lines.push(`receipts init: ${settingsPath} already has the receipts hook.`);
|
|
1985
2138
|
}
|
|
1986
|
-
const ignorePath =
|
|
2139
|
+
const ignorePath = join7(".claude", ".gitignore");
|
|
1987
2140
|
if (!existsSync4(ignorePath)) {
|
|
1988
2141
|
writeFileSync3(ignorePath, "*\n!settings.json\n!.gitignore\n");
|
|
1989
2142
|
written.push(ignorePath);
|
|
1990
2143
|
lines.push(`receipts init: wrote ${ignorePath} (commit settings.json, ignore the rest).`);
|
|
1991
2144
|
}
|
|
1992
2145
|
if (opts.agents?.includes("codex") || existsSync4(".codex")) {
|
|
1993
|
-
const codexPath =
|
|
2146
|
+
const codexPath = join7(".codex", "hooks.json");
|
|
1994
2147
|
const m = mergeHookIntoSettings(
|
|
1995
2148
|
codexPath,
|
|
1996
2149
|
"npx -y altimate-receipts@latest hook pre-push --agent codex"
|
|
@@ -2004,7 +2157,7 @@ function runInit(opts = {}) {
|
|
|
2004
2157
|
lines.push(`receipts init: ${codexPath} already has the receipts hook.`);
|
|
2005
2158
|
}
|
|
2006
2159
|
}
|
|
2007
|
-
const attrsPath =
|
|
2160
|
+
const attrsPath = join7(".receipts", ".gitattributes");
|
|
2008
2161
|
if (!existsSync4(attrsPath)) {
|
|
2009
2162
|
mkdirSync3(".receipts", { recursive: true });
|
|
2010
2163
|
writeFileSync3(attrsPath, "* linguist-generated\n");
|
|
@@ -2043,22 +2196,22 @@ function openAdoptionPr(written, lines) {
|
|
|
2043
2196
|
lines.push("receipts init: nothing new to commit \u2014 the repo is already integrated.");
|
|
2044
2197
|
return;
|
|
2045
2198
|
}
|
|
2046
|
-
if (!
|
|
2199
|
+
if (!git4(["rev-parse", "--git-dir"])) {
|
|
2047
2200
|
lines.push("receipts init: not a git repository \u2014 commit the files above manually.");
|
|
2048
2201
|
return;
|
|
2049
2202
|
}
|
|
2050
|
-
if (
|
|
2203
|
+
if (git4(["rev-parse", "--verify", "--quiet", ADOPT_BRANCH]) !== "") {
|
|
2051
2204
|
lines.push(
|
|
2052
2205
|
`receipts init: branch ${ADOPT_BRANCH} already exists \u2014 commit the files above to it manually.`
|
|
2053
2206
|
);
|
|
2054
2207
|
return;
|
|
2055
2208
|
}
|
|
2056
|
-
if (
|
|
2209
|
+
if (spawnSync5("git", ["checkout", "-b", ADOPT_BRANCH], { encoding: "utf8" }).status !== 0) {
|
|
2057
2210
|
lines.push(`receipts init: could not create branch ${ADOPT_BRANCH} \u2014 commit manually.`);
|
|
2058
2211
|
return;
|
|
2059
2212
|
}
|
|
2060
|
-
|
|
2061
|
-
const commit =
|
|
2213
|
+
spawnSync5("git", ["add", "--", ...written], { encoding: "utf8" });
|
|
2214
|
+
const commit = spawnSync5(
|
|
2062
2215
|
"git",
|
|
2063
2216
|
["commit", "-m", "chore: adopt receipts \u2014 zero-install agent-work verification"],
|
|
2064
2217
|
{ encoding: "utf8" }
|
|
@@ -2070,7 +2223,7 @@ function openAdoptionPr(written, lines) {
|
|
|
2070
2223
|
return;
|
|
2071
2224
|
}
|
|
2072
2225
|
lines.push(`receipts init: committed ${written.length} file(s) on ${ADOPT_BRANCH}.`);
|
|
2073
|
-
const push =
|
|
2226
|
+
const push = spawnSync5("git", ["push", "-u", "origin", ADOPT_BRANCH], { encoding: "utf8" });
|
|
2074
2227
|
if (push.status !== 0) {
|
|
2075
2228
|
lines.push(
|
|
2076
2229
|
"receipts init: push failed (no remote / auth?) \u2014 push the branch and open a PR manually."
|
|
@@ -2078,7 +2231,7 @@ function openAdoptionPr(written, lines) {
|
|
|
2078
2231
|
return;
|
|
2079
2232
|
}
|
|
2080
2233
|
lines.push(`receipts init: pushed ${ADOPT_BRANCH}.`);
|
|
2081
|
-
const gh =
|
|
2234
|
+
const gh = spawnSync5(
|
|
2082
2235
|
"gh",
|
|
2083
2236
|
[
|
|
2084
2237
|
"pr",
|
|
@@ -2094,7 +2247,7 @@ function openAdoptionPr(written, lines) {
|
|
|
2094
2247
|
lines.push(`receipts init: opened the PR \u2014 ${gh.stdout.trim().split("\n").pop()}`);
|
|
2095
2248
|
return;
|
|
2096
2249
|
}
|
|
2097
|
-
const remote =
|
|
2250
|
+
const remote = git4(["remote", "get-url", "origin"]);
|
|
2098
2251
|
const slug = remote ? /github\.com[:/]([^/]+\/[^/.]+)/.exec(remote)?.[1] : void 0;
|
|
2099
2252
|
lines.push(
|
|
2100
2253
|
slug ? `receipts init: open the PR here \u2014 https://github.com/${slug}/pull/new/${ADOPT_BRANCH}` : "receipts init: branch pushed \u2014 open a PR from it in your forge."
|
|
@@ -2135,7 +2288,11 @@ async function runHook(kind, dialect) {
|
|
|
2135
2288
|
return 1;
|
|
2136
2289
|
}
|
|
2137
2290
|
const stdin = await readStdin();
|
|
2138
|
-
const res = await runHookPrePush(
|
|
2291
|
+
const res = await runHookPrePush(
|
|
2292
|
+
dialect,
|
|
2293
|
+
stdin,
|
|
2294
|
+
async (o) => runPr({ pendingCommit: o?.pendingCommit })
|
|
2295
|
+
);
|
|
2139
2296
|
if (res.message) {
|
|
2140
2297
|
process.stderr.write(`${res.message}
|
|
2141
2298
|
`);
|
|
@@ -2159,7 +2316,7 @@ function runInstallHook() {
|
|
|
2159
2316
|
return 0;
|
|
2160
2317
|
}
|
|
2161
2318
|
function runSetupLocal() {
|
|
2162
|
-
const path =
|
|
2319
|
+
const path = join7(homedir(), ".claude", "settings.json");
|
|
2163
2320
|
const res = mergeHookIntoSettings(path);
|
|
2164
2321
|
if (!res.ok) {
|
|
2165
2322
|
process.stderr.write(`receipts setup-local: ${res.reason}.
|
|
@@ -2175,10 +2332,16 @@ function runSetupLocal() {
|
|
|
2175
2332
|
}
|
|
2176
2333
|
async function runRederive(file, opts = {}) {
|
|
2177
2334
|
if (!file) {
|
|
2178
|
-
process.stderr.write(
|
|
2335
|
+
process.stderr.write(
|
|
2336
|
+
"Usage: receipts rederive <transcript.jsonl> [--agent a] [--branch b] [--redact]\n"
|
|
2337
|
+
);
|
|
2179
2338
|
return 1;
|
|
2180
2339
|
}
|
|
2181
|
-
const receipt = await rederiveFromTranscript(file, {
|
|
2340
|
+
const receipt = await rederiveFromTranscript(file, {
|
|
2341
|
+
source: opts.source,
|
|
2342
|
+
branch: opts.branch,
|
|
2343
|
+
redact: opts.redact
|
|
2344
|
+
});
|
|
2182
2345
|
if (!receipt) {
|
|
2183
2346
|
process.stderr.write(`receipts rederive: could not read a session from ${file}
|
|
2184
2347
|
`);
|
|
@@ -2190,7 +2353,7 @@ async function runRederive(file, opts = {}) {
|
|
|
2190
2353
|
return 0;
|
|
2191
2354
|
}
|
|
2192
2355
|
async function runAssert(opts) {
|
|
2193
|
-
const repoRoot =
|
|
2356
|
+
const repoRoot = git4(["rev-parse", "--show-toplevel"]) || ".";
|
|
2194
2357
|
let asserts;
|
|
2195
2358
|
try {
|
|
2196
2359
|
asserts = loadAsserts(repoRoot);
|