@anthropologies/claudestory 0.1.35 → 0.1.37

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/cli.js CHANGED
@@ -1761,7 +1761,7 @@ function fencedBlock(content, lang) {
1761
1761
  ${content}
1762
1762
  ${fence}`;
1763
1763
  }
1764
- function formatStatus(state, format) {
1764
+ function formatStatus(state, format, activeSessions = []) {
1765
1765
  const phases = phasesWithStatus(state);
1766
1766
  const data = {
1767
1767
  project: state.config.project,
@@ -1781,7 +1781,8 @@ function formatStatus(state, format) {
1781
1781
  name: p.phase.name,
1782
1782
  status: p.status,
1783
1783
  leafCount: p.leafCount
1784
- }))
1784
+ })),
1785
+ ...activeSessions.length > 0 ? { activeSessions } : {}
1785
1786
  };
1786
1787
  if (format === "json") {
1787
1788
  return JSON.stringify(successEnvelope(data), null, 2);
@@ -1803,6 +1804,15 @@ function formatStatus(state, format) {
1803
1804
  const summary = p.phase.summary ?? truncate(p.phase.description, 80);
1804
1805
  lines.push(`${indicator} **${escapeMarkdownInline(p.phase.name)}** (${p.leafCount} tickets) \u2014 ${escapeMarkdownInline(summary)}`);
1805
1806
  }
1807
+ if (activeSessions.length > 0) {
1808
+ lines.push("");
1809
+ lines.push("## Active Sessions");
1810
+ lines.push("");
1811
+ for (const s of activeSessions) {
1812
+ const ticket = s.ticketId ? `${s.ticketId}: ${escapeMarkdownInline(s.ticketTitle ?? "")}` : "no ticket";
1813
+ lines.push(`- ${s.sessionId.slice(0, 8)}: ${s.state} -- ${ticket} (${s.mode} mode)`);
1814
+ }
1815
+ }
1806
1816
  if (state.isEmptyScaffold) {
1807
1817
  lines.push("");
1808
1818
  lines.push(EMPTY_SCAFFOLD_HEADING);
@@ -2691,15 +2701,71 @@ var init_output_formatter = __esm({
2691
2701
  }
2692
2702
  });
2693
2703
 
2704
+ // src/core/session-scan.ts
2705
+ import { readdirSync, readFileSync } from "fs";
2706
+ import { join as join4 } from "path";
2707
+ function scanActiveSessions(root) {
2708
+ const sessDir = join4(root, ".story", "sessions");
2709
+ let entries;
2710
+ try {
2711
+ entries = readdirSync(sessDir, { withFileTypes: true });
2712
+ } catch {
2713
+ return [];
2714
+ }
2715
+ const results = [];
2716
+ for (const entry of entries) {
2717
+ if (!entry.isDirectory()) continue;
2718
+ const statePath2 = join4(sessDir, entry.name, "state.json");
2719
+ let raw;
2720
+ try {
2721
+ raw = readFileSync(statePath2, "utf-8");
2722
+ } catch {
2723
+ continue;
2724
+ }
2725
+ let parsed;
2726
+ try {
2727
+ parsed = JSON.parse(raw);
2728
+ } catch {
2729
+ continue;
2730
+ }
2731
+ if (parsed.status !== "active") continue;
2732
+ if (parsed.state === "SESSION_END") continue;
2733
+ const lease = parsed.lease;
2734
+ if (lease?.expiresAt) {
2735
+ const expires = new Date(lease.expiresAt).getTime();
2736
+ if (!Number.isNaN(expires) && expires <= Date.now()) continue;
2737
+ } else {
2738
+ continue;
2739
+ }
2740
+ const ticket = parsed.ticket;
2741
+ results.push({
2742
+ sessionId: parsed.sessionId ?? entry.name,
2743
+ state: parsed.state ?? "unknown",
2744
+ mode: parsed.mode ?? "auto",
2745
+ ticketId: ticket?.id ?? null,
2746
+ ticketTitle: ticket?.title ?? null
2747
+ });
2748
+ }
2749
+ return results;
2750
+ }
2751
+ var init_session_scan = __esm({
2752
+ "src/core/session-scan.ts"() {
2753
+ "use strict";
2754
+ init_esm_shims();
2755
+ }
2756
+ });
2757
+
2694
2758
  // src/cli/commands/status.ts
2695
2759
  function handleStatus(ctx) {
2696
- return { output: formatStatus(ctx.state, ctx.format) };
2760
+ const sessions = scanActiveSessions(ctx.root);
2761
+ return { output: formatStatus(ctx.state, ctx.format, sessions) };
2697
2762
  }
2698
2763
  var init_status = __esm({
2699
2764
  "src/cli/commands/status.ts"() {
2700
2765
  "use strict";
2701
2766
  init_esm_shims();
2702
2767
  init_output_formatter();
2768
+ init_session_scan();
2703
2769
  }
2704
2770
  });
2705
2771
 
@@ -2787,6 +2853,7 @@ function validateProject(state) {
2787
2853
  }
2788
2854
  }
2789
2855
  }
2856
+ detectSupersedesCycles(state, findings);
2790
2857
  const phaseIDCounts = /* @__PURE__ */ new Map();
2791
2858
  for (const p of state.roadmap.phases) {
2792
2859
  phaseIDCounts.set(p.id, (phaseIDCounts.get(p.id) ?? 0) + 1);
@@ -2991,6 +3058,33 @@ function dfsBlocked(id, state, visited, inStack, findings) {
2991
3058
  inStack.delete(id);
2992
3059
  visited.add(id);
2993
3060
  }
3061
+ function detectSupersedesCycles(state, findings) {
3062
+ const visited = /* @__PURE__ */ new Set();
3063
+ const inStack = /* @__PURE__ */ new Set();
3064
+ for (const l of state.lessons) {
3065
+ if (l.supersedes == null || visited.has(l.id)) continue;
3066
+ dfsSupersedesChain(l.id, state, visited, inStack, findings);
3067
+ }
3068
+ }
3069
+ function dfsSupersedesChain(id, state, visited, inStack, findings) {
3070
+ if (inStack.has(id)) {
3071
+ findings.push({
3072
+ level: "error",
3073
+ code: "supersedes_cycle",
3074
+ message: `Cycle detected in supersedes chain involving ${id}.`,
3075
+ entity: id
3076
+ });
3077
+ return;
3078
+ }
3079
+ if (visited.has(id)) return;
3080
+ inStack.add(id);
3081
+ const lesson = state.lessonByID(id);
3082
+ if (lesson?.supersedes && lesson.supersedes !== id) {
3083
+ dfsSupersedesChain(lesson.supersedes, state, visited, inStack, findings);
3084
+ }
3085
+ inStack.delete(id);
3086
+ visited.add(id);
3087
+ }
2994
3088
  var init_validation = __esm({
2995
3089
  "src/core/validation.ts"() {
2996
3090
  "use strict";
@@ -3027,7 +3121,7 @@ __export(handover_exports, {
3027
3121
  });
3028
3122
  import { existsSync as existsSync4 } from "fs";
3029
3123
  import { mkdir as mkdir2 } from "fs/promises";
3030
- import { join as join4, resolve as resolve4 } from "path";
3124
+ import { join as join5, resolve as resolve4 } from "path";
3031
3125
  function handleHandoverList(ctx) {
3032
3126
  return { output: formatHandoverList(ctx.state.handoverFilenames, ctx.format) };
3033
3127
  }
@@ -3112,15 +3206,15 @@ async function handleHandoverCreate(content, slugRaw, format, root) {
3112
3206
  let filename;
3113
3207
  await withProjectLock(root, { strict: false }, async () => {
3114
3208
  const absRoot = resolve4(root);
3115
- const handoversDir = join4(absRoot, ".story", "handovers");
3209
+ const handoversDir = join5(absRoot, ".story", "handovers");
3116
3210
  await mkdir2(handoversDir, { recursive: true });
3117
- const wrapDir = join4(absRoot, ".story");
3211
+ const wrapDir = join5(absRoot, ".story");
3118
3212
  const datePrefix = `${date}-`;
3119
3213
  const seqRegex = new RegExp(`^${date}-(\\d{2})-`);
3120
3214
  let maxSeq = 0;
3121
- const { readdirSync: readdirSync2 } = await import("fs");
3215
+ const { readdirSync: readdirSync5 } = await import("fs");
3122
3216
  try {
3123
- for (const f of readdirSync2(handoversDir)) {
3217
+ for (const f of readdirSync5(handoversDir)) {
3124
3218
  const m = f.match(seqRegex);
3125
3219
  if (m) {
3126
3220
  const n = parseInt(m[1], 10);
@@ -3137,7 +3231,7 @@ async function handleHandoverCreate(content, slugRaw, format, root) {
3137
3231
  );
3138
3232
  }
3139
3233
  let candidate = `${date}-${String(nextSeq).padStart(2, "0")}-${slug}.md`;
3140
- let candidatePath = join4(handoversDir, candidate);
3234
+ let candidatePath = join5(handoversDir, candidate);
3141
3235
  while (existsSync4(candidatePath)) {
3142
3236
  nextSeq++;
3143
3237
  if (nextSeq > 99) {
@@ -3147,7 +3241,7 @@ async function handleHandoverCreate(content, slugRaw, format, root) {
3147
3241
  );
3148
3242
  }
3149
3243
  candidate = `${date}-${String(nextSeq).padStart(2, "0")}-${slug}.md`;
3150
- candidatePath = join4(handoversDir, candidate);
3244
+ candidatePath = join5(handoversDir, candidate);
3151
3245
  }
3152
3246
  await parseHandoverFilename(candidate, handoversDir);
3153
3247
  await guardPath(candidatePath, wrapDir);
@@ -3780,11 +3874,11 @@ __export(snapshot_exports, {
3780
3874
  });
3781
3875
  import { readdir as readdir3, readFile as readFile3, mkdir as mkdir3, unlink as unlink2 } from "fs/promises";
3782
3876
  import { existsSync as existsSync5 } from "fs";
3783
- import { join as join5, resolve as resolve5 } from "path";
3877
+ import { join as join6, resolve as resolve5 } from "path";
3784
3878
  import { z as z8 } from "zod";
3785
3879
  async function saveSnapshot(root, loadResult) {
3786
3880
  const absRoot = resolve5(root);
3787
- const snapshotsDir = join5(absRoot, ".story", "snapshots");
3881
+ const snapshotsDir = join6(absRoot, ".story", "snapshots");
3788
3882
  await mkdir3(snapshotsDir, { recursive: true });
3789
3883
  const { state, warnings } = loadResult;
3790
3884
  const now = /* @__PURE__ */ new Date();
@@ -3809,8 +3903,8 @@ async function saveSnapshot(root, loadResult) {
3809
3903
  } : {}
3810
3904
  };
3811
3905
  const json = JSON.stringify(snapshot, null, 2) + "\n";
3812
- const targetPath = join5(snapshotsDir, filename);
3813
- const wrapDir = join5(absRoot, ".story");
3906
+ const targetPath = join6(snapshotsDir, filename);
3907
+ const wrapDir = join6(absRoot, ".story");
3814
3908
  await guardPath(targetPath, wrapDir);
3815
3909
  await atomicWrite(targetPath, json);
3816
3910
  const pruned = await pruneSnapshots(snapshotsDir);
@@ -3818,13 +3912,13 @@ async function saveSnapshot(root, loadResult) {
3818
3912
  return { filename, retained: entries.length, pruned };
3819
3913
  }
3820
3914
  async function loadLatestSnapshot(root) {
3821
- const snapshotsDir = join5(resolve5(root), ".story", "snapshots");
3915
+ const snapshotsDir = join6(resolve5(root), ".story", "snapshots");
3822
3916
  if (!existsSync5(snapshotsDir)) return null;
3823
3917
  const files = await listSnapshotFiles(snapshotsDir);
3824
3918
  if (files.length === 0) return null;
3825
3919
  for (const filename of files) {
3826
3920
  try {
3827
- const content = await readFile3(join5(snapshotsDir, filename), "utf-8");
3921
+ const content = await readFile3(join6(snapshotsDir, filename), "utf-8");
3828
3922
  const parsed = JSON.parse(content);
3829
3923
  const snapshot = SnapshotV1Schema.parse(parsed);
3830
3924
  return { snapshot, filename };
@@ -4068,7 +4162,7 @@ async function pruneSnapshots(dir) {
4068
4162
  const toRemove = files.slice(MAX_SNAPSHOTS);
4069
4163
  for (const f of toRemove) {
4070
4164
  try {
4071
- await unlink2(join5(dir, f));
4165
+ await unlink2(join6(dir, f));
4072
4166
  } catch {
4073
4167
  }
4074
4168
  }
@@ -4487,7 +4581,7 @@ var init_lesson2 = __esm({
4487
4581
  });
4488
4582
 
4489
4583
  // src/core/recommend.ts
4490
- function recommend(state, count) {
4584
+ function recommend(state, count, options) {
4491
4585
  const effectiveCount = Math.max(1, Math.min(10, count));
4492
4586
  const dedup = /* @__PURE__ */ new Map();
4493
4587
  const phaseIndex = buildPhaseIndex(state);
@@ -4499,7 +4593,8 @@ function recommend(state, count) {
4499
4593
  () => generateNearCompleteUmbrellas(state, phaseIndex),
4500
4594
  () => generatePhaseMomentum(state),
4501
4595
  () => generateQuickWins(state, phaseIndex),
4502
- () => generateOpenIssues(state)
4596
+ () => generateOpenIssues(state),
4597
+ () => generateDebtTrend(state, options)
4503
4598
  ];
4504
4599
  for (const gen of generators) {
4505
4600
  for (const rec of gen()) {
@@ -4509,6 +4604,7 @@ function recommend(state, count) {
4509
4604
  }
4510
4605
  }
4511
4606
  }
4607
+ applyHandoverBoost(state, dedup, options);
4512
4608
  const curPhase = currentPhase(state);
4513
4609
  const curPhaseIdx = curPhase ? phaseIndex.get(curPhase.id) ?? 0 : 0;
4514
4610
  for (const [id, rec] of dedup) {
@@ -4698,7 +4794,76 @@ function sortByPhaseAndOrder(tickets, phaseIndex) {
4698
4794
  return a.order - b.order;
4699
4795
  });
4700
4796
  }
4701
- var SEVERITY_RANK, PHASE_DISTANCE_PENALTY, MAX_PHASE_PENALTY, CATEGORY_PRIORITY;
4797
+ function applyHandoverBoost(state, dedup, options) {
4798
+ if (!options?.latestHandoverContent) return;
4799
+ const content = options.latestHandoverContent;
4800
+ let actionableIds = extractTicketIdsFromActionableSections(content);
4801
+ if (actionableIds.size === 0) {
4802
+ const allIds = new Set(content.match(TICKET_ID_RE) ?? []);
4803
+ for (const id of allIds) {
4804
+ const ticket = state.ticketByID(id);
4805
+ if (ticket && ticket.status !== "complete" && ticket.status !== "inprogress") {
4806
+ actionableIds.add(id);
4807
+ }
4808
+ }
4809
+ }
4810
+ for (const id of actionableIds) {
4811
+ const ticket = state.ticketByID(id);
4812
+ if (!ticket || ticket.status === "complete") continue;
4813
+ const existing = dedup.get(id);
4814
+ if (existing) {
4815
+ dedup.set(id, {
4816
+ ...existing,
4817
+ score: existing.score + HANDOVER_BOOST,
4818
+ reason: existing.reason + " (handover context)"
4819
+ });
4820
+ } else {
4821
+ dedup.set(id, {
4822
+ id,
4823
+ kind: "ticket",
4824
+ title: ticket.title,
4825
+ category: "handover_context",
4826
+ reason: "Referenced in latest handover",
4827
+ score: HANDOVER_BASE_SCORE
4828
+ });
4829
+ }
4830
+ }
4831
+ }
4832
+ function extractTicketIdsFromActionableSections(content) {
4833
+ const ids = /* @__PURE__ */ new Set();
4834
+ const lines = content.split("\n");
4835
+ let inActionable = false;
4836
+ for (const line of lines) {
4837
+ if (/^#+\s/.test(line)) {
4838
+ inActionable = ACTIONABLE_HEADING_RE.test(line);
4839
+ }
4840
+ if (inActionable) {
4841
+ const matches = line.match(TICKET_ID_RE);
4842
+ if (matches) for (const m of matches) ids.add(m);
4843
+ }
4844
+ }
4845
+ return ids;
4846
+ }
4847
+ function generateDebtTrend(state, options) {
4848
+ if (options?.previousOpenIssueCount == null) return [];
4849
+ const currentOpen = state.issues.filter((i) => i.status !== "resolved").length;
4850
+ const previous = options.previousOpenIssueCount;
4851
+ if (previous <= 0) return [];
4852
+ const growth = (currentOpen - previous) / previous;
4853
+ const absolute = currentOpen - previous;
4854
+ if (growth > DEBT_GROWTH_THRESHOLD && absolute >= DEBT_ABSOLUTE_MINIMUM) {
4855
+ return [{
4856
+ id: "DEBT_TREND",
4857
+ kind: "action",
4858
+ title: "Issue debt growing",
4859
+ category: "debt_trend",
4860
+ reason: `Open issues grew from ${previous} to ${currentOpen} (+${Math.round(growth * 100)}%). Consider triaging or resolving issues before adding features.`,
4861
+ score: DEBT_TREND_SCORE
4862
+ }];
4863
+ }
4864
+ return [];
4865
+ }
4866
+ var SEVERITY_RANK, PHASE_DISTANCE_PENALTY, MAX_PHASE_PENALTY, CATEGORY_PRIORITY, TICKET_ID_RE, ACTIONABLE_HEADING_RE, HANDOVER_BOOST, HANDOVER_BASE_SCORE, DEBT_TREND_SCORE, DEBT_GROWTH_THRESHOLD, DEBT_ABSOLUTE_MINIMUM;
4702
4867
  var init_recommend = __esm({
4703
4868
  "src/core/recommend.ts"() {
4704
4869
  "use strict";
@@ -4720,17 +4885,52 @@ var init_recommend = __esm({
4720
4885
  high_impact_unblock: 4,
4721
4886
  near_complete_umbrella: 5,
4722
4887
  phase_momentum: 6,
4723
- quick_win: 7,
4724
- open_issue: 8
4888
+ debt_trend: 7,
4889
+ quick_win: 8,
4890
+ handover_context: 9,
4891
+ open_issue: 10
4725
4892
  };
4893
+ TICKET_ID_RE = /\bT-\d{3}[a-z]?\b/g;
4894
+ ACTIONABLE_HEADING_RE = /^#+\s.*(next|open|remaining|todo|blocked)/im;
4895
+ HANDOVER_BOOST = 50;
4896
+ HANDOVER_BASE_SCORE = 350;
4897
+ DEBT_TREND_SCORE = 450;
4898
+ DEBT_GROWTH_THRESHOLD = 0.25;
4899
+ DEBT_ABSOLUTE_MINIMUM = 2;
4726
4900
  }
4727
4901
  });
4728
4902
 
4729
4903
  // src/cli/commands/recommend.ts
4904
+ import { readFileSync as readFileSync2, readdirSync as readdirSync2 } from "fs";
4905
+ import { join as join7 } from "path";
4730
4906
  function handleRecommend(ctx, count) {
4731
- const result = recommend(ctx.state, count);
4907
+ const options = buildRecommendOptions(ctx);
4908
+ const result = recommend(ctx.state, count, options);
4732
4909
  return { output: formatRecommendations(result, ctx.state, ctx.format) };
4733
4910
  }
4911
+ function buildRecommendOptions(ctx) {
4912
+ const opts = {};
4913
+ try {
4914
+ const files = readdirSync2(ctx.handoversDir).filter((f) => f.endsWith(".md")).sort();
4915
+ if (files.length > 0) {
4916
+ opts.latestHandoverContent = readFileSync2(join7(ctx.handoversDir, files[files.length - 1]), "utf-8");
4917
+ }
4918
+ } catch {
4919
+ }
4920
+ try {
4921
+ const snapshotsDir = join7(ctx.root, ".story", "snapshots");
4922
+ const snapFiles = readdirSync2(snapshotsDir).filter((f) => f.endsWith(".json")).sort();
4923
+ if (snapFiles.length > 0) {
4924
+ const raw = readFileSync2(join7(snapshotsDir, snapFiles[snapFiles.length - 1]), "utf-8");
4925
+ const snap = JSON.parse(raw);
4926
+ if (snap.issues) {
4927
+ opts.previousOpenIssueCount = snap.issues.filter((i) => i.status !== "resolved").length;
4928
+ }
4929
+ }
4930
+ } catch {
4931
+ }
4932
+ return opts;
4933
+ }
4734
4934
  var init_recommend2 = __esm({
4735
4935
  "src/cli/commands/recommend.ts"() {
4736
4936
  "use strict";
@@ -5066,6 +5266,7 @@ var init_session_types = __esm({
5066
5266
  "FINALIZE",
5067
5267
  "COMPACT",
5068
5268
  "LESSON_CAPTURE",
5269
+ "ISSUE_FIX",
5069
5270
  "ISSUE_SWEEP"
5070
5271
  ]);
5071
5272
  IDLE_STATES = /* @__PURE__ */ new Set([
@@ -5094,6 +5295,7 @@ var init_session_types = __esm({
5094
5295
  "HANDOVER",
5095
5296
  "COMPLETE",
5096
5297
  "LESSON_CAPTURE",
5298
+ "ISSUE_FIX",
5097
5299
  "ISSUE_SWEEP",
5098
5300
  "SESSION_END"
5099
5301
  ];
@@ -5142,6 +5344,14 @@ var init_session_types = __esm({
5142
5344
  timestamp: z9.string()
5143
5345
  })).default([])
5144
5346
  }).default({ plan: [], code: [] }),
5347
+ // T-153: Current issue being fixed (null when working on a ticket)
5348
+ currentIssue: z9.object({
5349
+ id: z9.string(),
5350
+ title: z9.string(),
5351
+ severity: z9.string()
5352
+ }).nullable().default(null),
5353
+ // T-153: Issues resolved this session
5354
+ resolvedIssues: z9.array(z9.string()).default([]),
5145
5355
  // Completed tickets this session
5146
5356
  completedTickets: z9.array(z9.object({
5147
5357
  id: z9.string(),
@@ -5280,27 +5490,27 @@ __export(session_exports, {
5280
5490
  import { randomUUID } from "crypto";
5281
5491
  import {
5282
5492
  mkdirSync,
5283
- readdirSync,
5284
- readFileSync,
5493
+ readdirSync as readdirSync3,
5494
+ readFileSync as readFileSync3,
5285
5495
  writeFileSync,
5286
5496
  renameSync,
5287
5497
  unlinkSync,
5288
5498
  existsSync as existsSync6,
5289
5499
  rmSync
5290
5500
  } from "fs";
5291
- import { join as join6 } from "path";
5501
+ import { join as join8 } from "path";
5292
5502
  import lockfile2 from "proper-lockfile";
5293
5503
  function sessionsRoot(root) {
5294
- return join6(root, ".story", SESSIONS_DIR);
5504
+ return join8(root, ".story", SESSIONS_DIR);
5295
5505
  }
5296
5506
  function sessionDir(root, sessionId) {
5297
- return join6(sessionsRoot(root), sessionId);
5507
+ return join8(sessionsRoot(root), sessionId);
5298
5508
  }
5299
5509
  function statePath(dir) {
5300
- return join6(dir, "state.json");
5510
+ return join8(dir, "state.json");
5301
5511
  }
5302
5512
  function eventsPath(dir) {
5303
- return join6(dir, "events.log");
5513
+ return join8(dir, "events.log");
5304
5514
  }
5305
5515
  function createSession(root, recipe, workspaceId, configOverrides) {
5306
5516
  const id = randomUUID();
@@ -5356,7 +5566,7 @@ function readSession(dir) {
5356
5566
  const path2 = statePath(dir);
5357
5567
  let raw;
5358
5568
  try {
5359
- raw = readFileSync(path2, "utf-8");
5569
+ raw = readFileSync3(path2, "utf-8");
5360
5570
  } catch {
5361
5571
  return null;
5362
5572
  }
@@ -5399,7 +5609,7 @@ function readEvents(dir) {
5399
5609
  const path2 = eventsPath(dir);
5400
5610
  let raw;
5401
5611
  try {
5402
- raw = readFileSync(path2, "utf-8");
5612
+ raw = readFileSync3(path2, "utf-8");
5403
5613
  } catch {
5404
5614
  return { events: [], malformedCount: 0 };
5405
5615
  }
@@ -5456,7 +5666,7 @@ function findActiveSessionFull(root) {
5456
5666
  const sessDir = sessionsRoot(root);
5457
5667
  let entries;
5458
5668
  try {
5459
- entries = readdirSync(sessDir, { withFileTypes: true });
5669
+ entries = readdirSync3(sessDir, { withFileTypes: true });
5460
5670
  } catch {
5461
5671
  return null;
5462
5672
  }
@@ -5470,7 +5680,7 @@ function findActiveSessionFull(root) {
5470
5680
  let bestGuideCall = 0;
5471
5681
  for (const entry of entries) {
5472
5682
  if (!entry.isDirectory()) continue;
5473
- const dir = join6(sessDir, entry.name);
5683
+ const dir = join8(sessDir, entry.name);
5474
5684
  const session = readSession(dir);
5475
5685
  if (!session) continue;
5476
5686
  if (session.status !== "active") continue;
@@ -5493,7 +5703,7 @@ function findStaleSessions(root) {
5493
5703
  const sessDir = sessionsRoot(root);
5494
5704
  let entries;
5495
5705
  try {
5496
- entries = readdirSync(sessDir, { withFileTypes: true });
5706
+ entries = readdirSync3(sessDir, { withFileTypes: true });
5497
5707
  } catch {
5498
5708
  return [];
5499
5709
  }
@@ -5506,7 +5716,7 @@ function findStaleSessions(root) {
5506
5716
  const results = [];
5507
5717
  for (const entry of entries) {
5508
5718
  if (!entry.isDirectory()) continue;
5509
- const dir = join6(sessDir, entry.name);
5719
+ const dir = join8(sessDir, entry.name);
5510
5720
  const session = readSession(dir);
5511
5721
  if (!session) continue;
5512
5722
  if (session.status !== "active") continue;
@@ -5562,7 +5772,7 @@ function findResumableSession(root) {
5562
5772
  const sessDir = sessionsRoot(root);
5563
5773
  let entries;
5564
5774
  try {
5565
- entries = readdirSync(sessDir, { withFileTypes: true });
5775
+ entries = readdirSync3(sessDir, { withFileTypes: true });
5566
5776
  } catch {
5567
5777
  return null;
5568
5778
  }
@@ -5577,7 +5787,7 @@ function findResumableSession(root) {
5577
5787
  let bestPreparedAt = 0;
5578
5788
  for (const entry of entries) {
5579
5789
  if (!entry.isDirectory()) continue;
5580
- const dir = join6(sessDir, entry.name);
5790
+ const dir = join8(sessDir, entry.name);
5581
5791
  const session = readSession(dir);
5582
5792
  if (!session) continue;
5583
5793
  if (session.status !== "active") continue;
@@ -5601,7 +5811,7 @@ async function withSessionLock(root, fn) {
5601
5811
  release = await lockfile2.lock(sessDir, {
5602
5812
  retries: { retries: 3, minTimeout: 100, maxTimeout: 1e3 },
5603
5813
  stale: 3e4,
5604
- lockfilePath: join6(sessDir, ".lock")
5814
+ lockfilePath: join8(sessDir, ".lock")
5605
5815
  });
5606
5816
  return await fn();
5607
5817
  } finally {
@@ -5645,7 +5855,7 @@ var init_state_machine = __esm({
5645
5855
  // start does INIT + LOAD_CONTEXT internally
5646
5856
  LOAD_CONTEXT: ["PICK_TICKET"],
5647
5857
  // internal (never seen by Claude)
5648
- PICK_TICKET: ["PLAN", "SESSION_END"],
5858
+ PICK_TICKET: ["PLAN", "ISSUE_FIX", "SESSION_END"],
5649
5859
  PLAN: ["PLAN_REVIEW"],
5650
5860
  PLAN_REVIEW: ["IMPLEMENT", "WRITE_TESTS", "PLAN", "PLAN_REVIEW", "SESSION_END"],
5651
5861
  // approve → IMPLEMENT/WRITE_TESTS, reject → PLAN, stay for next round; SESSION_END for tiered exit
@@ -5659,8 +5869,11 @@ var init_state_machine = __esm({
5659
5869
  // approve → VERIFY/FINALIZE, reject → IMPLEMENT/PLAN, stay for next round; SESSION_END for tiered exit
5660
5870
  VERIFY: ["FINALIZE", "IMPLEMENT", "VERIFY"],
5661
5871
  // pass → FINALIZE, fail → IMPLEMENT, retry
5662
- FINALIZE: ["COMPLETE"],
5872
+ FINALIZE: ["COMPLETE", "PICK_TICKET"],
5873
+ // PICK_TICKET for issue-fix flow (bypass COMPLETE)
5663
5874
  COMPLETE: ["PICK_TICKET", "HANDOVER", "ISSUE_SWEEP", "SESSION_END"],
5875
+ ISSUE_FIX: ["FINALIZE", "PICK_TICKET", "ISSUE_FIX"],
5876
+ // T-153: fix done → FINALIZE, cancel → PICK_TICKET, retry self
5664
5877
  LESSON_CAPTURE: ["ISSUE_SWEEP", "HANDOVER", "LESSON_CAPTURE"],
5665
5878
  // advance → ISSUE_SWEEP, retry self, done → HANDOVER
5666
5879
  ISSUE_SWEEP: ["ISSUE_SWEEP", "HANDOVER", "PICK_TICKET"],
@@ -5917,16 +6130,16 @@ var init_git_inspector = __esm({
5917
6130
  });
5918
6131
 
5919
6132
  // src/autonomous/recipes/loader.ts
5920
- import { readFileSync as readFileSync2 } from "fs";
5921
- import { join as join7, dirname as dirname3 } from "path";
6133
+ import { readFileSync as readFileSync4 } from "fs";
6134
+ import { join as join9, dirname as dirname3 } from "path";
5922
6135
  import { fileURLToPath as fileURLToPath2 } from "url";
5923
6136
  function loadRecipe(recipeName) {
5924
6137
  if (!/^[A-Za-z0-9_-]+$/.test(recipeName)) {
5925
6138
  throw new Error(`Invalid recipe name: ${recipeName}`);
5926
6139
  }
5927
- const recipesDir = join7(dirname3(fileURLToPath2(import.meta.url)), "..", "recipes");
5928
- const path2 = join7(recipesDir, `${recipeName}.json`);
5929
- const raw = readFileSync2(path2, "utf-8");
6140
+ const recipesDir = join9(dirname3(fileURLToPath2(import.meta.url)), "..", "recipes");
6141
+ const path2 = join9(recipesDir, `${recipeName}.json`);
6142
+ const raw = readFileSync4(path2, "utf-8");
5930
6143
  return JSON.parse(raw);
5931
6144
  }
5932
6145
  function resolveRecipe(recipeName, projectOverrides) {
@@ -6202,7 +6415,7 @@ var init_types2 = __esm({
6202
6415
 
6203
6416
  // src/autonomous/stages/pick-ticket.ts
6204
6417
  import { existsSync as existsSync7, unlinkSync as unlinkSync2 } from "fs";
6205
- import { join as join8 } from "path";
6418
+ import { join as join10 } from "path";
6206
6419
  var PickTicketStage;
6207
6420
  var init_pick_ticket = __esm({
6208
6421
  "src/autonomous/stages/pick-ticket.ts"() {
@@ -6220,28 +6433,52 @@ var init_pick_ticket = __esm({
6220
6433
  (c, i) => `${i + 1}. **${c.ticket.id}: ${c.ticket.title}** (${c.ticket.type})`
6221
6434
  ).join("\n");
6222
6435
  }
6436
+ const highIssues = projectState.issues.filter(
6437
+ (i) => i.status === "open" && (i.severity === "critical" || i.severity === "high")
6438
+ );
6439
+ let issuesText = "";
6440
+ if (highIssues.length > 0) {
6441
+ issuesText = "\n\n## Open Issues (high+ severity)\n\n" + highIssues.map(
6442
+ (i, idx) => `${idx + 1}. **${i.id}: ${i.title}** (${i.severity})`
6443
+ ).join("\n");
6444
+ }
6223
6445
  const topCandidate = candidates.kind === "found" ? candidates.candidates[0] : null;
6446
+ const hasIssues = highIssues.length > 0;
6224
6447
  return {
6225
6448
  instruction: [
6226
- "# Pick a Ticket",
6449
+ "# Pick a Ticket or Issue",
6450
+ "",
6451
+ "## Ticket Candidates",
6227
6452
  "",
6228
6453
  candidatesText || "No ticket candidates found.",
6454
+ issuesText,
6229
6455
  "",
6230
- topCandidate ? `Pick **${topCandidate.ticket.id}** (highest priority) by calling \`claudestory_autonomous_guide\` now:` : "Pick a ticket by calling `claudestory_autonomous_guide` now:",
6456
+ topCandidate ? `Pick **${topCandidate.ticket.id}** (highest priority) or an open issue by calling \`claudestory_autonomous_guide\` now:` : hasIssues ? `Pick an issue to fix by calling \`claudestory_autonomous_guide\` now:` : "Pick a ticket by calling `claudestory_autonomous_guide` now:",
6231
6457
  "```json",
6232
6458
  topCandidate ? `{ "sessionId": "${ctx.state.sessionId}", "action": "report", "report": { "completedAction": "ticket_picked", "ticketId": "${topCandidate.ticket.id}" } }` : `{ "sessionId": "${ctx.state.sessionId}", "action": "report", "report": { "completedAction": "ticket_picked", "ticketId": "T-XXX" } }`,
6233
- "```"
6459
+ "```",
6460
+ ...hasIssues ? [
6461
+ "",
6462
+ "Or to fix an issue:",
6463
+ "```json",
6464
+ `{ "sessionId": "${ctx.state.sessionId}", "action": "report", "report": { "completedAction": "issue_picked", "issueId": "${highIssues[0].id}" } }`,
6465
+ "```"
6466
+ ] : []
6234
6467
  ].join("\n"),
6235
6468
  reminders: [
6236
- "Do NOT stop or summarize. Call autonomous_guide IMMEDIATELY to pick a ticket.",
6469
+ "Do NOT stop or summarize. Call autonomous_guide IMMEDIATELY to pick a ticket or issue.",
6237
6470
  "Do NOT ask the user for confirmation."
6238
6471
  ]
6239
6472
  };
6240
6473
  }
6241
6474
  async report(ctx, report) {
6475
+ const issueId = report.issueId;
6476
+ if (issueId) {
6477
+ return this.handleIssuePick(ctx, issueId);
6478
+ }
6242
6479
  const ticketId = report.ticketId;
6243
6480
  if (!ticketId) {
6244
- return { action: "retry", instruction: "report.ticketId is required when picking a ticket." };
6481
+ return { action: "retry", instruction: "report.ticketId or report.issueId is required." };
6245
6482
  }
6246
6483
  const { state: projectState } = await ctx.loadProject();
6247
6484
  const ticket = projectState.ticketByID(ticketId);
@@ -6257,7 +6494,7 @@ var init_pick_ticket = __esm({
6257
6494
  return { action: "retry", instruction: `Ticket ${ticketId} is ${ticket.status} \u2014 pick an open ticket.` };
6258
6495
  }
6259
6496
  }
6260
- const planPath = join8(ctx.dir, "plan.md");
6497
+ const planPath = join10(ctx.dir, "plan.md");
6261
6498
  try {
6262
6499
  if (existsSync7(planPath)) unlinkSync2(planPath);
6263
6500
  } catch {
@@ -6292,16 +6529,34 @@ ${ticket.description}` : "",
6292
6529
  }
6293
6530
  };
6294
6531
  }
6532
+ // T-153: Handle issue pick -- validate and route to ISSUE_FIX
6533
+ async handleIssuePick(ctx, issueId) {
6534
+ const { state: projectState } = await ctx.loadProject();
6535
+ const issue = projectState.issues.find((i) => i.id === issueId);
6536
+ if (!issue) {
6537
+ return { action: "retry", instruction: `Issue ${issueId} not found. Pick a valid issue or ticket.` };
6538
+ }
6539
+ if (issue.status !== "open") {
6540
+ return { action: "retry", instruction: `Issue ${issueId} is ${issue.status}. Pick an open issue.` };
6541
+ }
6542
+ ctx.updateDraft({
6543
+ currentIssue: { id: issue.id, title: issue.title, severity: issue.severity },
6544
+ ticket: void 0,
6545
+ reviews: { plan: [], code: [] },
6546
+ finalizeCheckpoint: null
6547
+ });
6548
+ return { action: "goto", target: "ISSUE_FIX" };
6549
+ }
6295
6550
  };
6296
6551
  }
6297
6552
  });
6298
6553
 
6299
6554
  // src/autonomous/stages/plan.ts
6300
- import { existsSync as existsSync8, readFileSync as readFileSync3 } from "fs";
6301
- import { join as join9 } from "path";
6555
+ import { existsSync as existsSync8, readFileSync as readFileSync5 } from "fs";
6556
+ import { join as join11 } from "path";
6302
6557
  function readFileSafe(path2) {
6303
6558
  try {
6304
- return readFileSync3(path2, "utf-8");
6559
+ return readFileSync5(path2, "utf-8");
6305
6560
  } catch {
6306
6561
  return "";
6307
6562
  }
@@ -6342,7 +6597,7 @@ var init_plan = __esm({
6342
6597
  };
6343
6598
  }
6344
6599
  async report(ctx, _report) {
6345
- const planPath = join9(ctx.dir, "plan.md");
6600
+ const planPath = join11(ctx.dir, "plan.md");
6346
6601
  if (!existsSync8(planPath)) {
6347
6602
  return { action: "retry", instruction: `Plan file not found at ${planPath}. Write your plan there and call me again.`, reminders: ["Save plan to .story/sessions/<id>/plan.md"] };
6348
6603
  }
@@ -6588,7 +6843,8 @@ var init_implement = __esm({
6588
6843
  reminders: [
6589
6844
  "Follow the plan exactly. Do NOT deviate without re-planning.",
6590
6845
  "Do NOT ask the user for confirmation.",
6591
- "If you discover pre-existing bugs, failing tests not caused by your changes, or other out-of-scope problems, file them as issues using claudestory_issue_create. Do not fix them inline."
6846
+ "If you discover pre-existing bugs, failing tests not caused by your changes, or other out-of-scope problems, file them as issues using claudestory_issue_create. Do not fix them inline.",
6847
+ "Track which files you create or modify. Only these files should be staged at commit time."
6592
6848
  ],
6593
6849
  transitionedFrom: ctx.state.previousState ?? void 0
6594
6850
  };
@@ -7285,10 +7541,13 @@ var init_finalize = __esm({
7285
7541
  "Code review passed. Time to commit.",
7286
7542
  "",
7287
7543
  ctx.state.ticket ? `1. Update ticket ${ctx.state.ticket.id} status to "complete" in .story/` : "",
7288
- "2. Stage all changed files (code + .story/ changes)",
7544
+ ctx.state.currentIssue ? `1. Ensure issue ${ctx.state.currentIssue.id} status is "resolved" in .story/issues/` : "",
7545
+ "2. Stage only the files you created or modified for this work (code + .story/ changes). Do NOT use `git add -A` or `git add .`",
7289
7546
  '3. Call me with completedAction: "files_staged"'
7290
7547
  ].filter(Boolean).join("\n"),
7291
- reminders: ["Stage both code changes and .story/ ticket update in the same commit."],
7548
+ reminders: [
7549
+ ctx.state.currentIssue ? "Stage both code changes and .story/ issue update in the same commit. Only stage files related to this fix." : "Stage both code changes and .story/ ticket update in the same commit. Only stage files related to this ticket."
7550
+ ],
7292
7551
  transitionedFrom: ctx.state.previousState ?? void 0
7293
7552
  };
7294
7553
  }
@@ -7331,9 +7590,9 @@ var init_finalize = __esm({
7331
7590
  const headResult = await gitHead(ctx.root);
7332
7591
  const previousHead = ctx.state.git.expectedHead ?? ctx.state.git.initHead;
7333
7592
  if (headResult.ok && previousHead && headResult.data.hash !== previousHead) {
7593
+ const treeResult = await gitDiffTreeNames(ctx.root, headResult.data.hash);
7334
7594
  const ticketId2 = ctx.state.ticket?.id;
7335
7595
  if (ticketId2) {
7336
- const treeResult = await gitDiffTreeNames(ctx.root, headResult.data.hash);
7337
7596
  const ticketPath = `.story/tickets/${ticketId2}.json`;
7338
7597
  if (treeResult.ok && !treeResult.data.includes(ticketPath)) {
7339
7598
  return {
@@ -7342,6 +7601,16 @@ var init_finalize = __esm({
7342
7601
  };
7343
7602
  }
7344
7603
  }
7604
+ const earlyIssueId = ctx.state.currentIssue?.id;
7605
+ if (earlyIssueId) {
7606
+ const issuePath = `.story/issues/${earlyIssueId}.json`;
7607
+ if (treeResult.ok && !treeResult.data.includes(issuePath)) {
7608
+ return {
7609
+ action: "retry",
7610
+ instruction: `Commit detected (${headResult.data.hash.slice(0, 7)}) but issue file ${issuePath} is not in the commit. Amend the commit to include it: \`git add ${issuePath} && git commit --amend --no-edit\`, then report completedAction: "commit_done" with the new hash.`
7611
+ };
7612
+ }
7613
+ }
7345
7614
  ctx.writeState({ finalizeCheckpoint: "precommit_passed" });
7346
7615
  return this.handleCommit(ctx, { ...report, commitHash: headResult.data.hash });
7347
7616
  }
@@ -7375,6 +7644,16 @@ var init_finalize = __esm({
7375
7644
  };
7376
7645
  }
7377
7646
  }
7647
+ const issueId = ctx.state.currentIssue?.id;
7648
+ if (issueId) {
7649
+ const issuePath = `.story/issues/${issueId}.json`;
7650
+ if (!stagedResult.data.includes(issuePath)) {
7651
+ return {
7652
+ action: "retry",
7653
+ instruction: `Issue file ${issuePath} is not staged. Run \`git add ${issuePath}\` and call me again with completedAction: "files_staged".`
7654
+ };
7655
+ }
7656
+ }
7378
7657
  ctx.writeState({
7379
7658
  finalizeCheckpoint: overlapOverridden ? "staged_override" : "staged"
7380
7659
  });
@@ -7422,6 +7701,16 @@ var init_finalize = __esm({
7422
7701
  };
7423
7702
  }
7424
7703
  }
7704
+ const precommitIssueId = ctx.state.currentIssue?.id;
7705
+ if (precommitIssueId) {
7706
+ const issuePath = `.story/issues/${precommitIssueId}.json`;
7707
+ if (!stagedResult.data.includes(issuePath)) {
7708
+ return {
7709
+ action: "retry",
7710
+ instruction: `Pre-commit hooks may have modified the staged set. Issue file ${issuePath} is no longer staged. Run \`git add ${issuePath}\` and call me again with completedAction: "files_staged".`
7711
+ };
7712
+ }
7713
+ }
7425
7714
  ctx.writeState({ finalizeCheckpoint: "precommit_passed" });
7426
7715
  return {
7427
7716
  action: "retry",
@@ -7459,6 +7748,21 @@ var init_finalize = __esm({
7459
7748
  if (previousHead && normalizedHash === previousHead) {
7460
7749
  return { action: "retry", instruction: `No new commit detected: HEAD (${normalizedHash}) has not changed. Create a commit first, then report the new hash.` };
7461
7750
  }
7751
+ const currentIssue = ctx.state.currentIssue;
7752
+ if (currentIssue) {
7753
+ ctx.writeState({
7754
+ finalizeCheckpoint: "committed",
7755
+ resolvedIssues: [...ctx.state.resolvedIssues ?? [], currentIssue.id],
7756
+ currentIssue: null,
7757
+ git: {
7758
+ ...ctx.state.git,
7759
+ mergeBase: normalizedHash,
7760
+ expectedHead: normalizedHash
7761
+ }
7762
+ });
7763
+ ctx.appendEvent("commit", { commitHash: normalizedHash, issueId: currentIssue.id });
7764
+ return { action: "goto", target: "PICK_TICKET" };
7765
+ }
7462
7766
  const completedTicket = ctx.state.ticket ? { id: ctx.state.ticket.id, title: ctx.state.ticket.title, commitHash: normalizedHash, risk: ctx.state.ticket.risk, realizedRisk: ctx.state.ticket.realizedRisk } : void 0;
7463
7767
  ctx.writeState({
7464
7768
  finalizeCheckpoint: "committed",
@@ -7683,6 +7987,85 @@ var init_lesson_capture = __esm({
7683
7987
  }
7684
7988
  });
7685
7989
 
7990
+ // src/autonomous/stages/issue-fix.ts
7991
+ var IssueFixStage;
7992
+ var init_issue_fix = __esm({
7993
+ "src/autonomous/stages/issue-fix.ts"() {
7994
+ "use strict";
7995
+ init_esm_shims();
7996
+ IssueFixStage = class {
7997
+ id = "ISSUE_FIX";
7998
+ async enter(ctx) {
7999
+ const issue = ctx.state.currentIssue;
8000
+ if (!issue) {
8001
+ return { action: "goto", target: "PICK_TICKET" };
8002
+ }
8003
+ const { state: projectState } = await ctx.loadProject();
8004
+ const fullIssue = projectState.issues.find((i) => i.id === issue.id);
8005
+ const details = fullIssue ? [
8006
+ `**${fullIssue.id}**: ${fullIssue.title}`,
8007
+ "",
8008
+ `Severity: ${fullIssue.severity}`,
8009
+ fullIssue.impact ? `Impact: ${fullIssue.impact}` : "",
8010
+ fullIssue.components.length > 0 ? `Components: ${fullIssue.components.join(", ")}` : "",
8011
+ fullIssue.location.length > 0 ? `Location: ${fullIssue.location.join(", ")}` : ""
8012
+ ].filter(Boolean).join("\n") : `**${issue.id}**: ${issue.title} (severity: ${issue.severity})`;
8013
+ return {
8014
+ instruction: [
8015
+ "# Fix Issue",
8016
+ "",
8017
+ details,
8018
+ "",
8019
+ 'Fix this issue, then update its status to "resolved" in `.story/issues/`.',
8020
+ "Add a resolution description explaining the fix.",
8021
+ "",
8022
+ "When done, call `claudestory_autonomous_guide` with:",
8023
+ "```json",
8024
+ `{ "sessionId": "${ctx.state.sessionId}", "action": "report", "report": { "completedAction": "issue_fixed" } }`,
8025
+ "```"
8026
+ ].join("\n"),
8027
+ reminders: [
8028
+ 'Update the issue JSON: set status to "resolved", add resolution text, set resolvedDate.',
8029
+ "Do NOT ask the user for confirmation."
8030
+ ]
8031
+ };
8032
+ }
8033
+ async report(ctx, _report) {
8034
+ const issue = ctx.state.currentIssue;
8035
+ if (!issue) {
8036
+ return { action: "goto", target: "PICK_TICKET" };
8037
+ }
8038
+ const { state: projectState } = await ctx.loadProject();
8039
+ const current = projectState.issues.find((i) => i.id === issue.id);
8040
+ if (!current || current.status !== "resolved") {
8041
+ return {
8042
+ action: "retry",
8043
+ instruction: `Issue ${issue.id} is still ${current?.status ?? "missing"}. Update its status to "resolved" in .story/issues/${issue.id}.json with a resolution description and resolvedDate, then report again.`,
8044
+ reminders: ["Set status to 'resolved', add resolution text, set resolvedDate."]
8045
+ };
8046
+ }
8047
+ return {
8048
+ action: "goto",
8049
+ target: "FINALIZE",
8050
+ result: {
8051
+ instruction: [
8052
+ "# Finalize Issue Fix",
8053
+ "",
8054
+ `Issue ${issue.id} resolved. Time to commit.`,
8055
+ "",
8056
+ `1. Ensure .story/issues/${issue.id}.json is updated with status: "resolved"`,
8057
+ "2. Stage only the files you modified for this fix (code + .story/ changes). Do NOT use `git add -A` or `git add .`",
8058
+ '3. Call me with completedAction: "files_staged"'
8059
+ ].join("\n"),
8060
+ reminders: ["Stage both code changes and .story/ issue update in the same commit. Only stage files related to this fix."],
8061
+ transitionedFrom: "ISSUE_FIX"
8062
+ }
8063
+ };
8064
+ }
8065
+ };
8066
+ }
8067
+ });
8068
+
7686
8069
  // src/autonomous/stages/issue-sweep.ts
7687
8070
  var IssueSweepStage;
7688
8071
  var init_issue_sweep = __esm({
@@ -7792,7 +8175,7 @@ Impact: ${nextIssue.impact}` : ""}` : `Fix issue ${next}.`,
7792
8175
 
7793
8176
  // src/autonomous/stages/handover.ts
7794
8177
  import { writeFileSync as writeFileSync2 } from "fs";
7795
- import { join as join10 } from "path";
8178
+ import { join as join12 } from "path";
7796
8179
  var HandoverStage;
7797
8180
  var init_handover2 = __esm({
7798
8181
  "src/autonomous/stages/handover.ts"() {
@@ -7829,7 +8212,7 @@ var init_handover2 = __esm({
7829
8212
  } catch {
7830
8213
  handoverFailed = true;
7831
8214
  try {
7832
- const fallbackPath = join10(ctx.dir, "handover-fallback.md");
8215
+ const fallbackPath = join12(ctx.dir, "handover-fallback.md");
7833
8216
  writeFileSync2(fallbackPath, content, "utf-8");
7834
8217
  } catch {
7835
8218
  }
@@ -7893,6 +8276,7 @@ var init_stages = __esm({
7893
8276
  init_finalize();
7894
8277
  init_complete();
7895
8278
  init_lesson_capture();
8279
+ init_issue_fix();
7896
8280
  init_issue_sweep();
7897
8281
  init_handover2();
7898
8282
  registerStage(new PickTicketStage());
@@ -7907,14 +8291,39 @@ var init_stages = __esm({
7907
8291
  registerStage(new FinalizeStage());
7908
8292
  registerStage(new CompleteStage());
7909
8293
  registerStage(new LessonCaptureStage());
8294
+ registerStage(new IssueFixStage());
7910
8295
  registerStage(new IssueSweepStage());
7911
8296
  registerStage(new HandoverStage());
7912
8297
  }
7913
8298
  });
7914
8299
 
7915
8300
  // src/autonomous/guide.ts
7916
- import { readFileSync as readFileSync4, writeFileSync as writeFileSync3 } from "fs";
7917
- import { join as join11 } from "path";
8301
+ import { readFileSync as readFileSync6, writeFileSync as writeFileSync3, readdirSync as readdirSync4 } from "fs";
8302
+ import { join as join13 } from "path";
8303
+ function buildGuideRecommendOptions(root) {
8304
+ const opts = {};
8305
+ try {
8306
+ const handoversDir = join13(root, ".story", "handovers");
8307
+ const files = readdirSync4(handoversDir, "utf-8").filter((f) => f.endsWith(".md")).sort();
8308
+ if (files.length > 0) {
8309
+ opts.latestHandoverContent = readFileSync6(join13(handoversDir, files[files.length - 1]), "utf-8");
8310
+ }
8311
+ } catch {
8312
+ }
8313
+ try {
8314
+ const snapshotsDir = join13(root, ".story", "snapshots");
8315
+ const snapFiles = readdirSync4(snapshotsDir, "utf-8").filter((f) => f.endsWith(".json")).sort();
8316
+ if (snapFiles.length > 0) {
8317
+ const raw = readFileSync6(join13(snapshotsDir, snapFiles[snapFiles.length - 1]), "utf-8");
8318
+ const snap = JSON.parse(raw);
8319
+ if (snap.issues) {
8320
+ opts.previousOpenIssueCount = snap.issues.filter((i) => i.status !== "resolved").length;
8321
+ }
8322
+ }
8323
+ } catch {
8324
+ }
8325
+ return opts;
8326
+ }
7918
8327
  async function recoverPendingMutation(dir, state, root) {
7919
8328
  const mutation = state.pendingProjectMutation;
7920
8329
  if (!mutation || typeof mutation !== "object") return state;
@@ -8286,7 +8695,7 @@ Staged: ${stagedResult.data.join(", ")}`
8286
8695
  }
8287
8696
  }
8288
8697
  const { state: projectState, warnings } = await loadProject(root);
8289
- const handoversDir = join11(root, ".story", "handovers");
8698
+ const handoversDir = join13(root, ".story", "handovers");
8290
8699
  const ctx = { state: projectState, warnings, root, handoversDir, format: "md" };
8291
8700
  let handoverText = "";
8292
8701
  try {
@@ -8303,7 +8712,7 @@ Staged: ${stagedResult.data.join(", ")}`
8303
8712
  }
8304
8713
  } catch {
8305
8714
  }
8306
- const rulesText = readFileSafe2(join11(root, "RULES.md"));
8715
+ const rulesText = readFileSafe2(join13(root, "RULES.md"));
8307
8716
  const lessonDigest = buildLessonDigest(projectState.lessons);
8308
8717
  const digestParts = [
8309
8718
  handoverText ? `## Recent Handovers
@@ -8319,7 +8728,7 @@ ${rulesText}` : "",
8319
8728
  ].filter(Boolean);
8320
8729
  const digest = digestParts.join("\n\n---\n\n");
8321
8730
  try {
8322
- writeFileSync3(join11(dir, "context-digest.md"), digest, "utf-8");
8731
+ writeFileSync3(join13(dir, "context-digest.md"), digest, "utf-8");
8323
8732
  } catch {
8324
8733
  }
8325
8734
  if (mode !== "auto" && args.ticketId) {
@@ -8338,6 +8747,18 @@ ${rulesText}` : "",
8338
8747
  return guideError(new Error(`Ticket ${args.ticketId} is blocked by: ${ticket.blockedBy.join(", ")}.`));
8339
8748
  }
8340
8749
  }
8750
+ if (mode !== "review") {
8751
+ const claimId = ticket.claimedBySession;
8752
+ if (claimId && typeof claimId === "string" && claimId !== session.sessionId) {
8753
+ const claimingSession = findSessionById(root, claimId);
8754
+ if (claimingSession && claimingSession.state.status === "active" && !isLeaseExpired(claimingSession.state)) {
8755
+ deleteSession(root, session.sessionId);
8756
+ return guideError(new Error(
8757
+ `Ticket ${args.ticketId} is claimed by active session ${claimId}. Wait for it to finish or stop it with "claudestory session stop ${claimId}".`
8758
+ ));
8759
+ }
8760
+ }
8761
+ }
8341
8762
  let entryState;
8342
8763
  if (mode === "review") {
8343
8764
  entryState = "CODE_REVIEW";
@@ -8433,12 +8854,22 @@ ${ticket.description}` : "",
8433
8854
  } else {
8434
8855
  candidatesText = "No tickets found.";
8435
8856
  }
8436
- const recResult = recommend(projectState, 5);
8857
+ const highIssues = projectState.issues.filter(
8858
+ (i) => i.status === "open" && (i.severity === "critical" || i.severity === "high")
8859
+ );
8860
+ let issuesText = "";
8861
+ if (highIssues.length > 0) {
8862
+ issuesText = "\n\n## Open Issues (high+ severity)\n\n" + highIssues.map(
8863
+ (i, idx) => `${idx + 1}. **${i.id}: ${i.title}** (${i.severity})`
8864
+ ).join("\n");
8865
+ }
8866
+ const guideRecOptions = buildGuideRecommendOptions(root);
8867
+ const recResult = recommend(projectState, 5, guideRecOptions);
8437
8868
  let recsText = "";
8438
8869
  if (recResult.recommendations.length > 0) {
8439
- const ticketRecs = recResult.recommendations.filter((r) => r.kind === "ticket");
8440
- if (ticketRecs.length > 0) {
8441
- recsText = "\n\n**Recommended:**\n" + ticketRecs.map(
8870
+ const actionableRecs = recResult.recommendations.filter((r) => r.kind === "ticket" || r.kind === "issue");
8871
+ if (actionableRecs.length > 0) {
8872
+ recsText = "\n\n**Recommended:**\n" + actionableRecs.map(
8442
8873
  (r) => `- ${r.id}: ${r.title} (${r.reason})`
8443
8874
  ).join("\n");
8444
8875
  }
@@ -8458,21 +8889,30 @@ ${ticket.description}` : "",
8458
8889
  const interval = updated.config.handoverInterval ?? 3;
8459
8890
  const sessionDesc = maxTickets > 0 ? `Work continuously until all tickets are done or you reach ${maxTickets} tickets.` : "Work continuously until all tickets are done.";
8460
8891
  const checkpointDesc = interval > 0 ? ` A checkpoint handover will be saved every ${interval} tickets.` : "";
8892
+ const hasHighIssues = highIssues.length > 0;
8461
8893
  const instruction = [
8462
8894
  "# Autonomous Session Started",
8463
8895
  "",
8464
8896
  `You are now in autonomous mode. ${sessionDesc}${checkpointDesc}`,
8465
- "Do NOT stop to summarize. Do NOT ask the user. Pick a ticket and start working immediately.",
8897
+ "Do NOT stop to summarize. Do NOT ask the user. Pick a ticket or issue and start working immediately.",
8466
8898
  "",
8467
8899
  "## Ticket Candidates",
8468
8900
  "",
8469
8901
  candidatesText,
8902
+ issuesText,
8470
8903
  recsText,
8471
8904
  "",
8472
- topCandidate ? `Pick **${topCandidate.ticket.id}** (highest priority) by calling \`claudestory_autonomous_guide\` now:` : "Pick a ticket by calling `claudestory_autonomous_guide` now:",
8905
+ topCandidate ? `Pick **${topCandidate.ticket.id}** (highest priority) or an open issue by calling \`claudestory_autonomous_guide\` now:` : hasHighIssues ? "Pick an issue to fix by calling `claudestory_autonomous_guide` now:" : "Pick a ticket by calling `claudestory_autonomous_guide` now:",
8473
8906
  "```json",
8474
8907
  topCandidate ? `{ "sessionId": "${updated.sessionId}", "action": "report", "report": { "completedAction": "ticket_picked", "ticketId": "${topCandidate.ticket.id}" } }` : `{ "sessionId": "${updated.sessionId}", "action": "report", "report": { "completedAction": "ticket_picked", "ticketId": "T-XXX" } }`,
8475
- "```"
8908
+ "```",
8909
+ ...hasHighIssues ? [
8910
+ "",
8911
+ "Or to fix an issue:",
8912
+ "```json",
8913
+ `{ "sessionId": "${updated.sessionId}", "action": "report", "report": { "completedAction": "issue_picked", "issueId": "${highIssues[0].id}" } }`,
8914
+ "```"
8915
+ ] : []
8476
8916
  ].join("\n");
8477
8917
  return guideResult(updated, "PICK_TICKET", {
8478
8918
  instruction,
@@ -8658,26 +9098,7 @@ async function handleResume(root, args) {
8658
9098
  ));
8659
9099
  }
8660
9100
  if (expectedHead && headResult.data.hash !== expectedHead) {
8661
- const recoveryMapping = {
8662
- PICK_TICKET: { state: "PICK_TICKET", resetPlan: false, resetCode: false },
8663
- COMPLETE: { state: "PICK_TICKET", resetPlan: false, resetCode: false },
8664
- HANDOVER: { state: "PICK_TICKET", resetPlan: false, resetCode: false },
8665
- PLAN: { state: "PLAN", resetPlan: true, resetCode: false },
8666
- IMPLEMENT: { state: "PLAN", resetPlan: true, resetCode: false },
8667
- WRITE_TESTS: { state: "PLAN", resetPlan: true, resetCode: false },
8668
- // T-139: baseline stale after HEAD change
8669
- VERIFY: { state: "IMPLEMENT", resetPlan: false, resetCode: true },
8670
- // T-131: reviewed code stale after HEAD drift
8671
- PLAN_REVIEW: { state: "PLAN", resetPlan: true, resetCode: true },
8672
- TEST: { state: "IMPLEMENT", resetPlan: false, resetCode: true },
8673
- // T-128: tests invalidated by HEAD change
8674
- CODE_REVIEW: { state: "PLAN", resetPlan: true, resetCode: true },
8675
- FINALIZE: { state: "IMPLEMENT", resetPlan: false, resetCode: true },
8676
- LESSON_CAPTURE: { state: "PICK_TICKET", resetPlan: false, resetCode: false },
8677
- ISSUE_SWEEP: { state: "PICK_TICKET", resetPlan: false, resetCode: false }
8678
- // T-128: post-complete, restart sweep
8679
- };
8680
- const mapping = recoveryMapping[resumeState] ?? { state: "PICK_TICKET", resetPlan: false, resetCode: false };
9101
+ const mapping = RECOVERY_MAPPING[resumeState] ?? { state: "PICK_TICKET", resetPlan: false, resetCode: false };
8681
9102
  const recoveryReviews = {
8682
9103
  plan: mapping.resetPlan ? [] : info.state.reviews.plan,
8683
9104
  code: mapping.resetCode ? [] : info.state.reviews.code
@@ -9025,12 +9446,12 @@ function guideError(err) {
9025
9446
  }
9026
9447
  function readFileSafe2(path2) {
9027
9448
  try {
9028
- return readFileSync4(path2, "utf-8");
9449
+ return readFileSync6(path2, "utf-8");
9029
9450
  } catch {
9030
9451
  return "";
9031
9452
  }
9032
9453
  }
9033
- var SEVERITY_MAP, workspaceLocks, MAX_AUTO_ADVANCE_DEPTH;
9454
+ var RECOVERY_MAPPING, SEVERITY_MAP, workspaceLocks, MAX_AUTO_ADVANCE_DEPTH;
9034
9455
  var init_guide = __esm({
9035
9456
  "src/autonomous/guide.ts"() {
9036
9457
  "use strict";
@@ -9052,6 +9473,23 @@ var init_guide = __esm({
9052
9473
  init_queries();
9053
9474
  init_recommend();
9054
9475
  init_handover();
9476
+ RECOVERY_MAPPING = {
9477
+ PICK_TICKET: { state: "PICK_TICKET", resetPlan: false, resetCode: false },
9478
+ COMPLETE: { state: "PICK_TICKET", resetPlan: false, resetCode: false },
9479
+ HANDOVER: { state: "PICK_TICKET", resetPlan: false, resetCode: false },
9480
+ PLAN: { state: "PLAN", resetPlan: true, resetCode: false },
9481
+ IMPLEMENT: { state: "PLAN", resetPlan: true, resetCode: false },
9482
+ WRITE_TESTS: { state: "PLAN", resetPlan: true, resetCode: false },
9483
+ BUILD: { state: "IMPLEMENT", resetPlan: false, resetCode: true },
9484
+ VERIFY: { state: "IMPLEMENT", resetPlan: false, resetCode: true },
9485
+ PLAN_REVIEW: { state: "PLAN", resetPlan: true, resetCode: true },
9486
+ TEST: { state: "IMPLEMENT", resetPlan: false, resetCode: true },
9487
+ CODE_REVIEW: { state: "PLAN", resetPlan: true, resetCode: true },
9488
+ FINALIZE: { state: "IMPLEMENT", resetPlan: false, resetCode: true },
9489
+ LESSON_CAPTURE: { state: "PICK_TICKET", resetPlan: false, resetCode: false },
9490
+ ISSUE_FIX: { state: "PICK_TICKET", resetPlan: false, resetCode: false },
9491
+ ISSUE_SWEEP: { state: "PICK_TICKET", resetPlan: false, resetCode: false }
9492
+ };
9055
9493
  SEVERITY_MAP = {
9056
9494
  critical: "critical",
9057
9495
  major: "high",
@@ -9260,8 +9698,8 @@ var init_session_report_formatter = __esm({
9260
9698
  });
9261
9699
 
9262
9700
  // src/cli/commands/session-report.ts
9263
- import { readFileSync as readFileSync5, existsSync as existsSync10 } from "fs";
9264
- import { join as join12 } from "path";
9701
+ import { readFileSync as readFileSync7, existsSync as existsSync10 } from "fs";
9702
+ import { join as join14 } from "path";
9265
9703
  async function handleSessionReport(sessionId, root, format = "md") {
9266
9704
  if (!UUID_REGEX.test(sessionId)) {
9267
9705
  return {
@@ -9280,7 +9718,7 @@ async function handleSessionReport(sessionId, root, format = "md") {
9280
9718
  isError: true
9281
9719
  };
9282
9720
  }
9283
- const statePath2 = join12(dir, "state.json");
9721
+ const statePath2 = join14(dir, "state.json");
9284
9722
  if (!existsSync10(statePath2)) {
9285
9723
  return {
9286
9724
  output: `Error: Session ${sessionId} corrupt \u2014 state.json missing.`,
@@ -9290,7 +9728,7 @@ async function handleSessionReport(sessionId, root, format = "md") {
9290
9728
  };
9291
9729
  }
9292
9730
  try {
9293
- const rawJson = JSON.parse(readFileSync5(statePath2, "utf-8"));
9731
+ const rawJson = JSON.parse(readFileSync7(statePath2, "utf-8"));
9294
9732
  if (rawJson && typeof rawJson === "object" && "schemaVersion" in rawJson && rawJson.schemaVersion !== CURRENT_SESSION_SCHEMA_VERSION) {
9295
9733
  return {
9296
9734
  output: `Error: Session ${sessionId} \u2014 unsupported session schema version ${rawJson.schemaVersion}.`,
@@ -9319,7 +9757,7 @@ async function handleSessionReport(sessionId, root, format = "md") {
9319
9757
  const events = readEvents(dir);
9320
9758
  let planContent = null;
9321
9759
  try {
9322
- planContent = readFileSync5(join12(dir, "plan.md"), "utf-8");
9760
+ planContent = readFileSync7(join14(dir, "plan.md"), "utf-8");
9323
9761
  } catch {
9324
9762
  }
9325
9763
  let gitLog = null;
@@ -9348,7 +9786,7 @@ var init_session_report = __esm({
9348
9786
  });
9349
9787
 
9350
9788
  // src/cli/commands/phase.ts
9351
- import { join as join13, resolve as resolve6 } from "path";
9789
+ import { join as join15, resolve as resolve6 } from "path";
9352
9790
  function validatePhaseId(id) {
9353
9791
  if (id.length > PHASE_ID_MAX_LENGTH) {
9354
9792
  throw new CliValidationError("invalid_input", `Phase ID "${id}" exceeds ${PHASE_ID_MAX_LENGTH} characters`);
@@ -9537,21 +9975,21 @@ async function handlePhaseDelete(id, reassign, format, root) {
9537
9975
  const updated = { ...ticket, phase: reassign, order: maxOrder };
9538
9976
  const parsed = TicketSchema.parse(updated);
9539
9977
  const content = serializeJSON(parsed);
9540
- const target = join13(wrapDir, "tickets", `${parsed.id}.json`);
9978
+ const target = join15(wrapDir, "tickets", `${parsed.id}.json`);
9541
9979
  operations.push({ op: "write", target, content });
9542
9980
  }
9543
9981
  for (const issue of affectedIssues) {
9544
9982
  const updated = { ...issue, phase: reassign };
9545
9983
  const parsed = IssueSchema.parse(updated);
9546
9984
  const content = serializeJSON(parsed);
9547
- const target = join13(wrapDir, "issues", `${parsed.id}.json`);
9985
+ const target = join15(wrapDir, "issues", `${parsed.id}.json`);
9548
9986
  operations.push({ op: "write", target, content });
9549
9987
  }
9550
9988
  const newPhases = state.roadmap.phases.filter((p) => p.id !== id);
9551
9989
  const newRoadmap = { ...state.roadmap, phases: newPhases };
9552
9990
  const parsedRoadmap = RoadmapSchema.parse(newRoadmap);
9553
9991
  const roadmapContent = serializeJSON(parsedRoadmap);
9554
- const roadmapTarget = join13(wrapDir, "roadmap.json");
9992
+ const roadmapTarget = join15(wrapDir, "roadmap.json");
9555
9993
  operations.push({ op: "write", target: roadmapTarget, content: roadmapContent });
9556
9994
  await runTransactionUnlocked(root, operations);
9557
9995
  } else {
@@ -9584,14 +10022,14 @@ var init_phase = __esm({
9584
10022
 
9585
10023
  // src/mcp/tools.ts
9586
10024
  import { z as z10 } from "zod";
9587
- import { join as join14 } from "path";
10025
+ import { join as join16 } from "path";
9588
10026
  function formatMcpError(code, message) {
9589
10027
  return `[${code}] ${message}`;
9590
10028
  }
9591
10029
  async function runMcpReadTool(pinnedRoot, handler) {
9592
10030
  try {
9593
10031
  const { state, warnings } = await loadProject(pinnedRoot);
9594
- const handoversDir = join14(pinnedRoot, ".story", "handovers");
10032
+ const handoversDir = join16(pinnedRoot, ".story", "handovers");
9595
10033
  const ctx = { state, warnings, root: pinnedRoot, handoversDir, format: "md" };
9596
10034
  const result = await handler(ctx);
9597
10035
  if (result.errorCode && INFRASTRUCTURE_ERROR_CODES.includes(result.errorCode)) {
@@ -10178,10 +10616,10 @@ var init_tools = __esm({
10178
10616
 
10179
10617
  // src/core/init.ts
10180
10618
  import { mkdir as mkdir4, stat as stat2, readFile as readFile4, writeFile as writeFile2 } from "fs/promises";
10181
- import { join as join15, resolve as resolve7 } from "path";
10619
+ import { join as join17, resolve as resolve7 } from "path";
10182
10620
  async function initProject(root, options) {
10183
10621
  const absRoot = resolve7(root);
10184
- const wrapDir = join15(absRoot, ".story");
10622
+ const wrapDir = join17(absRoot, ".story");
10185
10623
  let exists = false;
10186
10624
  try {
10187
10625
  const s = await stat2(wrapDir);
@@ -10201,11 +10639,11 @@ async function initProject(root, options) {
10201
10639
  ".story/ already exists. Use --force to overwrite config and roadmap."
10202
10640
  );
10203
10641
  }
10204
- await mkdir4(join15(wrapDir, "tickets"), { recursive: true });
10205
- await mkdir4(join15(wrapDir, "issues"), { recursive: true });
10206
- await mkdir4(join15(wrapDir, "handovers"), { recursive: true });
10207
- await mkdir4(join15(wrapDir, "notes"), { recursive: true });
10208
- await mkdir4(join15(wrapDir, "lessons"), { recursive: true });
10642
+ await mkdir4(join17(wrapDir, "tickets"), { recursive: true });
10643
+ await mkdir4(join17(wrapDir, "issues"), { recursive: true });
10644
+ await mkdir4(join17(wrapDir, "handovers"), { recursive: true });
10645
+ await mkdir4(join17(wrapDir, "notes"), { recursive: true });
10646
+ await mkdir4(join17(wrapDir, "lessons"), { recursive: true });
10209
10647
  const created = [
10210
10648
  ".story/config.json",
10211
10649
  ".story/roadmap.json",
@@ -10245,7 +10683,7 @@ async function initProject(root, options) {
10245
10683
  };
10246
10684
  await writeConfig(config, absRoot);
10247
10685
  await writeRoadmap(roadmap, absRoot);
10248
- const gitignorePath = join15(wrapDir, ".gitignore");
10686
+ const gitignorePath = join17(wrapDir, ".gitignore");
10249
10687
  await ensureGitignoreEntries(gitignorePath, STORY_GITIGNORE_ENTRIES);
10250
10688
  const warnings = [];
10251
10689
  if (options.force && exists) {
@@ -10293,7 +10731,7 @@ var init_init = __esm({
10293
10731
  // src/mcp/index.ts
10294
10732
  var mcp_exports = {};
10295
10733
  import { realpathSync as realpathSync2, existsSync as existsSync11 } from "fs";
10296
- import { resolve as resolve8, join as join16, isAbsolute } from "path";
10734
+ import { resolve as resolve8, join as join18, isAbsolute } from "path";
10297
10735
  import { z as z11 } from "zod";
10298
10736
  import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
10299
10737
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
@@ -10308,7 +10746,7 @@ function tryDiscoverRoot() {
10308
10746
  const resolved = resolve8(envRoot);
10309
10747
  try {
10310
10748
  const canonical = realpathSync2(resolved);
10311
- if (existsSync11(join16(canonical, CONFIG_PATH2))) {
10749
+ if (existsSync11(join18(canonical, CONFIG_PATH2))) {
10312
10750
  return canonical;
10313
10751
  }
10314
10752
  process.stderr.write(`Warning: No .story/config.json at ${canonical}
@@ -10411,7 +10849,7 @@ var init_mcp = __esm({
10411
10849
  init_init();
10412
10850
  ENV_VAR2 = "CLAUDESTORY_PROJECT_ROOT";
10413
10851
  CONFIG_PATH2 = ".story/config.json";
10414
- version = "0.1.35";
10852
+ version = "0.1.37";
10415
10853
  main().catch((err) => {
10416
10854
  process.stderr.write(`Fatal: ${err instanceof Error ? err.message : String(err)}
10417
10855
  `);
@@ -10447,7 +10885,7 @@ __export(run_exports, {
10447
10885
  runReadCommand: () => runReadCommand,
10448
10886
  writeOutput: () => writeOutput
10449
10887
  });
10450
- import { join as join17 } from "path";
10888
+ import { join as join19 } from "path";
10451
10889
  function writeOutput(text) {
10452
10890
  try {
10453
10891
  process.stdout.write(text + "\n");
@@ -10475,7 +10913,7 @@ async function runReadCommand(format, handler) {
10475
10913
  return;
10476
10914
  }
10477
10915
  const { state, warnings } = await loadProject(root);
10478
- const handoversDir = join17(root, ".story", "handovers");
10916
+ const handoversDir = join19(root, ".story", "handovers");
10479
10917
  const result = await handler({ state, warnings, root, handoversDir, format });
10480
10918
  writeOutput(result.output);
10481
10919
  let exitCode = result.exitCode ?? ExitCode.OK;
@@ -10510,7 +10948,7 @@ async function runDeleteCommand(format, force, handler) {
10510
10948
  return;
10511
10949
  }
10512
10950
  const { state, warnings } = await loadProject(root);
10513
- const handoversDir = join17(root, ".story", "handovers");
10951
+ const handoversDir = join19(root, ".story", "handovers");
10514
10952
  if (!force && hasIntegrityWarnings(warnings)) {
10515
10953
  writeOutput(
10516
10954
  formatError(
@@ -11024,7 +11462,7 @@ __export(setup_skill_exports, {
11024
11462
  });
11025
11463
  import { mkdir as mkdir5, writeFile as writeFile3, readFile as readFile5, rm, rename as rename2, unlink as unlink3 } from "fs/promises";
11026
11464
  import { existsSync as existsSync12 } from "fs";
11027
- import { join as join18, dirname as dirname4 } from "path";
11465
+ import { join as join20, dirname as dirname4 } from "path";
11028
11466
  import { homedir } from "os";
11029
11467
  import { execFileSync } from "child_process";
11030
11468
  import { fileURLToPath as fileURLToPath3 } from "url";
@@ -11033,10 +11471,10 @@ function log(msg) {
11033
11471
  }
11034
11472
  function resolveSkillSourceDir() {
11035
11473
  const thisDir = dirname4(fileURLToPath3(import.meta.url));
11036
- const bundledPath = join18(thisDir, "..", "src", "skill");
11037
- if (existsSync12(join18(bundledPath, "SKILL.md"))) return bundledPath;
11038
- const sourcePath = join18(thisDir, "..", "..", "skill");
11039
- if (existsSync12(join18(sourcePath, "SKILL.md"))) return sourcePath;
11474
+ const bundledPath = join20(thisDir, "..", "src", "skill");
11475
+ if (existsSync12(join20(bundledPath, "SKILL.md"))) return bundledPath;
11476
+ const sourcePath = join20(thisDir, "..", "..", "skill");
11477
+ if (existsSync12(join20(sourcePath, "SKILL.md"))) return sourcePath;
11040
11478
  throw new Error(
11041
11479
  `Cannot find bundled skill files. Checked:
11042
11480
  ${bundledPath}
@@ -11049,7 +11487,7 @@ function isHookWithCommand(entry, command) {
11049
11487
  return e.type === "command" && typeof e.command === "string" && e.command.trim() === command;
11050
11488
  }
11051
11489
  async function registerHook(hookType, hookEntry, settingsPath, matcher) {
11052
- const path2 = settingsPath ?? join18(homedir(), ".claude", "settings.json");
11490
+ const path2 = settingsPath ?? join20(homedir(), ".claude", "settings.json");
11053
11491
  let raw = "{}";
11054
11492
  if (existsSync12(path2)) {
11055
11493
  try {
@@ -11147,7 +11585,7 @@ async function registerStopHook(settingsPath) {
11147
11585
  return registerHook("Stop", { type: "command", command: STOP_HOOK_COMMAND, async: true }, settingsPath);
11148
11586
  }
11149
11587
  async function removeHook(hookType, command, settingsPath) {
11150
- const path2 = settingsPath ?? join18(homedir(), ".claude", "settings.json");
11588
+ const path2 = settingsPath ?? join20(homedir(), ".claude", "settings.json");
11151
11589
  let raw = "{}";
11152
11590
  if (existsSync12(path2)) {
11153
11591
  try {
@@ -11194,7 +11632,7 @@ async function removeHook(hookType, command, settingsPath) {
11194
11632
  }
11195
11633
  async function handleSetupSkill(options = {}) {
11196
11634
  const { skipHooks = false } = options;
11197
- const skillDir = join18(homedir(), ".claude", "skills", "story");
11635
+ const skillDir = join20(homedir(), ".claude", "skills", "story");
11198
11636
  await mkdir5(skillDir, { recursive: true });
11199
11637
  let srcSkillDir;
11200
11638
  try {
@@ -11207,19 +11645,19 @@ async function handleSetupSkill(options = {}) {
11207
11645
  process.exitCode = 1;
11208
11646
  return;
11209
11647
  }
11210
- const oldPrimeDir = join18(homedir(), ".claude", "skills", "prime");
11648
+ const oldPrimeDir = join20(homedir(), ".claude", "skills", "prime");
11211
11649
  if (existsSync12(oldPrimeDir)) {
11212
11650
  await rm(oldPrimeDir, { recursive: true, force: true });
11213
11651
  log("Removed old /prime skill (migrated to /story)");
11214
11652
  }
11215
- const existed = existsSync12(join18(skillDir, "SKILL.md"));
11216
- const skillContent = await readFile5(join18(srcSkillDir, "SKILL.md"), "utf-8");
11217
- await writeFile3(join18(skillDir, "SKILL.md"), skillContent, "utf-8");
11653
+ const existed = existsSync12(join20(skillDir, "SKILL.md"));
11654
+ const skillContent = await readFile5(join20(srcSkillDir, "SKILL.md"), "utf-8");
11655
+ await writeFile3(join20(skillDir, "SKILL.md"), skillContent, "utf-8");
11218
11656
  let referenceWritten = false;
11219
- const refSrcPath = join18(srcSkillDir, "reference.md");
11657
+ const refSrcPath = join20(srcSkillDir, "reference.md");
11220
11658
  if (existsSync12(refSrcPath)) {
11221
11659
  const refContent = await readFile5(refSrcPath, "utf-8");
11222
- await writeFile3(join18(skillDir, "reference.md"), refContent, "utf-8");
11660
+ await writeFile3(join20(skillDir, "reference.md"), refContent, "utf-8");
11223
11661
  referenceWritten = true;
11224
11662
  }
11225
11663
  log(`${existed ? "Updated" : "Installed"} /story skill at ${skillDir}/`);
@@ -11333,8 +11771,8 @@ var hook_status_exports = {};
11333
11771
  __export(hook_status_exports, {
11334
11772
  handleHookStatus: () => handleHookStatus
11335
11773
  });
11336
- import { readFileSync as readFileSync6, writeFileSync as writeFileSync4, renameSync as renameSync2, unlinkSync as unlinkSync4 } from "fs";
11337
- import { join as join19 } from "path";
11774
+ import { readFileSync as readFileSync8, writeFileSync as writeFileSync4, renameSync as renameSync2, unlinkSync as unlinkSync4 } from "fs";
11775
+ import { join as join21 } from "path";
11338
11776
  async function readStdinSilent() {
11339
11777
  try {
11340
11778
  const chunks = [];
@@ -11384,10 +11822,10 @@ function activePayload(session) {
11384
11822
  };
11385
11823
  }
11386
11824
  function ensureGitignore(root) {
11387
- const gitignorePath = join19(root, ".story", ".gitignore");
11825
+ const gitignorePath = join21(root, ".story", ".gitignore");
11388
11826
  let existing = "";
11389
11827
  try {
11390
- existing = readFileSync6(gitignorePath, "utf-8");
11828
+ existing = readFileSync8(gitignorePath, "utf-8");
11391
11829
  } catch {
11392
11830
  }
11393
11831
  const lines = existing.split("\n").map((l) => l.trim());
@@ -11403,7 +11841,7 @@ function ensureGitignore(root) {
11403
11841
  }
11404
11842
  function writeStatus(root, payload) {
11405
11843
  ensureGitignore(root);
11406
- const statusPath = join19(root, ".story", "status.json");
11844
+ const statusPath = join21(root, ".story", "status.json");
11407
11845
  const content = JSON.stringify(payload, null, 2) + "\n";
11408
11846
  atomicWriteSync(statusPath, content);
11409
11847
  }
@@ -11462,8 +11900,8 @@ var config_update_exports = {};
11462
11900
  __export(config_update_exports, {
11463
11901
  handleConfigSetOverrides: () => handleConfigSetOverrides
11464
11902
  });
11465
- import { readFileSync as readFileSync7 } from "fs";
11466
- import { join as join20 } from "path";
11903
+ import { readFileSync as readFileSync9 } from "fs";
11904
+ import { join as join22 } from "path";
11467
11905
  async function handleConfigSetOverrides(root, format, options) {
11468
11906
  const { json: jsonArg, clear } = options;
11469
11907
  if (!clear && !jsonArg) {
@@ -11491,8 +11929,8 @@ async function handleConfigSetOverrides(root, format, options) {
11491
11929
  }
11492
11930
  let resultOverrides = null;
11493
11931
  await withProjectLock(root, { strict: false }, async () => {
11494
- const configPath = join20(root, ".story", "config.json");
11495
- const rawContent = readFileSync7(configPath, "utf-8");
11932
+ const configPath = join22(root, ".story", "config.json");
11933
+ const rawContent = readFileSync9(configPath, "utf-8");
11496
11934
  const raw = JSON.parse(rawContent);
11497
11935
  if (clear) {
11498
11936
  delete raw.recipeOverrides;
@@ -13835,7 +14273,7 @@ async function runCli() {
13835
14273
  registerSessionCommand: registerSessionCommand2,
13836
14274
  registerRepairCommand: registerRepairCommand2
13837
14275
  } = await Promise.resolve().then(() => (init_register(), register_exports));
13838
- const version2 = "0.1.35";
14276
+ const version2 = "0.1.37";
13839
14277
  class HandledError extends Error {
13840
14278
  constructor() {
13841
14279
  super("HANDLED_ERROR");