instar 1.0.13 → 1.1.0

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 (57) hide show
  1. package/README.md +1 -1
  2. package/dist/cli.js +52 -2
  3. package/dist/cli.js.map +1 -1
  4. package/dist/commands/init.d.ts +17 -0
  5. package/dist/commands/init.d.ts.map +1 -1
  6. package/dist/commands/init.js +156 -64
  7. package/dist/commands/init.js.map +1 -1
  8. package/dist/commands/server.d.ts.map +1 -1
  9. package/dist/commands/server.js +68 -4
  10. package/dist/commands/server.js.map +1 -1
  11. package/dist/commands/setup.d.ts +3 -1
  12. package/dist/commands/setup.d.ts.map +1 -1
  13. package/dist/commands/setup.js +67 -28
  14. package/dist/commands/setup.js.map +1 -1
  15. package/dist/commands/worktree.d.ts +17 -4
  16. package/dist/commands/worktree.d.ts.map +1 -1
  17. package/dist/commands/worktree.js +28 -4
  18. package/dist/commands/worktree.js.map +1 -1
  19. package/dist/core/AgentWorktreeDetector.d.ts +63 -0
  20. package/dist/core/AgentWorktreeDetector.d.ts.map +1 -0
  21. package/dist/core/AgentWorktreeDetector.js +329 -0
  22. package/dist/core/AgentWorktreeDetector.js.map +1 -0
  23. package/dist/core/InstarWorktreeManager.d.ts +99 -0
  24. package/dist/core/InstarWorktreeManager.d.ts.map +1 -0
  25. package/dist/core/InstarWorktreeManager.js +447 -0
  26. package/dist/core/InstarWorktreeManager.js.map +1 -0
  27. package/dist/core/MachineIdentity.d.ts.map +1 -1
  28. package/dist/core/MachineIdentity.js +2 -0
  29. package/dist/core/MachineIdentity.js.map +1 -1
  30. package/dist/core/PostUpdateMigrator.d.ts +25 -0
  31. package/dist/core/PostUpdateMigrator.d.ts.map +1 -1
  32. package/dist/core/PostUpdateMigrator.js +258 -0
  33. package/dist/core/PostUpdateMigrator.js.map +1 -1
  34. package/dist/monitoring/PromptGate.d.ts +12 -0
  35. package/dist/monitoring/PromptGate.d.ts.map +1 -1
  36. package/dist/monitoring/PromptGate.js +44 -0
  37. package/dist/monitoring/PromptGate.js.map +1 -1
  38. package/dist/scaffold/templates.d.ts.map +1 -1
  39. package/dist/scaffold/templates.js +17 -0
  40. package/dist/scaffold/templates.js.map +1 -1
  41. package/package.json +1 -1
  42. package/scripts/lint-no-direct-destructive.js +7 -0
  43. package/scripts/resolve-release-tier.mjs +192 -0
  44. package/src/data/builtin-manifest.json +56 -48
  45. package/src/scaffold/templates.ts +17 -0
  46. package/src/templates/scripts/instar-watchdog.sh +169 -6
  47. package/src/templates/scripts/instar-worktree-create.sh +79 -0
  48. package/upgrades/1.1.0.md +320 -0
  49. package/upgrades/side-effects/agent-worktree-convention-layer-1-2-5.md +280 -0
  50. package/upgrades/side-effects/agent-worktree-convention-layer-3-4.md +285 -0
  51. package/upgrades/side-effects/feat-fix-parent-framework-intercept.md +59 -0
  52. package/upgrades/side-effects/feat-install-claude-gating.md +67 -0
  53. package/upgrades/side-effects/feat-install-framework-flag.md +65 -0
  54. package/upgrades/side-effects/feat-portability-shadow-capabilities.md +63 -0
  55. package/upgrades/side-effects/feat-setup-framework-flag.md +65 -0
  56. package/upgrades/side-effects/lockdown-layer-2.md +123 -0
  57. package/upgrades/side-effects/watchdog-bind-failure-probe.md +174 -0
@@ -24,6 +24,7 @@ import os from 'node:os';
24
24
  import { execFileSync } from 'node:child_process';
25
25
  import crypto from 'node:crypto';
26
26
  import { SafeGitExecutor } from './SafeGitExecutor.js';
27
+ import { resolveAgentHome as resolveAgentHomeForWorktree } from './InstarWorktreeManager.js';
27
28
  import { fileURLToPath } from 'node:url';
28
29
  import { TreeGenerator } from '../knowledge/TreeGenerator.js';
29
30
  import { HTTP_HOOK_TEMPLATES, buildHttpHookSettings } from '../data/http-hook-templates.js';
@@ -126,6 +127,7 @@ export class PostUpdateMigrator {
126
127
  };
127
128
  this.migrateHooks(result);
128
129
  this.migrateClaudeMd(result);
130
+ this.migrateFrameworkShadowCapabilities(result);
129
131
  this.migrateScripts(result);
130
132
  this.migrateSettings(result);
131
133
  this.migrateConfig(result);
@@ -144,6 +146,7 @@ export class PostUpdateMigrator {
144
146
  this.migrateFleetWatchdog(result);
145
147
  this.migrateParitySentinelTrust(result);
146
148
  this.migrateConversationalCatalogPlaybookManifest(result);
149
+ this.migrateWorktreeConvention(result);
147
150
  return result;
148
151
  }
149
152
  // ── Conversational-action catalog Playbook manifest (v0.2) ─────────
@@ -206,6 +209,138 @@ export class PostUpdateMigrator {
206
209
  fs.writeFileSync(targetPath, templateContent);
207
210
  result.upgraded.push('conversational-catalog-playbook: installed/updated manifest template');
208
211
  }
212
+ // ── Agent worktree convention (Layer 3) ────────────────────────────────
213
+ //
214
+ // Spec: docs/specs/AGENT-WORKTREE-CONVENTION-SPEC.md §"Layer 3 —
215
+ // PostUpdateMigrator step (single-agent scope)".
216
+ //
217
+ // For every agent whose binary just updated, ensure the on-disk surface
218
+ // is wired up so the convention works:
219
+ // 1. Install/refresh `<agent_home>/.bin/instar-worktree-create.sh`
220
+ // (always-overwrite, per Migration Parity Standard).
221
+ // 2. `<agent_home>/.gitignore` already covered by `migrateGitignore`
222
+ // consumers; we add a one-line direct ensure here as
223
+ // defense-in-depth for hosts whose project gitignore isn't reached.
224
+ // 3. Ensure `<agent_home>/.worktrees/` exists with `0700`.
225
+ // 4. If a pre-existing `worktree.repoUrlAllowlist` config blocks the
226
+ // resolved instar repo (or no repo is reachable), emit one
227
+ // AttentionItem so the operator can fix before the wrapper runs.
228
+ //
229
+ // Refuses any filesystem mutation when:
230
+ // - The agent home doesn't pass `<instarHome>/agents/<name>/` shape +
231
+ // registry-membership validation (project-bound agents living
232
+ // somewhere else simply opt out — the wrapper isn't applicable).
233
+ // - `<agent_home>/.bin` exists as a symlink (defeats the
234
+ // /usr/local/bin clobber attack surface).
235
+ migrateWorktreeConvention(result) {
236
+ const agentHome = path.dirname(this.config.stateDir);
237
+ // Validate the agent home against Layer 1's contract. We import the
238
+ // resolver from InstarWorktreeManager so the rule lives in exactly one
239
+ // place. If validation fails, the convention doesn't apply to this
240
+ // agent and we skip silently (no error — project-bound agents with
241
+ // bespoke layouts pass through unchanged).
242
+ let resolved;
243
+ try {
244
+ resolved = resolveAgentHomeForWorktree({ env: { INSTAR_AGENT_HOME: agentHome } });
245
+ }
246
+ catch (err) {
247
+ result.skipped.push(`worktree-convention: agent home does not match the convention (${err instanceof Error ? err.message.split('\n')[0] : String(err)})`);
248
+ return;
249
+ }
250
+ // `.bin/` must be a real directory inside the agent home.
251
+ const binDir = path.join(resolved.agentHome, '.bin');
252
+ if (fs.existsSync(binDir)) {
253
+ try {
254
+ const lst = fs.lstatSync(binDir);
255
+ if (lst.isSymbolicLink()) {
256
+ result.errors.push(`worktree-convention: ${binDir} is a symlink — refused (defeats /usr/local/bin clobber surface)`);
257
+ return;
258
+ }
259
+ }
260
+ catch (err) {
261
+ result.errors.push(`worktree-convention: stat ${binDir} failed: ${err instanceof Error ? err.message : String(err)}`);
262
+ return;
263
+ }
264
+ }
265
+ else {
266
+ fs.mkdirSync(binDir, { recursive: true, mode: 0o755 });
267
+ }
268
+ // Always-overwrite the wrapper (Migration Parity Standard for hook
269
+ // scripts: built-in templates are authoritative).
270
+ const wrapperTargetPath = path.join(binDir, 'instar-worktree-create.sh');
271
+ const templateCandidates = [
272
+ path.resolve(__dirname, '..', 'templates', 'scripts', 'instar-worktree-create.sh'),
273
+ path.resolve(__dirname, '..', '..', 'src', 'templates', 'scripts', 'instar-worktree-create.sh'),
274
+ ];
275
+ let templatePath = '';
276
+ for (const cand of templateCandidates) {
277
+ if (fs.existsSync(cand)) {
278
+ templatePath = cand;
279
+ break;
280
+ }
281
+ }
282
+ if (!templatePath) {
283
+ result.skipped.push('worktree-convention: wrapper template not found in package install');
284
+ return;
285
+ }
286
+ try {
287
+ const templateContent = fs.readFileSync(templatePath, 'utf-8');
288
+ const existing = fs.existsSync(wrapperTargetPath)
289
+ ? fs.readFileSync(wrapperTargetPath, 'utf-8')
290
+ : null;
291
+ if (existing !== templateContent) {
292
+ fs.writeFileSync(wrapperTargetPath, templateContent, { mode: 0o755 });
293
+ // Re-assert mode (umask masking can drop the +x bit on existing files).
294
+ fs.chmodSync(wrapperTargetPath, 0o755);
295
+ result.upgraded.push('worktree-convention: installed/refreshed instar-worktree-create.sh');
296
+ }
297
+ else {
298
+ // Re-assert mode even when content matches — defends against an
299
+ // operator's `chmod -R` that may have dropped the +x bit.
300
+ try {
301
+ fs.chmodSync(wrapperTargetPath, 0o755);
302
+ }
303
+ catch { /* @silent-fallback-ok — best-effort */ }
304
+ result.skipped.push('worktree-convention: instar-worktree-create.sh up to date');
305
+ }
306
+ }
307
+ catch (err) {
308
+ result.errors.push(`worktree-convention: wrapper install failed: ${err instanceof Error ? err.message : String(err)}`);
309
+ return;
310
+ }
311
+ // Ensure `<agent_home>/.gitignore` contains `.worktrees/`. Defense-in-
312
+ // depth — the v1.1.0 `GITIGNORE_ENTRIES` change covers fresh inits and
313
+ // any agent whose .gitignore flows through `ensureGitignore` on
314
+ // update, but agents with hand-crafted gitignores may not reach it.
315
+ try {
316
+ const gitignorePath = path.join(resolved.agentHome, '.gitignore');
317
+ const existing = fs.existsSync(gitignorePath) ? fs.readFileSync(gitignorePath, 'utf-8') : '';
318
+ const hasEntry = /^\s*\.worktrees\/?\s*$/m.test(existing);
319
+ if (!hasEntry) {
320
+ const sep = existing.length > 0 && !existing.endsWith('\n') ? '\n' : '';
321
+ const block = `${sep}\n# Sandbox-safe worktrees (per-machine; multi-GB foreign-repo contents)\n.worktrees/\n`;
322
+ fs.writeFileSync(gitignorePath, existing + block);
323
+ result.upgraded.push('worktree-convention: added .worktrees/ to agent-home .gitignore');
324
+ }
325
+ else {
326
+ result.skipped.push('worktree-convention: .gitignore already excludes .worktrees/');
327
+ }
328
+ }
329
+ catch (err) {
330
+ result.errors.push(`worktree-convention: gitignore patch failed: ${err instanceof Error ? err.message : String(err)}`);
331
+ }
332
+ // Ensure `<agent_home>/.worktrees/` exists with 0700.
333
+ try {
334
+ const worktreesDir = path.join(resolved.agentHome, '.worktrees');
335
+ if (!fs.existsSync(worktreesDir)) {
336
+ fs.mkdirSync(worktreesDir, { recursive: true, mode: 0o700 });
337
+ }
338
+ fs.chmodSync(worktreesDir, 0o700);
339
+ }
340
+ catch (err) {
341
+ result.errors.push(`worktree-convention: .worktrees/ ensure failed: ${err instanceof Error ? err.message : String(err)}`);
342
+ }
343
+ }
209
344
  /**
210
345
  * Async migration superset — runs the sync migrate() then performs the
211
346
  * async parity-renderings backfill that needs rule.remediate() Promises.
@@ -2012,6 +2147,26 @@ The user has been talking to you (possibly for days). A generic greeting like "H
2012
2147
  else {
2013
2148
  result.skipped.push('CLAUDE.md: File Viewer section already present');
2014
2149
  }
2150
+ // Worktree Convention section (Migration Parity Standard backfill for
2151
+ // Layer 2 of the agent worktree convention — fresh inits get this via
2152
+ // generateClaudeMd; existing agents get it here on update).
2153
+ if (!content.includes('Worktree Convention') && !content.includes('instar worktree create <branch>')) {
2154
+ const section = `
2155
+ ## Worktree Convention
2156
+
2157
+ Create worktrees for collaborator repos with \`instar worktree create <branch>\` — it resolves your agent's home automatically. Never hardcode another agent's name or place worktrees inside the shared checkout.
2158
+
2159
+ **Why:** the macOS sandbox can revoke filesystem access to anything outside the agent home mid-session, with no in-session recovery path. The agent home (\`~/.instar/agents/<agent>/\`) is the one location the sandbox cannot revoke. \`instar worktree create\` places the worktree at \`~/.instar/agents/<agent>/.worktrees/<slug>/\` and refuses any other destination. Spec: \`docs/specs/AGENT-WORKTREE-CONVENTION-SPEC.md\`.
2160
+
2161
+ **Caveat — git identity env vars:** the CLI sets per-worktree \`user.name\` / \`user.email\` to \`Instar Agent (<name>)\` / \`<name>@instar.local\`. \`GIT_AUTHOR_NAME\` / \`GIT_COMMITTER_EMAIL\` in the calling environment override that local config. Agents that care about commit attribution must avoid exporting those vars.
2162
+ `;
2163
+ content += '\n' + section;
2164
+ patched = true;
2165
+ result.upgraded.push('CLAUDE.md: added Worktree Convention section');
2166
+ }
2167
+ else {
2168
+ result.skipped.push('CLAUDE.md: Worktree Convention section already present');
2169
+ }
2015
2170
  if (patched) {
2016
2171
  try {
2017
2172
  fs.writeFileSync(claudeMdPath, content);
@@ -2021,6 +2176,109 @@ The user has been talking to you (possibly for days). A generic greeting like "H
2021
2176
  }
2022
2177
  }
2023
2178
  }
2179
+ /**
2180
+ * Mirror capability-instruction sections from the freshly-patched CLAUDE.md
2181
+ * into any non-Claude framework shadows (AGENTS.md, GEMINI.md) that exist
2182
+ * on disk. Portability audit Gap 6 minimal-shim implementation:
2183
+ * `generateClaudeMd` + `migrateClaudeMd` produce a rich capability/
2184
+ * instructions document for Claude Code, but Codex/Gemini shadows had no
2185
+ * equivalent — agents on those runtimes received the canonical identity
2186
+ * (Gap 1, shipped) but none of the "here's what you can do" sections.
2187
+ *
2188
+ * The shim slices each known capability section out of CLAUDE.md (from its
2189
+ * marker through the start of the next top-level heading) and appends any
2190
+ * section missing from the shadow. The section bodies are NOT duplicated
2191
+ * — they are literally copied from the just-patched CLAUDE.md, so the two
2192
+ * cannot drift. Runs AFTER `migrateClaudeMd` so the source is current.
2193
+ *
2194
+ * Idempotent: each section is only appended when its marker is absent from
2195
+ * the shadow. No-op when CLAUDE.md is absent or no shadow exists. Safe for
2196
+ * Claude-only installs (they have no AGENTS.md/GEMINI.md so nothing
2197
+ * happens). A full refactor that extracts the section *bodies* into a
2198
+ * shared array is deliberately NOT done here per the operator's
2199
+ * "minimal shim" decision — that would touch ~360 lines of inline content
2200
+ * in `migrateClaudeMd` and is high-risk for low marginal benefit.
2201
+ */
2202
+ migrateFrameworkShadowCapabilities(result) {
2203
+ const claudeMdPath = path.join(this.config.projectDir, 'CLAUDE.md');
2204
+ if (!fs.existsSync(claudeMdPath)) {
2205
+ result.skipped.push('shadow capabilities (CLAUDE.md absent — nothing to mirror)');
2206
+ return;
2207
+ }
2208
+ let claudeMd;
2209
+ try {
2210
+ claudeMd = fs.readFileSync(claudeMdPath, 'utf-8');
2211
+ }
2212
+ catch (err) {
2213
+ result.errors.push(`shadow capabilities: CLAUDE.md read failed: ${err instanceof Error ? err.message : String(err)}`);
2214
+ return;
2215
+ }
2216
+ // Markers identifying each capability section migrateClaudeMd may have
2217
+ // ensured exists in CLAUDE.md. Kept in document order so appended
2218
+ // sections preserve narrative ordering in the shadow.
2219
+ const markers = [
2220
+ '### Self-Discovery',
2221
+ '**Private Viewing**',
2222
+ '**Cloudflare Tunnel**',
2223
+ '**Dashboard**',
2224
+ '**File Viewer',
2225
+ '### Coherence Gate',
2226
+ '### External Operation Safety',
2227
+ '### Playbook — Adaptive Context Engineering',
2228
+ '## Threadline Network (Agent-to-Agent Communication)',
2229
+ '## Worktree Convention',
2230
+ ];
2231
+ for (const shadowName of ['AGENTS.md', 'GEMINI.md']) {
2232
+ const shadowPath = path.join(this.config.projectDir, shadowName);
2233
+ if (!fs.existsSync(shadowPath))
2234
+ continue;
2235
+ let shadowContent;
2236
+ try {
2237
+ shadowContent = fs.readFileSync(shadowPath, 'utf-8');
2238
+ }
2239
+ catch (err) {
2240
+ result.errors.push(`${shadowName} read: ${err instanceof Error ? err.message : String(err)}`);
2241
+ continue;
2242
+ }
2243
+ let appended = shadowContent;
2244
+ let mirrored = 0;
2245
+ for (const marker of markers) {
2246
+ if (appended.includes(marker))
2247
+ continue;
2248
+ const start = claudeMd.indexOf(marker);
2249
+ if (start < 0)
2250
+ continue;
2251
+ // Slice from the marker through the start of the next top-level
2252
+ // heading (##/### line) or EOF. The leading "\n" guard avoids
2253
+ // matching headings inside fenced code blocks at column 0 only
2254
+ // when they begin a line.
2255
+ const after = claudeMd.slice(start);
2256
+ // Skip past the marker's own header line, then look for the next
2257
+ // heading. Without this, a marker that itself starts with "###"
2258
+ // would zero-length match.
2259
+ const nlAfterMarker = after.indexOf('\n');
2260
+ const searchFrom = nlAfterMarker >= 0 ? nlAfterMarker + 1 : 0;
2261
+ const tail = after.slice(searchFrom);
2262
+ const nextRel = tail.search(/(^|\n)(##|###) [^#\n]/);
2263
+ const sectionEnd = nextRel >= 0 ? searchFrom + nextRel : after.length;
2264
+ const section = after.slice(0, sectionEnd).trimEnd();
2265
+ appended = appended.trimEnd() + '\n\n' + section + '\n';
2266
+ mirrored++;
2267
+ }
2268
+ if (mirrored > 0) {
2269
+ try {
2270
+ fs.writeFileSync(shadowPath, appended);
2271
+ result.upgraded.push(`${shadowName}: mirrored ${mirrored} capability section(s) from CLAUDE.md`);
2272
+ }
2273
+ catch (err) {
2274
+ result.errors.push(`${shadowName} write: ${err instanceof Error ? err.message : String(err)}`);
2275
+ }
2276
+ }
2277
+ else {
2278
+ result.skipped.push(`${shadowName}: capability sections already present`);
2279
+ }
2280
+ }
2281
+ }
2024
2282
  /**
2025
2283
  * Install any new scripts that don't exist yet.
2026
2284
  * Never overwrites existing scripts (user may have customized them).