altimate-receipts 0.5.0 → 0.5.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{chunk-QGUQOQXO.js → chunk-EKMFU3ES.js} +2 -2
- package/dist/{chunk-2QTR3AF4.js → chunk-EYM5WETZ.js} +36 -2
- package/dist/chunk-EYM5WETZ.js.map +1 -0
- package/dist/{chunk-WISVSYA7.js → chunk-GOLRNSZT.js} +29 -6
- package/dist/chunk-GOLRNSZT.js.map +1 -0
- package/dist/cli.js +244 -31
- 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-2QTR3AF4.js.map +0 -1
- package/dist/chunk-WISVSYA7.js.map +0 -1
- /package/dist/{chunk-QGUQOQXO.js.map → chunk-EKMFU3ES.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-GOLRNSZT.js";
|
|
17
17
|
import {
|
|
18
18
|
computeTrends,
|
|
19
19
|
deriveTargets,
|
|
20
20
|
renderTrends,
|
|
21
21
|
upsertTrendsSection
|
|
22
|
-
} from "./chunk-
|
|
22
|
+
} from "./chunk-EKMFU3ES.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-EYM5WETZ.js";
|
|
47
47
|
|
|
48
48
|
// src/cli.ts
|
|
49
|
-
import { spawnSync as
|
|
49
|
+
import { spawnSync as spawnSync4 } from "child_process";
|
|
50
50
|
import {
|
|
51
51
|
existsSync as existsSync4,
|
|
52
52
|
mkdirSync as mkdirSync3,
|
|
@@ -177,17 +177,13 @@ function wirePrepareScript(pkgPath) {
|
|
|
177
177
|
import { spawnSync as spawnSync2 } from "child_process";
|
|
178
178
|
import { existsSync as existsSync2 } from "fs";
|
|
179
179
|
import { join as join2 } from "path";
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
function git2(args, cwd) {
|
|
183
|
-
const r = spawnSync2("git", args, { encoding: "utf8", cwd });
|
|
184
|
-
return r.status === 0 ? r.stdout.trim() : "";
|
|
185
|
-
}
|
|
180
|
+
|
|
181
|
+
// src/trace/gitCommand.ts
|
|
186
182
|
var WRAPPERS = /* @__PURE__ */ new Set(["command", "exec", "nohup", "time", "env"]);
|
|
187
183
|
var GIT_VALUE_FLAGS = /* @__PURE__ */ new Set(["-C", "-c", "--git-dir", "--work-tree", "--exec-path"]);
|
|
188
|
-
|
|
189
|
-
function isGitPush(command) {
|
|
184
|
+
function gitInvocations(command) {
|
|
190
185
|
const blanked = command.replace(/\\["']/g, " ").replace(/'[^']*'/g, " ").replace(/"[^"]*"/g, " ");
|
|
186
|
+
const out = [];
|
|
191
187
|
for (const simple of blanked.split(/(?:&&|\|\||[;|\n])/)) {
|
|
192
188
|
const tokens = simple.trim().split(/\s+/).filter(Boolean);
|
|
193
189
|
let i = 0;
|
|
@@ -205,15 +201,31 @@ function isGitPush(command) {
|
|
|
205
201
|
i++;
|
|
206
202
|
}
|
|
207
203
|
}
|
|
208
|
-
|
|
204
|
+
const sub = tokens[i];
|
|
205
|
+
if (sub) {
|
|
206
|
+
out.push({ sub, rest: tokens.slice(i + 1) });
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
return out;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
// src/hook/prePush.ts
|
|
213
|
+
var REPUSH_EXIT = 42;
|
|
214
|
+
var ATTACH_SUBJECT = (branch) => `chore(receipts): attach agent receipt for ${branch}`;
|
|
215
|
+
function git2(args, cwd) {
|
|
216
|
+
const r = spawnSync2("git", args, { encoding: "utf8", cwd });
|
|
217
|
+
return r.status === 0 ? r.stdout.trim() : "";
|
|
218
|
+
}
|
|
219
|
+
var TAG_REFSPEC = /^(refs\/tags\/|v\d+(\.\d+)*$)/;
|
|
220
|
+
function isGitPush(command) {
|
|
221
|
+
for (const { sub, rest } of gitInvocations(command)) {
|
|
222
|
+
if (sub !== "push") {
|
|
209
223
|
continue;
|
|
210
224
|
}
|
|
211
|
-
const rest = tokens.slice(i + 1);
|
|
212
225
|
if (rest.includes("--dry-run") || rest.includes("-n") || rest.includes("--tags")) {
|
|
213
226
|
continue;
|
|
214
227
|
}
|
|
215
|
-
const
|
|
216
|
-
const refspecs = positionals.slice(1);
|
|
228
|
+
const refspecs = rest.filter((t) => !t.startsWith("-")).slice(1);
|
|
217
229
|
if (refspecs.length > 0 && refspecs.every((r) => TAG_REFSPEC.test(r))) {
|
|
218
230
|
continue;
|
|
219
231
|
}
|
|
@@ -224,8 +236,19 @@ function isGitPush(command) {
|
|
|
224
236
|
function gitStdinPushesBranch(stdin) {
|
|
225
237
|
return stdin.split("\n").some((line) => line.trim().startsWith("refs/heads/"));
|
|
226
238
|
}
|
|
239
|
+
var hasMarker = (root) => existsSync2(join2(root, ".github", "workflows", "receipts.yml")) || existsSync2(join2(root, ".receipts"));
|
|
227
240
|
function repoOptedIn(repoRoot) {
|
|
228
|
-
|
|
241
|
+
if (hasMarker(repoRoot)) {
|
|
242
|
+
return true;
|
|
243
|
+
}
|
|
244
|
+
const common = git2(["rev-parse", "--git-common-dir"], repoRoot);
|
|
245
|
+
if (common.endsWith("/.git")) {
|
|
246
|
+
const primary = common.slice(0, -"/.git".length);
|
|
247
|
+
if (primary && primary !== repoRoot) {
|
|
248
|
+
return hasMarker(primary);
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
return false;
|
|
229
252
|
}
|
|
230
253
|
function headIsAttachCommit(branch, cwd) {
|
|
231
254
|
return git2(["log", "-1", "--format=%s"], cwd) === ATTACH_SUBJECT(branch);
|
|
@@ -245,7 +268,7 @@ async function runHookPrePush(dialect, stdin, generate) {
|
|
|
245
268
|
if ((process.env.RECEIPTS_STORE || "commit").toLowerCase() === "none") {
|
|
246
269
|
return { exit: 0 };
|
|
247
270
|
}
|
|
248
|
-
if (dialect === "claude") {
|
|
271
|
+
if (dialect === "claude" || dialect === "codex") {
|
|
249
272
|
let payload;
|
|
250
273
|
try {
|
|
251
274
|
payload = JSON.parse(stdin);
|
|
@@ -301,7 +324,7 @@ async function runHookPrePush(dialect, stdin, generate) {
|
|
|
301
324
|
if (dialect === "git") {
|
|
302
325
|
return {
|
|
303
326
|
exit: REPUSH_EXIT,
|
|
304
|
-
message: `\u{1F4CE} Receipts: attached an agent receipt for '${branch}' \u2014
|
|
327
|
+
message: `\u{1F4CE} Receipts: attached an agent receipt for '${branch}' \u2014 rerun the same \`git push\` command to include it.`
|
|
305
328
|
};
|
|
306
329
|
}
|
|
307
330
|
return {
|
|
@@ -317,7 +340,7 @@ async function runHookPrePush(dialect, stdin, generate) {
|
|
|
317
340
|
import { existsSync as existsSync3, mkdirSync as mkdirSync2, readFileSync as readFileSync2, writeFileSync as writeFileSync2 } from "fs";
|
|
318
341
|
import { dirname } from "path";
|
|
319
342
|
var HOOK_COMMAND = "npx -y altimate-receipts@latest hook pre-push";
|
|
320
|
-
function mergeHookIntoSettings(path) {
|
|
343
|
+
function mergeHookIntoSettings(path, command = HOOK_COMMAND) {
|
|
321
344
|
let settings = {};
|
|
322
345
|
if (existsSync3(path)) {
|
|
323
346
|
let parsed;
|
|
@@ -345,11 +368,11 @@ function mergeHookIntoSettings(path) {
|
|
|
345
368
|
if (!Array.isArray(pre)) {
|
|
346
369
|
return { ok: false, reason: `${path} has a non-array hooks.PreToolUse \u2014 leaving it untouched` };
|
|
347
370
|
}
|
|
348
|
-
const present = pre.some((e) => e?.hooks?.some((h) => h?.command ===
|
|
371
|
+
const present = pre.some((e) => e?.hooks?.some((h) => h?.command === command));
|
|
349
372
|
if (present) {
|
|
350
373
|
return { ok: true, changed: false };
|
|
351
374
|
}
|
|
352
|
-
pre.push({ matcher: "Bash", hooks: [{ type: "command", command
|
|
375
|
+
pre.push({ matcher: "Bash", hooks: [{ type: "command", command }] });
|
|
353
376
|
mkdirSync2(dirname(path), { recursive: true });
|
|
354
377
|
writeFileSync2(path, `${JSON.stringify(settings, null, 2)}
|
|
355
378
|
`);
|
|
@@ -1003,6 +1026,72 @@ function renderHandoffMarkdown(h) {
|
|
|
1003
1026
|
`;
|
|
1004
1027
|
}
|
|
1005
1028
|
|
|
1029
|
+
// src/trace/commitMatch.ts
|
|
1030
|
+
import { spawnSync as spawnSync3 } from "child_process";
|
|
1031
|
+
var SHA_CAP = 200;
|
|
1032
|
+
function branchShas(base, cwd) {
|
|
1033
|
+
if (!base) {
|
|
1034
|
+
return [];
|
|
1035
|
+
}
|
|
1036
|
+
const r = spawnSync3("git", ["log", `--max-count=${SHA_CAP}`, "--format=%H", `${base}..HEAD`], {
|
|
1037
|
+
encoding: "utf8",
|
|
1038
|
+
cwd
|
|
1039
|
+
});
|
|
1040
|
+
if (r.status !== 0) {
|
|
1041
|
+
return [];
|
|
1042
|
+
}
|
|
1043
|
+
return r.stdout.split("\n").filter((s) => /^[0-9a-f]{40}$/.test(s));
|
|
1044
|
+
}
|
|
1045
|
+
var HEX_RUN = /\b[0-9a-f]{7,40}\b/g;
|
|
1046
|
+
function commandOf(input) {
|
|
1047
|
+
if (typeof input === "string") {
|
|
1048
|
+
const t = input.trim();
|
|
1049
|
+
if (t.startsWith("{")) {
|
|
1050
|
+
try {
|
|
1051
|
+
return commandOf(JSON.parse(t));
|
|
1052
|
+
} catch {
|
|
1053
|
+
return input;
|
|
1054
|
+
}
|
|
1055
|
+
}
|
|
1056
|
+
return input;
|
|
1057
|
+
}
|
|
1058
|
+
if (input && typeof input === "object") {
|
|
1059
|
+
const o = input;
|
|
1060
|
+
for (const key of ["command", "cmd"]) {
|
|
1061
|
+
const v = o[key];
|
|
1062
|
+
if (typeof v === "string") {
|
|
1063
|
+
return v;
|
|
1064
|
+
}
|
|
1065
|
+
if (Array.isArray(v)) {
|
|
1066
|
+
return v.filter((t) => typeof t === "string").join(" ");
|
|
1067
|
+
}
|
|
1068
|
+
}
|
|
1069
|
+
}
|
|
1070
|
+
return "";
|
|
1071
|
+
}
|
|
1072
|
+
function isGitWrite(command) {
|
|
1073
|
+
return gitInvocations(command).some((g) => g.sub === "commit" || g.sub === "push");
|
|
1074
|
+
}
|
|
1075
|
+
function authoredBranch(spans, shas) {
|
|
1076
|
+
if (shas.length === 0) {
|
|
1077
|
+
return false;
|
|
1078
|
+
}
|
|
1079
|
+
for (const s of spans) {
|
|
1080
|
+
if (s.kind !== "tool" || typeof s.output !== "string") {
|
|
1081
|
+
continue;
|
|
1082
|
+
}
|
|
1083
|
+
if (!isGitWrite(commandOf(s.input))) {
|
|
1084
|
+
continue;
|
|
1085
|
+
}
|
|
1086
|
+
for (const token of s.output.match(HEX_RUN) ?? []) {
|
|
1087
|
+
if (shas.some((full) => full.startsWith(token))) {
|
|
1088
|
+
return true;
|
|
1089
|
+
}
|
|
1090
|
+
}
|
|
1091
|
+
}
|
|
1092
|
+
return false;
|
|
1093
|
+
}
|
|
1094
|
+
|
|
1006
1095
|
// src/cli.ts
|
|
1007
1096
|
var HELP = `
|
|
1008
1097
|
\u{1F9FE} receipts \u2014 proof, not vibes
|
|
@@ -1032,9 +1121,10 @@ Usage
|
|
|
1032
1121
|
receipts sarif [receipt] SARIF 2.1.0 for GitHub code-scanning (inline + Security tab; --out f)
|
|
1033
1122
|
receipts prune [dir] Remove committed receipts for merged/deleted branches (--dry-run)
|
|
1034
1123
|
receipts init One-command adopt: PR-check workflow + the repo-committed
|
|
1035
|
-
agent hook
|
|
1124
|
+
agent hook; --pr also branches, commits, pushes + opens the PR
|
|
1125
|
+
(--prepare wires the git floor; --agents codex too)
|
|
1036
1126
|
receipts hook pre-push (called by hooks, not humans) attach the receipt on push
|
|
1037
|
-
(--agent claude | git selects the payload dialect)
|
|
1127
|
+
(--agent claude | codex | git selects the payload dialect)
|
|
1038
1128
|
receipts install-hook Write the self-updating pre-push hook into .git/hooks
|
|
1039
1129
|
receipts setup-local Add the hook to YOUR ~/.claude/settings.json (only fires
|
|
1040
1130
|
in repos that have adopted the Receipts workflow)
|
|
@@ -1112,6 +1202,7 @@ function parseArgs(argv) {
|
|
|
1112
1202
|
dryRun: false,
|
|
1113
1203
|
wholeSession: false,
|
|
1114
1204
|
prepare: false,
|
|
1205
|
+
pr: false,
|
|
1115
1206
|
color: !process.env.NO_COLOR && process.stdout.isTTY === true
|
|
1116
1207
|
};
|
|
1117
1208
|
const positionals = [];
|
|
@@ -1156,12 +1247,16 @@ function parseArgs(argv) {
|
|
|
1156
1247
|
parsed.wholeSession = true;
|
|
1157
1248
|
} else if (a === "--prepare") {
|
|
1158
1249
|
parsed.prepare = true;
|
|
1250
|
+
} else if (a === "--pr") {
|
|
1251
|
+
parsed.pr = true;
|
|
1252
|
+
} else if (a === "--agents") {
|
|
1253
|
+
parsed.agents = (args[++i] ?? "").split(",").filter(Boolean);
|
|
1159
1254
|
} else if (a === "--agent") {
|
|
1160
1255
|
const next = args[i + 1];
|
|
1161
1256
|
if (next && agentIds().includes(next)) {
|
|
1162
1257
|
parsed.agent = next;
|
|
1163
1258
|
i++;
|
|
1164
|
-
} else if (next === "claude" || next === "git") {
|
|
1259
|
+
} else if (next === "claude" || next === "codex" || next === "git") {
|
|
1165
1260
|
parsed.hookDialect = next;
|
|
1166
1261
|
i++;
|
|
1167
1262
|
}
|
|
@@ -1222,7 +1317,7 @@ async function run(argv) {
|
|
|
1222
1317
|
return runPrune(args.file, { dryRun: args.dryRun });
|
|
1223
1318
|
}
|
|
1224
1319
|
if (args.command === "init") {
|
|
1225
|
-
return runInit({ prepare: args.prepare });
|
|
1320
|
+
return runInit({ prepare: args.prepare, agents: args.agents, pr: args.pr });
|
|
1226
1321
|
}
|
|
1227
1322
|
if (args.command === "hook") {
|
|
1228
1323
|
return runHook(args.file, args.hookDialect ?? "claude");
|
|
@@ -1342,7 +1437,7 @@ Run a coding-agent session first, then try again.
|
|
|
1342
1437
|
return 0;
|
|
1343
1438
|
}
|
|
1344
1439
|
function git3(args) {
|
|
1345
|
-
const r =
|
|
1440
|
+
const r = spawnSync4("git", args, { encoding: "utf8" });
|
|
1346
1441
|
return r.status === 0 ? r.stdout.trim() : "";
|
|
1347
1442
|
}
|
|
1348
1443
|
var PR_SELECT_SCAN = 150;
|
|
@@ -1360,7 +1455,7 @@ function branchBirthMs(base) {
|
|
|
1360
1455
|
function staleForBranch(endedAt, birthMs) {
|
|
1361
1456
|
return birthMs != null && endedAt != null && endedAt < birthMs;
|
|
1362
1457
|
}
|
|
1363
|
-
async function pickForDiff(all, branch, repoRoot, files, birthMs = null) {
|
|
1458
|
+
async function pickForDiff(all, branch, repoRoot, files, birthMs = null, shas = []) {
|
|
1364
1459
|
const load = async (sum) => {
|
|
1365
1460
|
const session = await loadSession(sum);
|
|
1366
1461
|
if (!session) {
|
|
@@ -1368,6 +1463,14 @@ async function pickForDiff(all, branch, repoRoot, files, birthMs = null) {
|
|
|
1368
1463
|
}
|
|
1369
1464
|
return { summary: sum, session, derived: deriveSpans(session) };
|
|
1370
1465
|
};
|
|
1466
|
+
if (shas.length > 0) {
|
|
1467
|
+
for (const sum of all.slice(0, PR_SELECT_SCAN)) {
|
|
1468
|
+
const cand = await load(sum);
|
|
1469
|
+
if (cand && authoredBranch(cand.derived.spans, shas)) {
|
|
1470
|
+
return cand;
|
|
1471
|
+
}
|
|
1472
|
+
}
|
|
1473
|
+
}
|
|
1371
1474
|
const primarySum = selectForBranch(all, branch, repoRoot);
|
|
1372
1475
|
const primary = primarySum ? await load(primarySum) : null;
|
|
1373
1476
|
if (primary && diffOverlap(primary.derived, files) > 0) {
|
|
@@ -1398,7 +1501,14 @@ async function runPr(opts) {
|
|
|
1398
1501
|
}
|
|
1399
1502
|
const diff = opts.wholeSession ? null : changedFiles(opts.base);
|
|
1400
1503
|
const all = await listSessions();
|
|
1401
|
-
const picked = diff ? await pickForDiff(
|
|
1504
|
+
const picked = diff ? await pickForDiff(
|
|
1505
|
+
all,
|
|
1506
|
+
branch,
|
|
1507
|
+
repoRoot || void 0,
|
|
1508
|
+
diff.files,
|
|
1509
|
+
branchBirthMs(diff.base),
|
|
1510
|
+
branchShas(diff.base)
|
|
1511
|
+
) : await (async () => {
|
|
1402
1512
|
const sum = selectForBranch(all, branch, repoRoot || void 0);
|
|
1403
1513
|
return sum ? { summary: sum, session: await loadSession(sum), derived: null } : null;
|
|
1404
1514
|
})();
|
|
@@ -1417,7 +1527,7 @@ Build the branch with a coding agent first, or run \`receipts --list\`.
|
|
|
1417
1527
|
let scope = { kind: "session" };
|
|
1418
1528
|
let scopeNote = "whole session";
|
|
1419
1529
|
if (diff) {
|
|
1420
|
-
const sd = applyDiffScope(derived, findings, diff.files);
|
|
1530
|
+
const sd = applyDiffScope(derived, findings, diff.files, session.projectPath);
|
|
1421
1531
|
derived = sd.derived;
|
|
1422
1532
|
findings = sd.findings;
|
|
1423
1533
|
scope = { kind: "diff", base: diff.base, files: diff.files };
|
|
@@ -1465,6 +1575,11 @@ Build the branch with a coding agent first, or run \`receipts --list\`.
|
|
|
1465
1575
|
const dir = join6(repoRoot || ".", ".receipts");
|
|
1466
1576
|
const out = opts.out ?? join6(dir, `${safe}.json`);
|
|
1467
1577
|
mkdirSync3(dir, { recursive: true });
|
|
1578
|
+
const attrs = join6(dir, ".gitattributes");
|
|
1579
|
+
if (!existsSync4(attrs)) {
|
|
1580
|
+
writeFileSync3(attrs, "* linguist-generated\n");
|
|
1581
|
+
git3(["add", attrs]);
|
|
1582
|
+
}
|
|
1468
1583
|
writeFileSync3(out, json);
|
|
1469
1584
|
git3(["add", out]);
|
|
1470
1585
|
const rel = repoRoot ? relative(repoRoot, out) : out;
|
|
@@ -1844,6 +1959,7 @@ async function runEval(opts = {}) {
|
|
|
1844
1959
|
}
|
|
1845
1960
|
function runInit(opts = {}) {
|
|
1846
1961
|
const lines = [];
|
|
1962
|
+
const written = [];
|
|
1847
1963
|
const v = getVersion();
|
|
1848
1964
|
const major = /^(\d+)\.\d+\.\d+/.exec(v)?.[1];
|
|
1849
1965
|
const tag = major ? `v${major}` : "v0";
|
|
@@ -1854,6 +1970,7 @@ function runInit(opts = {}) {
|
|
|
1854
1970
|
} else {
|
|
1855
1971
|
mkdirSync3(dir, { recursive: true });
|
|
1856
1972
|
writeFileSync3(path, workflowContent(tag, v));
|
|
1973
|
+
written.push(path);
|
|
1857
1974
|
lines.push(`receipts init: wrote ${path} (tracking ${tag}, quiet + non-blocking).`);
|
|
1858
1975
|
}
|
|
1859
1976
|
const settingsPath = join6(".claude", "settings.json");
|
|
@@ -1861,6 +1978,7 @@ function runInit(opts = {}) {
|
|
|
1861
1978
|
if (!merged.ok) {
|
|
1862
1979
|
lines.push(`receipts init: ${merged.reason}.`);
|
|
1863
1980
|
} else if (merged.changed) {
|
|
1981
|
+
written.push(settingsPath);
|
|
1864
1982
|
lines.push(`receipts init: added the receipts pre-push hook to ${settingsPath}.`);
|
|
1865
1983
|
} else {
|
|
1866
1984
|
lines.push(`receipts init: ${settingsPath} already has the receipts hook.`);
|
|
@@ -1868,13 +1986,37 @@ function runInit(opts = {}) {
|
|
|
1868
1986
|
const ignorePath = join6(".claude", ".gitignore");
|
|
1869
1987
|
if (!existsSync4(ignorePath)) {
|
|
1870
1988
|
writeFileSync3(ignorePath, "*\n!settings.json\n!.gitignore\n");
|
|
1989
|
+
written.push(ignorePath);
|
|
1871
1990
|
lines.push(`receipts init: wrote ${ignorePath} (commit settings.json, ignore the rest).`);
|
|
1872
1991
|
}
|
|
1992
|
+
if (opts.agents?.includes("codex") || existsSync4(".codex")) {
|
|
1993
|
+
const codexPath = join6(".codex", "hooks.json");
|
|
1994
|
+
const m = mergeHookIntoSettings(
|
|
1995
|
+
codexPath,
|
|
1996
|
+
"npx -y altimate-receipts@latest hook pre-push --agent codex"
|
|
1997
|
+
);
|
|
1998
|
+
if (!m.ok) {
|
|
1999
|
+
lines.push(`receipts init: ${m.reason}.`);
|
|
2000
|
+
} else if (m.changed) {
|
|
2001
|
+
written.push(codexPath);
|
|
2002
|
+
lines.push(`receipts init: added the receipts pre-push hook to ${codexPath}.`);
|
|
2003
|
+
} else {
|
|
2004
|
+
lines.push(`receipts init: ${codexPath} already has the receipts hook.`);
|
|
2005
|
+
}
|
|
2006
|
+
}
|
|
2007
|
+
const attrsPath = join6(".receipts", ".gitattributes");
|
|
2008
|
+
if (!existsSync4(attrsPath)) {
|
|
2009
|
+
mkdirSync3(".receipts", { recursive: true });
|
|
2010
|
+
writeFileSync3(attrsPath, "* linguist-generated\n");
|
|
2011
|
+
written.push(attrsPath);
|
|
2012
|
+
lines.push(`receipts init: wrote ${attrsPath} (receipts collapse in PR diffs).`);
|
|
2013
|
+
}
|
|
1873
2014
|
if (opts.prepare) {
|
|
1874
2015
|
const wired = wirePrepareScript("package.json");
|
|
1875
2016
|
if (!wired.ok) {
|
|
1876
2017
|
lines.push(`receipts init: ${wired.reason}.`);
|
|
1877
2018
|
} else if (wired.changed) {
|
|
2019
|
+
written.push("package.json");
|
|
1878
2020
|
lines.push(
|
|
1879
2021
|
"receipts init: wired `prepare` in package.json \u2014 `npm install` now self-installs the git hook."
|
|
1880
2022
|
);
|
|
@@ -1882,11 +2024,82 @@ function runInit(opts = {}) {
|
|
|
1882
2024
|
lines.push("receipts init: package.json `prepare` already wired.");
|
|
1883
2025
|
}
|
|
1884
2026
|
}
|
|
1885
|
-
|
|
2027
|
+
if (opts.pr) {
|
|
2028
|
+
openAdoptionPr(written, lines);
|
|
2029
|
+
} else {
|
|
2030
|
+
lines.push(" Commit these, open a PR, and receipts attach themselves from then on.");
|
|
2031
|
+
lines.push(" (Or let init do that too: `npx altimate-receipts init --pr`.)");
|
|
2032
|
+
}
|
|
2033
|
+
if (written.includes(settingsPath)) {
|
|
2034
|
+
lines.push(" Note: agent sessions load hooks at startup \u2014 restart any running session once.");
|
|
2035
|
+
}
|
|
1886
2036
|
process.stdout.write(`${lines.join("\n")}
|
|
1887
2037
|
`);
|
|
1888
2038
|
return 0;
|
|
1889
2039
|
}
|
|
2040
|
+
var ADOPT_BRANCH = "chore/adopt-receipts";
|
|
2041
|
+
function openAdoptionPr(written, lines) {
|
|
2042
|
+
if (written.length === 0) {
|
|
2043
|
+
lines.push("receipts init: nothing new to commit \u2014 the repo is already integrated.");
|
|
2044
|
+
return;
|
|
2045
|
+
}
|
|
2046
|
+
if (!git3(["rev-parse", "--git-dir"])) {
|
|
2047
|
+
lines.push("receipts init: not a git repository \u2014 commit the files above manually.");
|
|
2048
|
+
return;
|
|
2049
|
+
}
|
|
2050
|
+
if (git3(["rev-parse", "--verify", "--quiet", ADOPT_BRANCH]) !== "") {
|
|
2051
|
+
lines.push(
|
|
2052
|
+
`receipts init: branch ${ADOPT_BRANCH} already exists \u2014 commit the files above to it manually.`
|
|
2053
|
+
);
|
|
2054
|
+
return;
|
|
2055
|
+
}
|
|
2056
|
+
if (spawnSync4("git", ["checkout", "-b", ADOPT_BRANCH], { encoding: "utf8" }).status !== 0) {
|
|
2057
|
+
lines.push(`receipts init: could not create branch ${ADOPT_BRANCH} \u2014 commit manually.`);
|
|
2058
|
+
return;
|
|
2059
|
+
}
|
|
2060
|
+
spawnSync4("git", ["add", "--", ...written], { encoding: "utf8" });
|
|
2061
|
+
const commit = spawnSync4(
|
|
2062
|
+
"git",
|
|
2063
|
+
["commit", "-m", "chore: adopt receipts \u2014 zero-install agent-work verification"],
|
|
2064
|
+
{ encoding: "utf8" }
|
|
2065
|
+
);
|
|
2066
|
+
if (commit.status !== 0) {
|
|
2067
|
+
lines.push(
|
|
2068
|
+
`receipts init: commit failed (pre-commit hooks?) \u2014 files are staged on ${ADOPT_BRANCH}; finish manually.`
|
|
2069
|
+
);
|
|
2070
|
+
return;
|
|
2071
|
+
}
|
|
2072
|
+
lines.push(`receipts init: committed ${written.length} file(s) on ${ADOPT_BRANCH}.`);
|
|
2073
|
+
const push = spawnSync4("git", ["push", "-u", "origin", ADOPT_BRANCH], { encoding: "utf8" });
|
|
2074
|
+
if (push.status !== 0) {
|
|
2075
|
+
lines.push(
|
|
2076
|
+
"receipts init: push failed (no remote / auth?) \u2014 push the branch and open a PR manually."
|
|
2077
|
+
);
|
|
2078
|
+
return;
|
|
2079
|
+
}
|
|
2080
|
+
lines.push(`receipts init: pushed ${ADOPT_BRANCH}.`);
|
|
2081
|
+
const gh = spawnSync4(
|
|
2082
|
+
"gh",
|
|
2083
|
+
[
|
|
2084
|
+
"pr",
|
|
2085
|
+
"create",
|
|
2086
|
+
"--title",
|
|
2087
|
+
"chore: adopt receipts \u2014 zero-install agent-work verification",
|
|
2088
|
+
"--body",
|
|
2089
|
+
"Adds the Verified-by-Receipts PR check and the repo-committed agent hook (generated by `npx altimate-receipts init --pr`). Contributors install nothing \u2014 see https://github.com/AltimateAI/altimate-receipts/blob/main/docs/onboarding.md"
|
|
2090
|
+
],
|
|
2091
|
+
{ encoding: "utf8" }
|
|
2092
|
+
);
|
|
2093
|
+
if (gh.status === 0) {
|
|
2094
|
+
lines.push(`receipts init: opened the PR \u2014 ${gh.stdout.trim().split("\n").pop()}`);
|
|
2095
|
+
return;
|
|
2096
|
+
}
|
|
2097
|
+
const remote = git3(["remote", "get-url", "origin"]);
|
|
2098
|
+
const slug = remote ? /github\.com[:/]([^/]+\/[^/.]+)/.exec(remote)?.[1] : void 0;
|
|
2099
|
+
lines.push(
|
|
2100
|
+
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."
|
|
2101
|
+
);
|
|
2102
|
+
}
|
|
1890
2103
|
function workflowContent(tag, v) {
|
|
1891
2104
|
return `name: Verified by Receipts
|
|
1892
2105
|
|