@shmulikdav/solix 1.0.4 → 1.1.1

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/dist/index.js CHANGED
@@ -926,6 +926,7 @@ function getDb() {
926
926
  db.exec(SCHEMA);
927
927
  ensureColumn(db, "sessions", "kind", "kind TEXT NOT NULL DEFAULT 'user'");
928
928
  ensureColumn(db, "sessions", "advisor_role", "advisor_role TEXT");
929
+ ensureColumn(db, "sessions", "worktree_path", "worktree_path TEXT");
929
930
  ensureColumn(db, "advisors", "texture_pack", "texture_pack TEXT");
930
931
  ensureColumn(db, "missions", "error_summary", "error_summary TEXT");
931
932
  _db = db;
@@ -1011,7 +1012,8 @@ function rowToSession(row) {
1011
1012
  currentMissionId: row.current_mission_id ?? void 0,
1012
1013
  lastCompletedMissionId: row.last_completed_mission_id ?? void 0,
1013
1014
  orbitSlot: row.orbit_slot,
1014
- name: row.name ?? void 0
1015
+ name: row.name ?? void 0,
1016
+ worktreePath: row.worktree_path ?? void 0
1015
1017
  };
1016
1018
  }
1017
1019
  function nextOrbitSlot(db, projectId) {
@@ -1046,9 +1048,9 @@ function upsertSession(db, input) {
1046
1048
  `INSERT INTO sessions (
1047
1049
  id, pid, project_id, parent_session_id, origin, model, status,
1048
1050
  context_usage_pct, orbit_slot, cwd, name, kind, advisor_role,
1049
- created_at, updated_at
1051
+ worktree_path, created_at, updated_at
1050
1052
  )
1051
- VALUES (?, ?, ?, ?, ?, ?, ?, 0, ?, ?, NULL, ?, ?, ?, ?)`
1053
+ VALUES (?, ?, ?, ?, ?, ?, ?, 0, ?, ?, NULL, ?, ?, ?, ?, ?)`
1052
1054
  ).run(
1053
1055
  input.id,
1054
1056
  input.pid,
@@ -1061,6 +1063,7 @@ function upsertSession(db, input) {
1061
1063
  input.cwd,
1062
1064
  kind,
1063
1065
  input.advisorRole ?? null,
1066
+ input.worktreePath ?? null,
1064
1067
  ts2,
1065
1068
  ts2
1066
1069
  );
@@ -1078,7 +1081,8 @@ function upsertSession(db, input) {
1078
1081
  advisorRole: input.advisorRole,
1079
1082
  parentSessionId: input.parentSessionId,
1080
1083
  contextUsagePct: 0,
1081
- orbitSlot
1084
+ orbitSlot,
1085
+ worktreePath: input.worktreePath
1082
1086
  };
1083
1087
  }
1084
1088
  function setSessionStatus(db, sessionId, status) {
@@ -2409,9 +2413,51 @@ function mimeFor(filePath) {
2409
2413
  }
2410
2414
 
2411
2415
  // ../server/src/launcher.ts
2412
- import { spawn } from "child_process";
2413
- import { existsSync as existsSync7 } from "fs";
2416
+ import { spawn, spawnSync as spawnSync2 } from "child_process";
2417
+ import { existsSync as existsSync7, mkdirSync as mkdirSync3 } from "fs";
2418
+ import { homedir as homedir5 } from "os";
2419
+ import { basename as basename2, join as join9 } from "path";
2414
2420
  import { nanoid as nanoid4 } from "nanoid";
2421
+ function ensureWorktree(opts) {
2422
+ const repoRoot = (() => {
2423
+ const r = spawnSync2("git", ["rev-parse", "--show-toplevel"], {
2424
+ cwd: opts.repoCwd,
2425
+ encoding: "utf8"
2426
+ });
2427
+ if (r.status !== 0) {
2428
+ throw new Error(`not a git repository: ${opts.repoCwd}`);
2429
+ }
2430
+ return (r.stdout ?? "").trim();
2431
+ })();
2432
+ const repoName = basename2(repoRoot);
2433
+ const safeBranch = opts.branch.replace(/[^a-zA-Z0-9._-]+/g, "-");
2434
+ const worktreesDir = join9(homedir5(), ".solix", "worktrees");
2435
+ const path = join9(worktreesDir, `${repoName}-${safeBranch}`);
2436
+ const list = spawnSync2("git", ["worktree", "list", "--porcelain"], {
2437
+ cwd: repoRoot,
2438
+ encoding: "utf8"
2439
+ });
2440
+ if (list.status === 0 && (list.stdout ?? "").includes(`worktree ${path}`)) {
2441
+ return { path, created: false };
2442
+ }
2443
+ mkdirSync3(worktreesDir, { recursive: true });
2444
+ const branchProbe = spawnSync2(
2445
+ "git",
2446
+ ["rev-parse", "--verify", "--quiet", `refs/heads/${opts.branch}`],
2447
+ { cwd: repoRoot, encoding: "utf8" }
2448
+ );
2449
+ const args = branchProbe.status === 0 ? ["worktree", "add", path, opts.branch] : ["worktree", "add", path, "-b", opts.branch, opts.baseRef ?? "HEAD"];
2450
+ const add = spawnSync2("git", args, {
2451
+ cwd: repoRoot,
2452
+ encoding: "utf8"
2453
+ });
2454
+ if (add.status !== 0) {
2455
+ throw new Error(
2456
+ `git worktree add failed: ${(add.stderr ?? "").slice(0, 280)}`
2457
+ );
2458
+ }
2459
+ return { path, created: true };
2460
+ }
2415
2461
  var FAKE_CLAUDE = process.env.SOLIX_FAKE_CLAUDE === "1";
2416
2462
  var Launcher = class {
2417
2463
  constructor(db, broadcaster) {
@@ -2565,14 +2611,44 @@ var Launcher = class {
2565
2611
  */
2566
2612
  launch(opts) {
2567
2613
  if (!opts.initialPrompt.trim()) return { ok: false };
2614
+ let spawnCwd = opts.cwd;
2615
+ let worktreePath;
2616
+ if (opts.worktreeBranch?.trim()) {
2617
+ try {
2618
+ const wt = ensureWorktree({
2619
+ repoCwd: opts.cwd,
2620
+ branch: opts.worktreeBranch.trim(),
2621
+ baseRef: opts.worktreeBaseRef?.trim() || void 0
2622
+ });
2623
+ spawnCwd = wt.path;
2624
+ worktreePath = wt.path;
2625
+ this.broadcaster.broadcast({
2626
+ type: "toast",
2627
+ level: "info",
2628
+ message: wt.created ? `Worktree created at ${wt.path}` : `Reusing worktree ${wt.path}`
2629
+ });
2630
+ } catch (err) {
2631
+ this.broadcaster.broadcast({
2632
+ type: "toast",
2633
+ level: "error",
2634
+ message: `Worktree setup failed: ${err.message}`
2635
+ });
2636
+ return { ok: false };
2637
+ }
2638
+ }
2568
2639
  if (FAKE_CLAUDE) {
2569
- return this.launchSynthetic(opts);
2640
+ return this.launchSynthetic({
2641
+ cwd: spawnCwd,
2642
+ model: opts.model,
2643
+ initialPrompt: opts.initialPrompt,
2644
+ worktreePath
2645
+ });
2570
2646
  }
2571
- if (!existsSync7(opts.cwd)) {
2647
+ if (!existsSync7(spawnCwd)) {
2572
2648
  this.broadcaster.broadcast({
2573
2649
  type: "toast",
2574
2650
  level: "error",
2575
- message: `Launch failed: cwd does not exist (${opts.cwd})`
2651
+ message: `Launch failed: cwd does not exist (${spawnCwd})`
2576
2652
  });
2577
2653
  return { ok: false };
2578
2654
  }
@@ -2582,9 +2658,10 @@ var Launcher = class {
2582
2658
  const sessionId = `task-${nanoid4(8)}`;
2583
2659
  return this.spawnPrint({
2584
2660
  sessionId,
2585
- cwd: opts.cwd,
2661
+ cwd: spawnCwd,
2586
2662
  args,
2587
- isFollowUp: false
2663
+ isFollowUp: false,
2664
+ worktreePath
2588
2665
  });
2589
2666
  }
2590
2667
  sendPromptToInternal(sessionId, text) {
@@ -2630,6 +2707,15 @@ var Launcher = class {
2630
2707
  isFollowUp: true
2631
2708
  }).ok;
2632
2709
  }
2710
+ /** Returns the worktree path the launcher resolved for an internal task,
2711
+ * if any. Used by router.onSessionStart to persist worktree_path on the
2712
+ * session row when claude reports its session_start hook. */
2713
+ worktreePathForInternalCwd(cwd) {
2714
+ for (const rec of this.internalTasks.values()) {
2715
+ if (rec.cwd === cwd && rec.worktreePath) return rec.worktreePath;
2716
+ }
2717
+ return void 0;
2718
+ }
2633
2719
  spawnPrint(opts) {
2634
2720
  let child;
2635
2721
  try {
@@ -2649,7 +2735,10 @@ var Launcher = class {
2649
2735
  }
2650
2736
  const pid = child.pid ?? 0;
2651
2737
  if (!opts.isFollowUp) {
2652
- this.internalTasks.set(opts.sessionId, { cwd: opts.cwd });
2738
+ this.internalTasks.set(opts.sessionId, {
2739
+ cwd: opts.cwd,
2740
+ worktreePath: opts.worktreePath
2741
+ });
2653
2742
  }
2654
2743
  let stdout = "";
2655
2744
  let stderr = "";
@@ -2702,7 +2791,8 @@ var Launcher = class {
2702
2791
  projectId: project.id,
2703
2792
  cwd: opts.cwd,
2704
2793
  origin: "internal",
2705
- model: opts.model ?? "sonnet"
2794
+ model: opts.model ?? "sonnet",
2795
+ worktreePath: opts.worktreePath
2706
2796
  });
2707
2797
  const active = setSessionStatus(this.db, sessionId, "active");
2708
2798
  if (active)
@@ -2845,6 +2935,7 @@ var EventRouter = class {
2845
2935
  const project = ensureProject(this.db, event.cwd);
2846
2936
  const sessionId = this.extractSessionId(event);
2847
2937
  const advisorRole = this.launcher?.advisorRoleForPid(event.pid);
2938
+ const worktreePath = this.launcher?.worktreePathForInternalCwd(event.cwd);
2848
2939
  const session = upsertSession(this.db, {
2849
2940
  id: sessionId,
2850
2941
  pid: event.pid,
@@ -2854,7 +2945,8 @@ var EventRouter = class {
2854
2945
  model: this.extractModel(event),
2855
2946
  parentSessionId: this.extractParentSessionId(event),
2856
2947
  kind: advisorRole ? "advisor" : "user",
2857
- advisorRole
2948
+ advisorRole,
2949
+ worktreePath
2858
2950
  });
2859
2951
  this.broadcaster.broadcast({ type: "session_upsert", session });
2860
2952
  if (!session.parentSessionId) {
@@ -3132,7 +3224,9 @@ var EventRouter = class {
3132
3224
  return this.launcher.launch({
3133
3225
  cwd: opts.cwd,
3134
3226
  model: opts.model,
3135
- initialPrompt: opts.initialPrompt
3227
+ initialPrompt: opts.initialPrompt,
3228
+ worktreeBranch: opts.worktreeBranch,
3229
+ worktreeBaseRef: opts.worktreeBaseRef
3136
3230
  });
3137
3231
  }
3138
3232
  sendPromptToSession(sessionId, text) {
@@ -3244,7 +3338,9 @@ function handleClientMessage(ctx, _ws, msg) {
3244
3338
  ctx.router.launchInternalSession({
3245
3339
  cwd: msg.cwd,
3246
3340
  model: msg.model,
3247
- initialPrompt: msg.initialPrompt
3341
+ initialPrompt: msg.initialPrompt,
3342
+ worktreeBranch: msg.worktreeBranch,
3343
+ worktreeBaseRef: msg.worktreeBaseRef
3248
3344
  });
3249
3345
  break;
3250
3346
  case "invoke_advisor":
@@ -3274,9 +3370,9 @@ import {
3274
3370
  statSync as statSync5,
3275
3371
  watch
3276
3372
  } from "fs";
3277
- import { homedir as homedir5 } from "os";
3278
- import { join as join9 } from "path";
3279
- var TRANSCRIPT_BASE = join9(homedir5(), ".claude", "projects");
3373
+ import { homedir as homedir6 } from "os";
3374
+ import { join as join10 } from "path";
3375
+ var TRANSCRIPT_BASE = join10(homedir6(), ".claude", "projects");
3280
3376
  var CONTEXT_BUDGETS_BY_MODEL = {
3281
3377
  "claude-opus-4-7": 2e5,
3282
3378
  "claude-opus-4-6": 2e5,
@@ -3289,7 +3385,7 @@ function encodeProjectPath(cwd) {
3289
3385
  return cwd.replace(/[/\\]/g, "-");
3290
3386
  }
3291
3387
  function transcriptPathFor(cwd, sessionId) {
3292
- return join9(TRANSCRIPT_BASE, encodeProjectPath(cwd), `${sessionId}.jsonl`);
3388
+ return join10(TRANSCRIPT_BASE, encodeProjectPath(cwd), `${sessionId}.jsonl`);
3293
3389
  }
3294
3390
  var TranscriptWatcherManager = class {
3295
3391
  constructor(db, broadcaster) {