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
@@ -67,6 +67,25 @@ function findFreePortFallback() {
67
67
  }
68
68
  return 4040; // All ports in range are busy — return default and let server fail with a clear error
69
69
  }
70
+ /**
71
+ * Resolve the user-facing `--framework` choice into the persisted
72
+ * `enabledFrameworks` array stored in `.instar/config.json`. The migrator,
73
+ * sentinel, and runtime spawn paths all read this single field. Default
74
+ * (no flag) is `['claude-code']` so behavior is unchanged for existing
75
+ * users and the historical `npx instar` flow.
76
+ */
77
+ export function resolveEnabledFrameworks(choice) {
78
+ switch (choice) {
79
+ case 'codex-cli':
80
+ return ['codex-cli'];
81
+ case 'both':
82
+ return ['claude-code', 'codex-cli'];
83
+ case 'claude-code':
84
+ case undefined:
85
+ default:
86
+ return ['claude-code'];
87
+ }
88
+ }
70
89
  /**
71
90
  * Main init entry point. Handles both fresh and existing project modes.
72
91
  */
@@ -101,6 +120,11 @@ async function initFreshProject(projectName, options) {
101
120
  console.log(` Project names must start with a letter or number and contain only letters, numbers, dots, hyphens, and underscores.`);
102
121
  process.exit(1);
103
122
  }
123
+ // Resolve framework choice once for the whole init flow. Default unchanged
124
+ // for users who don't pass --framework. PR 2 of 4 (portability install
125
+ // upgrade): codex-only and gemini-only installs skip all `.claude/` writes.
126
+ const enabledFrameworks = resolveEnabledFrameworks(options.framework);
127
+ const claudeEnabled = enabledFrameworks.includes('claude-code');
104
128
  if (projectName === '.' || projectName === '..' || projectName.includes('/') || projectName.includes('\\')) {
105
129
  console.log(pc.red(` Invalid project name: "${projectName}"`));
106
130
  process.exit(1);
@@ -261,6 +285,7 @@ async function initFreshProject(projectName, options) {
261
285
  visibility: 'public',
262
286
  capabilities: ['chat'],
263
287
  },
288
+ enabledFrameworks: [...resolveEnabledFrameworks(options.framework)],
264
289
  };
265
290
  const configFilePath = path.join(stateDir, 'config.json');
266
291
  fs.writeFileSync(configFilePath, JSON.stringify(config, null, 2), { mode: 0o600 });
@@ -291,22 +316,30 @@ async function initFreshProject(projectName, options) {
291
316
  const canonicalState = new CanonicalState({ stateDir });
292
317
  canonicalState.initialize(projectName, projectDir);
293
318
  console.log(` ${pc.green('✓')} Created .instar/quick-facts.json, anti-patterns.json, project-registry.json`);
294
- // Create .claude/ structure
295
- installClaudeSettings(projectDir, port);
296
- console.log(` ${pc.green('✓')} Created .claude/settings.json`);
297
- installHealthWatchdog(projectDir, port, projectName);
298
- console.log(` ${pc.green('✓')} Created .claude/scripts/health-watchdog.sh`);
299
- installSmartFetch(projectDir);
300
- console.log(` ${pc.green('✓')} Created .claude/scripts/smart-fetch.py (agentic web conventions)`);
301
- installGitSyncGate(projectDir);
302
- console.log(` ${pc.green('✓')} Created .claude/scripts/git-sync-gate.sh (git sync pre-screening)`);
319
+ // Create .claude/ structure (gated on claude-code enabled — PR 2 of 4)
320
+ if (claudeEnabled) {
321
+ installClaudeSettings(projectDir, port);
322
+ console.log(` ${pc.green('✓')} Created .claude/settings.json`);
323
+ installHealthWatchdog(projectDir, port, projectName);
324
+ console.log(` ${pc.green('✓')} Created .claude/scripts/health-watchdog.sh`);
325
+ installSmartFetch(projectDir);
326
+ console.log(` ${pc.green('✓')} Created .claude/scripts/smart-fetch.py (agentic web conventions)`);
327
+ installGitSyncGate(projectDir);
328
+ console.log(` ${pc.green('✓')} Created .claude/scripts/git-sync-gate.sh (git sync pre-screening)`);
329
+ }
330
+ else {
331
+ console.log(` ${pc.dim('·')} Skipped .claude/ scaffolding (enabledFrameworks: ${enabledFrameworks.join(', ')})`);
332
+ }
333
+ // Framework-neutral: always install. Lives in .instar/scripts/.
303
334
  installSerendipityCapture(projectDir);
304
335
  console.log(` ${pc.green('✓')} Created .instar/scripts/serendipity-capture.sh`);
305
- // Create .claude/skills/ directory and install built-in skills
306
- const skillsDir = path.join(projectDir, '.claude', 'skills');
307
- fs.mkdirSync(skillsDir, { recursive: true });
308
- installBuiltinSkills(skillsDir, port);
309
- console.log(` ${pc.green('✓')} Created .claude/skills/ (with built-in evolution skills)`);
336
+ // Create .claude/skills/ directory and install built-in skills (gated)
337
+ if (claudeEnabled) {
338
+ const skillsDir = path.join(projectDir, '.claude', 'skills');
339
+ fs.mkdirSync(skillsDir, { recursive: true });
340
+ installBuiltinSkills(skillsDir, port);
341
+ console.log(` ${pc.green('✓')} Created .claude/skills/ (with built-in evolution skills)`);
342
+ }
310
343
  // Phase 2 — install built-in agentmd jobs (markdown templates + manifests).
311
344
  // Legacy jobs.json continues to seed below; the loader handles overlap.
312
345
  try {
@@ -319,10 +352,15 @@ async function initFreshProject(projectName, options) {
319
352
  catch (err) {
320
353
  console.log(` ${pc.yellow('!')} Built-in agentmd jobs install skipped: ${err instanceof Error ? err.message : String(err)}`);
321
354
  }
322
- // Write CLAUDE.md (standalone version for fresh projects)
323
- const claudeMd = generateClaudeMd(projectName, identity.name, port, false);
324
- fs.writeFileSync(path.join(projectDir, 'CLAUDE.md'), claudeMd);
325
- console.log(` ${pc.green('✓')} Created CLAUDE.md`);
355
+ // Write CLAUDE.md (standalone version for fresh projects) — gated on
356
+ // claude-code enabled. For codex-only/gemini-only installs the rich
357
+ // Claude capability doc is meaningless and is skipped; AGENTS.md /
358
+ // GEMINI.md are rendered from canonical AGENT.md below.
359
+ if (claudeEnabled) {
360
+ const claudeMd = generateClaudeMd(projectName, identity.name, port, false);
361
+ fs.writeFileSync(path.join(projectDir, 'CLAUDE.md'), claudeMd);
362
+ console.log(` ${pc.green('✓')} Created CLAUDE.md`);
363
+ }
326
364
  // Additively render non-Claude identity shadows (AGENTS.md / GEMINI.md)
327
365
  // from canonical .instar/AGENT.md so a Codex/Gemini install has its
328
366
  // identity file from init-time, not only after the first server spawn.
@@ -445,6 +483,10 @@ node_modules/
445
483
  async function initExistingProject(options) {
446
484
  const projectDir = path.resolve(options.dir || process.cwd());
447
485
  const projectName = options.name || path.basename(projectDir);
486
+ // Framework choice — gates `.claude/` writes for codex-only installs.
487
+ // Default behavior unchanged for users who don't pass --framework.
488
+ const enabledFrameworks = resolveEnabledFrameworks(options.framework);
489
+ const claudeEnabled = enabledFrameworks.includes('claude-code');
448
490
  // Auto-allocate a port if not explicitly specified (multi-instance support)
449
491
  let port;
450
492
  if (options.port) {
@@ -559,6 +601,7 @@ async function initExistingProject(options) {
559
601
  visibility: 'public',
560
602
  capabilities: ['chat'],
561
603
  },
604
+ enabledFrameworks: [...resolveEnabledFrameworks(options.framework)],
562
605
  };
563
606
  const configPath = path.join(stateDir, 'config.json');
564
607
  if (!fs.existsSync(configPath)) {
@@ -636,26 +679,30 @@ async function initExistingProject(options) {
636
679
  if (stateResult.created.length > 0) {
637
680
  console.log(pc.green(' Created:') + ` canonical state (${stateResult.created.join(', ')})`);
638
681
  }
639
- // Configure Claude Code settings with hooks
640
- installClaudeSettings(projectDir, port);
641
- console.log(pc.green(' Created:') + ' .claude/settings.json (hook configuration)');
642
- // Install health watchdog
643
- installHealthWatchdog(projectDir, port, projectName);
644
- console.log(pc.green(' Created:') + ' .claude/scripts/health-watchdog.sh');
645
- // Install smart-fetch for agentic web conventions
646
- installSmartFetch(projectDir);
647
- console.log(pc.green(' Created:') + ' .claude/scripts/smart-fetch.py (agentic web conventions)');
648
- // Install git-sync gate script
649
- installGitSyncGate(projectDir);
650
- console.log(pc.green(' Created:') + ' .claude/scripts/git-sync-gate.sh (git sync pre-screening)');
651
- // Install serendipity capture script
682
+ // Configure Claude Code settings with hooks (gated — PR 2 of 4)
683
+ if (claudeEnabled) {
684
+ installClaudeSettings(projectDir, port);
685
+ console.log(pc.green(' Created:') + ' .claude/settings.json (hook configuration)');
686
+ installHealthWatchdog(projectDir, port, projectName);
687
+ console.log(pc.green(' Created:') + ' .claude/scripts/health-watchdog.sh');
688
+ installSmartFetch(projectDir);
689
+ console.log(pc.green(' Created:') + ' .claude/scripts/smart-fetch.py (agentic web conventions)');
690
+ installGitSyncGate(projectDir);
691
+ console.log(pc.green(' Created:') + ' .claude/scripts/git-sync-gate.sh (git sync pre-screening)');
692
+ }
693
+ else {
694
+ console.log(pc.dim(' Skipped:') + ` .claude/ scaffolding (enabledFrameworks: ${enabledFrameworks.join(', ')})`);
695
+ }
696
+ // Framework-neutral: always install. Lives in .instar/scripts/.
652
697
  installSerendipityCapture(projectDir);
653
698
  console.log(pc.green(' Created:') + ' .instar/scripts/serendipity-capture.sh');
654
- // Create .claude/skills/ directory and install built-in skills
655
- const skillsDir = path.join(projectDir, '.claude', 'skills');
656
- fs.mkdirSync(skillsDir, { recursive: true });
657
- installBuiltinSkills(skillsDir, port);
658
- console.log(pc.green(' Created:') + ' .claude/skills/ (with built-in evolution skills)');
699
+ // Create .claude/skills/ directory and install built-in skills (gated)
700
+ if (claudeEnabled) {
701
+ const skillsDir = path.join(projectDir, '.claude', 'skills');
702
+ fs.mkdirSync(skillsDir, { recursive: true });
703
+ installBuiltinSkills(skillsDir, port);
704
+ console.log(pc.green(' Created:') + ' .claude/skills/ (with built-in evolution skills)');
705
+ }
659
706
  // Append to .gitignore
660
707
  const gitignorePath = path.join(projectDir, '.gitignore');
661
708
  const instarIgnores = '\n# Instar per-machine state (sessions, logs, secrets)\n.instar/state/\n.instar/logs/\n.instar/config.json\n';
@@ -748,6 +795,9 @@ async function initStandaloneAgent(agentName, options) {
748
795
  console.log(' Maximum 64 characters.');
749
796
  process.exit(1);
750
797
  }
798
+ // Framework choice — gates `.claude/` writes. Default unchanged.
799
+ const enabledFrameworks = resolveEnabledFrameworks(options.framework);
800
+ const claudeEnabled = enabledFrameworks.includes('claude-code');
751
801
  const projectDir = path.join(standaloneAgentsDir(), agentName);
752
802
  const stateDir = path.join(projectDir, '.instar');
753
803
  // Check if already exists
@@ -854,31 +904,40 @@ async function initStandaloneAgent(agentName, options) {
854
904
  enabled: true,
855
905
  type: 'quick',
856
906
  },
907
+ enabledFrameworks: [...resolveEnabledFrameworks(options.framework)],
857
908
  };
858
909
  fs.writeFileSync(path.join(stateDir, 'config.json'), JSON.stringify(config, null, 2));
859
910
  console.log(` ${pc.green('✓')} Created config.json`);
860
911
  // Write empty jobs and users
861
912
  fs.writeFileSync(path.join(stateDir, 'jobs.json'), JSON.stringify([], null, 2));
862
913
  fs.writeFileSync(path.join(stateDir, 'users.json'), JSON.stringify([], null, 2));
863
- // Create CLAUDE.md at project root
864
- fs.writeFileSync(path.join(projectDir, 'CLAUDE.md'), generateClaudeMd(agentName, agentName, port, false));
865
- console.log(` ${pc.green('✓')} Created CLAUDE.md`);
914
+ // Create CLAUDE.md at project root (gated on claude-code enabled —
915
+ // PR 2 of 4 of the install/wizard portability series).
916
+ if (claudeEnabled) {
917
+ fs.writeFileSync(path.join(projectDir, 'CLAUDE.md'), generateClaudeMd(agentName, agentName, port, false));
918
+ console.log(` ${pc.green('✓')} Created CLAUDE.md`);
919
+ }
866
920
  // Additively render non-Claude identity shadows (AGENTS.md / GEMINI.md)
867
921
  // from canonical .instar/AGENT.md — see the paired call in the primary
868
922
  // init path. claude-code is intentionally skipped.
869
923
  for (const s of renderNonClaudeIdentityShadows(projectDir, { stateDir })) {
870
924
  console.log(` ${pc.green('✓')} Created ${path.relative(projectDir, s)}`);
871
925
  }
872
- // Create .claude/ structure
873
- const claudeDir = path.join(projectDir, '.claude');
874
- fs.mkdirSync(path.join(claudeDir, 'scripts'), { recursive: true });
875
- fs.mkdirSync(path.join(claudeDir, 'skills'), { recursive: true });
876
- fs.writeFileSync(path.join(claudeDir, 'settings.json'), JSON.stringify({
877
- hooks: {
878
- PreToolUse: [],
879
- PostToolUse: [],
880
- },
881
- }, null, 2));
926
+ // Create .claude/ structure (gated)
927
+ if (claudeEnabled) {
928
+ const claudeDir = path.join(projectDir, '.claude');
929
+ fs.mkdirSync(path.join(claudeDir, 'scripts'), { recursive: true });
930
+ fs.mkdirSync(path.join(claudeDir, 'skills'), { recursive: true });
931
+ fs.writeFileSync(path.join(claudeDir, 'settings.json'), JSON.stringify({
932
+ hooks: {
933
+ PreToolUse: [],
934
+ PostToolUse: [],
935
+ },
936
+ }, null, 2));
937
+ }
938
+ else {
939
+ console.log(` ${pc.dim('·')} Skipped .claude/ scaffolding (enabledFrameworks: ${enabledFrameworks.join(', ')})`);
940
+ }
882
941
  // Create .gitignore
883
942
  fs.writeFileSync(path.join(projectDir, '.gitignore'), [
884
943
  '# Runtime state',
@@ -3170,25 +3229,39 @@ If everything is coherent and no reflection is needed, exit silently. Only repor
3170
3229
  */
3171
3230
  export function refreshHooksAndSettings(projectDir, stateDir) {
3172
3231
  installHooks(stateDir);
3173
- // Read port from config.json so HTTP hooks get resolved URLs
3232
+ // Read port + enabledFrameworks from config.json so HTTP hooks get
3233
+ // resolved URLs AND Claude-Code-only writes can be gated for codex-only
3234
+ // installs (portability audit PR 2 of 4).
3174
3235
  let serverPort;
3236
+ let enabledFrameworks = ['claude-code'];
3175
3237
  try {
3176
3238
  const configPath = path.join(stateDir, 'config.json');
3177
3239
  if (fs.existsSync(configPath)) {
3178
3240
  const config = JSON.parse(fs.readFileSync(configPath, 'utf-8'));
3179
3241
  serverPort = config.port;
3242
+ if (Array.isArray(config.enabledFrameworks) && config.enabledFrameworks.length > 0) {
3243
+ const filtered = config.enabledFrameworks.filter((f) => f === 'claude-code' || f === 'codex-cli');
3244
+ if (filtered.length > 0)
3245
+ enabledFrameworks = filtered;
3246
+ }
3180
3247
  }
3181
3248
  }
3182
3249
  catch { /* non-fatal */ }
3183
- installClaudeSettings(projectDir, serverPort);
3184
- refreshClaudeMd(projectDir, stateDir);
3250
+ const claudeEnabled = enabledFrameworks.includes('claude-code');
3251
+ if (claudeEnabled) {
3252
+ installClaudeSettings(projectDir, serverPort);
3253
+ refreshClaudeMd(projectDir, stateDir);
3254
+ }
3185
3255
  refreshJobs(stateDir);
3186
3256
  refreshScripts(projectDir, stateDir);
3187
- // Deploy any missing built-in skills (e.g., guardian job skills added after initial setup).
3188
- // installBuiltinSkills is already non-destructive — only writes missing SKILL.md files.
3189
- const skillsDir = path.join(projectDir, '.claude', 'skills');
3190
- fs.mkdirSync(skillsDir, { recursive: true });
3191
- installBuiltinSkills(skillsDir, serverPort ?? 4321);
3257
+ // Deploy any missing built-in skills. Skills target `.claude/skills/` so
3258
+ // they're Claude-Code-only by destination; skip for codex-only installs to
3259
+ // avoid creating a `.claude/` directory the operator did not ask for.
3260
+ if (claudeEnabled) {
3261
+ const skillsDir = path.join(projectDir, '.claude', 'skills');
3262
+ fs.mkdirSync(skillsDir, { recursive: true });
3263
+ installBuiltinSkills(skillsDir, serverPort ?? 4321);
3264
+ }
3192
3265
  }
3193
3266
  /**
3194
3267
  * Merge new default jobs into existing jobs.json without overwriting user changes.
@@ -3312,19 +3385,38 @@ function refreshScripts(projectDir, stateDir) {
3312
3385
  if (!config)
3313
3386
  return;
3314
3387
  const port = config.port || 4040;
3315
- // Install telegram-reply.sh if Telegram is configured
3388
+ // Resolve enabled-frameworks for `.claude/scripts/` gating (PR 2 of 4).
3389
+ // Default ['claude-code'] preserves historical behavior.
3390
+ const enabled = (() => {
3391
+ const raw = config.enabledFrameworks;
3392
+ if (Array.isArray(raw) && raw.length > 0) {
3393
+ const filtered = raw.filter((f) => f === 'claude-code' || f === 'codex-cli');
3394
+ if (filtered.length > 0)
3395
+ return filtered;
3396
+ }
3397
+ return ['claude-code'];
3398
+ })();
3399
+ const claudeEnabled = enabled.includes('claude-code');
3400
+ // Install telegram-reply.sh if Telegram is configured (framework-neutral —
3401
+ // installs to both `.claude/scripts/` for Claude and `.instar/scripts/`
3402
+ // for runtime-neutral after v1.0.10 Gap 4)
3316
3403
  if (isTelegramConfigured(stateDir)) {
3317
3404
  installTelegramRelay(projectDir, port);
3318
3405
  }
3319
- // Install whatsapp-reply.sh if WhatsApp is configured
3406
+ // Install whatsapp-reply.sh if WhatsApp is configured (lives in
3407
+ // `.instar/scripts/` already per init.ts; framework-neutral)
3320
3408
  if (isWhatsAppConfigured(stateDir)) {
3321
3409
  installWhatsAppRelay(projectDir, port);
3322
3410
  }
3323
- // Always install smart-fetch.py (agentic web conventions)
3324
- installSmartFetch(projectDir);
3325
- // Always install git-sync-gate.sh (pre-screening for git-sync job)
3326
- installGitSyncGate(projectDir);
3327
- // Always install serendipity-capture.sh
3411
+ // smart-fetch.py + git-sync-gate.sh both target `.claude/scripts/` and
3412
+ // are Claude-Code-specific (the agentic-web fetch script and the
3413
+ // pre-`git push` screening hook). Gate on claude-code enabled.
3414
+ if (claudeEnabled) {
3415
+ installSmartFetch(projectDir);
3416
+ installGitSyncGate(projectDir);
3417
+ }
3418
+ // Always install serendipity-capture.sh (lives in `.instar/scripts/`,
3419
+ // framework-neutral).
3328
3420
  installSerendipityCapture(projectDir);
3329
3421
  }
3330
3422
  /**