instar 1.3.999 → 1.3.1001

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.999",
3
+ "version": "1.3.1001",
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",
@@ -46,6 +46,7 @@
46
46
  import fs from 'node:fs';
47
47
  import path from 'node:path';
48
48
  import crypto from 'node:crypto';
49
+ import { execFileSync } from 'node:child_process';
49
50
 
50
51
  const ROOT = path.resolve(path.dirname(new URL(import.meta.url).pathname), '..', '..', '..');
51
52
 
@@ -157,6 +158,107 @@ function buildToolchain() {
157
158
  }
158
159
  }
159
160
 
161
+ /**
162
+ * Verify the commit gate is actually INSTALLED before writing a trace that implies it ran.
163
+ *
164
+ * THE FAILURE THIS EXISTS TO CATCH. A worktree created with `git worktree add` and a
165
+ * symlinked (or absent) node_modules has no husky shim, so `core.hooksPath` points at a
166
+ * directory that does not exist. `git commit` then runs NO hook: it prints nothing and
167
+ * succeeds. **A working gate and an uninstalled gate look identical on screen — silence.**
168
+ * That is not hypothetical; it happened on 2026-07-27 and a whole change was committed
169
+ * believing the gate had passed it, when the gate had never executed.
170
+ *
171
+ * The gate cannot report its own absence — an uninstalled hook cannot run to say it is
172
+ * uninstalled. So the check belongs at the nearest chokepoint the agent DOES invoke by
173
+ * hand, which is this script. Writing a trace is the step that asserts "this change came
174
+ * through the skill", and that assertion is false if the gate cannot execute.
175
+ *
176
+ * SCOPE HONESTY: this is a SIGNAL, not an authority. It refuses to write the trace and
177
+ * says why; it cannot block a commit, because the very condition it detects is the one
178
+ * where no commit hook runs. Without a trace an in-scope commit is refused ANYWAY once the
179
+ * hook IS installed — so the two layers compose rather than overlap. Its whole job is to
180
+ * turn a silent absence into a loud one at the moment the agent would otherwise proceed.
181
+ *
182
+ * Set INSTAR_DEV_ALLOW_UNINSTALLED_GATE=1 to proceed anyway (a genuinely hookless
183
+ * environment, e.g. a test harness). The override is recorded IN THE TRACE rather than
184
+ * merely permitted, so a trace written without a live gate can never later be mistaken for
185
+ * one the gate approved.
186
+ */
187
+ function inspectGateInstallation() {
188
+ const result = { installed: false, reason: '', hooksPath: null };
189
+ let hooksPath;
190
+ try {
191
+ hooksPath = execFileSync('git', ['config', '--get', 'core.hooksPath'], {
192
+ cwd: ROOT, encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'],
193
+ }).trim();
194
+ } catch {
195
+ hooksPath = '';
196
+ }
197
+
198
+ if (!hooksPath) {
199
+ // No hooksPath set at all — git falls back to .git/hooks. Check there instead of
200
+ // assuming absence: a repo may legitimately install the hook the classic way.
201
+ let gitDir;
202
+ try {
203
+ gitDir = execFileSync('git', ['rev-parse', '--git-dir'], {
204
+ cwd: ROOT, encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'],
205
+ }).trim();
206
+ } catch {
207
+ // NOT A GIT REPOSITORY AT ALL — the check does not apply.
208
+ //
209
+ // This is not a loophole, it is the check's actual scope. The hazard being guarded
210
+ // is "a git repo whose commit hook will not run". Where there is no repo there is no
211
+ // commit, so there is nothing to gate and nothing to be misled about. Test harnesses
212
+ // that drive this script inside a bare temp directory land here.
213
+ //
214
+ // Deliberately NOT solved by making every such harness set the override env var:
215
+ // that would push the burden onto whoever writes the next test remembering to do it,
216
+ // which is the willpower-over-structure trade this whole change exists to remove.
217
+ // It is also not a meaningful bypass — an agent cannot make the instar repo stop
218
+ // being a git repository.
219
+ return { installed: true, reason: '', hooksPath: null, notApplicable: true };
220
+ }
221
+ const classic = path.resolve(ROOT, gitDir, 'hooks', 'pre-commit');
222
+ if (fs.existsSync(classic)) return { installed: true, reason: '', hooksPath: classic };
223
+ return { ...result, reason: 'core.hooksPath is unset and .git/hooks/pre-commit does not exist' };
224
+ }
225
+
226
+ result.hooksPath = hooksPath;
227
+ const resolved = path.resolve(ROOT, hooksPath);
228
+ if (!fs.existsSync(resolved)) {
229
+ return { ...result, reason: `core.hooksPath is "${hooksPath}" but that directory does not exist — husky is not installed in this worktree (run \`npm ci\` then \`npx husky\`)` };
230
+ }
231
+ const preCommit = path.join(resolved, 'pre-commit');
232
+ if (!fs.existsSync(preCommit)) {
233
+ return { ...result, reason: `core.hooksPath is "${hooksPath}" but it contains no pre-commit hook` };
234
+ }
235
+ return { installed: true, reason: '', hooksPath };
236
+ }
237
+
238
+ const gate = inspectGateInstallation();
239
+ const gateOverridden = process.env.INSTAR_DEV_ALLOW_UNINSTALLED_GATE === '1';
240
+ if (!gate.installed && !gateOverridden) {
241
+ console.error('');
242
+ console.error(' ╔════════════════════════════════════════════════════════════════════╗');
243
+ console.error(' ║ REFUSING to write a trace — the commit gate is NOT installed ║');
244
+ console.error(' ╚════════════════════════════════════════════════════════════════════╝');
245
+ console.error('');
246
+ console.error(` ${gate.reason}`);
247
+ console.error('');
248
+ console.error(' A trace asserts this change came through /instar-dev. With no hook');
249
+ console.error(' installed, `git commit` runs NOTHING and succeeds silently — a passing');
250
+ console.error(' gate and an absent gate look identical on screen. Writing the trace now');
251
+ console.error(' would record an approval that was never given.');
252
+ console.error('');
253
+ console.error(' Fix it in this worktree: npm ci && npx husky');
254
+ console.error(' Then verify: git config core.hooksPath && ls .husky/_');
255
+ console.error('');
256
+ console.error(' If this environment genuinely has no hooks (e.g. a test harness), set');
257
+ console.error(' INSTAR_DEV_ALLOW_UNINSTALLED_GATE=1 — the override is recorded in the trace.');
258
+ console.error('');
259
+ process.exit(1);
260
+ }
261
+
160
262
  const artifactPath = path.resolve(ROOT, artifact);
161
263
  if (!fs.existsSync(artifactPath)) {
162
264
  console.error(`Artifact not found: ${artifact}`);
@@ -235,6 +337,10 @@ const trace = {
235
337
  // Duplicate-build guard §3.4 — omitted when no stub exists so pre-guard
236
338
  // traces round-trip byte-identically.
237
339
  ...(duplicateBuildCheck ? { duplicateBuildCheck } : {}),
340
+ // Recorded ONLY when the gate-installation check was overridden, so a trace written
341
+ // without a live commit gate can never later be mistaken for one the gate approved.
342
+ // Absent on every normal trace, so existing traces round-trip byte-identically.
343
+ ...(gate.installed ? {} : { gateInstallationOverridden: true, gateInstallationReason: gate.reason }),
238
344
  };
239
345
 
240
346
  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-27T09:13:24.898Z",
5
- "instarVersion": "1.3.999",
4
+ "generatedAt": "2026-07-27T10:24:33.892Z",
5
+ "instarVersion": "1.3.1001",
6
6
  "entryCount": 202,
7
7
  "entries": {
8
8
  "hook:session-start": {
@@ -0,0 +1,60 @@
1
+ # Upgrade Guide — vNEXT
2
+
3
+ <!-- assembled-by: assemble-next-md -->
4
+ <!-- bump: patch -->
5
+
6
+ ## What Changed
7
+
8
+ `skills/instar-dev/scripts/write-trace.mjs` now verifies the instar-dev commit gate is actually
9
+ installed before writing a trace, and refuses with an explanation when it is not.
10
+
11
+ A worktree created with `git worktree add` inherits `core.hooksPath` (typically `.husky/_`) from the
12
+ shared repo config, while that directory does not exist until `npm ci` plus `npx husky` have run. In
13
+ that state `git commit` executes no hook: it prints nothing and succeeds. A working gate and an
14
+ uninstalled gate are indistinguishable on screen, because both are silent — and on 2026-07-27 a
15
+ change was committed on the belief the gate had passed it when the gate had never run.
16
+
17
+ The gate cannot detect its own absence, since an uninstalled hook cannot execute to say so. The check
18
+ therefore lives at the nearest chokepoint invoked by hand: writing the trace, which is the step that
19
+ asserts the change came through the skill. `INSTAR_DEV_ALLOW_UNINSTALLED_GATE=1` overrides it, and
20
+ the override is recorded in the trace so an unguarded trace can never be mistaken for an approved one.
21
+
22
+ ## What to Tell Your User
23
+
24
+ None — internal change (no user-facing surface).
25
+
26
+ ## Summary of New Capabilities
27
+
28
+ None — internal change (no user-facing surface).
29
+
30
+ ## Evidence
31
+
32
+ Verified against a real hookless worktree before any test was written — `core.hooksPath` was already
33
+ `.husky/_` in a freshly added worktree with no shim, confirming silently-no-gate is the default state
34
+ rather than an exotic misconfiguration:
35
+
36
+ ```
37
+ core.hooksPath is ".husky/_" but that directory does not exist — husky is not installed
38
+ ```
39
+
40
+ Falsified by neutering the check in the script:
41
+
42
+ ```
43
+ × THE FIX: refuses to write a trace when hooksPath points at a missing directory
44
+ → expected +0 to be 1
45
+ × refuses when hooksPath is unset AND no classic hook exists
46
+ Tests 2 failed | 4 passed (6)
47
+ ```
48
+
49
+ Restored byte-identical. The over-block was found by running the suites chosen from the diff rather
50
+ than the files authored: the first implementation broke seven tests in `write-trace-tier` and
51
+ `duplicate-build-guard-gates`, both of which drive the script inside bare temp directories. Scoping
52
+ the check to git repositories only fixed it — a directory that is not a repo has no commit to gate.
53
+ Final: `Test Files 3 passed (3) · Tests 23 passed (23)`.
54
+
55
+ ## Known limits
56
+
57
+ The check verifies a pre-commit hook exists; it does not verify it is executable, non-empty, or that
58
+ it is the instar-dev gate rather than another hook. A hook that exits 0 immediately would satisfy it
59
+ while gating nothing. It also cannot help retroactively — a commit already made without the gate
60
+ leaves no marker, because nothing ran to leave one.
@@ -0,0 +1,48 @@
1
+ # Upgrade Guide — vNEXT
2
+
3
+ <!-- assembled-by: assemble-next-md -->
4
+ <!-- bump: patch -->
5
+
6
+ ## What Changed
7
+
8
+ Adds `docs/specs/decision-replayability-standard.md` (plus its ELI16 companion) — the written
9
+ standard for what must be recorded so a decision can be replayed and re-evaluated later, rather than
10
+ merely asserted to have happened.
11
+
12
+ It answers the question the operator asked directly: **is a screenshot an acceptable thing to store?**
13
+ For a TERMINAL prompt the answer is no, and not for privacy reasons — the scrubbed pane TEXT of the
14
+ prompt region carries identical information to an image of it, but only text is scrubbable,
15
+ greppable, and diffable. Text is therefore *more* auditable, not less. Images remain the right medium
16
+ only where no text form exists, such as a GUI or a browser.
17
+
18
+ The standard carries three bounds so the rule cannot quietly become unbounded surveillance: capture
19
+ the prompt REGION rather than full scrollback; scrub before write, never after; and mark a record
20
+ `replayability: degraded` when scrubbing has hollowed it out, so a log can never silently become
21
+ useless while still looking complete.
22
+
23
+ ## What to Tell Your User
24
+
25
+ None — internal change (no user-facing surface).
26
+
27
+ ## Summary of New Capabilities
28
+
29
+ None — internal change (no user-facing surface).
30
+
31
+ ## Evidence
32
+
33
+ Docs-only: no runtime surface, no endpoint, no config key, no behaviour change. The standard's own
34
+ `## Decision points touched` and `## Multi-machine posture` sections are present as the spec format
35
+ requires.
36
+
37
+ Shipped as its own change deliberately. The document was written and staged inside an unrelated
38
+ worktree that is blocked awaiting operator approval — so a finished deliverable was sitting
39
+ uncommitted behind a blocker it has no dependency on, one worktree removal away from being lost.
40
+ Extracting it is the concrete application of the project's own rule that deferral is deletion.
41
+
42
+ ## Known limits
43
+
44
+ This ships the standard, not its enforcement. Applying it to the first runtime consumer
45
+ (`PermissionPromptAutoResolver`, which today records matched-pattern names and a one-way fingerprint
46
+ but no replayable prompt region) is tracked separately as ACT-1312 and is deliberately NOT bundled
47
+ here. Until that lands, the standard is a written rule with no mechanical guard behind it — which by
48
+ this project's own measure means it is not yet enforced.
@@ -0,0 +1,83 @@
1
+ # Side-effects review — write-trace gate-installation self-check
2
+
3
+ **Change:** `skills/instar-dev/scripts/write-trace.mjs` refuses to write a trace when the instar-dev
4
+ commit gate is not installed (hooksPath points at a missing directory, or no pre-commit hook exists).
5
+ Overridable via `INSTAR_DEV_ALLOW_UNINSTALLED_GATE=1`, and the override is recorded in the trace.
6
+
7
+ **Decision point touched?** Yes. This adds a refusal — the script now declines an operation it
8
+ previously always performed. Per `docs/signal-vs-authority.md` the refusal is deliberately scoped as
9
+ a SIGNAL: it cannot block a commit (the detected condition is precisely the one where no commit hook
10
+ runs), it only refuses to emit an artifact that would assert an approval that never happened.
11
+
12
+ ---
13
+
14
+ ## 1. Over-block
15
+
16
+ Real and found during the build, not theorised. The first implementation refused inside any directory
17
+ whose hooks were absent, which broke seven existing tests in `write-trace-tier.test.ts` and
18
+ `duplicate-build-guard-gates.test.ts` — both drive the script inside bare temp directories.
19
+
20
+ Resolved by scoping the check to its actual hazard: a *git repository* whose hook will not run. A
21
+ directory that is not a git repo has no commit to gate, so the check does not apply. Deliberately NOT
22
+ resolved by setting the override env var in each harness — that would push the burden onto whoever
23
+ writes the next test remembering to do it, which is the willpower-over-structure trade this change
24
+ exists to remove.
25
+
26
+ Remaining over-block risk: a repo that installs its pre-commit hook by some mechanism other than
27
+ `core.hooksPath` or `.git/hooks/pre-commit` would be refused incorrectly. No such mechanism is used
28
+ in this repo. The override exists for exactly that case and records itself.
29
+
30
+ ## 2. Under-block
31
+
32
+ The check verifies a pre-commit hook *exists*; it does not verify it is executable, non-empty, or that
33
+ it is actually the instar-dev gate rather than some other hook. A repo with a hook file that exits 0
34
+ immediately would pass this check while gating nothing. Named rather than fixed: deeper verification
35
+ would mean executing or parsing the hook, which is a larger change than the failure being closed.
36
+
37
+ It also cannot help after the fact. A commit already made without the gate leaves no marker, because
38
+ nothing ran to leave one.
39
+
40
+ ## 3. Level-of-abstraction fit
41
+
42
+ Correct, and the placement is the substance of the change. The gate cannot detect its own absence —
43
+ an uninstalled hook cannot execute to report that it is uninstalled — so the check must live in
44
+ something that actually runs. Trace-writing is the nearest chokepoint the agent invokes by hand, and
45
+ it is the step whose whole meaning is "this change came through the skill". Putting the check
46
+ anywhere the agent might skip would reproduce the original problem one layer out.
47
+
48
+ ## 4. Signal vs authority compliance
49
+
50
+ Compliant by construction. It holds no blocking authority over commits and could not acquire any:
51
+ the condition it detects is defined by the absence of the mechanism that would do the blocking. It
52
+ refuses to produce an artifact, which is the weakest possible action, and it fails loudly rather than
53
+ silently. The escape hatch is recorded rather than merely permitted, so an overridden trace is
54
+ distinguishable from an approved one by field presence.
55
+
56
+ ## 5. Interactions
57
+
58
+ Composes with the pre-commit gate rather than duplicating it: without a trace an in-scope commit is
59
+ refused anyway *once the hook is installed*, so the two layers cover different halves of the same
60
+ failure. The trace gains two fields (`gateInstallationOverridden`, `gateInstallationReason`) emitted
61
+ ONLY on override, so every normal trace round-trips byte-identically and existing readers are
62
+ unaffected.
63
+
64
+ Adds two `git config` / `git rev-parse` subprocess calls to a script that already shells out. No hot
65
+ path; runs once per commit at most.
66
+
67
+ ## 6. External surfaces
68
+
69
+ None. `write-trace.mjs` is a development-time script in the instar repo, not shipped behaviour, not
70
+ an endpoint, not agent-visible at runtime. The only observers are instar developers and CI.
71
+
72
+ ## 7. Multi-machine posture
73
+
74
+ Not applicable — this is a development-time script operating on the local checkout. It introduces no
75
+ state, no persistence, and nothing to replicate, proxy, or reconcile. Every machine that develops
76
+ instar runs its own copy against its own worktree, which is the correct and only sensible posture.
77
+
78
+ ## 8. Rollback cost
79
+
80
+ Trivial. Remove the check function and its call site; the trace fields disappear with it (they are
81
+ emitted only on override, so nothing else references them). No data migration, no persisted state, no
82
+ agent state. The rollback re-creates the original silent-gate hazard, so it should be accompanied by
83
+ a reason.