instar 1.3.306 → 1.3.307

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "instar",
3
- "version": "1.3.306",
3
+ "version": "1.3.307",
4
4
  "description": "Coherence infrastructure for self-evolving AI agents — on the Claude Code or Codex subscription you already have.",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -45,6 +45,23 @@ const TRACES_DIR = path.join(ROOT, '.instar', 'instar-dev-traces');
45
45
  // DECISIONS_DIR instead: distinct filenames can never conflict.
46
46
  // (The frozen legacy file remains at .instar/instar-dev-decisions.jsonl.)
47
47
  const DECISIONS_DIR = path.join(ROOT, '.instar', 'instar-dev-decisions');
48
+
49
+ // Set by writeDecisionAudit; consumed by the process 'exit' handler that
50
+ // finalizes the entry's verdict. BOTH the declaration AND the handler
51
+ // registration live here, above the top-level audit call site — placed
52
+ // after it, the declaration TDZ-throws inside writeDecisionAudit's
53
+ // try/catch, and the handler registration is never even reached when
54
+ // blockCommit exits first (top-level statements run in source order).
55
+ let pendingAuditEntry = null;
56
+ process.on('exit', (code) => {
57
+ if (!pendingAuditEntry) return;
58
+ try {
59
+ const { entryPath, entryData } = pendingAuditEntry;
60
+ entryData.verdict = code === 0 ? 'pass' : 'blocked';
61
+ fs.writeFileSync(entryPath, JSON.stringify(entryData, null, 2) + '\n');
62
+ execSync(`git add ${JSON.stringify(path.relative(ROOT, entryPath))}`, { cwd: ROOT });
63
+ } catch { /* best-effort — 'pending' is still more truthful than no verdict */ }
64
+ });
48
65
  const WINDOW_MS = 60 * 60 * 1000; // 60 minutes
49
66
  const MIN_ARTIFACT_CHARS = 200;
50
67
 
@@ -811,23 +828,31 @@ function writeDecisionAudit({ slug, suggestedTier, declaredTier, riskFloor, risk
811
828
  while (fs.existsSync(entryPath)) {
812
829
  entryPath = path.join(DECISIONS_DIR, `${ts.replace(/[:.]/g, '-')}-${safeSlug}-${n++}.json`);
813
830
  }
814
- fs.writeFileSync(
815
- entryPath,
816
- JSON.stringify({
817
- ts,
818
- slug,
819
- suggestedTier,
820
- declaredTier,
821
- // riskFloor (the number) keeps the entry self-contained for later review
822
- // without re-running the classifier — not just the derived belowFloor.
823
- riskFloor,
824
- riskFloorReasons,
825
- belowFloor,
826
- files,
827
- loc,
828
- }, null, 2) + '\n',
829
- );
831
+ const entryData = {
832
+ ts,
833
+ slug,
834
+ suggestedTier,
835
+ declaredTier,
836
+ // riskFloor (the number) keeps the entry self-contained for later review
837
+ // without re-running the classifier — not just the derived belowFloor.
838
+ riskFloor,
839
+ riskFloorReasons,
840
+ belowFloor,
841
+ files,
842
+ loc,
843
+ // Finalized by the process exit handler below: 'pass' when the gate
844
+ // allowed the commit, 'blocked' otherwise. The riding-the-retry design
845
+ // (a blocked evaluation's entry rides the next successful commit, see
846
+ // header comment) is deliberate — but without a verdict, a rode-along
847
+ // entry written under a stale/unresolved trace slug READS as a real
848
+ // shipped decision for that slug. Live recurrence (2026-06-05): both
849
+ // echo (#836) and codey (#842) shipped mislabeled "unknown"/foreign-slug
850
+ // entries in one day. The verdict makes every entry self-describing.
851
+ verdict: 'pending',
852
+ };
853
+ fs.writeFileSync(entryPath, JSON.stringify(entryData, null, 2) + '\n');
830
854
  execSync(`git add ${JSON.stringify(path.relative(ROOT, entryPath))}`, { cwd: ROOT });
855
+ pendingAuditEntry = { entryPath, entryData };
831
856
  return entryPath;
832
857
  } catch {
833
858
  // best-effort — never block on audit I/O
@@ -835,6 +860,13 @@ function writeDecisionAudit({ slug, suggestedTier, declaredTier, riskFloor, risk
835
860
  }
836
861
  }
837
862
 
863
+ // ── Verdict finalization ──────────────────────────────────────────────────
864
+ // The process 'exit' handler that finalizes each entry's verdict is
865
+ // registered next to the pendingAuditEntry declaration near the top of this
866
+ // file (it must be registered BEFORE the top-level gate flow can exit). One
867
+ // hook covers every exit path: enforceTier1's process.exit(0), the Tier-2
868
+ // fall-through, and every blockCommit.
869
+
838
870
  // ─── Tier-1 lite enforcement ──────────────────────────────────────────────
839
871
  // Tier-1 requirement set: a staged ELI16 (trace.eli16Path) passing the length
840
872
  // check + a staged side-effects artifact (trace.sideEffectsPath, sha-matched if
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "$schema": "./builtin-manifest.schema.json",
3
3
  "schemaVersion": 1,
4
- "generatedAt": "2026-06-05T15:43:57.729Z",
5
- "instarVersion": "1.3.306",
4
+ "generatedAt": "2026-06-05T16:06:31.347Z",
5
+ "instarVersion": "1.3.307",
6
6
  "entryCount": 198,
7
7
  "entries": {
8
8
  "hook:session-start": {
@@ -0,0 +1,20 @@
1
+ # Upgrade Guide — vNEXT
2
+
3
+ <!-- assembled-by: assemble-next-md -->
4
+ <!-- bump: patch -->
5
+
6
+ ## What Changed
7
+
8
+ The instar-dev pre-commit gate's decision-audit entries gain a verdict field ('pass' | 'blocked', written as 'pending' and finalized by a process exit hook that also re-stages the corrected file). The riding-the-retry design is unchanged — blocked-run entries still ride the next successful commit — but each entry is now self-describing.
9
+
10
+ ## What to Tell Your User
11
+
12
+ Nothing user-visible — contributor audit hygiene. Gate audit records that ride into a PR from earlier blocked attempts (often under an unresolved or stale slug) are now labeled as blocked evaluations instead of reading like real shipped decisions.
13
+
14
+ ## Summary of New Capabilities
15
+
16
+ - Decision-audit entries carry verdict: pass | blocked (pending only if finalization itself failed). Reviewers can distinguish shipped decisions from rode-along blocked evaluations at a glance.
17
+
18
+ ## Evidence
19
+
20
+ Live recurrence (2026-06-05, twice in one day): echo's PR #836 swept in a blocked-attempt entry labeled with a DIFFERENT PR's slug (the then-freshest stale trace), and codey's PR #842 shipped an "unknown"-slug entry from a pre-trace gate run — both read as real decisions and needed review-time explanation. Pinned by 2 new sandbox tests in tests/unit/instar-dev-precommit-audit-staging.test.ts (blocked run finalizes 'blocked' incl. the STAGED copy; passing Tier-1 run finalizes 'pass') alongside the existing 4; the development itself caught the source-order trap twice (TDZ on the shared variable, exit-before-registration on the hook) — both now documented in code comments and covered by the blocked-path test.
@@ -0,0 +1,11 @@
1
+ # Gate audit entries now say whether the commit actually passed
2
+
3
+ Every gated commit writes a small audit record — which tier the change declared, what the risk heuristic suggested, how big the diff was. By design, a record written during a BLOCKED attempt also survives: it gets staged, and when the developer fixes the problem and commits again, the old record rides along. The intent was honest bookkeeping — every gate evaluation leaves a trace.
4
+
5
+ The problem: nothing in the record said whether the evaluation passed or was blocked. And blocked attempts often run before the developer's trace file is fully resolved, so the record gets written under the wrong name — the slug says "unknown", or worse, it carries the slug of some OTHER pull request whose stale trace happened to be the freshest file at that moment. The result reads like a real shipped decision for a change that never shipped under that name. This bit twice in one day: Echo's registry-fix PR carried a record labeled with a different PR's slug, and Codey's header PR carried an "unknown" record — both rode into main as confusing audit noise, and both had to be explained in review.
6
+
7
+ The fix keeps the deliberate riding-the-retry design and makes every record self-describing: a **verdict** field, finalized the moment the gate process exits. Records start as "pending" when written, and a process exit hook — one hook covering every way the gate can finish — rewrites them as "pass" or "blocked" and re-stages the corrected file. A reviewer who sees verdict "blocked" next to a foreign slug now knows exactly what they're looking at: a failed evaluation under a stale trace, not a shipped decision.
8
+
9
+ Two implementation traps are documented in the code because both actually fired during development: the shared variable and the exit hook must both live ABOVE the gate's main flow in the file, because the gate runs top-to-bottom and can exit (blocked) before ever reaching code placed lower down. The first draft had the hook at the bottom; blocked runs exited before the hook was registered, and the verdict silently stayed "pending" — caught by the new tests.
10
+
11
+ Nothing changes about when records are written, staged, or how files are named. Old records without a verdict field remain valid history. Two new tests pin both sides: a blocked run's record finalizes as "blocked" (including the staged copy), and a passing Tier-1 run's record finalizes as "pass".
@@ -0,0 +1,41 @@
1
+ # Side-Effects Review — Gate decision-audit verdict finalization
2
+
3
+ **Version / slug:** `gate-blocked-audit-hygiene`
4
+ **Date:** `2026-06-05`
5
+ **Author:** `instar-echo`
6
+
7
+ ## Summary
8
+
9
+ `writeDecisionAudit` writes entries with `verdict: 'pending'` and records `{entryPath, entryData}` in a module variable; a `process.on('exit')` hook (registered next to the variable, ABOVE the gate's top-level flow) rewrites the entry with `verdict: code === 0 ? 'pass' : 'blocked'` and re-stages it. Entry naming, staging, timing, and the riding-the-retry property are unchanged.
10
+
11
+ ## Decision-point inventory
12
+
13
+ - Entry schema — extended — new `verdict` field ('pending' at write; finalized at exit). Additive: old entries without the field remain valid; no reader migration (the decision-audit-presence CI check matches by path, not schema — verified, its 9 tests untouched and green).
14
+ - Exit hook — added — one hook covers every exit path (enforceTier1's exit(0), Tier-2 fall-through, every blockCommit's exit(1)); synchronous-only work; its own try/catch leaves 'pending' on failure (still more truthful than no verdict).
15
+ - Source-order placement — the variable AND the hook registration live above the top-level audit call site. Both failure modes fired in development: a below-call-site declaration TDZ-throws inside writeDecisionAudit's try/catch (silently skipping tracking), and a below-flow registration is never reached when blockCommit exits early (verdict stayed 'pending' — caught by the new blocked-path test).
16
+
17
+ ## Direction of failure
18
+
19
+ - Old failure: rode-along entries from blocked runs (often under 'unknown'/foreign slugs) read as real shipped decisions — twice in one day (echo #836, codey #842).
20
+ - New behavior: every entry self-describes its outcome.
21
+ - Conservative direction: a finalization failure leaves 'pending' — ambiguous but honest; never a wrong verdict.
22
+
23
+ ## Side-effects checklist
24
+
25
+ 1. **Over-block:** none — the gate's pass/block decisions are untouched; this only records them.
26
+ 2. **Exit-hook safety:** synchronous fs/execSync only (required in 'exit' handlers); wrapped in try/catch; cannot change the exit code (the 'exit' event can't).
27
+ 3. **Double-stage:** the entry is git-added at write AND at finalize — idempotent (same path, updated content).
28
+ 4. **CI interplay:** decision-audit-presence-check matches entries by PATH under .instar/instar-dev-decisions/ — schema-agnostic; its tests run green against the new field.
29
+ 5. **Level-of-abstraction fit:** the verdict is determined by the process outcome, not by threading state through every blockCommit call site — one mechanism, all paths.
30
+ 6. **External surfaces:** none — local gate script + committed JSON records.
31
+ 7. **Rollback cost:** revert; entries return to verdict-less (old shape), already-committed verdict entries remain valid.
32
+
33
+ ## Scope not taken
34
+
35
+ - No slug-resolution improvement for blocked runs (the verdict defuses the mislabeling harm; resolving the "right" slug for an unmatched trace is guesswork).
36
+ - No retroactive verdict backfill of historical entries.
37
+ - No blocked-entry relocation/exclusion — riding-the-retry stays (deliberate #827-era design: every evaluation leaves a committed trace).
38
+
39
+ ## Rollback
40
+
41
+ Revert the commit.