@staff0rd/assist 0.451.0 → 0.453.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.
package/dist/index.js CHANGED
@@ -6,7 +6,7 @@ import { Command } from "commander";
6
6
  // package.json
7
7
  var package_default = {
8
8
  name: "@staff0rd/assist",
9
- version: "0.451.0",
9
+ version: "0.453.0",
10
10
  type: "module",
11
11
  main: "dist/index.js",
12
12
  bin: {
@@ -436,6 +436,7 @@ var assistConfigSchema = z2.strictObject({
436
436
  worktree: z2.strictObject({
437
437
  enabled: z2.boolean().default(false),
438
438
  trunk: z2.boolean().default(false),
439
+ includeDrafts: z2.boolean().default(false),
439
440
  root: z2.string().optional(),
440
441
  install: z2.union([z2.boolean(), z2.string()]).default(true),
441
442
  commitBeforeManualChecks: z2.boolean().default(false),
@@ -11322,6 +11323,11 @@ function registerImportCommand(cmd) {
11322
11323
  // src/commands/backlog/add/index.ts
11323
11324
  import chalk74 from "chalk";
11324
11325
 
11326
+ // src/lib/isClaudeCode.ts
11327
+ function isClaudeCode() {
11328
+ return process.env.CLAUDECODE !== void 0;
11329
+ }
11330
+
11325
11331
  // src/commands/backlog/insertPhaseAt.ts
11326
11332
  import { eq as eq23 } from "drizzle-orm";
11327
11333
 
@@ -11488,6 +11494,15 @@ async function promptAcceptanceCriteria() {
11488
11494
 
11489
11495
  // src/commands/backlog/add/index.ts
11490
11496
  async function add(options2) {
11497
+ if (isClaudeCode()) {
11498
+ console.error(
11499
+ chalk74.red(
11500
+ "Error: 'assist backlog add' is for human use. Compose the whole item \u2014 name, type, description, acceptance criteria and every phase \u2014 and run 'assist backlog propose --json <file|->' so it is previewed and approved before anything is written."
11501
+ )
11502
+ );
11503
+ process.exitCode = 1;
11504
+ return;
11505
+ }
11491
11506
  if (!ensureRemoteOrigin()) return;
11492
11507
  const type = options2.type ?? await promptType();
11493
11508
  const name = options2.name ?? await promptName();
@@ -11502,7 +11517,7 @@ async function add(options2) {
11502
11517
  console.log(chalk74.green(`Added item ${formatItemId(id)}: ${name}`));
11503
11518
  }
11504
11519
 
11505
- // src/commands/backlog/addPhase.ts
11520
+ // src/commands/backlog/addPhase/index.ts
11506
11521
  import chalk76 from "chalk";
11507
11522
 
11508
11523
  // src/commands/backlog/resolveInsertPosition.ts
@@ -11525,76 +11540,8 @@ async function resolveInsertPosition(orm, itemId2, position) {
11525
11540
  return pos - 1;
11526
11541
  }
11527
11542
 
11528
- // src/commands/backlog/addPhase.ts
11529
- async function addPhase(id, name, options2) {
11530
- if (!ensureRemoteOrigin()) return;
11531
- const found = await findOneItem(id);
11532
- if (!found) return;
11533
- const tasks = options2.task ?? [];
11534
- if (tasks.length === 0) {
11535
- console.log(chalk76.red("At least one --task is required."));
11536
- process.exitCode = 1;
11537
- return;
11538
- }
11539
- const { orm } = found;
11540
- const itemId2 = found.item.id;
11541
- const phaseIdx = await resolveInsertPosition(orm, itemId2, options2.position);
11542
- if (phaseIdx === void 0) return;
11543
- await insertPhaseAt(
11544
- orm,
11545
- itemId2,
11546
- phaseIdx,
11547
- name,
11548
- tasks,
11549
- serializeManualChecks(options2.manualCheck),
11550
- found.item.currentPhase
11551
- );
11552
- const verb = options2.position !== void 0 ? "Inserted" : "Added";
11553
- console.log(
11554
- chalk76.green(
11555
- `${verb} phase ${phaseIdx + 1} "${name}" to item ${formatItemId(itemId2)} with ${tasks.length} task(s).`
11556
- )
11557
- );
11558
- }
11559
-
11560
- // src/commands/backlog/list/index.ts
11561
- import chalk77 from "chalk";
11562
- function filterItems(items2, options2) {
11563
- if (options2.status) return items2.filter((i) => i.status === options2.status);
11564
- if (!options2.all)
11565
- return items2.filter((i) => i.status !== "done" && i.status !== "wontdo");
11566
- return items2;
11567
- }
11568
- function repoPrefixer(items2, allRepos) {
11569
- if (!allRepos) return () => "";
11570
- const labels = originDisplayLabels(
11571
- items2.flatMap((i) => i.origin ? [i.origin] : [])
11572
- );
11573
- const repoNameOf = (item) => item.origin ? labels.get(item.origin) ?? "" : "";
11574
- const width = Math.max(0, ...items2.map((i) => repoNameOf(i).length));
11575
- return (item) => `${chalk77.dim(repoNameOf(item).padEnd(width))} `;
11576
- }
11577
- async function list2(options2) {
11578
- const allItems = await loadBacklog(options2.allRepos);
11579
- const items2 = filterItems(allItems, options2);
11580
- if (items2.length === 0) {
11581
- console.log(chalk77.dim("Backlog is empty."));
11582
- return;
11583
- }
11584
- const prefixOf = repoPrefixer(items2, !!options2.allRepos);
11585
- for (const item of items2) {
11586
- console.log(
11587
- `${prefixOf(item)}${statusIcon(item.status)} ${typeLabel(item.type)} ${chalk77.dim(formatItemId(item.id))} ${starMarker(item)}${item.name}${phaseLabel(item)}${dependencyLabel(item, allItems)}`
11588
- );
11589
- if (options2.verbose) {
11590
- printVerboseDetails(item);
11591
- }
11592
- }
11593
- }
11594
-
11595
- // src/commands/backlog/propose/index.ts
11543
+ // src/commands/backlog/addPhase/reviewProposedPhase.ts
11596
11544
  import { randomUUID as randomUUID2 } from "crypto";
11597
- import chalk79 from "chalk";
11598
11545
 
11599
11546
  // src/commands/sessions/shared/reportPreviewRejection.ts
11600
11547
  function reportPreviewRejection(subject, decision) {
@@ -11686,6 +11633,113 @@ async function awaitPreviewApproval(subject, request) {
11686
11633
  return decision;
11687
11634
  }
11688
11635
 
11636
+ // src/commands/backlog/renderPhaseSection.ts
11637
+ function renderPhaseSection(phase, idx) {
11638
+ const parts = [
11639
+ `### Phase ${idx + 1}: ${phase.name}`,
11640
+ phase.tasks.map((task) => `- ${task}`).join("\n")
11641
+ ];
11642
+ const manualChecks = phase.manualChecks ?? [];
11643
+ if (manualChecks.length > 0)
11644
+ parts.push(
11645
+ "**Manual checks:**",
11646
+ manualChecks.map((check2) => `- ${check2}`).join("\n")
11647
+ );
11648
+ return parts.join("\n\n");
11649
+ }
11650
+
11651
+ // src/commands/backlog/addPhase/reviewProposedPhase.ts
11652
+ async function reviewProposedPhase(item, phaseIdx, phase) {
11653
+ const sessionId = process.env.ASSIST_SESSION_ID;
11654
+ if (!isClaudeCode() || process.env.ASSIST_SESSION !== "1" || !sessionId)
11655
+ return;
11656
+ await awaitPreviewApproval("Backlog phase preview", {
11657
+ sessionId,
11658
+ requestId: randomUUID2(),
11659
+ title: `Add a phase to ${formatItemId(item.id)}: ${item.name}`,
11660
+ body: renderPhaseSection(phase, phaseIdx),
11661
+ prNumber: null,
11662
+ kind: "backlog-item",
11663
+ itemType: item.type
11664
+ });
11665
+ }
11666
+
11667
+ // src/commands/backlog/addPhase/index.ts
11668
+ async function addPhase(id, name, options2) {
11669
+ if (!ensureRemoteOrigin()) return;
11670
+ const found = await findOneItem(id);
11671
+ if (!found) return;
11672
+ const tasks = options2.task ?? [];
11673
+ if (tasks.length === 0) {
11674
+ console.log(chalk76.red("At least one --task is required."));
11675
+ process.exitCode = 1;
11676
+ return;
11677
+ }
11678
+ const { orm } = found;
11679
+ const itemId2 = found.item.id;
11680
+ const phaseIdx = await resolveInsertPosition(orm, itemId2, options2.position);
11681
+ if (phaseIdx === void 0) return;
11682
+ await reviewProposedPhase(found.item, phaseIdx, {
11683
+ name,
11684
+ tasks,
11685
+ manualChecks: options2.manualCheck
11686
+ });
11687
+ await insertPhaseAt(
11688
+ orm,
11689
+ itemId2,
11690
+ phaseIdx,
11691
+ name,
11692
+ tasks,
11693
+ serializeManualChecks(options2.manualCheck),
11694
+ found.item.currentPhase
11695
+ );
11696
+ const verb = options2.position !== void 0 ? "Inserted" : "Added";
11697
+ console.log(
11698
+ chalk76.green(
11699
+ `${verb} phase ${phaseIdx + 1} "${name}" to item ${formatItemId(itemId2)} with ${tasks.length} task(s).`
11700
+ )
11701
+ );
11702
+ }
11703
+
11704
+ // src/commands/backlog/list/index.ts
11705
+ import chalk77 from "chalk";
11706
+ function filterItems(items2, options2) {
11707
+ if (options2.status) return items2.filter((i) => i.status === options2.status);
11708
+ if (!options2.all)
11709
+ return items2.filter((i) => i.status !== "done" && i.status !== "wontdo");
11710
+ return items2;
11711
+ }
11712
+ function repoPrefixer(items2, allRepos) {
11713
+ if (!allRepos) return () => "";
11714
+ const labels = originDisplayLabels(
11715
+ items2.flatMap((i) => i.origin ? [i.origin] : [])
11716
+ );
11717
+ const repoNameOf = (item) => item.origin ? labels.get(item.origin) ?? "" : "";
11718
+ const width = Math.max(0, ...items2.map((i) => repoNameOf(i).length));
11719
+ return (item) => `${chalk77.dim(repoNameOf(item).padEnd(width))} `;
11720
+ }
11721
+ async function list2(options2) {
11722
+ const allItems = await loadBacklog(options2.allRepos);
11723
+ const items2 = filterItems(allItems, options2);
11724
+ if (items2.length === 0) {
11725
+ console.log(chalk77.dim("Backlog is empty."));
11726
+ return;
11727
+ }
11728
+ const prefixOf = repoPrefixer(items2, !!options2.allRepos);
11729
+ for (const item of items2) {
11730
+ console.log(
11731
+ `${prefixOf(item)}${statusIcon(item.status)} ${typeLabel(item.type)} ${chalk77.dim(formatItemId(item.id))} ${starMarker(item)}${item.name}${phaseLabel(item)}${dependencyLabel(item, allItems)}`
11732
+ );
11733
+ if (options2.verbose) {
11734
+ printVerboseDetails(item);
11735
+ }
11736
+ }
11737
+ }
11738
+
11739
+ // src/commands/backlog/propose/index.ts
11740
+ import { randomUUID as randomUUID3 } from "crypto";
11741
+ import chalk79 from "chalk";
11742
+
11689
11743
  // src/commands/backlog/propose/readProposedItem.ts
11690
11744
  import { readFileSync as readFileSync23 } from "fs";
11691
11745
  import chalk78 from "chalk";
@@ -11741,18 +11795,6 @@ ${issues}`);
11741
11795
  }
11742
11796
 
11743
11797
  // src/commands/backlog/propose/renderProposedItem.ts
11744
- function renderPhase(phase, i) {
11745
- const parts = [
11746
- `### Phase ${i + 1}: ${phase.name}`,
11747
- phase.tasks.map((task) => `- ${task}`).join("\n")
11748
- ];
11749
- if (phase.manualChecks.length > 0)
11750
- parts.push(
11751
- "**Manual checks:**",
11752
- phase.manualChecks.map((check2) => `- ${check2}`).join("\n")
11753
- );
11754
- return parts.join("\n\n");
11755
- }
11756
11798
  function renderProposedItem(item) {
11757
11799
  const sections = [`**Type:** ${item.type}`];
11758
11800
  if (item.description) sections.push("## Description", item.description);
@@ -11762,7 +11804,7 @@ function renderProposedItem(item) {
11762
11804
  item.acceptanceCriteria.map((ac, i) => `${i + 1}. ${ac}`).join("\n")
11763
11805
  );
11764
11806
  if (item.phases.length > 0)
11765
- sections.push("## Plan", ...item.phases.map(renderPhase));
11807
+ sections.push("## Plan", ...item.phases.map(renderPhaseSection));
11766
11808
  return sections.join("\n\n");
11767
11809
  }
11768
11810
 
@@ -11773,7 +11815,7 @@ async function reviewProposal(item) {
11773
11815
  if (process.env.ASSIST_SESSION === "1" && sessionId) {
11774
11816
  await awaitPreviewApproval("Backlog item preview", {
11775
11817
  sessionId,
11776
- requestId: randomUUID2(),
11818
+ requestId: randomUUID3(),
11777
11819
  title: item.name,
11778
11820
  body,
11779
11821
  prNumber: null,
@@ -12046,7 +12088,7 @@ import chalk87 from "chalk";
12046
12088
  import enquirer7 from "enquirer";
12047
12089
 
12048
12090
  // src/commands/backlog/launchMode.ts
12049
- import { randomUUID as randomUUID3 } from "crypto";
12091
+ import { randomUUID as randomUUID4 } from "crypto";
12050
12092
 
12051
12093
  // src/shared/spawnCodex.ts
12052
12094
  function spawnCodex(prompt, options2 = {}) {
@@ -12167,7 +12209,7 @@ async function launchMode(slashCommand, options2) {
12167
12209
  const resumeSessionId = options2?.resumeSessionId;
12168
12210
  if (!resumeSessionId) pullIfConfigured();
12169
12211
  process.env.ASSIST_SESSION_ID ??= String(process.pid);
12170
- const claudeSessionId = resumeSessionId ?? randomUUID3();
12212
+ const claudeSessionId = resumeSessionId ?? randomUUID4();
12171
12213
  emitActivity({
12172
12214
  kind: "command",
12173
12215
  name: slashCommand,
@@ -13949,11 +13991,6 @@ function colorize(plainOutput) {
13949
13991
  }).join("\n");
13950
13992
  }
13951
13993
 
13952
- // src/lib/isClaudeCode.ts
13953
- function isClaudeCode() {
13954
- return process.env.CLAUDECODE !== void 0;
13955
- }
13956
-
13957
13994
  // src/commands/permitCliReads/mapAsync.ts
13958
13995
  async function mapAsync(items2, concurrency, fn) {
13959
13996
  const results = Array.from({ length: items2.length });
@@ -17640,7 +17677,7 @@ function registerRefineLaunch(program2, resumeFlag) {
17640
17677
  }
17641
17678
 
17642
17679
  // src/commands/reviewPrComments.ts
17643
- import { randomUUID as randomUUID4 } from "crypto";
17680
+ import { randomUUID as randomUUID5 } from "crypto";
17644
17681
 
17645
17682
  // src/commands/review/checkoutPr.ts
17646
17683
  import { execFileSync as execFileSync6 } from "child_process";
@@ -17786,6 +17823,7 @@ function checkDurabilitySync(cwd) {
17786
17823
  var DISABLED = {
17787
17824
  enabled: false,
17788
17825
  trunk: false,
17826
+ includeDrafts: false,
17789
17827
  install: true,
17790
17828
  commitBeforeManualChecks: false,
17791
17829
  copy: [".env", "settings.local.json", ".claude/settings.local.json"]
@@ -17807,6 +17845,12 @@ function allocateTree(requestedCwd, boundTreeRoots2, options2 = {}) {
17807
17845
  if (!cfg.enabled)
17808
17846
  return { cwd: requestedCwd, kind: "primary", created: false };
17809
17847
  const clone = mainWorktree(repoRoot) ?? repoRoot;
17848
+ if (options2.draftLike === true && cfg.includeDrafts !== true) {
17849
+ daemonLog(
17850
+ `draft-type session kept in the clone ${clone}: worktree.includeDrafts is off`
17851
+ );
17852
+ return { cwd: clone, kind: "primary", created: false, clone };
17853
+ }
17810
17854
  if (planAllocation(clone, boundTreeRoots2) === "primary" && !wouldDisturbWorkInProgress(clone, options2.forCheckout === true))
17811
17855
  return { cwd: clone, kind: "primary", created: false, clone };
17812
17856
  const path71 = createWorktree(
@@ -18021,7 +18065,7 @@ async function checkoutPr(number) {
18021
18065
  // src/commands/reviewPrComments.ts
18022
18066
  async function reviewPrComments(number) {
18023
18067
  if (number) await checkoutPr(number);
18024
- const claudeSessionId = randomUUID4();
18068
+ const claudeSessionId = randomUUID5();
18025
18069
  emitActivity({
18026
18070
  kind: "command",
18027
18071
  name: "review-pr-comments",
@@ -19877,7 +19921,7 @@ async function placePr(prNumber, title, body, options2) {
19877
19921
  }
19878
19922
 
19879
19923
  // src/commands/prs/previewAndPlace.ts
19880
- import { randomUUID as randomUUID5 } from "crypto";
19924
+ import { randomUUID as randomUUID6 } from "crypto";
19881
19925
 
19882
19926
  // src/commands/prs/appendScreenshots.ts
19883
19927
  function appendScreenshots(body, screenshots) {
@@ -19893,7 +19937,7 @@ ${screenshots.join("\n\n")}`;
19893
19937
  async function previewAndPlace(args) {
19894
19938
  const decision = await awaitPreviewApproval("PR preview", {
19895
19939
  sessionId: args.sessionId,
19896
- requestId: randomUUID5(),
19940
+ requestId: randomUUID6(),
19897
19941
  title: args.title,
19898
19942
  body: args.body,
19899
19943
  prNumber: args.prNumber
@@ -26722,7 +26766,7 @@ var ClientHub = class extends Set {
26722
26766
  };
26723
26767
 
26724
26768
  // src/commands/sessions/daemon/createSession.ts
26725
- import { randomUUID as randomUUID6 } from "crypto";
26769
+ import { randomUUID as randomUUID7 } from "crypto";
26726
26770
 
26727
26771
  // src/commands/sessions/daemon/resolveRunConfig.ts
26728
26772
  function resolveRunConfig(name, cwd) {
@@ -26861,7 +26905,7 @@ function sessionBase(id, status3) {
26861
26905
  // src/commands/sessions/daemon/createSession.ts
26862
26906
  function createSession(id, prompt, cwd, design, harness, holdPty) {
26863
26907
  if (harness === "pi") return createPiSession(id, prompt, cwd, holdPty);
26864
- const claudeSessionId = randomUUID6();
26908
+ const claudeSessionId = randomUUID7();
26865
26909
  return {
26866
26910
  ...sessionBase(id, prompt ? "running" : "waiting"),
26867
26911
  name: prompt?.slice(0, 40) || `Session ${id}`,
@@ -27029,10 +27073,26 @@ function dismissSession(sessions, id) {
27029
27073
  return true;
27030
27074
  }
27031
27075
 
27076
+ // src/commands/sessions/daemon/isDraftCommand.ts
27077
+ var DRAFT_COMMANDS = ["draft", "bug", "refine"];
27078
+ function isDraftCommand(command) {
27079
+ return command != null && DRAFT_COMMANDS.includes(command);
27080
+ }
27081
+
27082
+ // src/commands/sessions/daemon/worktree/isCloneBoundDraft.ts
27083
+ function isCloneBoundDraft(session) {
27084
+ if (session.worktree || !session.cwd) return false;
27085
+ if (session.commandType !== "assist") return false;
27086
+ if (!isDraftCommand(session.assistArgs?.[0])) return false;
27087
+ const cfg = worktreeConfigFor(session.cwd);
27088
+ return cfg.enabled === true && cfg.includeDrafts !== true;
27089
+ }
27090
+
27032
27091
  // src/commands/sessions/daemon/worktree/closeGateApplies.ts
27033
27092
  function closeGateApplies(sessions, session) {
27034
27093
  if (otherTreeHolders(sessions, session).length > 0) return false;
27035
27094
  if (session.worktree) return true;
27095
+ if (isCloneBoundDraft(session)) return false;
27036
27096
  if (!session.cwd) return false;
27037
27097
  if (session.status !== "running" && session.status !== "waiting")
27038
27098
  return false;
@@ -28046,7 +28106,7 @@ function shouldAutoRun(session, status3 = session.status) {
28046
28106
  reason: `command type is "${session.commandType}", not "assist"`
28047
28107
  };
28048
28108
  const cmd = session.assistArgs?.[0];
28049
- if (cmd !== "draft" && cmd !== "bug" && cmd !== "refine")
28109
+ if (!isDraftCommand(cmd))
28050
28110
  return {
28051
28111
  run: false,
28052
28112
  reason: `command "${cmd ?? "(none)"}" is not draft/bug/refine`
@@ -28161,7 +28221,7 @@ function respawnSession(session, respawn, status3, clients, onStatusChange) {
28161
28221
  }
28162
28222
 
28163
28223
  // src/commands/sessions/daemon/respawnPlan.ts
28164
- import { randomUUID as randomUUID7 } from "crypto";
28224
+ import { randomUUID as randomUUID8 } from "crypto";
28165
28225
 
28166
28226
  // src/commands/sessions/daemon/assistResumeArgs.ts
28167
28227
  function assistResumeArgs(session) {
@@ -28202,7 +28262,7 @@ function respawnPlan(session) {
28202
28262
  return null;
28203
28263
  }
28204
28264
  function freshClaudePlan(session, prompt, cwd) {
28205
- const claudeSessionId = randomUUID7();
28265
+ const claudeSessionId = randomUUID8();
28206
28266
  return {
28207
28267
  spawn: () => {
28208
28268
  session.claudeSessionId = claudeSessionId;
@@ -28801,9 +28861,8 @@ function runRetry(sessions, id, replace, deps2) {
28801
28861
  return null;
28802
28862
  }
28803
28863
 
28804
- // src/commands/sessions/daemon/reuseSessionForRun.ts
28805
- function reuseSessionForRun(session, itemId2, clients, onStatusChange) {
28806
- const assistArgs = ["backlog", "run", String(itemId2)];
28864
+ // src/commands/sessions/daemon/resetCardForRun.ts
28865
+ function resetCardForRun(session, assistArgs) {
28807
28866
  session.pty?.kill();
28808
28867
  session.gitWatcher?.close();
28809
28868
  session.gitWatcher = void 0;
@@ -28820,9 +28879,70 @@ function reuseSessionForRun(session, itemId2, clients, onStatusChange) {
28820
28879
  session.usageSeeded = false;
28821
28880
  setStatus2(session, "running");
28822
28881
  session.restored = void 0;
28823
- session.pty = spawnPty(["assist", ...assistArgs], session.cwd, session.id);
28882
+ }
28883
+
28884
+ // src/commands/sessions/daemon/worktree/boundTreeRoots.ts
28885
+ function boundTreeRoots(sessions, exclude) {
28886
+ const roots = /* @__PURE__ */ new Set();
28887
+ for (const session of sessions.values()) {
28888
+ if (session === exclude) continue;
28889
+ const root = treeRootOf(session);
28890
+ if (root && !roots.has(root) && holdsTree(session, root)) roots.add(root);
28891
+ }
28892
+ return roots;
28893
+ }
28894
+ function treeRootOf(session) {
28895
+ if (!session.cwd) return void 0;
28896
+ return findRepoRoot(session.cwd) ?? session.cwd;
28897
+ }
28898
+ function holdsTree(session, root) {
28899
+ if (session.closing) return true;
28900
+ if (session.status !== "done" && session.status !== "error") return true;
28901
+ if (!worktreeConfigFor(root).enabled) return true;
28902
+ const durability = checkDurabilitySync(root);
28903
+ daemonLog(
28904
+ durability.durable ? `tree ${root} free for allocation: session ${session.id} finished and its work is landed` : `tree ${root} still held by finished session ${session.id}: ${durability.reason}`
28905
+ );
28906
+ return !durability.durable;
28907
+ }
28908
+
28909
+ // src/commands/sessions/daemon/worktree/planReuseTree.ts
28910
+ function planReuseTree(session, ctx) {
28911
+ if (!ctx || !session.cwd || session.worktree) return void 0;
28912
+ const alloc = allocateTree(
28913
+ session.cwd,
28914
+ boundTreeRoots(ctx.sessions, session)
28915
+ );
28916
+ if (alloc.kind !== "worktree" || !alloc.cwd) return void 0;
28917
+ daemonLog(
28918
+ `session ${session.id} moved from ${session.cwd} to ${alloc.cwd} for its chained run`
28919
+ );
28920
+ return alloc;
28921
+ }
28922
+
28923
+ // src/commands/sessions/daemon/reuseSessionForRun.ts
28924
+ function reuseSessionForRun(session, itemId2, clients, onStatusChange, tree) {
28925
+ const assistArgs = ["backlog", "run", String(itemId2)];
28926
+ resetCardForRun(session, assistArgs);
28927
+ const alloc = planReuseTree(session, tree);
28928
+ if (alloc) session.cwd = alloc.cwd;
28929
+ Object.assign(
28930
+ session,
28931
+ startOrHoldPty(
28932
+ () => spawnPty(["assist", ...assistArgs], session.cwd, session.id),
28933
+ alloc !== void 0
28934
+ )
28935
+ );
28824
28936
  broadcast(clients, { type: "clear", sessionId: session.id });
28825
- wirePtyEvents(session, clients, onStatusChange);
28937
+ if (alloc)
28938
+ bindNewWorktree(
28939
+ session,
28940
+ alloc,
28941
+ tree?.notify ?? (() => {
28942
+ }),
28943
+ tree?.startHeld
28944
+ );
28945
+ else wirePtyEvents(session, clients, onStatusChange);
28826
28946
  daemonLog(
28827
28947
  `session ${session.id} reused for backlog run ${itemId2}: ${session.name}`
28828
28948
  );
@@ -29624,30 +29744,6 @@ function resumeSession(id, sessionId, cwd, name) {
29624
29744
  };
29625
29745
  }
29626
29746
 
29627
- // src/commands/sessions/daemon/worktree/boundTreeRoots.ts
29628
- function boundTreeRoots(sessions) {
29629
- const roots = /* @__PURE__ */ new Set();
29630
- for (const session of sessions.values()) {
29631
- const root = treeRootOf(session);
29632
- if (root && !roots.has(root) && holdsTree(session, root)) roots.add(root);
29633
- }
29634
- return roots;
29635
- }
29636
- function treeRootOf(session) {
29637
- if (!session.cwd) return void 0;
29638
- return findRepoRoot(session.cwd) ?? session.cwd;
29639
- }
29640
- function holdsTree(session, root) {
29641
- if (session.closing) return true;
29642
- if (session.status !== "done" && session.status !== "error") return true;
29643
- if (!worktreeConfigFor(root).enabled) return true;
29644
- const durability = checkDurabilitySync(root);
29645
- daemonLog(
29646
- durability.durable ? `tree ${root} free for allocation: session ${session.id} finished and its work is landed` : `tree ${root} still held by finished session ${session.id}: ${durability.reason}`
29647
- );
29648
- return !durability.durable;
29649
- }
29650
-
29651
29747
  // src/commands/sessions/daemon/worktree/isPrCheckoutArgs.ts
29652
29748
  var CHECKOUT_COMMANDS = /* @__PURE__ */ new Set(["review", "review-pr-comments"]);
29653
29749
  function isPrCheckoutArgs(args) {
@@ -29676,7 +29772,10 @@ function spawnAssistInTree(ctx, assistArgs, cwd, meta) {
29676
29772
  ctx,
29677
29773
  cwd,
29678
29774
  (sid, resolvedCwd, holdUntilSeeded) => createAssistSession(sid, assistArgs, resolvedCwd, meta, holdUntilSeeded),
29679
- { forCheckout: isPrCheckoutArgs(assistArgs) }
29775
+ {
29776
+ forCheckout: isPrCheckoutArgs(assistArgs),
29777
+ draftLike: isDraftCommand(assistArgs[0])
29778
+ }
29680
29779
  );
29681
29780
  }
29682
29781
  function resumeInTree(ctx, sessionId, cwd, name) {
@@ -29827,7 +29926,13 @@ var SessionManager = class {
29827
29926
  this.sessions,
29828
29927
  (id) => this.dismissSession(id),
29829
29928
  () => this.notify(),
29830
- (session, itemId2) => reuseSessionForRun(session, itemId2, this.clients, this.onStatusChange)
29929
+ (session, itemId2) => reuseSessionForRun(
29930
+ session,
29931
+ itemId2,
29932
+ this.clients,
29933
+ this.onStatusChange,
29934
+ this.treeCtx()
29935
+ )
29831
29936
  );
29832
29937
  writeToSession(id, data) {
29833
29938
  writeToSession(this.sessions, id, data, this.onStatusChange);
@@ -30402,6 +30507,11 @@ var sessionsConfigHelp = [
30402
30507
  setter: "assist config set worktree.trunk true -g --repo",
30403
30508
  note: "trunk-based: a spilled worktree lands on the mainline (default off: it starts off the remote default with no mainline tracking, so the session raises its own branch and PR)"
30404
30509
  },
30510
+ {
30511
+ key: "worktree.includeDrafts",
30512
+ setter: "assist config set worktree.includeDrafts true -g --repo",
30513
+ note: "draft/bug/refine sessions get their own <clone>-N too (default off: they run in the clone, since they change no code)"
30514
+ },
30405
30515
  {
30406
30516
  key: "worktree.root",
30407
30517
  setter: "assist config set worktree.root ~/git -g --repo",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@staff0rd/assist",
3
- "version": "0.451.0",
3
+ "version": "0.453.0",
4
4
  "type": "module",
5
5
  "main": "dist/index.js",
6
6
  "bin": {