instar 1.3.289 → 1.3.290

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.289",
3
+ "version": "1.3.290",
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",
@@ -36,7 +36,15 @@ const __filename = fileURLToPath(import.meta.url);
36
36
  const __dirname = path.dirname(__filename);
37
37
  const ROOT = path.resolve(__dirname, '..');
38
38
  const TRACES_DIR = path.join(ROOT, '.instar', 'instar-dev-traces');
39
- const DECISIONS_LOG = path.join(ROOT, '.instar', 'instar-dev-decisions.jsonl');
39
+ // Legacy single-file audit log (frozen 2026-06-05 — read-only history).
40
+ // Every gated commit used to APPEND one line here; because the line rides the
41
+ // commit (the task-#62 fix), any two PRs in flight both modified this file's
42
+ // tail and conflicted at the merge point — a structural conflict generator at
43
+ // parallel-PR cadence (live hit: PR #824 went CI-green then failed merge on
44
+ // exactly this file). New entries are one-file-per-decision under
45
+ // DECISIONS_DIR instead: distinct filenames can never conflict.
46
+ // (The frozen legacy file remains at .instar/instar-dev-decisions.jsonl.)
47
+ const DECISIONS_DIR = path.join(ROOT, '.instar', 'instar-dev-decisions');
40
48
  const WINDOW_MS = 60 * 60 * 1000; // 60 minutes
41
49
  const MIN_ARTIFACT_CHARS = 200;
42
50
 
@@ -257,7 +265,7 @@ const decision = decideRequirementSet(declaredTier);
257
265
  // belowFloor = the agent declared UNDER the risk-signaled floor. We never
258
266
  // block on it (the mind holds authority) — the record is the backstop.
259
267
  const belowFloor = declaredTier != null && declaredTier < tierSignal.riskFloor;
260
- writeDecisionAudit({
268
+ const decisionEntryPath = writeDecisionAudit({
261
269
  slug,
262
270
  suggestedTier: tierSignal.suggestedTier,
263
271
  declaredTier,
@@ -276,7 +284,7 @@ if (belowFloor) {
276
284
  console.error(` declared Tier ${declaredTier} < risk floor ${tierSignal.riskFloor}. Risk signals:`);
277
285
  for (const r of tierSignal.reasons) console.error(` • ${r}`);
278
286
  if (tierReasoning) console.error(` Your tierReasoning: ${tierReasoning}`);
279
- console.error(` Recorded to ${path.relative(ROOT, DECISIONS_LOG)} (belowFloor:true).`);
287
+ console.error(` Recorded to ${path.relative(ROOT, decisionEntryPath ?? DECISIONS_DIR)} (belowFloor:true).`);
280
288
  console.error('');
281
289
  }
282
290
 
@@ -791,26 +799,39 @@ function blockCommit(files, reason) {
791
799
  // lines describe real gate evaluations.
792
800
  function writeDecisionAudit({ slug, suggestedTier, declaredTier, riskFloor, riskFloorReasons, belowFloor, files, loc }) {
793
801
  try {
794
- fs.mkdirSync(path.dirname(DECISIONS_LOG), { recursive: true });
795
- fs.appendFileSync(
796
- DECISIONS_LOG,
802
+ fs.mkdirSync(DECISIONS_DIR, { recursive: true });
803
+ const ts = new Date().toISOString();
804
+ // Filename: sortable timestamp + sanitized slug → chronological `ls`,
805
+ // and a DISTINCT file per decision so parallel PRs can never conflict
806
+ // on the audit trail (each adds its own file; git merges additions of
807
+ // different paths trivially, including GitHub's server-side merge).
808
+ const safeSlug = String(slug).toLowerCase().replace(/[^a-z0-9-]+/g, '-').replace(/^-+|-+$/g, '') || 'unknown';
809
+ let entryPath = path.join(DECISIONS_DIR, `${ts.replace(/[:.]/g, '-')}-${safeSlug}.json`);
810
+ let n = 1;
811
+ while (fs.existsSync(entryPath)) {
812
+ entryPath = path.join(DECISIONS_DIR, `${ts.replace(/[:.]/g, '-')}-${safeSlug}-${n++}.json`);
813
+ }
814
+ fs.writeFileSync(
815
+ entryPath,
797
816
  JSON.stringify({
798
- ts: new Date().toISOString(),
817
+ ts,
799
818
  slug,
800
819
  suggestedTier,
801
820
  declaredTier,
802
- // riskFloor (the number) keeps the line self-contained for later review
821
+ // riskFloor (the number) keeps the entry self-contained for later review
803
822
  // without re-running the classifier — not just the derived belowFloor.
804
823
  riskFloor,
805
824
  riskFloorReasons,
806
825
  belowFloor,
807
826
  files,
808
827
  loc,
809
- }) + '\n',
828
+ }, null, 2) + '\n',
810
829
  );
811
- execSync(`git add ${JSON.stringify(path.relative(ROOT, DECISIONS_LOG))}`, { cwd: ROOT });
830
+ execSync(`git add ${JSON.stringify(path.relative(ROOT, entryPath))}`, { cwd: ROOT });
831
+ return entryPath;
812
832
  } catch {
813
833
  // best-effort — never block on audit I/O
834
+ return null;
814
835
  }
815
836
  }
816
837
 
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "$schema": "./builtin-manifest.schema.json",
3
3
  "schemaVersion": 1,
4
- "generatedAt": "2026-06-05T11:25:48.194Z",
5
- "instarVersion": "1.3.289",
4
+ "generatedAt": "2026-06-05T11:59:51.712Z",
5
+ "instarVersion": "1.3.290",
6
6
  "entryCount": 198,
7
7
  "entries": {
8
8
  "hook:session-start": {
@@ -0,0 +1,21 @@
1
+ # Upgrade Guide — vNEXT
2
+
3
+ <!-- assembled-by: assemble-next-md -->
4
+ <!-- bump: patch -->
5
+
6
+ ## What Changed
7
+
8
+ `writeDecisionAudit` in `scripts/instar-dev-precommit.js` writes one file per decision instead of appending a line to one shared JSONL. Two PRs each ADDING a distinct path can never merge-conflict; appending to the same file's tail always did. Same payload, same best-effort/never-block semantics, collision-suffixed filenames.
9
+
10
+ ## What to Tell Your User
11
+
12
+ Nothing user-visible — this is contributor infrastructure. Pull requests no longer fail to merge over the development decision-audit log.
13
+
14
+ ## Summary of New Capabilities
15
+
16
+ - Each gated commit's decision-audit record is now its own file under `.instar/instar-dev-decisions/<timestamp>-<slug>.json` (chronological by filename), staged so it still rides the commit it describes.
17
+ - The legacy `.instar/instar-dev-decisions.jsonl` is frozen history — no new appends.
18
+
19
+ ## Evidence
20
+
21
+ Live failure this fixes (2026-06-05): PR #824 passed all 19 CI checks, then `gh pr merge --squash --admin` failed with "Pull Request has merge conflicts" — `git merge` against main showed exactly ONE conflicted file, `.instar/instar-dev-decisions.jsonl`, because sibling PRs had appended their own audit lines to the same tail while #824 was in CI. Unblocked manually by union-resolving; this change removes the class. Pinned by `tests/unit/instar-dev-precommit-audit-staging.test.ts`: the gate-blocked commit still stages its entry file (the #814 property preserved), two evaluations produce two DISTINCT paths (the conflict-immunity property), and the frozen legacy JSONL is not appended to; the deferrals suite's audit-shape test re-pinned to the per-entry record (riskFloor number, belowFloor, suggestedTier, reasons array). 16/16 tests green.
@@ -0,0 +1,5 @@
1
+ # Parallel PRs stop colliding on the decision-audit log
2
+
3
+ Every gated commit records a small audit note (what tier was declared, what the risk classifier suggested). A recent fix made that note ride along inside the commit itself — which solved one problem (notes were evaporating with temporary build folders) but quietly created another: all the notes went into ONE shared file, each commit appending a line at the end. Git can't merge two different "last lines" of the same file, so whenever two pull requests were in flight at the same time — which is now the normal state with three agents shipping in parallel — the second one to merge hit a conflict on that file. This actually happened within hours: a PR passed every check and then failed to merge over nothing but the audit log.
4
+
5
+ The fix: each decision now gets its own tiny file (named by timestamp and change name) inside a decisions folder, instead of a line appended to a shared file. Two PRs each ADDING a different file never conflict — git and GitHub merge that trivially. Nothing about the audit content changes, the note still rides the commit it describes, and the old log file stays in place as frozen history. The chronological order you used to get from reading the file top-to-bottom now comes from sorting filenames, which start with the timestamp.
@@ -0,0 +1,44 @@
1
+ # Side-Effects Review — per-entry decision-audit files (task #80)
2
+
3
+ **Version / slug:** `decisions-per-entry`
4
+ **Date:** `2026-06-05`
5
+ **Author:** `echo`
6
+ **Second-pass reviewer:** `self-review under the Tier-1 lite lane; the audit-continuity question (does the trail survive the format change?) addressed below`
7
+
8
+ ## Summary of the change
9
+
10
+ `writeDecisionAudit` in the pre-commit gate writes each decision as its own file `.instar/instar-dev-decisions/<ts>-<slug>.json` (staged immediately, preserving #814's ride-the-commit property) instead of appending one line to the shared `.instar/instar-dev-decisions.jsonl`. Distinct paths per PR cannot merge-conflict — including GitHub's server-side merge, which does not honor custom merge drivers. The legacy JSONL freezes in place as history.
11
+
12
+ ## Decision-point inventory
13
+
14
+ - `writeDecisionAudit` — modified — per-entry file write + stage; same payload fields (ts/slug/tiers/riskFloor/reasons/belowFloor/files/loc); collision-suffix counter for same-ms-same-slug; returns the entry path (used by the belowFloor console message).
15
+ - `DECISIONS_LOG` const — removed (legacy path noted in comment); `DECISIONS_DIR` added.
16
+ - Tests — `instar-dev-precommit-audit-staging.test.ts` re-pinned to the per-entry contract + a NEW conflict-immunity test (two evaluations → two distinct paths) + asserts the legacy JSONL is NOT created; `instar-dev-precommit-deferrals.test.ts` audit-shape test re-pinned.
17
+
18
+ ## 1. Direction-of-failure analysis
19
+
20
+ - **Old failure (live, PR #824):** CI-green PR fails admin-merge on the audit file tail — recurring at parallel-PR cadence, blocks every second merge, requires manual union resolution each time.
21
+ - **New worst case:** the writer remains best-effort (try/catch, never blocks the gate) — an audit I/O failure loses ONE entry, exactly as before. Filename collision (same ms + same slug) is suffix-countered, pinned by test.
22
+ - **Audit continuity:** the legacy file is untouched history; new entries are chronologically sortable by filename (`ls` order = time order). Readers: only tests read the trail today (grep-verified); both updated.
23
+ - **In-flight PRs written under the old scheme:** once main's shared file stops growing, their single appended line vs main's unchanged tail merges CLEANLY — landing this fix un-jams the conflict class for already-open PRs too (the conflict needed BOTH sides appending).
24
+
25
+ ## 2. Over-permit
26
+
27
+ None — the gate's blocking logic is untouched; only where the audit record lands.
28
+
29
+ ## 3. Scope deliberately NOT taken
30
+
31
+ - No migration of legacy JSONL lines into per-entry files — history stays where it was written; a reader that wants the full trail reads both (documented in the script comment).
32
+ - No `.gitattributes merge=union` belt for the legacy file — GitHub's server-side merge ignores it, and with the file frozen the local-rebase benefit is moot.
33
+
34
+ ## 4. Migration parity
35
+
36
+ None needed — the gate script lives in the repo (`scripts/`), not in agent-installed files; every checkout gets it with the commit. `.instar/instar-dev-decisions/` is created on demand by the writer.
37
+
38
+ ## 5. Token/cost impact
39
+
40
+ None — same write volume, one file create vs one append per gated commit.
41
+
42
+ ## 6. Rollback
43
+
44
+ Revert the commit; the writer returns to appending the shared JSONL (and the conflict class returns with it).