instar 1.3.77 → 1.3.79

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.
Files changed (32) hide show
  1. package/dist/core/InstarWorktreeManager.d.ts +2 -0
  2. package/dist/core/InstarWorktreeManager.d.ts.map +1 -1
  3. package/dist/core/InstarWorktreeManager.js +23 -9
  4. package/dist/core/InstarWorktreeManager.js.map +1 -1
  5. package/dist/core/SafeGitExecutor.d.ts +11 -0
  6. package/dist/core/SafeGitExecutor.d.ts.map +1 -1
  7. package/dist/core/SafeGitExecutor.js +41 -8
  8. package/dist/core/SafeGitExecutor.js.map +1 -1
  9. package/dist/monitoring/MentorStageA.d.ts +47 -0
  10. package/dist/monitoring/MentorStageA.d.ts.map +1 -1
  11. package/dist/monitoring/MentorStageA.js +82 -0
  12. package/dist/monitoring/MentorStageA.js.map +1 -1
  13. package/dist/monitoring/TokenLedger.d.ts.map +1 -1
  14. package/dist/monitoring/TokenLedger.js +12 -4
  15. package/dist/monitoring/TokenLedger.js.map +1 -1
  16. package/dist/scheduler/MentorOnboardingRunner.d.ts +9 -0
  17. package/dist/scheduler/MentorOnboardingRunner.d.ts.map +1 -1
  18. package/dist/scheduler/MentorOnboardingRunner.js.map +1 -1
  19. package/dist/server/AgentServer.d.ts +8 -0
  20. package/dist/server/AgentServer.d.ts.map +1 -1
  21. package/dist/server/AgentServer.js +32 -2
  22. package/dist/server/AgentServer.js.map +1 -1
  23. package/package.json +1 -1
  24. package/scripts/lib/pre-push-scope.mjs +29 -0
  25. package/scripts/pre-push-smoke.mjs +54 -4
  26. package/src/data/builtin-manifest.json +3 -3
  27. package/upgrades/1.3.78.md +30 -0
  28. package/upgrades/1.3.79.md +33 -0
  29. package/upgrades/side-effects/mentor-active-task-driving.md +75 -0
  30. package/upgrades/side-effects/prepush-smoke-failed-files-retry.md +82 -0
  31. package/upgrades/side-effects/token-ledger-schema-order.md +74 -0
  32. package/upgrades/side-effects/worktree-resolve-agent-home.md +81 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "instar",
3
- "version": "1.3.77",
3
+ "version": "1.3.79",
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",
@@ -130,3 +130,32 @@ export function summarizeVitestList(stdout) {
130
130
  const files = new Set(tests.map(line => line.split(' > ')[0]).filter(Boolean));
131
131
  return { testCaseCount: tests.length, testFileCount: files.size };
132
132
  }
133
+
134
+ function normalizeTestFileName(name, cwd) {
135
+ if (typeof name !== 'string' || name.length === 0) return '';
136
+ if (!cwd || !name.startsWith(`${cwd}/`)) return name;
137
+ return name.slice(cwd.length + 1);
138
+ }
139
+
140
+ function resultHasFailure(result) {
141
+ if (!result || typeof result !== 'object') return false;
142
+ if (result.status === 'failed') return true;
143
+ if (Array.isArray(result.assertionResults)) {
144
+ return result.assertionResults.some(assertion => assertion?.status === 'failed');
145
+ }
146
+ return false;
147
+ }
148
+
149
+ export function failedTestFilesFromVitestJson(jsonText, opts = {}) {
150
+ const cwd = opts.cwd ?? process.cwd();
151
+ const parsed = JSON.parse(String(jsonText));
152
+ const files = new Set();
153
+
154
+ for (const result of parsed?.testResults ?? []) {
155
+ if (!resultHasFailure(result)) continue;
156
+ const file = normalizeTestFileName(result.name, cwd);
157
+ if (file) files.add(file);
158
+ }
159
+
160
+ return [...files].sort();
161
+ }
@@ -1,9 +1,13 @@
1
1
  #!/usr/bin/env node
2
2
  // safe-git-allow: pre-push smoke runner — read-only git, then Vitest.
3
+ import fs from 'node:fs';
4
+ import os from 'node:os';
5
+ import path from 'node:path';
3
6
  import { spawnSync } from 'node:child_process';
4
7
  import {
5
8
  changedFilesSince,
6
9
  evaluateSmokeBreadth,
10
+ failedTestFilesFromVitestJson,
7
11
  readSmokeLimits,
8
12
  resolvePrePushBase,
9
13
  summarizeVitestList,
@@ -24,6 +28,55 @@ function printSkip(reason) {
24
28
  console.log(' The PR test matrix still runs the full suite before merge.');
25
29
  }
26
30
 
31
+ function readFailedFiles(reportFile) {
32
+ try {
33
+ return failedTestFilesFromVitestJson(fs.readFileSync(reportFile, 'utf-8'), { cwd: process.cwd() });
34
+ } catch (err) {
35
+ console.warn(`pre-push smoke: could not read failed test files from Vitest JSON (${err instanceof Error ? err.message : err}).`);
36
+ return [];
37
+ }
38
+ }
39
+
40
+ function runAffectedSmoke(baseRef) {
41
+ const reportDir = fs.mkdtempSync(path.join(os.tmpdir(), 'instar-pre-push-smoke-'));
42
+ const reportFile = path.join(reportDir, 'vitest-results.json');
43
+ try {
44
+ const result = run('npx', [
45
+ 'vitest',
46
+ 'run',
47
+ '--config',
48
+ 'vitest.push.config.ts',
49
+ '--changed',
50
+ baseRef,
51
+ '--reporter=default',
52
+ '--reporter=json',
53
+ `--outputFile.json=${reportFile}`,
54
+ ], {
55
+ stdio: 'inherit',
56
+ });
57
+
58
+ if ((result.status ?? 1) === 0) return 0;
59
+
60
+ const failedFiles = readFailedFiles(reportFile);
61
+ if (failedFiles.length === 0) {
62
+ console.warn('pre-push smoke: no failed test files were found for focused retry; preserving original failure.');
63
+ return result.status ?? 1;
64
+ }
65
+
66
+ console.log('');
67
+ console.log(`⚠️ Attempt 1 failed — retrying ${failedFiles.length} failed test file${failedFiles.length === 1 ? '' : 's'} once.`);
68
+ for (const file of failedFiles) console.log(` - ${file}`);
69
+ console.log('');
70
+
71
+ const retry = run('npx', ['vitest', 'run', '--config', 'vitest.push.config.ts', ...failedFiles], {
72
+ stdio: 'inherit',
73
+ });
74
+ return retry.status ?? 1;
75
+ } finally {
76
+ fs.rmSync(reportDir, { recursive: true, force: true });
77
+ }
78
+ }
79
+
27
80
  const base = resolvePrePushBase();
28
81
  const limits = readSmokeLimits();
29
82
  let changed = [];
@@ -79,7 +132,4 @@ if (selected.testCaseCount === 0) {
79
132
  process.exit(0);
80
133
  }
81
134
 
82
- const result = run('npx', ['vitest', 'run', '--config', 'vitest.push.config.ts', '--changed', base.ref], {
83
- stdio: 'inherit',
84
- });
85
- process.exit(result.status ?? 1);
135
+ process.exit(runAffectedSmoke(base.ref));
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "$schema": "./builtin-manifest.schema.json",
3
3
  "schemaVersion": 1,
4
- "generatedAt": "2026-05-29T07:53:32.454Z",
5
- "instarVersion": "1.3.77",
4
+ "generatedAt": "2026-05-29T08:53:20.270Z",
5
+ "instarVersion": "1.3.79",
6
6
  "entryCount": 193,
7
7
  "entries": {
8
8
  "hook:session-start": {
@@ -1457,7 +1457,7 @@
1457
1457
  "type": "subsystem",
1458
1458
  "domain": "server",
1459
1459
  "sourcePath": "src/server/AgentServer.ts",
1460
- "contentHash": "24636c91006739740344ff9c9c566f4d6131f52b0b9bbb8f5bec17768954cec7",
1460
+ "contentHash": "11dfce264118d9e24484060fcad8d26d36272045beb088fbeb0accb3b404cb90",
1461
1461
  "since": "2025-01-01"
1462
1462
  },
1463
1463
  "subsystem:session-manager": {
@@ -0,0 +1,30 @@
1
+ # Instar Upgrade Guide — NEXT
2
+
3
+ <!-- bump: patch -->
4
+
5
+ ## What Changed
6
+
7
+ **Fixes a second cause of a permanently-unavailable token ledger: a schema-migration ordering bug.**
8
+
9
+ `TokenLedger`'s `SCHEMA` setup ran `CREATE INDEX idx_token_events_key_ts ON token_events(attribution_key, ts)` BEFORE the `ALTER TABLE token_events ADD COLUMN attribution_key` migration. On any agent whose `token_events` table was created before the `attribution_key` column existed, `CREATE TABLE IF NOT EXISTS` no-ops (the column is never added), the index then throws `no such column: attribution_key`, and the init loop only swallows `duplicate column name` — so the error rethrows, TokenLedger init fails, and `/tokens/summary`, `/tokens/by-project`, `/tokens/sessions`, `/tokens/orphans` all return 503 (`token ledger unavailable`) forever.
10
+
11
+ This was found while verifying the heal fix shipped in 1.3.77 (#509): Echo's own `/tokens/summary` was still 503, but with `SqliteError: no such column: attribution_key` rather than the ABI mismatch #509 addressed — a distinct, fleet-wide cause affecting every agent with a pre-`attribution_key` ledger DB.
12
+
13
+ The fix reorders the `SCHEMA` array so the `ALTER … ADD COLUMN attribution_key` migration runs immediately after `CREATE TABLE token_events` and before any index/query that references the column. Fresh DBs are unaffected (the column is created with the table; the relocated ALTER throws duplicate-column → swallowed). Pre-attribution DBs get the column added, then the index and the `attribution_key` queries succeed. A code comment records the invariant: a column-adding migration must precede anything that references the column.
14
+
15
+ ## What to Tell Your User
16
+
17
+ If your agent's token usage tab or token endpoints have been showing as unavailable, this likely fixes it. The token ledger is built on a small local database, and a recent version added a new column to it. On agents whose database was created before that column existed, the setup steps ran in the wrong order — it tried to build an index that uses the new column before the step that adds the column to older databases, so the whole setup gave up and the ledger stayed off. The fix simply runs those setup steps in the right order, so older databases get upgraded cleanly. Nothing for you to do; the token data comes back the next time the agent restarts onto this version. Brand-new agents were never affected.
18
+
19
+ ## Summary of New Capabilities
20
+
21
+ | Capability | How to Use |
22
+ |-----------|-----------|
23
+ | Pre-existing token-ledger DBs self-migrate | Automatic. The `attribution_key` column migration now runs before the index that depends on it, so agents with an older token-ledger database recover their `/tokens/*` endpoints on restart instead of staying at 503. No config. |
24
+
25
+ ## Evidence
26
+
27
+ - Unit: `tests/unit/token-ledger.test.ts` — new regression block builds a `token_events` table with the OLD schema (no `attribution_key`) plus a pre-existing row, then constructs `TokenLedger`: it does NOT throw, `PRAGMA table_info` shows the migrated column, `summary()` + `byAttributionKey()` work, the pre-existing row is backfilled with the `'unknown::pre-attribution'` DEFAULT, and re-initialising is idempotent. All 19 pre-existing TokenLedger tests still pass (fresh-DB path unchanged).
28
+ - Live: Echo's on-disk `token_events` table verified to lack `attribution_key`; its `/tokens/summary` returned 503 with `SqliteError: no such column: attribution_key` at TokenLedger init — the exact failure this reorder removes.
29
+
30
+ Spec: `docs/specs/token-ledger-native-heal.md` (2026-05-29 Amendment 2 — in-scope, finishes AC#7).
@@ -0,0 +1,33 @@
1
+ # Instar Upgrade Guide — NEXT
2
+
3
+ <!-- bump: patch -->
4
+
5
+ ## What Changed
6
+
7
+ **The Framework-Onboarding Mentor can now actively drive a mentee through a concrete onboarding agenda — instead of only passive "how's it going?" check-ins. Ships dark (opt-in).**
8
+
9
+ Grounded in the live Codey dogfooding run (topic 13435): the high-signal interactions were the ones where the human handed Codey concrete tasks (capability checks + real dev work); passive check-ins on an idle mentee were low-signal ("nothing to do"). Two gaps were found and fixed:
10
+
11
+ 1. **The mentor's Stage-A surface builder (`getSurface`) was a stub** returning an empty surface, so the mentor was blind — it could only ever choose `observe-only` or emit a generic message, despite the `assign-next` action already in its repertoire.
12
+ 2. **No backlog** of concrete onboarding tasks to assign.
13
+
14
+ The change adds an optional `onboardingAgenda` (the mentor's own ordered list of next tasks) to `MentorConfig` and the Stage-A surface, includes it in the leak-check surface (so assigning an agenda task is not flagged as peeking at internals), and replaces the stub with a real surface built from the mentee's recent replies (`mentor-replies.jsonl`, parsed defensively) plus the agenda. When the mentee is idle and the agenda has uncovered items, the mentor hands over the next concrete task; if the mentee is mid-task, blocked, or asked something, it still waits / unblocks / answers.
15
+
16
+ **Double-gated dark:** the mentor is off by default (`mentor.enabled:false`/`mode:'off'`), and the agenda is empty by default — so even an enabled mentor keeps today's passive behaviour until an operator sets `mentor.onboardingAgenda`. Shipped as a conservative patch (no major.minor boundary crossing → no lifeline-restart churn) because it is dormant until opted in. Known limitation, flagged for a follow-up: the surface feeds the mentee's replies but not the mentor's own prior prompts (their content isn't logged yet), so agenda rotation is inferred from the mentee's replies.
17
+
18
+ ## What to Tell Your User
19
+
20
+ Nothing changes unless you turn it on. The optional onboarding mentor — the piece that can shepherd a newly-onboarded agent through learning the system — used to only manage vague check-ins, because it was effectively handed a blank page about what the new agent was doing. It now gets a real picture (the new agent's recent replies) and can be given an onboarding to-do list, so it hands over concrete next tasks instead of hollow check-ins. The mentor stays off by default, and even once enabled it behaves exactly as before until you give it a to-do list. If you want the mentor to proactively walk a new agent through specific capabilities, that is now possible — just ask and I can set it up.
21
+
22
+ ## Summary of New Capabilities
23
+
24
+ | Capability | How to Use |
25
+ |-----------|-----------|
26
+ | Mentor onboarding agenda (active task-driving) | Set `mentor.onboardingAgenda` to an ordered list of task descriptions in `.instar/config.json`. When the mentor is enabled and the mentee is idle, it assigns the next uncovered item via `assign-next` instead of `observe-only`. Empty/unset → unchanged passive behaviour. |
27
+
28
+ ## Evidence
29
+
30
+ - Unit: `tests/unit/MentorStageA.test.ts` — agenda block present/absent in `buildStageAContext`; agenda counted as surface-legitimate so assigning it is not a leak; `buildConversationSurface` formats mentee replies into history, caps to most-recent N, computes time-since-last-contact, sets agenda only when non-empty; `parseMenteeReplies` skips blank/garbage/empty-message lines, coerces `ts`, filters to the named mentee, never throws. All 43 mentor unit tests (MentorStageA + Tick + Runner) pass; full build green.
31
+ - Live origin: the Codey dogfooding run — passive observe-only on an idle mentee was low-signal; active task-driving (the human assigning concrete tasks) was where every real issue surfaced.
32
+
33
+ Spec: `docs/specs/FRAMEWORK-ONBOARDING-MENTOR-SPEC.md` (2026-05-29 amendment — in-scope; ships dark per the spec's graduated-rollout framing).
@@ -0,0 +1,75 @@
1
+ # Side-Effects Review — Mentor active task-driving (onboarding agenda)
2
+
3
+ **Version / slug:** `mentor-active-task-driving`
4
+ **Date:** `2026-05-29`
5
+ **Author:** Echo (instar developer agent)
6
+ **Spec:** `docs/specs/FRAMEWORK-ONBOARDING-MENTOR-SPEC.md` (2026-05-29 amendment — in-scope)
7
+ **Second-pass reviewer:** required (changes mentor decision behaviour + replaces the getSurface stub)
8
+
9
+ ## Summary of the change
10
+
11
+ The Framework-Onboarding Mentor's Stage-A `getSurface` was a stub returning an empty surface, so the mentor was blind and could only ever `observe-only` / produce a generic check-in. This adds an optional onboarding agenda (the mentor's own backlog of capability checks + starter dev tasks) and replaces the stub with a real surface (the mentee's recent replies + the agenda + timing), so an idle mentee gets a concrete next task via the already-existing `assign-next` action — the active task-driving pattern that proved high-signal while dogfooding Codey.
12
+
13
+ Files touched:
14
+ - `src/monitoring/MentorStageA.ts` — `ConversationSurface.onboardingAgenda?`; `surfaceText` includes it (leak-legitimacy); `buildStageAContext` adds an agenda block + assign-next steering ONLY when an agenda is present; new pure `buildConversationSurface()` + `parseMenteeReplies()` (+ `MenteeReplyLine` type).
15
+ - `src/scheduler/MentorOnboardingRunner.ts` — `MentorConfig.onboardingAgenda?`.
16
+ - `src/server/AgentServer.ts` — `getSurface` now builds the real surface; new `readRecentMenteeReplies()` thin glue (read `mentor-replies.jsonl` → `parseMenteeReplies`).
17
+ - `tests/unit/MentorStageA.test.ts` — agenda-in-prompt (present/absent), agenda leak-safety, `buildConversationSurface` (formatting/cap/empty), `parseMenteeReplies` (defensive parsing/filter/throw-safety).
18
+
19
+ Decision-point inventory: one — the Stage-A action choice (`unblock|answer|assign-next|observe-only`). This change adds CONTEXT (agenda + real conversation) to that LLM decision and steers it toward `assign-next` when idle-with-agenda; it does not add a new authority or a new deterministic gate. The two-hats boundary (empty tool grant, surface-only, leak detector) is unchanged.
20
+
21
+ ---
22
+
23
+ ## 1. Over-block
24
+
25
+ No allow/deny surface. The change makes the mentor produce a *more useful* action (assign a concrete task) rather than a hollow check-in. Nothing legitimate is newly rejected. The leak detector is unchanged and now correctly treats agenda-derived tasks as legitimate (they're in `surfaceText`).
26
+
27
+ ---
28
+
29
+ ## 2. Under-block
30
+
31
+ - **Agenda rotation without the mentor's own prior prompts.** The surface feeds the mentee's *replies* but not the mentor's own prior prompts (their content isn't logged today; `a2a-sent.jsonl` is metadata-only). So the LLM infers "what's already been assigned" from the mentee's replies — imperfect if replies are terse. Mitigated by: (a) the mentor-outstanding tracker blocks a new send while a prompt is unreplied (no back-to-back dupes), and (b) the feature ships dark, so rotation quality only matters after deliberate opt-in. Logging sent-prompt content is a scoped follow-up (named in the spec).
32
+ - **Leaky agenda content.** If an operator puts an internal-looking ref (a source path / PR#) into an agenda item, it's in `surfaceText` so it won't trip the leak detector — but that's the operator's deliberate choice (the agenda is the mentor's plan), not an internal leaking in. Acceptable.
33
+
34
+ ---
35
+
36
+ ## 3. Level-of-abstraction fit
37
+
38
+ Right layers: the agenda + prompt logic live in the surface module (`MentorStageA`, pure + tested); the config field on `MentorConfig`; the file-read glue on the server. The pure functions are extracted specifically so the logic is unit-testable without file IO.
39
+
40
+ ---
41
+
42
+ ## 4. Signal vs authority compliance
43
+
44
+ The mentor's Stage A is an LLM acting AS the user — it produces a SIGNAL (a suggested next message), never an authority over the mentee. This change only enriches the signal's context (agenda + conversation). No authority moves; the empty-tool-grant + leak-detector boundary is intact. Compliant with `docs/signal-vs-authority.md`.
45
+
46
+ ---
47
+
48
+ ## 5. Interactions
49
+
50
+ - **MentorOnboardingTick / Runner.** Unchanged control flow — they still call `getSurface` → `spawnStageA(buildStageAContext(surface))`. The surface is just no longer empty.
51
+ - **Two-hats leak detector.** `surfaceText` now includes the agenda, so agenda-derived task references are legitimate (tested). No weakening of the detector for non-surface internals.
52
+ - **mentor-outstanding tracker / deliverToMentee.** Unchanged; still gates back-to-back sends.
53
+ - **Dark-by-default.** Mentor `enabled:false`/`mode:'off'` by default AND empty agenda by default — two independent gates. No agent gets new behaviour without an operator setting both.
54
+
55
+ ---
56
+
57
+ ## 6. External surfaces
58
+
59
+ No new HTTP route, so no Tier-2 route / Tier-3 alive test applies (the standard's route-liveness tier targets API features; this is internal mentor behaviour). No `PostUpdateMigrator` config migration needed: `onboardingAgenda` is OPTIONAL — absent → unchanged behaviour, so existing agents need no patch. No CLAUDE.md template change: the mentor is an operator-configured internal job, not an agent-surfaced conversational capability. Observable effect (only after opt-in): an enabled mentor with a populated agenda hands an idle mentee concrete tasks instead of generic check-ins.
60
+
61
+ ---
62
+
63
+ ## 7. Rollback cost
64
+
65
+ Revert the `onboardingAgenda` field + the `buildStageAContext` agenda block + the `getSurface` builder (back to the empty-surface stub) + the new pure helpers/tests. One feature, code-only, no state/migration/contract. Reverted-to state = today's passive mentor. ~10 minutes.
66
+
67
+ ---
68
+
69
+ ## Second-pass review
70
+
71
+ **Concern:** Does replacing the empty-surface stub with real file reads risk throwing into the mentor tick? No — `readRecentMenteeReplies` is best-effort (missing/unreadable/garbled file → `[]`), and `parseMenteeReplies` skips malformed lines and never throws (tested). Worst case the surface shows "(no prior conversation)" — the prior stub's behaviour.
72
+
73
+ **Concern:** Does this change behaviour for existing agents on upgrade? No — double-gated (mentor off by default + empty agenda by default). The prompt is byte-identical to before when no agenda is set (M5).
74
+
75
+ **Concurrence:** Core logic is pure + unit-tested on both sides of the decision boundary (agenda present vs absent; replies present vs empty; well-formed vs garbage), the server change is thin tested-glue, it ships dark, and rollback is code-only to a strictly-unchanged state. The one real limitation (no sent-prompt history) is named in the spec as a follow-up and is acceptable for a dark feature. Concurred.
@@ -0,0 +1,82 @@
1
+ # Side-Effects Review — Pre-push smoke failed-files retry
2
+
3
+ **Version / slug:** `prepush-smoke-failed-files-retry`
4
+ **Date:** `2026-05-29`
5
+ **Author:** `instar-codey`
6
+ **Second-pass reviewer:** `instar-codey second-pass checklist`
7
+
8
+ ## Summary of the change
9
+
10
+ This change keeps the existing pre-push smoke base resolver and breadth guard, then makes the smoke retry file-scoped. `scripts/pre-push-smoke.mjs` now writes a Vitest JSON report during the affected smoke run; if that run fails, it extracts unique failed test files and reruns only those files once. `scripts/lib/pre-push-scope.mjs` owns the failed-file extraction helper, `tests/unit/scripts/pre-push-scope.test.ts` covers the parsing behavior, and `.husky/pre-push` no longer retries the whole smoke command after the smoke runner has handled its own retry. The full push-suite path keeps its previous whole-command retry behavior.
11
+
12
+ ## Decision-point inventory
13
+
14
+ - `scripts/pre-push-smoke.mjs#runAffectedSmoke` — add — decides whether a failed smoke run has a trustworthy failed-file list and performs one focused retry.
15
+ - `scripts/lib/pre-push-scope.mjs#failedTestFilesFromVitestJson` — add — extracts failed test file names from Vitest JSON output.
16
+ - `.husky/pre-push` — modify — decides whether retry is owned by the smoke runner or by the outer hook loop.
17
+
18
+ ---
19
+
20
+ ## 1. Over-block
21
+
22
+ The new JSON parser can fail to identify failed files if Vitest changes its JSON shape or the report file is not written. In that case the smoke runner preserves the original failure rather than passing the push. That can leave a developer with a normal failed smoke run instead of a focused retry, but it does not create a new block beyond the failure already observed.
23
+
24
+ A file can be retried even if only one test case inside it failed. That is intentional: file-scoped retry avoids needing brittle test-name parsing while still shrinking the second run substantially.
25
+
26
+ ## 2. Under-block
27
+
28
+ If a test passes on focused retry after failing in the affected-set run, the hook passes. That is the existing retry semantics applied more narrowly. A real ordering or cross-file interaction could be masked by retrying only the failed file, but PR CI remains the full authority and will run the broader matrix before merge.
29
+
30
+ The change does not detect failures outside Vitest's affected-test selection. That limitation already exists in local smoke and is explicitly bounded by CI.
31
+
32
+ ## 3. Level-of-abstraction fit
33
+
34
+ The failed-file parser is at the right layer: it consumes Vitest's structured report instead of scraping terminal prose. The retry decision lives in the smoke runner because that runner has the selected base, first-run result, and report file path. The outer Husky hook stays as the orchestration wrapper and still owns the full-suite retry path.
35
+
36
+ ## 4. Signal vs authority compliance
37
+
38
+ **Required reference:** [docs/signal-vs-authority.md](../../docs/signal-vs-authority.md)
39
+
40
+ - [ ] No — this change produces a signal consumed by an existing smart gate.
41
+ - [ ] No — this change has no block/allow surface.
42
+ - [ ] Yes — but the logic is a smart gate with full conversational context (LLM-backed with recent history or equivalent).
43
+ - [x] Not applicable to conversational/product judgment — this is local developer-tooling retry control.
44
+
45
+ The logic has local authority over which smoke files to retry, but it does not judge user intent, message meaning, external operations, or product behavior. It is deterministic process tooling. When the structured signal is unavailable, it fails conservatively by preserving the original failure.
46
+
47
+ ## 5. Interactions
48
+
49
+ - **Shadowing:** The smoke runner's focused retry replaces the outer hook's whole-smoke retry only for `npm run test:smoke`. The full push-suite path is unchanged.
50
+ - **Double-fire:** The outer hook now runs the smoke command once, so the focused retry cannot be followed by a whole affected-set retry from the wrapper.
51
+ - **Races:** The JSON report is written into a per-run temporary directory and deleted in `finally`. No persistent state is shared across pushes.
52
+ - **Feedback loops:** No runtime state feeds back into future runs. The retry list is computed only from the current Vitest report.
53
+
54
+ ## 6. External surfaces
55
+
56
+ This affects local developers and agents pushing Instar branches. Terminal output changes when the first smoke attempt fails: the hook now prints the failed files being retried. CI, GitHub checks, Telegram, dashboard behavior, persisted ledgers, and installed-agent runtime behavior are unchanged.
57
+
58
+ ## 7. Rollback cost
59
+
60
+ Rollback is a pure code revert of `.husky/pre-push`, `scripts/pre-push-smoke.mjs`, `scripts/lib/pre-push-scope.mjs`, the focused parser tests, and this artifact/spec pair. No data migration, agent state repair, or user notification is required. The regression during rollback would be returning to whole affected-set retry for local smoke failures.
61
+
62
+ ## Conclusion
63
+
64
+ The review found one real tradeoff: a focused retry can pass after a cross-file interaction fails in the first affected run. That is acceptable for local smoke because CI is the authority and the first failure remains visible in the terminal output. The change is clear to ship with parser tests, a Vitest JSON compatibility probe, and the instar-dev gate.
65
+
66
+ ---
67
+
68
+ ## Second-pass review (if required)
69
+
70
+ **Reviewer:** instar-codey second-pass checklist
71
+ **Independent read of the artifact:** concur
72
+
73
+ The second pass agrees that this is local developer-tooling behavior, not a product/message authority. The full-suite authority boundary remains CI, and malformed report handling is conservative.
74
+
75
+ ---
76
+
77
+ ## Evidence pointers
78
+
79
+ - `tests/unit/scripts/pre-push-scope.test.ts`
80
+ - `scripts/pre-push-smoke.mjs`
81
+ - `scripts/lib/pre-push-scope.mjs`
82
+ - Manual Vitest 2 JSON reporter probe using a temporary failing test file; report shape confirmed as `testResults[].name/status/assertionResults[]`.
@@ -0,0 +1,74 @@
1
+ # Side-Effects Review — TokenLedger schema migration ordering
2
+
3
+ **Version / slug:** `token-ledger-schema-order`
4
+ **Date:** `2026-05-29`
5
+ **Author:** Echo (instar developer agent)
6
+ **Spec:** `docs/specs/token-ledger-native-heal.md` (2026-05-29 Amendment 2 — in-scope, finishes AC#7)
7
+ **Second-pass reviewer:** required (touches a shared SQLite init path on every agent)
8
+
9
+ ## Summary of the change
10
+
11
+ `TokenLedger`'s `SCHEMA` DDL array ran `CREATE INDEX idx_token_events_key_ts ON token_events(attribution_key, ts)` BEFORE the `ALTER TABLE token_events ADD COLUMN attribution_key` migration. On any agent whose `token_events` table predates `attribution_key`, `CREATE TABLE IF NOT EXISTS` no-ops (column not added), the index then throws `no such column: attribution_key`, and the init loop only swallows `duplicate column name` → the error rethrows → TokenLedger init fails → `/tokens/*` return 503 permanently. Verified live: Echo's `token_events` table on disk lacks the column.
12
+
13
+ Fix: reorder `SCHEMA` so the `ALTER … ADD COLUMN attribution_key` runs immediately after `CREATE TABLE token_events` and before any index/query referencing the column. Pure reorder; no logic change. Code comment establishes the invariant: a column-adding migration MUST precede any index/query that references the new column.
14
+
15
+ Files touched:
16
+ - `src/monitoring/TokenLedger.ts` — move the `attribution_key` ALTER up within the `SCHEMA` array (before `idx_token_events_key_ts`); comments updated.
17
+ - `tests/unit/token-ledger.test.ts` — new regression block: a pre-attribution DB migrates on init (no 503), the column is added, `summary()`/`byAttributionKey()` work, the pre-existing row is backfilled with the DEFAULT, and re-init is idempotent.
18
+
19
+ Decision-point inventory: none. This is a deterministic schema-setup ordering fix — no LLM-backed decision, no allow/deny surface, no authority change.
20
+
21
+ ---
22
+
23
+ ## 1. Over-block
24
+
25
+ No block/allow surface exists here. The change makes a previously-failing init succeed on pre-attribution DBs. No legitimate input is newly rejected.
26
+
27
+ ---
28
+
29
+ ## 2. Under-block
30
+
31
+ - **Other unmigrated columns.** This fixes the `attribution_key` ordering specifically. `head_hash` on `file_offsets` has no dependent index, so its ALTER-after-table ordering is safe and unchanged. Any FUTURE column added with a dependent index could reintroduce the class — mitigated by the documented invariant comment, not by a structural guard (a generic "all ALTERs before all indexes" reorderer was considered and judged over-engineering for two migrations).
32
+ - **Genuinely-corrupt DB.** If `token_events` is malformed beyond a missing column, init still throws (correct — that's not this bug).
33
+
34
+ ---
35
+
36
+ ## 3. Level-of-abstraction fit
37
+
38
+ Right layer: the bug is in TokenLedger's own schema-setup array, and the fix lives there. No call-site or API change; the migration was already present (just mis-ordered).
39
+
40
+ ---
41
+
42
+ ## 4. Signal vs authority compliance
43
+
44
+ No authority or signal involved — deterministic DDL ordering. Not applicable to product/message/external-operation judgment. Compliant by vacuity with `docs/signal-vs-authority.md`.
45
+
46
+ ---
47
+
48
+ ## 5. Interactions
49
+
50
+ - **NativeModuleHealer / Amendment 1.** Orthogonal: Amendment 1 fixes the ABI-mismatch open path; this fixes the post-open schema migration. An agent could hit one, the other, or both; the fixes compose (heal opens the DB, then the reordered schema migrates it).
51
+ - **TokenLedgerPoller / BurnDetector.** Both read `token_events` via TokenLedger; once init succeeds they see the migrated table with the backfilled `attribution_key` DEFAULT. No change to their logic.
52
+ - **Fresh installs.** Unchanged: the column is created with the table; the relocated ALTER throws duplicate-column → swallowed exactly as before.
53
+
54
+ ---
55
+
56
+ ## 6. External surfaces
57
+
58
+ No API/config/contract change. No `PostUpdateMigrator` entry needed — this is package code, not an agent-installed file; the migration runs in TokenLedger's own init on the next server start after the upgrade. Observable effect is positive: `/tokens/summary`, `/tokens/by-project`, `/tokens/sessions`, `/tokens/orphans` and the Dashboard Tokens tab go from 503 to live on every pre-attribution agent once it restarts onto the fixed version.
59
+
60
+ ---
61
+
62
+ ## 7. Rollback cost
63
+
64
+ Revert the `SCHEMA` reorder in one file. No data migration, no state, no contract. Reverted-to state = pre-attribution DBs stay 503 (the bug being fixed) — never worse. ~5 minutes.
65
+
66
+ ---
67
+
68
+ ## Second-pass review
69
+
70
+ **Concern:** Could reordering break a fresh DB, where the column already exists when the ALTER runs? No — `ALTER ADD COLUMN` on an existing column throws `duplicate column name`, which the init loop already swallows; the new regression test's idempotency case (B3) plus all 19 pre-existing TokenLedger tests (fresh `:memory:` DBs) confirm the fresh path is unchanged.
71
+
72
+ **Concern:** Data loss on the pre-existing row? No — `ALTER TABLE ADD COLUMN … DEFAULT` backfills existing rows with the default; test B2 asserts the pre-existing row survives with `attribution_key = 'unknown::pre-attribution'`.
73
+
74
+ **Concurrence:** Mechanically narrow (a single statement moved within an array), both sides of the boundary tested (pre-attribution DB migrates; fresh DB unchanged + idempotent re-init), one-file rollback to a strictly-not-worse state. Concurred.
@@ -0,0 +1,81 @@
1
+ # Side-Effects Review — Worktree resolver accepts agent-home source checkouts
2
+
3
+ **Version / slug:** `worktree-resolve-agent-home`
4
+ **Date:** `2026-05-29`
5
+ **Author:** `instar-codey`
6
+ **Second-pass reviewer:** `instar-codey second-pass checklist`
7
+
8
+ ## Summary of the change
9
+
10
+ This change fixes `instar worktree create` for developer agents whose canonical Instar checkout is their own agent home or current working tree. `src/core/InstarWorktreeManager.ts` now considers the current working directory and `INSTAR_AGENT_HOME` as repo candidates before the hardcoded fallback paths, resolves subdirectory candidates to the git top-level, and still validates the remote URL allowlist and hooks path. `src/core/SafeGitExecutor.ts` gains a narrow `sourceTreeWorktreeManagerOk` escape so the worktree manager can perform only the source-tree git shapes it legitimately needs: read-only resolver checks, `git worktree add/prune`, and per-worktree identity config writes. Tests cover cwd and agent-home discovery, source-tree-shaped fixtures, and the closed SafeGitExecutor escape.
11
+
12
+ ## Decision-point inventory
13
+
14
+ - `resolveInstarRepo` — modify — chooses which validated Instar checkout the worktree manager operates against.
15
+ - `SafeGitExecutor.sourceTreeWorktreeManagerOk` — add — allows a closed set of worktree-manager git shapes against a source tree while keeping SourceTreeGuard default-deny for every other mutation.
16
+ - `createWorktree` git helper — modify — opts the manager's git calls into the narrow source-tree worktree-manager allowance.
17
+
18
+ ---
19
+
20
+ ## 1. Over-block
21
+
22
+ The resolver still rejects legitimate Instar forks whose `remote.origin.url` is not in the default or configured worktree repo allowlist. That is intentional and unchanged; operators can extend `worktree.repoUrlAllowlist` for trusted forks.
23
+
24
+ The new SafeGitExecutor escape can still block future worktree-manager operations if the manager adds a new git shape not listed here. That is preferable to silently widening source-tree mutation authority; future shapes should be added deliberately with tests.
25
+
26
+ ## 2. Under-block
27
+
28
+ The resolver now falls back from an invalid `INSTAR_REPO` to cwd, agent home, and configured fallback paths. That matches the previous fallback style but means a bad env var does not force failure if another legitimate repo candidate is available. Integrity validation still applies to the repo ultimately selected.
29
+
30
+ The worktree-manager escape allows `git worktree add/prune` against a validated source checkout. A bug in branch/slug/path validation could still cause a bad worktree target, but the manager's existing slug validation and `.worktrees` containment checks run before the add call.
31
+
32
+ ## 3. Level-of-abstraction fit
33
+
34
+ Repo discovery belongs in `InstarWorktreeManager`, because that manager already owns agent-home resolution, repo validation, worktree path containment, and audit ledger writes. The SourceTreeGuard bypass belongs in `SafeGitExecutor`, not as a raw subprocess bypass, because the executor remains the single funnel and can enforce the exact allowed shapes.
35
+
36
+ ## 4. Signal vs authority compliance
37
+
38
+ **Required reference:** [docs/signal-vs-authority.md](../../docs/signal-vs-authority.md)
39
+
40
+ - [ ] No — this change produces a signal consumed by an existing smart gate.
41
+ - [ ] No — this change has no block/allow surface.
42
+ - [ ] Yes — but the logic is a smart gate with full conversational context (LLM-backed with recent history or equivalent).
43
+ - [x] Not applicable to conversational/product judgment — this is a structural filesystem/git safety guard.
44
+
45
+ This is a brittle blocker, but it falls under the documented SourceTreeGuard carve-out for safety guards on irreversible actions. The change narrows an explicit exception rather than weakening the guard generally.
46
+
47
+ ## 5. Interactions
48
+
49
+ - **Shadowing:** `sourceTreeWorktreeManagerOk` is checked inside SafeGitExecutor at the same point as `sourceTreeReadOk`; it does not bypass verb classification or env sanitization.
50
+ - **Double-fire:** SourceTreeGuard still fires for all non-enumerated source-tree mutations. The new option only prevents false blocking for the worktree manager's enumerated shapes.
51
+ - **Races:** Candidate repo resolution reads git metadata from cwd/agent home/fallback paths. Concurrent remote config changes could affect which candidate passes, but every candidate is validated at call time.
52
+ - **Feedback loops:** Worktree creation still writes the existing audit ledgers after success. No new persistent state is introduced.
53
+
54
+ ## 6. External surfaces
55
+
56
+ This affects Instar developers and agents invoking the `instar worktree create` helper. It should turn a false failure into successful worktree creation when the agent home/current checkout is a valid Instar repo. It does not change Telegram, dashboard behavior, runtime server APIs, or CI behavior.
57
+
58
+ ## 7. Rollback cost
59
+
60
+ Rollback is a pure code revert of `src/core/InstarWorktreeManager.ts`, `src/core/SafeGitExecutor.ts`, the tests, and this artifact. No data migration or agent state repair is required. Existing worktrees created while the fix is live remain ordinary git worktrees and can be removed with existing tooling if needed.
61
+
62
+ ## Conclusion
63
+
64
+ The diagnosis found that the resolver missed valid cwd/agent-home source checkouts and the guard path needed a narrow worktree-manager exception once those checkouts became candidates. The implemented fix preserves the remote allowlist, hooks-path validation, SourceTreeGuard default-deny behavior, and path containment. Focused unit and integration tests pass.
65
+
66
+ ---
67
+
68
+ ## Second-pass review (if required)
69
+
70
+ **Reviewer:** instar-codey second-pass checklist
71
+ **Independent read of the artifact:** concur
72
+
73
+ The second pass agrees that this is the narrowest practical fix: no raw git bypass, no general SourceTreeGuard weakening, and tests prove unrelated source-tree mutations still throw.
74
+
75
+ ---
76
+
77
+ ## Evidence pointers
78
+
79
+ - `tests/unit/InstarWorktreeManager.test.ts`
80
+ - `tests/unit/SafeGitExecutor-sourceTreeReadOk.test.ts`
81
+ - `tests/integration/instar-worktree-create.test.ts`