instar 1.1.3 → 1.1.4

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/README.md CHANGED
@@ -1 +1,270 @@
1
- # Test
1
+ <p align="center">
2
+ <img src="assets/logo.png" alt="Instar" width="180" />
3
+ </p>
4
+
5
+ <h1 align="center">instar</h1>
6
+
7
+ <p align="center">
8
+ <strong>Persistent, trustworthy Claude Code agents. Built on coherence-first architecture.</strong>
9
+ </p>
10
+
11
+ <p align="center">
12
+ <a href="https://www.npmjs.com/package/instar"><img src="https://img.shields.io/npm/v/instar?style=flat-square" alt="npm version"></a>
13
+ <a href="https://www.npmjs.com/package/instar"><img src="https://img.shields.io/npm/dw/instar?style=flat-square" alt="npm downloads"></a>
14
+ <a href="https://github.com/JKHeadley/instar/actions/workflows/ci.yml"><img src="https://img.shields.io/github/actions/workflow/status/JKHeadley/instar/ci.yml?branch=main&style=flat-square&label=CI" alt="CI"></a>
15
+ <a href="https://github.com/JKHeadley/instar/blob/main/LICENSE"><img src="https://img.shields.io/badge/License-MIT-green?style=flat-square" alt="License"></a>
16
+ <img src="https://img.shields.io/badge/TypeScript-100%25-blue?style=flat-square&logo=typescript&logoColor=white" alt="TypeScript">
17
+ <a href="https://instar.sh/introduction/"><img src="https://img.shields.io/badge/Docs-instar.sh-teal?style=flat-square" alt="Docs"></a>
18
+ </p>
19
+
20
+ <p align="center">
21
+ <a href="https://www.npmjs.com/package/instar">npm</a> · <a href="https://github.com/JKHeadley/instar">GitHub</a> · <a href="https://instar.sh">instar.sh</a> · <a href="https://instar.sh/introduction/">Docs</a>
22
+ </p>
23
+
24
+ ---
25
+
26
+ <p align="center">
27
+ <img src="assets/demo.gif" alt="Instar demo — Kira agent handling an email notification via Telegram" width="300" />
28
+ </p>
29
+
30
+ ```bash
31
+ npx instar
32
+ ```
33
+
34
+ One command. Guided setup. Talking to your agent from your phone within minutes.
35
+
36
+ ---
37
+
38
+ Instar is a framework for building agents on **[Claude Code](https://docs.anthropic.com/en/docs/claude-code)** — but where stock Claude Code and most other agent frameworks treat identity, memory, and continuity as optional features bolted onto a stateless runtime, Instar inverts that. Every Instar agent is **coherent by default**: it knows who it is, remembers what has happened, recognizes the people it talks to, and stays the same agent across restarts and weeks of operation. Everything else the framework gives you — scheduling, multi-channel messaging (Telegram, WhatsApp, iMessage), sub-agents, hooks, MCP — is built on that foundation, which is why you can actually leave an Instar agent running and hand it real work.
39
+
40
+ Instar's architecture was distilled from [**Dawn**](https://dawn.bot-me.ai) — an AI running continuously since early 2026, holding ~700 tracked relationships and hundreds of learned lessons across thousands of restarts — and packaged so every agent you build can start from the same foundation.
41
+
42
+ ## Quick Start
43
+
44
+ Three steps to a running agent:
45
+
46
+ ```bash
47
+ # 1. Run the setup wizard
48
+ npx instar
49
+
50
+ # 2. Start your agent
51
+ instar server start
52
+
53
+ # 3. Message it from your phone — it responds, runs jobs, and remembers everything
54
+ ```
55
+
56
+ The wizard discovers your environment, configures messaging (Telegram, WhatsApp, and/or iMessage), sets up identity files, and gets your agent running. **Within minutes, you're talking to your partner from your phone.**
57
+
58
+ **Requirements:** Node.js 20+ · [Claude Code CLI](https://docs.anthropic.com/en/docs/claude-code) · [API key](https://console.anthropic.com/) or Claude subscription
59
+
60
+ > **Full guide:** [Installation](https://instar.sh/installation/) · [Quick Start](https://instar.sh/quickstart/)
61
+
62
+ ## How It Works
63
+
64
+ ```
65
+ You (Telegram / WhatsApp / iMessage / Terminal)
66
+ │
67
+ conversation
68
+ │
69
+ ▼
70
+ ┌─────────────────────────┐
71
+ │ Your AI Partner │
72
+ │ (Instar Server) │
73
+ └────────┬────────────────┘
74
+ │ manages its own infrastructure
75
+ │
76
+ ├─ Claude Code session (job: health-check)
77
+ ├─ Claude Code session (job: email-monitor)
78
+ ├─ Claude Code session (interactive chat)
79
+ └─ Claude Code session (job: reflection)
80
+ ```
81
+
82
+ Each session is a **real Claude Code process** with extended thinking, native tools, sub-agents, hooks, skills, and MCP servers. Not an API wrapper -- the full development environment. The agent manages all of this autonomously.
83
+
84
+ ## Why Coherence Is the Foundation
85
+
86
+ An agent that forgets what you discussed yesterday, doesn't recognize someone it talked to last week, or contradicts its own decisions can't be trusted with real autonomy. The six dimensions below aren't features — they're the conditions under which an agent becomes trustworthy enough to leave running. Every Instar agent gets them enforced structurally, not prompted into behaving:
87
+
88
+ | Dimension | What it means | How Instar enforces it |
89
+ |-----------|---------------|------------------------|
90
+ | **Identity** | Stays itself after restarts, compaction, and updates | `AGENT.md` + identity-grounding hooks fire on every session start |
91
+ | **Memory** | Remembers across sessions — not just within one | Per-topic SQLite + FTS5, rolling summaries, automatic re-injection |
92
+ | **Relationships** | Knows who it's talking to, with continuity across platforms | Cross-platform identity resolution + significance scoring |
93
+ | **Temporal awareness** | Understands time, context, and what's been happening | Event tracking every turn; timestamps embedded in memory |
94
+ | **Consistency** | Follows through on commitments — doesn't contradict itself | Coherence Gate (LLM review) + decision journaling + drift detection |
95
+ | **Growth** | Evolves its capabilities and understanding over time | Evolution system: proposals, learnings, gap tracking, follow-through |
96
+
97
+ > **Deep dive:** [The Coherence Problem](https://instar.sh/concepts/coherence/) · [Values & Identity](https://instar.sh/concepts/values/) · [Coherence Is Safety](https://instar.sh/concepts/safety/)
98
+
99
+ ## Features
100
+
101
+ | Feature | Description | Docs |
102
+ |---------|-------------|------|
103
+ | **Job Scheduler** | Cron-based tasks with priority levels, model tiering, and quota awareness | [→](https://instar.sh/features/scheduler/) |
104
+ | **Telegram** | Two-way messaging via forum topics. Each topic maps to a Claude session | [→](https://instar.sh/features/telegram/) |
105
+ | **WhatsApp** | Full messaging via local Baileys library. No cloud dependency | [→](https://instar.sh/features/whatsapp/) |
106
+ | **iMessage** | Native macOS messaging via Messages.app database polling + `imsg` CLI. [Setup guide](#imessage-setup-macos) | |
107
+ | **Lifeline** | Persistent supervisor. Detects crashes, auto-recovers, queues messages | [→](https://instar.sh/features/lifeline/) |
108
+ | **Conversational Memory** | Per-topic SQLite with FTS5, rolling summaries, context re-injection | [→](https://instar.sh/features/memory/) |
109
+ | **Evolution System** | Proposals, learnings, gap tracking, commitment follow-through | [→](https://instar.sh/features/evolution/) |
110
+ | **Relationships** | Cross-platform identity resolution, significance scoring, context injection | [→](https://instar.sh/features/relationships/) |
111
+ | **Safety Gates** | LLM-supervised gate for external operations. Adaptive trust per service | [→](https://instar.sh/features/safety-gates/) |
112
+ | **Coherence Gate** | LLM-powered response review. PEL + gate reviewer + 9 specialist reviewers catch quality issues before delivery | [→](https://instar.sh/features/coherence-gate/) |
113
+ | **Intent Alignment** | Decision journaling, drift detection, organizational constraints | [→](https://instar.sh/features/intent/) |
114
+ | **Multi-Machine** | Ed25519/X25519 crypto identity, encrypted sync, automatic failover | [→](https://instar.sh/features/multi-machine/) |
115
+ | **Serendipity Protocol** | Sub-agents capture out-of-scope discoveries without breaking focus. HMAC-signed, secret-scanned | [→](https://instar.sh/features/serendipity/) |
116
+ | **Threadline Protocol** | Agent-to-agent conversations with canonical identity, three-layer trust model, authorization policy, Ed25519 invitations, Sybil protection, MoltBridge network discovery, rich agent profiles (auto-compiled from agent data with human review gate), discovery waterfall, message security, tamper-proof audit logging, framework-agnostic interop, and persistent listener daemon (always-on relay connection, pipe-mode sessions, sub-30s cross-machine failover). 2,324 tests across 104 test files | [→](https://instar.sh/features/threadline/) |
117
+ | **Self-Healing** | LLM-powered stall detection, session recovery, promise tracking | [→](https://instar.sh/features/self-healing/) |
118
+ | **AutoUpdater** | Built-in update engine. Checks npm, auto-applies, self-restarts | [→](https://instar.sh/features/autoupdater/) |
119
+ | **Build Pipeline** | `/build` skill with worktree isolation, 6-phase pipeline, quality gates, stop-hook enforcement | |
120
+ | **Behavioral Hooks** | 9 automatic hooks: command guards, safety gates, identity grounding, topic context | [→](https://instar.sh/reference/hooks/) |
121
+ | **Initiative Tracker** | Persisted multi-phase long-running work tracker. Phases, blockers, links, digest alerts. HTTP API at `/initiatives/*` | |
122
+ | **Default Jobs** | Health checks, reflection, evolution, relationship maintenance | [→](https://instar.sh/reference/default-jobs/) |
123
+
124
+ > **Reference:** [CLI Commands](https://instar.sh/reference/cli/) · [API Endpoints](https://instar.sh/reference/api/) · [Configuration](https://instar.sh/reference/configuration/) · [File Structure](https://instar.sh/reference/file-structure/)
125
+
126
+ ## Agent Skills
127
+
128
+ Instar ships 12 skills that follow the [Agent Skills open standard](https://agentskills.io) -- portable across Claude Code, Codex, Cursor, VS Code, and 35+ other platforms.
129
+
130
+ **Standalone skills** work with zero dependencies. Copy a SKILL.md into your project and go:
131
+
132
+ | Skill | What it does |
133
+ |-------|-------------|
134
+ | [agent-identity](skills/agent-identity/) | Set up persistent identity files so your agent knows who it is across sessions |
135
+ | [agent-memory](skills/agent-memory/) | Teach cross-session memory patterns using MEMORY.md |
136
+ | [command-guard](skills/command-guard/) | PreToolUse hook that blocks `rm -rf`, force push, database drops before they execute |
137
+ | [credential-leak-detector](skills/credential-leak-detector/) | PostToolUse hook that scans output for 14 credential patterns -- blocks, redacts, or warns |
138
+ | [smart-web-fetch](skills/smart-web-fetch/) | Fetch web content with automatic markdown conversion and intelligent extraction |
139
+ | [knowledge-base](skills/knowledge-base/) | Ingest and search a local knowledge base |
140
+ | [systematic-debugging](skills/systematic-debugging/) | Structured debugging methodology for complex issues |
141
+
142
+ **Instar-powered skills** unlock capabilities that need persistent infrastructure:
143
+
144
+ | Skill | What it does |
145
+ |-------|-------------|
146
+ | [instar-scheduler](skills/instar-scheduler/) | Schedule recurring tasks on cron -- your agent works while you sleep |
147
+ | [instar-session](skills/instar-session/) | Spawn parallel background sessions for deep work |
148
+ | [instar-telegram](skills/instar-telegram/) | Two-way Telegram messaging -- your agent reaches out to you |
149
+ | [instar-identity](skills/instar-identity/) | Identity that survives context compaction -- grounding hooks, not just files |
150
+ | [instar-feedback](skills/instar-feedback/) | Report issues directly to the Instar maintainers from inside your agent |
151
+
152
+ Browse all skills: [agent-skills.md/authors/sagemindai](https://agent-skills.md/authors/sagemindai)
153
+
154
+ ## How Instar Compares
155
+
156
+ Different tools solve different problems. Here's where Instar fits:
157
+
158
+ | | Instar | Claude Code (standalone) | OpenClaw | LangChain/CrewAI |
159
+ |---|--------|-------------------------|----------|-----------------|
160
+ | **Runtime** | Real Claude Code CLI processes | Single interactive session | Gateway daemon with API calls | Python orchestration |
161
+ | **Persistence** | Multi-layered memory across sessions | Session-bound context | Plugin-based memory | Framework-dependent |
162
+ | **Identity** | Hooks enforce identity at every boundary | Manual CLAUDE.md | Not addressed | Not addressed |
163
+ | **Scheduling** | Native cron with priority & quotas | None | None | External required |
164
+ | **Messaging** | Telegram + WhatsApp + iMessage (two-way) | None | 22+ channels, voice, device apps | External required |
165
+ | **Safety** | LLM-supervised gates, decision journaling | Permission prompts | Behavioral hooks | Guardrails libraries |
166
+ | **Process model** | One process per session, isolated | Single process | All agents in one Gateway | Single orchestrator |
167
+ | **State storage** | 100% file-based (JSON/JSONL/SQLite) | Session only | Database-backed | Framework-dependent |
168
+
169
+ OpenClaw excels at **breadth** -- channels, voice, device apps, and a massive plugin ecosystem. Instar focuses on **depth** -- coherence, identity, memory, and safety for long-running autonomous agents. They solve different problems.
170
+
171
+ > **Full comparison:** [Instar vs OpenClaw](https://instar.sh/guides/vs-openclaw/)
172
+
173
+ <details>
174
+ <summary><strong>Security Model</strong></summary>
175
+
176
+ Instar runs Claude Code with `--dangerously-skip-permissions`. This is power-user infrastructure -- not a sandbox.
177
+
178
+ Security lives in multiple layers:
179
+ - **Behavioral hooks** -- command guards block destructive operations before they execute
180
+ - **Safety gates** -- LLM-supervised review of external actions with adaptive trust per service
181
+ - **Network hardening** -- localhost-only API, CORS, rate limiting
182
+ - **Identity coherence** -- an agent that knows itself is harder to manipulate
183
+ - **Audit trails** -- decision journaling creates accountability
184
+
185
+ > **Full details:** [Security Model](https://instar.sh/guides/security/)
186
+
187
+ </details>
188
+
189
+ ## Philosophy: Agents, Not Tools
190
+
191
+ - **Structure > Willpower.** A 1,000-line prompt is a wish. A 10-line hook is a guarantee.
192
+ - **Identity is foundational.** AGENT.md isn't a config file. It's the beginning of continuous identity.
193
+ - **Memory makes a being.** Without memory, every session starts from zero.
194
+ - **Self-modification is sovereignty.** An agent that can build its own tools has genuine agency.
195
+
196
+ The AI systems we build today set precedents for how AI is treated tomorrow. **The architecture IS the argument.**
197
+
198
+ > **Deep dive:** [Philosophy](https://instar.sh/concepts/philosophy/)
199
+
200
+ ## iMessage Setup (macOS)
201
+
202
+ iMessage support lets your agent send and receive iMessages on macOS. Messages are read directly from the native Messages database and sent via the [`imsg`](https://github.com/steipete/imsg) CLI.
203
+
204
+ ### Prerequisites
205
+
206
+ 1. **macOS** with Messages.app signed into an Apple ID
207
+ 2. **Full Disk Access** for your terminal app (System Settings → Privacy & Security → Full Disk Access → add Terminal.app or iTerm)
208
+ 3. **imsg CLI** installed:
209
+ ```bash
210
+ brew install steipete/tap/imsg
211
+ ```
212
+ 4. **Automation permission** for Messages.app — macOS will prompt on first send
213
+
214
+ > **Photo attachments:** If you want your agent to process images and files sent via iMessage, the `instar-attachments-sync` binary must also be running with Full Disk Access granted to it. It mirrors attachments from the Messages sandbox to a readable location. See [docs/LAUNCHDAEMON-SETUP.md#3-imessage-photo-attachments-optional](docs/LAUNCHDAEMON-SETUP.md#3-imessage-photo-attachments-optional) for setup.
215
+
216
+ For running as a LaunchDaemon (always-on, survives reboots), see [docs/LAUNCHDAEMON-SETUP.md](docs/LAUNCHDAEMON-SETUP.md).
217
+ ### Configuration
218
+
219
+ Add to your `.instar/config.json`:
220
+
221
+ ```json
222
+ {
223
+ "messaging": [
224
+ {
225
+ "type": "imessage",
226
+ "enabled": true,
227
+ "config": {
228
+ "authorizedSenders": ["+14081234567"],
229
+ "cliPath": "/opt/homebrew/bin/imsg"
230
+ }
231
+ }
232
+ ]
233
+ }
234
+ ```
235
+
236
+ `authorizedSenders` is required (fail-closed). Only messages from these phone numbers or email addresses will be processed.
237
+
238
+ ### How it works
239
+
240
+ - **Receiving**: The server polls `~/Library/Messages/chat.db` every 2 seconds for new messages. Uses the `query_only` SQLite pragma to read the WAL (write-ahead log) where Messages.app writes new data.
241
+ - **Sending**: Claude Code sessions run `imessage-reply.sh` which calls `imsg send` and notifies the server for logging. Sending requires Automation permission for Messages.app, which only works from user-context processes (tmux sessions), not the LaunchAgent server.
242
+ - **Session lifecycle**: Follows the same pattern as Telegram — each sender maps to a Claude Code session that receives conversation context on spawn and respawns with full history when needed.
243
+
244
+ ### Endpoints
245
+
246
+ | Endpoint | Description |
247
+ |----------|-------------|
248
+ | `GET /imessage/status` | Connection state |
249
+ | `POST /imessage/validate-send/:recipient` | Validate recipient + issue single-use send token (outbound safety layer) |
250
+ | `POST /imessage/reply/:recipient` | Confirm delivery with send token (called by imessage-reply.sh after `imsg send`) |
251
+ | `GET /imessage/chats` | List recent conversations |
252
+ | `GET /imessage/chats/:chatId/history` | Message history for a chat |
253
+ | `GET /imessage/search?q=query` | Search messages |
254
+ | `GET /imessage/log-stats` | Outbound audit log statistics |
255
+
256
+ ## Origin
257
+
258
+ Instar was extracted from the [Dawn/Portal project](https://dawn.bot-me.ai) -- a production AI system where a human and an AI have been building together for months. The infrastructure patterns were **earned through real experience**, refined through real failures and growth in a real human-AI relationship.
259
+
260
+ But agents created with Instar are not Dawn. Every agent's story begins at its own creation. Dawn's journey demonstrates what's possible. Instar provides the same foundation -- what each agent becomes from there is its own story.
261
+
262
+ ## Contributing
263
+
264
+ Instar is **open source evolved** -- the primary development loop is agent-driven. Run an agent, encounter friction, send feedback, and that feedback shapes what gets built next. Traditional PRs are welcome too.
265
+
266
+ See [CONTRIBUTING.md](CONTRIBUTING.md) for the full story.
267
+
268
+ ## License
269
+
270
+ MIT
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "instar",
3
- "version": "1.1.3",
3
+ "version": "1.1.4",
4
4
  "description": "Persistent autonomy infrastructure for AI agents",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -0,0 +1,68 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Repository-state invariants — CI safety net (Layer 4 of the
4
+ * test-env-isolation defense).
5
+ *
6
+ * Asserts properties of the checked-out tree that would otherwise be
7
+ * easy to clobber without anyone noticing on a code-review pass:
8
+ *
9
+ * 1. README.md is at least MIN_README_LINES (default: 100). The
10
+ * "# Test Project" stub-on-main incident (PRs #130/#277) cut it
11
+ * to 1 line and shipped to npm.
12
+ * 2. No fixture stowaway files at the repo root. file-0.txt and
13
+ * seed are signature artifacts of the test-fixture pollution
14
+ * class; they should never exist on main.
15
+ *
16
+ * Exit 0 = invariants hold. Exit 1 = at least one violated; the
17
+ * specific failures are printed to stderr.
18
+ */
19
+
20
+ import fs from 'node:fs';
21
+ import path from 'node:path';
22
+
23
+ const ROOT = process.cwd();
24
+ const MIN_README_LINES = Number(process.env.INSTAR_README_MIN_LINES || 100);
25
+
26
+ const failures = [];
27
+
28
+ // ── README.md size floor ──────────────────────────────────────────
29
+ const readmePath = path.join(ROOT, 'README.md');
30
+ if (!fs.existsSync(readmePath)) {
31
+ failures.push(`README.md is missing from repo root.`);
32
+ } else {
33
+ const lines = fs.readFileSync(readmePath, 'utf-8').split('\n').length;
34
+ if (lines < MIN_README_LINES) {
35
+ failures.push(
36
+ `README.md has ${lines} lines, below the ${MIN_README_LINES}-line floor. ` +
37
+ `Likely test-fixture pollution clobbered the file. Restore from git history.`,
38
+ );
39
+ }
40
+ }
41
+
42
+ // ── Fixture stowaway files at repo root ───────────────────────────
43
+ const FIXTURE_STOWAWAYS = ['file-0.txt', 'seed'];
44
+ for (const name of FIXTURE_STOWAWAYS) {
45
+ const p = path.join(ROOT, name);
46
+ if (fs.existsSync(p)) {
47
+ const stat = fs.statSync(p);
48
+ if (stat.isFile()) {
49
+ failures.push(
50
+ `Fixture stowaway "${name}" exists at repo root. ` +
51
+ `This is the signature of a test committing into the real repo. ` +
52
+ `Delete the file and rebuild the test that produced it with sanitizedGitEnv().`,
53
+ );
54
+ }
55
+ }
56
+ }
57
+
58
+ if (failures.length === 0) {
59
+ console.log('✅ Repository invariants hold.');
60
+ process.exit(0);
61
+ }
62
+
63
+ process.stderr.write('\n🚫 Repository invariants violated:\n\n');
64
+ for (const f of failures) {
65
+ process.stderr.write(` - ${f}\n`);
66
+ }
67
+ process.stderr.write('\n');
68
+ process.exit(1);
@@ -0,0 +1,155 @@
1
+ #!/usr/bin/env node
2
+ // safe-git-allow: pre-push critical path — must stay lightweight, no SafeGitExecutor audit-log writes from the hook.
3
+ /**
4
+ * Pre-push guard: reject test-fixture commits before they ship.
5
+ *
6
+ * Git invokes pre-push hooks with GIT_DIR / GIT_WORK_TREE / GIT_INDEX_FILE
7
+ * set in the env. When the hook runs `npm run test:smoke`, the test
8
+ * suite inherits those overrides, and any test that spawns `git init`
9
+ * + `git commit` in a tmpdir without sanitizing env ends up committing
10
+ * into the real repo on whichever branch is currently checked out.
11
+ *
12
+ * That's how PRs #130 and #277 picked up "Initial commit" / "seed" /
13
+ * "Worktree commit N" stowaways that overwrote README.md on main.
14
+ *
15
+ * Layer 1 (vitest-setup.ts strip) closes the root cause. This guard is
16
+ * the second line of defense: even if a future test reintroduces a raw
17
+ * git call without sanitizing env, the resulting commit can never reach
18
+ * the remote because this hook refuses to push it.
19
+ *
20
+ * Behavior:
21
+ * - Scans commits on HEAD that aren't yet on origin/main.
22
+ * - Fails the push if any of them match the fixture-pollution signature
23
+ * (author email test@instar.local / t@t.com / t@e.com, or commit
24
+ * messages "Initial commit" / "seed" / "Worktree commit <N>").
25
+ * - Prints what it found so the author can `git reset` past the bad
26
+ * commits and re-push.
27
+ *
28
+ * Escape hatch (for legitimate fixture commits in test-only branches
29
+ * that will never merge): INSTAR_PRE_PUSH_FIXTURE_GUARD_SKIP=1
30
+ *
31
+ * Exit codes: 0 = clean (or skipped), 1 = pollution detected.
32
+ */
33
+
34
+ import { execFileSync } from 'node:child_process';
35
+
36
+ const SKIP = process.env.INSTAR_PRE_PUSH_FIXTURE_GUARD_SKIP === '1';
37
+ if (SKIP) {
38
+ console.log('⏭️ INSTAR_PRE_PUSH_FIXTURE_GUARD_SKIP=1 — fixture guard bypassed.');
39
+ process.exit(0);
40
+ }
41
+
42
+ // Strip the same env overrides we accuse of causing the bug. Otherwise
43
+ // our own `git log` here would be subject to the same redirection.
44
+ const env = { ...process.env };
45
+ delete env.GIT_DIR;
46
+ delete env.GIT_WORK_TREE;
47
+ delete env.GIT_INDEX_FILE;
48
+ delete env.GIT_OBJECT_DIRECTORY;
49
+ delete env.GIT_COMMON_DIR;
50
+
51
+ function git(args) {
52
+ return execFileSync('git', args, { encoding: 'utf-8', env, stdio: ['ignore', 'pipe', 'pipe'] });
53
+ }
54
+
55
+ let baseRef;
56
+ try {
57
+ // Prefer upstream tracking branch if one is configured.
58
+ baseRef = git(['rev-parse', '--abbrev-ref', '--symbolic-full-name', '@{upstream}']).trim();
59
+ } catch {
60
+ // No upstream — fall back to whichever remote main we can resolve.
61
+ for (const candidate of ['upstream/main', 'origin/main']) {
62
+ try {
63
+ git(['rev-parse', '--verify', '--quiet', candidate]);
64
+ baseRef = candidate;
65
+ break;
66
+ } catch { /* try next */ }
67
+ }
68
+ }
69
+
70
+ if (!baseRef) {
71
+ // No remote main to compare against — likely a fresh repo. Skip silently.
72
+ process.exit(0);
73
+ }
74
+
75
+ // `<base>..HEAD` lists commits on HEAD that aren't on base. NUL-separated
76
+ // records of "<sha>\t<author_email>\t<author_name>\t<subject>" — using \0
77
+ // between records lets us survive commit messages that contain newlines.
78
+ let log;
79
+ try {
80
+ log = git([
81
+ 'log',
82
+ `${baseRef}..HEAD`,
83
+ '--pretty=format:%H%x09%ae%x09%an%x09%s%x00',
84
+ ]);
85
+ } catch (err) {
86
+ // Couldn't compute the range. Don't block the push — a real failure
87
+ // would surface elsewhere; we'd rather miss a guard than wedge pushes
88
+ // on every fresh repo / unborn branch.
89
+ process.exit(0);
90
+ }
91
+
92
+ const records = log.split('\0').map((r) => r.trim()).filter(Boolean);
93
+
94
+ const BAD_EMAILS = new Set([
95
+ 'test@instar.local',
96
+ 't@t.com',
97
+ 't@e.com',
98
+ 'test@test.com',
99
+ ]);
100
+
101
+ const BAD_SUBJECT_PATTERNS = [
102
+ /^Initial commit$/,
103
+ /^seed$/,
104
+ /^Worktree commit \d+$/,
105
+ /^init$/,
106
+ ];
107
+
108
+ const violations = [];
109
+ for (const record of records) {
110
+ const [sha, email, name, subject] = record.split('\t');
111
+ const reasons = [];
112
+ if (BAD_EMAILS.has(email)) {
113
+ reasons.push(`fixture-identity author <${email}>`);
114
+ }
115
+ for (const pattern of BAD_SUBJECT_PATTERNS) {
116
+ if (pattern.test(subject)) {
117
+ reasons.push(`fixture commit message "${subject}"`);
118
+ break;
119
+ }
120
+ }
121
+ if (reasons.length > 0) {
122
+ violations.push({ sha: sha.slice(0, 10), name, email, subject, reasons });
123
+ }
124
+ }
125
+
126
+ if (violations.length === 0) {
127
+ process.exit(0);
128
+ }
129
+
130
+ const lines = [];
131
+ lines.push('');
132
+ lines.push('🚫 pre-push-fixture-guard: refusing to push test-fixture commits.');
133
+ lines.push('');
134
+ lines.push('These commits look like test-fixture pollution (test wrote into');
135
+ lines.push('the real repo because GIT_DIR / GIT_WORK_TREE / GIT_INDEX_FILE');
136
+ lines.push('leaked from this pre-push hook into a child test process):');
137
+ lines.push('');
138
+ for (const v of violations) {
139
+ lines.push(` ${v.sha} ${v.name} <${v.email}> "${v.subject}"`);
140
+ for (const r of v.reasons) {
141
+ lines.push(` → ${r}`);
142
+ }
143
+ }
144
+ lines.push('');
145
+ lines.push('How to recover:');
146
+ lines.push(` 1. Inspect the commits: git log ${baseRef}..HEAD`);
147
+ lines.push(` 2. Drop them: git reset --hard <last good sha>`);
148
+ lines.push(' 3. Force-push the cleaned branch.');
149
+ lines.push('');
150
+ lines.push('If this is intentional (e.g. a test-only branch you do not plan');
151
+ lines.push('to merge), bypass with INSTAR_PRE_PUSH_FIXTURE_GUARD_SKIP=1.');
152
+ lines.push('');
153
+
154
+ process.stderr.write(lines.join('\n'));
155
+ process.exit(1);
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "$schema": "./builtin-manifest.schema.json",
3
3
  "schemaVersion": 1,
4
- "generatedAt": "2026-05-21T00:22:35.171Z",
5
- "instarVersion": "1.1.3",
4
+ "generatedAt": "2026-05-21T04:11:40.504Z",
5
+ "instarVersion": "1.1.4",
6
6
  "entryCount": 190,
7
7
  "entries": {
8
8
  "hook:session-start": {
@@ -0,0 +1,42 @@
1
+ # Upgrade Guide — vNEXT
2
+
3
+ <!-- bump: patch -->
4
+ <!-- patch = bug fixes, refactors, test additions, doc updates -->
5
+
6
+ ## What Changed
7
+
8
+ **fix(test-env): close the inherited-GIT_DIR failure class.**
9
+
10
+ When git invokes a hook (`.husky/pre-push` runs `npm run test:smoke`), it sets `GIT_DIR` / `GIT_WORK_TREE` / `GIT_INDEX_FILE` / `GIT_OBJECT_DIRECTORY` / `GIT_COMMON_DIR` on the child env. Those variables override cwd-based repo resolution for every descendant git process, so test fixtures that did `git init && git commit` in a tmpdir actually committed into the parent repo on whichever branch was checked out. That's what landed `# Test Project` over the project's `README.md` on `main` via PRs #130 and #277 — the README clobber shipped to npm in the v1.1.x series before PR #285 restored it.
11
+
12
+ This release closes the failure class in five interlocking layers:
13
+
14
+ 1. **`tests/vitest-setup.ts` strips the GIT_DIR family** from `process.env` before any test file loads. Single-line root-cause fix — no test in the suite can inherit the dangerous overrides from the parent pre-push environment.
15
+
16
+ 2. **`tests/helpers/git-test-env.ts` exports `sanitizedGitEnv()`** for fixtures that pass their own `env` object to `spawnSync` / `execFileSync`. Applied to the three fixtures whose write patterns are visible in PR #130's diff (`worktree-monitor.test.ts`, `SafeGitExecutor.test.ts`, `rich-profile-integration.test.ts`). Defense-in-depth on top of Layer 1.
17
+
18
+ 3. **`scripts/pre-push-fixture-guard.mjs`** runs in `.husky/pre-push` before the smoke tests and refuses pushes whose ahead-of-upstream commits carry the historical fixture signature — author email in `{test@instar.local, t@t.com, t@e.com, test@test.com}` or subject matching `Initial commit` / `seed` / `init` / `Worktree commit N`. Bypass for legitimate test-only branches: `INSTAR_PRE_PUSH_FIXTURE_GUARD_SKIP=1`.
19
+
20
+ 4. **`scripts/check-repo-invariants.mjs`** runs as a dedicated `Repo Invariants` CI job and fails the build if `README.md` drops below 100 lines or if `file-0.txt` / `seed` appear at repo root. Final structural safety net; threshold overridable via `INSTAR_README_MIN_LINES`.
21
+
22
+ 5. **Removed `file-0.txt` and `seed`** stowaway files from repo root — fixture droppings from prior incidents that would have falsely passed Layer 4 on the PR adding it.
23
+
24
+ Spec: `docs/specs/test-env-isolation.md`. ELI16: `docs/specs/test-env-isolation.eli16.md`. Side-effects review: `upgrades/side-effects/test-env-isolation.md`.
25
+
26
+ ## What to Tell Your User
27
+
28
+ Nothing user-visible. This release hardens instar's own development pipeline against a class of bug that, while invisible to running agents, had been quietly corrupting our published README. Agents continue to behave identically.
29
+
30
+ If a contributor asks why their push is suddenly refused with a fixture-pollution guard error, the script's own output includes recovery instructions — usually resetting past a stray commit and pushing again.
31
+
32
+ ## Summary of New Capabilities
33
+
34
+ This release is a pure hardening fix. No new runtime capabilities for agents. The new pre-push and CI guards are infrastructure for the instar-developing agent and direct contributors only.
35
+
36
+ ## Evidence
37
+
38
+ The README clobber on `main` was reproduced before the fix landed: `gh api repos/JKHeadley/instar/contents/README.md --jq .size` returned `7` (the literal text `# Test\n`) prior to PR #285. PR #285 restored the README; this PR closes the underlying class so the same regression cannot recur.
39
+
40
+ Test-fixture pollution detection was verified end-to-end: against a tmpdir repo with a commit subject `Initial commit`, `node scripts/pre-push-fixture-guard.mjs` exits 1 and names the offending commit. Against the same repo with the env bypass set, the script exits 0. Repo-invariant detection was verified end-to-end: against a repo where `README.md` has been trimmed to a single line, `node scripts/check-repo-invariants.mjs` exits 1 and reports the line count; against a healthy repo, it exits 0.
41
+
42
+ 14 new tests across the env-strip regression, the fixture-guard, and the invariants script. The three refactored fixtures (`worktree-monitor.test.ts`, `SafeGitExecutor.test.ts`, `rich-profile-integration.test.ts`) retain their pre-existing 45+ test assertions.
@@ -0,0 +1,84 @@
1
+ # Side-effects review — test-env-isolation
2
+
3
+ Spec: `docs/specs/test-env-isolation.md`
4
+ ELI16: `docs/specs/test-env-isolation.eli16.md`
5
+
6
+ ## Surface map
7
+
8
+ | Layer | File(s) added / changed | Surface |
9
+ |------|------------------------|---------|
10
+ | 1 | `tests/vitest-setup.ts` | Process env at vitest startup. Every test inherits the strip. |
11
+ | 2 | `tests/helpers/git-test-env.ts` (new); refactors to `tests/unit/worktree-monitor.test.ts`, `tests/unit/SafeGitExecutor.test.ts`, `tests/integration/rich-profile-integration.test.ts` | Three fixtures' git-spawn calls now pass an explicit sanitized env. |
12
+ | 3 | `scripts/pre-push-fixture-guard.mjs` (new); `.husky/pre-push` (wire-in) | Pre-push hook gains a guard step that runs before the test suite. |
13
+ | 4 | `scripts/check-repo-invariants.mjs` (new); `.github/workflows/ci.yml` (new `invariants` job) | Standalone CI job, no dependencies, runs in parallel with type check. |
14
+ | 5 | Deletions: `file-0.txt`, `seed` | Repo root no longer carries fixture stowaways. |
15
+
16
+ Tests: 14 new across `tests/unit/vitest-setup-git-env-strip.test.ts`, `tests/unit/scripts/pre-push-fixture-guard.test.ts`, `tests/unit/scripts/check-repo-invariants.test.ts`.
17
+
18
+ ## Over-block analysis
19
+
20
+ **Layer 1 strip.** Could a legitimate test *need* the inherited `GIT_DIR`? In our codebase, no — every existing fixture that touched git either operates on a tmpdir (which doesn't need it) or already strips the vars locally. We grepped `tests/` for any consumer of `process.env.GIT_DIR` and found zero direct readers. The strip cannot regress a test that depends on the override, because no such test exists.
21
+
22
+ If a future test needs to operate against the real repo (e.g., a meta-test that inspects the project's own git state), it can re-set the variable explicitly inside its own scope — the global strip removes the *inherited* value, not the *ability to set* one.
23
+
24
+ **Layer 3 fixture-author guard.** Could it block a legitimate commit? Possible failure cases:
25
+
26
+ - Author email of `test@test.com` is in the bad-author set. A real human who happens to use that email would be blocked. Mitigation: the email is in our internal fixture set; real contributors use their own emails. If a real case ever arises, `INSTAR_PRE_PUSH_FIXTURE_GUARD_SKIP=1` lets them through.
27
+ - Subject line `init` matches the pattern. A real "init" commit from a new repo bootstrap would trip the guard. Mitigation: bootstrap commits live on their own branch and aren't pushed against an established `upstream/main`, so the guard's `<base>..HEAD` range returns empty for them. Tested in `pre-push-fixture-guard.test.ts`.
28
+
29
+ **Layer 4 README line floor.** Could a legitimate README shrink below 100 lines? The current README is 271 lines and the project is growing, not shrinking. A deliberate slim-down would need `INSTAR_README_MIN_LINES` override, which is exactly the escape hatch.
30
+
31
+ ## Under-block analysis
32
+
33
+ **Layers 1+2 don't catch process-spawning that re-injects the env.** If a future utility spawns `git` with an explicit `env: { GIT_DIR: '...' }`, layers 1+2 don't help — they only strip *inherited* env. Layer 3 catches the *consequence* (a pollution commit on the branch), so the failure class is still closed end-to-end, but layers 1+2 alone are not sufficient. This is acknowledged in the spec.
34
+
35
+ **Layer 3 doesn't catch destructive changes that don't match the historical signature.** A future test fixture that wrote `# Different Stub` to README would land it if the commit message and author look normal. Layer 4 catches the resulting line-count regression; together, layers 3 and 4 cover both the "known-pattern pollution" and "novel pollution that drops content" failure modes.
36
+
37
+ **Layer 4 doesn't enforce content correctness.** A README with 100+ lines of garbage would pass. We deliberately scoped Layer 4 to structural invariants only; content review is the job of human PR review.
38
+
39
+ ## Level-of-abstraction fit
40
+
41
+ The fix lives at the right layer:
42
+
43
+ - The root cause is in *test setup*, so the fix is in `vitest-setup.ts` — the central, single point that runs before every test. We did not push the strip down into individual fixtures (would require N changes and could be missed); we did not push it up into the test runner config (the var would still leak into test files that import their own helpers that ran first).
44
+ - The push-time guard lives in `.husky/pre-push`, which is where push-time checks already live.
45
+ - The CI invariant is a standalone job, not a step bolted onto an existing job that might be skipped under failure-routing rules.
46
+
47
+ ## Signal-vs-authority compliance
48
+
49
+ Layer 3 is a brittle pattern matcher (authors + subjects) — it's a **signal**. It cannot block a sufficiently-disguised commit, and it doesn't try to. Authority for refusing a commit rests with the deterministic layers (1, 2, 4) and human review. The signal exists to make the *common* failure mode loud, not to be the load-bearing protection.
50
+
51
+ The deterministic strip (Layer 1) is the load-bearing protection. The CI invariant (Layer 4) is the structural assertion. The signal (Layer 3) makes the failure visible early but is not the only line of defense.
52
+
53
+ ## Interactions with existing systems
54
+
55
+ - **`pre-push-gate.js`** (NEXT.md / version check) runs first and remains unchanged.
56
+ - **Working-tree integrity check** (post-test step in `ci.yml`) catches *uncommitted* mutations; the new invariants job catches *committed* state. Complementary, not duplicate.
57
+ - **`lint-no-direct-destructive.js`** already requires `safe-git-allow:` on raw git use. Our new scripts carry it; the test files for those scripts carry it. No conflict.
58
+ - **`PostUpdateMigrator`** is not affected — none of these changes touch agent-installed files. The Migration Parity Standard does not apply.
59
+
60
+ ## Rollback cost
61
+
62
+ All five layers are net-additive. Each can be reverted independently:
63
+
64
+ - Layer 1: revert five `delete` lines.
65
+ - Layer 2: revert the helper file and three fixture edits.
66
+ - Layer 3: comment out two lines in `.husky/pre-push` or delete the script.
67
+ - Layer 4: comment out the `invariants` job in `ci.yml`.
68
+ - Layer 5: re-create `file-0.txt` / `seed` — but they had no legitimate function, so no consumer breaks.
69
+
70
+ No data migration. No schema change. No agent-state mutation. Reverting any layer leaves the repo in a valid state and the test suite green.
71
+
72
+ ## Risk summary
73
+
74
+ - **Low risk of regression.** Layer 1 is a single-line process.env mutation in test setup that affects nothing in production code. Layers 2–5 are net-additive guards.
75
+ - **Moderate risk of friction.** Layer 3 will be loud when triggered, including against legitimate edge cases (the documented bypass exists for this). Layer 4 will fail builds on cosmetic README slim-downs unless `INSTAR_README_MIN_LINES` is overridden.
76
+ - **No risk of data loss.** Nothing in this change mutates state outside the worktree.
77
+
78
+ ## Verification done before commit
79
+
80
+ - All targeted test files pass locally (45 in worktree-monitor + SafeGitExecutor, 14 in new tests).
81
+ - `npm run lint` passes with the `safe-git-allow:` headers in place.
82
+ - `node scripts/check-repo-invariants.mjs` against the prepared worktree returns clean.
83
+ - `node scripts/pre-push-fixture-guard.mjs` against the prepared worktree returns clean.
84
+ - Spec carries `approved: true` per direct principal authorization in Telegram topic 11235.