From 4c6159a9f2d4f8510c636adb769729ea1033371f Mon Sep 17 00:00:00 2001 From: mgillr <36207807+mgillr@users.noreply.github.com> Date: Wed, 21 Oct 2026 19:13:23 +0200 Subject: [PATCH] pre-push: gate a commit whose tree does not track the contract The contract deliberately stays untracked: scope and hygiene keep proofline.json out of the tree, so a pushed commit does not carry it. gateAtCommit materialized a worktree of the commit and refused with "no proofline.json" because the fresh worktree never holds an untracked file. Copy the root's contract into the worktree when the commit does not track one, so the gate runs against the same bytes the root gates. --- src/index.mjs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/index.mjs b/src/index.mjs index 24c3283..5647418 100644 --- a/src/index.mjs +++ b/src/index.mjs @@ -354,6 +354,12 @@ function gateAtCommit(root, sha, id) { const add = git(['worktree', 'add', '--detach', '--force', wt, sha], root); if (!add.ok) return { ok: false, msg: `${id} at ${sha}: worktree failed: ${add.err}\nbase ${baseRef}` }; added = true; + // The contract deliberately stays untracked (scope and hygiene keep it out of the + // tree), so a commit does not carry it. A gate at that commit still needs it: copy + // the root's contract into the fresh worktree when the commit does not track one. + for (const f of ['proofline.json']) { + if (!fs.existsSync(path.join(wt, f)) && fs.existsSync(path.join(root, f))) fs.copyFileSync(path.join(root, f), path.join(wt, f)); + } let g; try { g = E.gate(wt, id, { skipTypes: ['pushed'], base: baseRef }); } catch (e) { return { ok: false, msg: `${id} at ${sha}: ${e.message}\nbase ${baseRef}` }; }