instar 1.3.832 → 1.3.833

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.
@@ -467,6 +467,46 @@ try {
467
467
  // grep not available — skip gracefully
468
468
  }
469
469
 
470
+ // ── 7. Duplicate-build advisory (docs/specs/duplicate-build-guard.md FD1) ──
471
+ //
472
+ // warnings[] ONLY — NEVER errors[]: a duplicate build is recoverable (close
473
+ // the PR), so the irreversible-action carve-out does not apply and a
474
+ // deterministic push-block would be brittle authority over a judgment call
475
+ // (docs/signal-vs-authority.md). The teeth live at build-start (the PreToolUse
476
+ // gate) + the precommit presence backstop; this is the last-look signal.
477
+ //
478
+ // Honors INSTAR_PRE_PUSH_SKIP (mirrors the husky skip) and the
479
+ // INSTAR_DUP_BUILD_CHECK=off master switch; the open-PR scan is skipped under
480
+ // CI inside the library (§3.3/§5). The library re-fetches origin/main and
481
+ // re-keys its cache at phase 'pre-push' so a merge that landed mid-build is
482
+ // actually seen at push time (§3.2). ANY failure degrades to silence — the
483
+ // guard's own breakage must never block or delay a push (FD5).
484
+
485
+ if (process.env.INSTAR_PRE_PUSH_SKIP !== '1') {
486
+ try {
487
+ const dup = await import('./lib/duplicate-build-check.mjs');
488
+ if (!dup.isGuardOff(process.env, ROOT)) {
489
+ const specPath = dup.resolveSpecForAdvisory(ROOT);
490
+ if (specPath) {
491
+ const result = dup.runDuplicateBuildCheck({ specPath, root: ROOT, phase: 'pre-push', env: process.env });
492
+ if (result && (result.verdict === 'likely-duplicate' || result.verdict === 'verify')) {
493
+ const top = (result.evidence || []).slice(0, 3).map((e) => ` • ${e.id} [${e.source}] ${e.detail}`);
494
+ warnings.push(
495
+ `Duplicate-build check: ${result.verdict}` +
496
+ (result.cause ? ` (cause: ${result.cause})` : '') +
497
+ ` for spec ${path.relative(ROOT, specPath)}.` +
498
+ (top.length ? `\n${top.join('\n')}` : '') +
499
+ `\n Advisory only (never blocks a push) — review the overlap before merging.` +
500
+ `\n docs/specs/duplicate-build-guard.md | off-switch: INSTAR_DUP_BUILD_CHECK=off`
501
+ );
502
+ }
503
+ }
504
+ }
505
+ } catch {
506
+ // Advisory only — the guard's own failure never blocks a push (FD5).
507
+ }
508
+ }
509
+
470
510
  // ── Report ────────────────────────────────────────────────────────────
471
511
 
472
512
  if (errors.length > 0 || warnings.length > 0) {
@@ -177,6 +177,39 @@ fs.mkdirSync(traceDir, { recursive: true });
177
177
  const traceFile = path.join(traceDir, `${timestamp.replace(/[:.]/g, '-')}-${slug}-${traceId}.json`);
178
178
  const toolchain = buildToolchain();
179
179
  const isTier1 = tier === 1;
180
+
181
+ /**
182
+ * Duplicate-build guard (docs/specs/duplicate-build-guard.md §3.4): fold the
183
+ * build-start check's recorded verdict + disposition (the worktree stub at
184
+ * .instar/dup-build-check.json, written by scripts/lib/duplicate-build-check.mjs
185
+ * and/or the build-start PreToolUse gate) into the trace as the
186
+ * `duplicateBuildCheck` field the precommit PRESENCE backstop looks for.
187
+ * Fail-open: a missing/unreadable stub simply omits the field — the backstop
188
+ * (not this writer) decides what that means for the commit.
189
+ */
190
+ function readDuplicateBuildCheck() {
191
+ try {
192
+ const stubPath = path.join(ROOT, '.instar', 'dup-build-check.json');
193
+ if (!fs.existsSync(stubPath)) return undefined;
194
+ const stub = JSON.parse(fs.readFileSync(stubPath, 'utf8'));
195
+ if (!stub || typeof stub.verdict !== 'string') return undefined;
196
+ const d = stub.disposition && typeof stub.disposition === 'object' ? stub.disposition : {};
197
+ return {
198
+ verdict: stub.verdict,
199
+ cause: stub.cause ?? null,
200
+ ...(Array.isArray(stub.causes) ? { causes: stub.causes } : {}),
201
+ decision: d.decision ?? null,
202
+ reason: d.reason ?? null,
203
+ acknowledgedEvidenceIds: Array.isArray(d.acknowledgedEvidenceIds) ? d.acknowledgedEvidenceIds : [],
204
+ ...(stub.checkedAt ? { checkedAt: stub.checkedAt } : {}),
205
+ ...(stub.specSlug ? { specSlug: stub.specSlug } : {}),
206
+ };
207
+ } catch {
208
+ return undefined; // fail-open: never block trace-writing on the guard
209
+ }
210
+ }
211
+ const duplicateBuildCheck = readDuplicateBuildCheck();
212
+
180
213
  const trace = {
181
214
  version: toolchain ? 3 : 2, // v3 only when enriched; readers ignore unknown fields either way
182
215
  sessionId: process.env.INSTAR_SESSION_ID || process.env.CLAUDE_CODE_SESSION_ID || 'unknown',
@@ -196,6 +229,9 @@ const trace = {
196
229
  secondPass,
197
230
  reviewerConcurred,
198
231
  ...(toolchain ? { toolchain } : {}),
232
+ // Duplicate-build guard §3.4 — omitted when no stub exists so pre-guard
233
+ // traces round-trip byte-identically.
234
+ ...(duplicateBuildCheck ? { duplicateBuildCheck } : {}),
199
235
  };
200
236
 
201
237
  fs.writeFileSync(traceFile, JSON.stringify(trace, null, 2) + '\n');
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "$schema": "./builtin-manifest.schema.json",
3
3
  "schemaVersion": 1,
4
- "generatedAt": "2026-07-12T22:59:24.984Z",
5
- "instarVersion": "1.3.832",
4
+ "generatedAt": "2026-07-13T01:20:32.273Z",
5
+ "instarVersion": "1.3.833",
6
6
  "entryCount": 202,
7
7
  "entries": {
8
8
  "hook:session-start": {
@@ -0,0 +1,22 @@
1
+ # Upgrade Guide — vNEXT
2
+
3
+ <!-- assembled-by: assemble-next-md -->
4
+ <!-- bump: patch -->
5
+
6
+ ## What Changed
7
+
8
+ The instar-dev workflow gains a **duplicate-build guard** (ACT-592): before an agent building instar spends a session implementing a spec, a deterministic check asks "is someone already building this?" — scanning a local sibling-session in-flight ledger (write-first-then-scan, PID-reuse-proof liveness, terminal cleanup), open PRs (bounded two-stage `gh` scan), and recently-merged commits — plus a calibrated fuzzy match that survives renames. On a `likely-duplicate`/`verify` verdict, a repo-local PreToolUse hook blocks the FIRST implementation write until the author records a disposition (`{decision, reason, acknowledgedEvidenceIds[]}`); the precommit gate carries a presence-only backstop and pre-push a `warnings[]`-only advisory. Everything is fail-open-total (any error → non-blocking, visible `check-errored` stub) and signal-not-authority (no layer ever gates on the verdict's meaning; the author is the authority). Kill switch `INSTAR_DUP_BUILD_CHECK=off`. Born from the 2026-07-12 incident where a full session rebuilt work already merged under a different tracking id — and live-verified: run on its own spec, the guard correctly surfaced the literal incident commits as evidence.
9
+
10
+ ## What to Tell Your User
11
+
12
+ None — internal development-workflow tooling with no user-facing surface. This guard runs only inside the instar repo's own development gates (a repo-local hook + the commit/push gates); it never touches agent behavior, messaging, sessions, or any fleet-installed file. Nothing changes for any instar user or agent operator.
13
+
14
+ ## Summary of New Capabilities
15
+
16
+ None user-facing. Internally: the instar development workflow now structurally prevents the "two parallel builds of the same feature under different tracking ids" failure — the first implementation write literally cannot happen until a detected overlap has been confronted and dispositioned, with a full audit trail (`logs/dup-build-check.jsonl`).
17
+
18
+ ## Evidence
19
+
20
+ - Side-effects review: `upgrades/side-effects/duplicate-build-guard.md` (independent second-pass CONCUR; its two actionable findings — terminal marker removal wiring, ledger realpath jail — fixed in-line before commit).
21
+ - Converged + approved spec: `docs/specs/duplicate-build-guard.md` (6 review rounds, cross-model codex/GPT-5.5 every round; both gating reviewers CONVERGED with zero material findings).
22
+ - Tests: `tests/unit/duplicate-build-check.test.ts` (50), `tests/unit/duplicate-build-gate-hook.test.ts` (14), `tests/integration/duplicate-build-guard-gates.test.ts` (13), `tests/e2e/duplicate-build-guard-incident.test.ts` (3 — the real incident-geometry regression fixture), plus a committed Jaccard calibration corpus (100% recall on known duplicate pairs, 100% precision on adversarial non-dup pairs at the shipped 0.22 threshold). Full lint chain clean; 107/107 touched-suite regression green.
@@ -0,0 +1,77 @@
1
+ # Side-Effects Review — Duplicate-Build Guard (ACT-592)
2
+
3
+ **Version / slug:** `duplicate-build-guard`
4
+ **Date:** `2026-07-12`
5
+ **Author:** `echo`
6
+ **Second-pass reviewer:** `independent reviewer subagent — CONCUR (all load-bearing claims verified against real code; see Second-pass review section). Its two actionable findings were FIXED IN-LINE before commit: (1) terminal ledger-marker removal is now wired at both precommit success exits (fail-open, 3s timeout); (2) ledgerPathFor realpath-jails the agent home. Re-verified: lint exit 0, 103/103 tests green after the fixes.`
7
+
8
+ ## Summary of the change
9
+
10
+ Implements the converged + approved spec `docs/specs/duplicate-build-guard.md` (6 review rounds, cross-model every round): a concurrency-first duplicate-build detector for the instar-dev workflow, born from the 2026-07-12 ACT-562-vs-#1458 incident (a full session spent building work already merged under a different tracking id). Four pieces: (1) `scripts/lib/duplicate-build-check.mjs` — a deterministic, fail-open, security-hardened check that extracts a spec's targets (exact census/symbol/file targets + a calibrated token-set-Jaccard fuzzy floor) and scans three concurrency sources (a local sibling in-flight ledger with write-first-then-scan + liveness-conjunction + terminal lifecycle; a bounded two-stage open-PR scan; a merged-commit lookback) plus a `main`-state corroborator, emitting a TOTAL verdict ladder (`likely-duplicate`/`verify`/`clear`) with cause tagging; (2) `.claude/hooks/duplicate-build-start-gate.js` — a repo-local PreToolUse hook (Write|Edit|MultiEdit) that blocks the FIRST implementation write on a `likely-duplicate`/`verify` verdict until the author records a disposition (`{decision, reason, acknowledgedEvidenceIds[]}`), with a `check-errored` fail-open auto-stub; (3) a presence-only backstop in `scripts/instar-dev-precommit.js` (refuses a trace lacking the `duplicateBuildCheck` field only when the guard is provably live; warns loudly on `check-errored`; NEVER gates on the verdict's value); (4) a `warnings[]`-only advisory in `scripts/pre-push-gate.js`. Audit trail at `logs/dup-build-check.jsonl` (metadata only). Kill switch `INSTAR_DUP_BUILD_CHECK=off`. **Live-verified on its own spec**: the check returned `likely-duplicate (cause: concurrency)` with evidence ids pointing at the literal incident commits (`4502e4e9` #1458, `88da6009` #1461) — the origin incident geometry is provably caught.
11
+
12
+ ## Decision-point inventory
13
+
14
+ - **Overlap detection** (`duplicate-build-check.mjs`) — add — a pure signal-producer: deterministic reads, emits a verdict + evidence, never blocks anything itself. `invariant` per the spec's Decision-points section.
15
+ - **Build-start PreToolUse gate** (`duplicate-build-start-gate.js`) — add — blocks the first implementation write ONLY on the absence of a recorded disposition (a structural field-validator, the signal-vs-authority "hard-invariant validation" carve-out). The proceed/abandon judgment is the author's; any recorded disposition (including proceed-on-likely-duplicate) unblocks. Fail-open on every error.
16
+ - **Precommit presence backstop** (`instar-dev-precommit.js`) — modify — presence-only field validation; accepts `decision:"proceed"` on `likely-duplicate`; warns (never blocks) on the `check-errored` stub; armed only when the guard is provably live (no retro-refusal of pre-guard traces).
17
+ - **Pre-push advisory** (`pre-push-gate.js`) — modify — appends to `warnings[]` only, never `errors[]`; honors `INSTAR_PRE_PUSH_SKIP` + `INSTAR_DUP_BUILD_CHECK=off`; a lib failure is silent (advisory absence, never a blocked push).
18
+
19
+ ---
20
+
21
+ ## 1. Over-block
22
+
23
+ The one blocking surface is the PreToolUse gate, and its over-block risk is bounded by design: it blocks only when (a) a verdict of `likely-duplicate`/`verify` exists AND (b) no disposition is recorded — and recording ANY disposition (a one-command CLI call) unblocks. A false-positive verdict therefore costs the author one look + one command, never a lost capability. Legitimate follow-on work that extends existing substrate (the #1461-extends-#1458 pattern) surfaces as `verify`/`likely-duplicate` with evidence the author acknowledges and proceeds — exactly the intended friction, dogfooded on this very build (this build's own spec triggered `likely-duplicate` because it cites the incident commits; the recorded disposition explains why it is not a duplicate). WEAK-only overlaps (common file/symbol recurrence) are a silent `clear`+note by the ladder, so the frequent-benign case never blocks or nags. The precommit backstop cannot over-block historically: it arms only when the guard was provably live for the build.
24
+
25
+ ## 2. Under-block
26
+
27
+ Stated, tracked residuals (all in the spec, none silent): (a) a duplicate whose spec shares no exact target AND falls below the Jaccard threshold yields `clear` — compensated by the sibling ledger + open-PR file overlap; (b) two builds on DIFFERENT machines (or different agent homes on one box) before either opens a PR are invisible — tracked cross-machine registry `<!-- tracked: ACT-595 -->`; (c) a build whose first implementation mutation is a shell command bypasses the file-edit PreToolUse gate — tracked secondary net `<!-- tracked: ACT-600 -->`, precommit backstop still catches the PR; (d) a hard check error fail-opens with the visible `check-errored` stub (loud precommit warning) rather than blocking — the deliberate safety direction.
28
+
29
+ ## 3. Level-of-abstraction fit
30
+
31
+ Correct layer: a dev-lifecycle tool wired into the existing chokepoints (the repo's own PreToolUse hooks, `instar-dev-precommit.js`, `pre-push-gate.js`, the write-trace fold) rather than a new parallel surface. The WEAK file-overlap signal reuses `scripts/lib/pre-push-scope.mjs` (no re-implemented diff). It feeds the existing authority structure — the human/agent author — rather than creating a new one.
32
+
33
+ ## 4. Signal vs authority compliance
34
+
35
+ **Compliant, and reviewed as such through 6 convergence rounds.** The detector is a pure signal-producer. The two enforcement moments are structural field-validators (`docs/signal-vs-authority.md` "hard-invariant validation" carve-out): the PreToolUse gate validates a disposition EXISTS, the precommit backstop validates the field EXISTS — neither ever judges the verdict's meaning or the author's reason. A hard `git push` block was explicitly rejected in convergence (a duplicate build is recoverable, so the irreversible-action carve-out does not apply). No graded/verdict output feeds back into any routing/model/prompt decision.
36
+
37
+ ## 5. Interactions
38
+
39
+ - **Precommit:** the backstop runs inside the existing gate on both Tier-1 and Tier-2 pass paths, after the trace checks — it adds one field validation, shadows nothing, and is inert when the guard isn't live. Verified by the touched-suite regression run (107/107 green incl. `instar-dev-precommit-*`, `pre-push-gate`, `write-trace-tier`, `self-action-convergence`).
40
+ - **Pre-push:** the advisory dynamic-imports the lib in try/catch — absence/failure is silent; it cannot turn a green push red (warnings[] only).
41
+ - **PreToolUse hook:** run-once semantics via a marker file (`.instar/dup-build-gate.marker.json`) — after the first allow, every subsequent tool call is a single `existsSync` (~20ms measured), so no per-edit latency tax. Instar-repo-only (package.json name check) — it never fires in other projects.
42
+ - **Write-trace:** the disposition folds into the trace fail-open; pre-guard traces round-trip byte-identically (no retro-breakage).
43
+ - **No self-action loop:** the check runs at three human/build-initiated moments (build-start, commit, push) — no timer, no tick, no self-triggered retry.
44
+
45
+ ## 6. External surfaces
46
+
47
+ None beyond the repo: no HTTP routes, no Telegram, no fleet propagation. The `gh pr list`/`gh pr diff` calls are read-only GitHub queries (bounded, non-interactive, timed, CI-skipped, generic-error-on-failure — stderr never echoed). Untrusted inputs (spec text, PR titles/bodies, ledger values) are type-validated, byte-capped, control-char-stripped, and never shelled (argv arrays with `--` separators throughout; the `safe-git-allow:` marker documents the argv-only discipline in lieu of the TS SafeGitExecutor, which is unavailable in hook context without a guaranteed dist build — substance preserved, deviation recorded).
48
+
49
+ ## 7. Multi-machine posture (Cross-Machine Coherence)
50
+
51
+ Per the spec's converged posture: the open-PR + merged-commit sources are **`unified`** (read from `origin/main` — fetched/normalized — and GitHub, shared truth; the cache is a worktree-scratch ephemeral, not durable per-machine state). The sibling in-flight ledger is **`machine-local` BY DESIGN** (`machine-local-justification: hardware-bound-resource` — a concurrently-building sibling process/worktree physically exists on one machine's disk; the same-agent-home scope is stated honestly, and the cross-machine early window is the tracked follow-up `<!-- tracked: ACT-595 -->`). No user-facing notices, no generated URLs, no state that strands on topic transfer.
52
+
53
+ ## 8. Rollback cost
54
+
55
+ **Trivial.** `INSTAR_DUP_BUILD_CHECK=off` no-ops every layer instantly (check, hook, backstop, advisory). Full revert is a plain git revert: no data migration, no agent-state repair, no fleet coordination (Migration Parity does not apply — dev-lifecycle tooling, not agent-installed files). The ledger/audit files are append-only local artifacts that can simply be deleted. Because no layer ever gates on the verdict's meaning, a rollback cannot regress any build/commit/push behavior beyond removing the new advisories.
56
+
57
+ ## Second-pass review (independent)
58
+
59
+ **Concur with the review.** Verified adversarially against the real diff, the spec, and by running the tests myself — not from the author's claims.
60
+
61
+ **A — Fail-open totality: verified.** `runDuplicateBuildCheck` wraps `runInner` in a total try/catch returning `check-errored` (duplicate-build-check.mjs:943-959); the CLI exits 0 on EVERY path including its own crash handler (:1447-1453) and usage errors (:1413). Every subprocess is deadline-bounded (`remainingMs` floors at 1ms; git ≤3s / gh ≤5s / total 8s — :342-345, :624-636, :668-677). The hook parses as ESM (package.json `"type":"module"`), and `main().catch(() => process.exit(0))` (:226-229) plus explicit fail-open exits (unreadable stdin :114, lib-unimportable → auto-stub+allow :131-142, unknown verdict :222) mean a hook crash cannot block. Precommit: `enforceDuplicateBuildBackstop` is try/catch fail-open on its own bugs, while a real refusal rides `blockCommit`'s `process.exit(1)` (uncatchable — the catch can't swallow a legit refusal into a no-op either). Retro-refusal is impossible: refusal arms only on `explicitlyOn || stubExists`; env-unset + no-stub → advisory warn only. Pre-push: the whole advisory sits in try/catch, only ever `warnings.push`; the report section exits 1 only when `errors.length > 0` — integration tests prove exit-0-with-⚠️, `INSTAR_PRE_PUSH_SKIP`, and the off-switch.
62
+
63
+ **B — Never a meaning-authority: verified, with one precision note.** Precommit reads `field.verdict` solely to WARN on `check-errored`; it blocks only on field ABSENCE and returns unconditionally when the field exists (a `proceed`-on-`likely-duplicate` and even a null-decision field pass). The hook blocks only on (a) no disposition, (b) the author's OWN recorded `abandon` (hook :194-199), (c) the §3.4 schema-completeness rule for a likely-duplicate proceed (reason + ack). Precision note: the artifact's "any recorded disposition … unblocks" overstates slightly — an `abandon` disposition keeps blocking (correct per spec: it enforces the author's own decision, not the verdict's meaning; re-record proceed is one command).
64
+
65
+ **C — Hardening real: verified.** grep-confirmed zero `execSync`/shell-string subprocess calls in the new code (6 callsites, all `execFileSync`/`spawnSync` argv arrays); typed validators reject leading-`-` tokens and non-repo-relative paths BEFORE argv, with `--` separators on grep/ls-tree; byte caps enforced at ingest (:660) AND re-enforced at the similarity site (:1107, covers injected/fixture sources); gh stderr is piped but never read or echoed (generic notes only); `git grep` pathspec-scoped to the census file + `src/` with a 50-match cap + 300-char clamp; sibling `worktreePath` gets lstat-symlink-reject + realpath jail under allowed roots (:443-472); `clampUntrusted` strips control/escape chars on every evidence string. Minor letter-of-spec nit: the LEDGER path itself is not realpath-resolved (§3.2a says to) — same-trust-domain (whoever plants that symlink owns the agent home already), marginal.
66
+
67
+ **D — Ledger invariants: verified, one wiring gap flagged.** Write-FIRST-then-scan order is real (:1041-1047, build-start only; pre-push scans without appending); liveness is the full conjunction pid ∧ procStartToken ∧ worktreePath-exists+jailed; torn-line skip with tornLast → verify-on-substrate; read-time compaction past 256KB. **Gap (non-blocking, needs a follow-up):** terminal removal exists only as a PRIMITIVE (`removeLedgerMarker` + the `--remove-marker` CLI, unit-tested) — repo-wide grep finds ZERO callers: precommit success does not remove the marker, `recordDisposition --decision abandon` does not remove it, SKILL.md is untouched, and there is no boot sweep. Spec §3.2 marks removal-at-terminal MANDATORY. Failure direction is pure noise (a completed build's marker stays "live" while its session process + worktree persist → a later overlapping build can get a false `likely-duplicate` costing one disposition command; self-heals on process death / worktree removal / compaction) — it can never wedge anything, so I concur rather than block, but wiring the removal (precommit success path + the abandon disposition) should be a tracked follow-up. **[RESOLVED BEFORE COMMIT]** — both wirings landed in this change: removeDupBuildMarkerOnCommitSuccess() now runs at BOTH precommit success exits (fail-open, 3s timeout), recordDisposition(abandon) removes the marker at the abandon terminal transition, and ledgerPathFor realpath-jails the agent home. Re-verified after the fixes: lint exit 0, 103/103 targeted tests green. No open follow-up remains from this finding <!-- tracked: ACT-600 -->.
68
+
69
+ **E — Run-once cost: verified.** The marker `existsSync` is the FIRST check after the off-switch (:101-102); I measured ~20ms/invocation with the marker present in a temp fixture. Marker written on every allow path; if the marker write fails but the stub exists, the fallback per call is a stub JSON read (cheap, no scan). The full-check-per-call tax requires BOTH `.instar/` writes failing — a worktree where the gated Write would fail anyway.
70
+
71
+ **F — Passes its own gate honestly: verified.** The stub at `.instar/dup-build-check.json` carries verdict `likely-duplicate` (cause concurrency) with a recorded `proceed` disposition (substantive reason + ack `EV-1`); EV-1/EV-2 are literally `88da6009` (#1461) and `4502e4e9` (#1458) — the live-verify claim checks out. write-trace's fold reads the same path from the correct ROOT (3 dirs up = repo root) and precommit will accept it. `git check-ignore` confirms all four runtime paths are ignored (stub/marker/cache explicitly; the audit JSONL via the new `/logs/`; nothing was tracked under `logs/`). Honesty note: the hook did NOT fire on my own artifact edit (my reviewer session predates the settings.json registration — hooks load at session start), so I proved the path manually instead: piping the exact tool input (`{"tool_input":{"file_path":"upgrades/side-effects/duplicate-build-guard.md"}}`) into the hook with `CLAUDE_PROJECT_DIR` set → exit 0, marker written `via: "dispositioned-likely-duplicate-proceed"` (marker then removed to leave worktree state as found).
72
+
73
+ **Ran myself:** all 4 new test files — 80/80 pass; the 5 existing touched suites (`instar-dev-precommit-*`, `pre-push-gate`, `write-trace-tier`) — 45/45 pass; the 4 most relevant lint scripts (no-direct-destructive, sync-subprocess-chokepoint, no-blocking-process-scans, no-wholefile-sync-read) — clean; the E2E uses fixture adapters (openPrSource/prDiffSource/livenessProbe), no network.
74
+
75
+ **Follow-ups recommended (none blocking):** (1) wire `--remove-marker`/`removeLedgerMarker` into the terminal transitions (precommit success + the abandon disposition) per spec §3.2; (2) realpath-resolve the ledger path itself per §3.2a's letter; (3) tighten the artifact's "any recorded disposition unblocks" phrasing to exclude `abandon`.
76
+
77
+ — independent second-pass reviewer, 2026-07-12