@anthropologies/claudestory 0.1.35 → 0.1.36

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";
@@ -5280,27 +5480,27 @@ __export(session_exports, {
5280
5480
  import { randomUUID } from "crypto";
5281
5481
  import {
5282
5482
  mkdirSync,
5283
- readdirSync,
5284
- readFileSync,
5483
+ readdirSync as readdirSync3,
5484
+ readFileSync as readFileSync3,
5285
5485
  writeFileSync,
5286
5486
  renameSync,
5287
5487
  unlinkSync,
5288
5488
  existsSync as existsSync6,
5289
5489
  rmSync
5290
5490
  } from "fs";
5291
- import { join as join6 } from "path";
5491
+ import { join as join8 } from "path";
5292
5492
  import lockfile2 from "proper-lockfile";
5293
5493
  function sessionsRoot(root) {
5294
- return join6(root, ".story", SESSIONS_DIR);
5494
+ return join8(root, ".story", SESSIONS_DIR);
5295
5495
  }
5296
5496
  function sessionDir(root, sessionId) {
5297
- return join6(sessionsRoot(root), sessionId);
5497
+ return join8(sessionsRoot(root), sessionId);
5298
5498
  }
5299
5499
  function statePath(dir) {
5300
- return join6(dir, "state.json");
5500
+ return join8(dir, "state.json");
5301
5501
  }
5302
5502
  function eventsPath(dir) {
5303
- return join6(dir, "events.log");
5503
+ return join8(dir, "events.log");
5304
5504
  }
5305
5505
  function createSession(root, recipe, workspaceId, configOverrides) {
5306
5506
  const id = randomUUID();
@@ -5356,7 +5556,7 @@ function readSession(dir) {
5356
5556
  const path2 = statePath(dir);
5357
5557
  let raw;
5358
5558
  try {
5359
- raw = readFileSync(path2, "utf-8");
5559
+ raw = readFileSync3(path2, "utf-8");
5360
5560
  } catch {
5361
5561
  return null;
5362
5562
  }
@@ -5399,7 +5599,7 @@ function readEvents(dir) {
5399
5599
  const path2 = eventsPath(dir);
5400
5600
  let raw;
5401
5601
  try {
5402
- raw = readFileSync(path2, "utf-8");
5602
+ raw = readFileSync3(path2, "utf-8");
5403
5603
  } catch {
5404
5604
  return { events: [], malformedCount: 0 };
5405
5605
  }
@@ -5456,7 +5656,7 @@ function findActiveSessionFull(root) {
5456
5656
  const sessDir = sessionsRoot(root);
5457
5657
  let entries;
5458
5658
  try {
5459
- entries = readdirSync(sessDir, { withFileTypes: true });
5659
+ entries = readdirSync3(sessDir, { withFileTypes: true });
5460
5660
  } catch {
5461
5661
  return null;
5462
5662
  }
@@ -5470,7 +5670,7 @@ function findActiveSessionFull(root) {
5470
5670
  let bestGuideCall = 0;
5471
5671
  for (const entry of entries) {
5472
5672
  if (!entry.isDirectory()) continue;
5473
- const dir = join6(sessDir, entry.name);
5673
+ const dir = join8(sessDir, entry.name);
5474
5674
  const session = readSession(dir);
5475
5675
  if (!session) continue;
5476
5676
  if (session.status !== "active") continue;
@@ -5493,7 +5693,7 @@ function findStaleSessions(root) {
5493
5693
  const sessDir = sessionsRoot(root);
5494
5694
  let entries;
5495
5695
  try {
5496
- entries = readdirSync(sessDir, { withFileTypes: true });
5696
+ entries = readdirSync3(sessDir, { withFileTypes: true });
5497
5697
  } catch {
5498
5698
  return [];
5499
5699
  }
@@ -5506,7 +5706,7 @@ function findStaleSessions(root) {
5506
5706
  const results = [];
5507
5707
  for (const entry of entries) {
5508
5708
  if (!entry.isDirectory()) continue;
5509
- const dir = join6(sessDir, entry.name);
5709
+ const dir = join8(sessDir, entry.name);
5510
5710
  const session = readSession(dir);
5511
5711
  if (!session) continue;
5512
5712
  if (session.status !== "active") continue;
@@ -5562,7 +5762,7 @@ function findResumableSession(root) {
5562
5762
  const sessDir = sessionsRoot(root);
5563
5763
  let entries;
5564
5764
  try {
5565
- entries = readdirSync(sessDir, { withFileTypes: true });
5765
+ entries = readdirSync3(sessDir, { withFileTypes: true });
5566
5766
  } catch {
5567
5767
  return null;
5568
5768
  }
@@ -5577,7 +5777,7 @@ function findResumableSession(root) {
5577
5777
  let bestPreparedAt = 0;
5578
5778
  for (const entry of entries) {
5579
5779
  if (!entry.isDirectory()) continue;
5580
- const dir = join6(sessDir, entry.name);
5780
+ const dir = join8(sessDir, entry.name);
5581
5781
  const session = readSession(dir);
5582
5782
  if (!session) continue;
5583
5783
  if (session.status !== "active") continue;
@@ -5601,7 +5801,7 @@ async function withSessionLock(root, fn) {
5601
5801
  release = await lockfile2.lock(sessDir, {
5602
5802
  retries: { retries: 3, minTimeout: 100, maxTimeout: 1e3 },
5603
5803
  stale: 3e4,
5604
- lockfilePath: join6(sessDir, ".lock")
5804
+ lockfilePath: join8(sessDir, ".lock")
5605
5805
  });
5606
5806
  return await fn();
5607
5807
  } finally {
@@ -5917,16 +6117,16 @@ var init_git_inspector = __esm({
5917
6117
  });
5918
6118
 
5919
6119
  // src/autonomous/recipes/loader.ts
5920
- import { readFileSync as readFileSync2 } from "fs";
5921
- import { join as join7, dirname as dirname3 } from "path";
6120
+ import { readFileSync as readFileSync4 } from "fs";
6121
+ import { join as join9, dirname as dirname3 } from "path";
5922
6122
  import { fileURLToPath as fileURLToPath2 } from "url";
5923
6123
  function loadRecipe(recipeName) {
5924
6124
  if (!/^[A-Za-z0-9_-]+$/.test(recipeName)) {
5925
6125
  throw new Error(`Invalid recipe name: ${recipeName}`);
5926
6126
  }
5927
- const recipesDir = join7(dirname3(fileURLToPath2(import.meta.url)), "..", "recipes");
5928
- const path2 = join7(recipesDir, `${recipeName}.json`);
5929
- const raw = readFileSync2(path2, "utf-8");
6127
+ const recipesDir = join9(dirname3(fileURLToPath2(import.meta.url)), "..", "recipes");
6128
+ const path2 = join9(recipesDir, `${recipeName}.json`);
6129
+ const raw = readFileSync4(path2, "utf-8");
5930
6130
  return JSON.parse(raw);
5931
6131
  }
5932
6132
  function resolveRecipe(recipeName, projectOverrides) {
@@ -6202,7 +6402,7 @@ var init_types2 = __esm({
6202
6402
 
6203
6403
  // src/autonomous/stages/pick-ticket.ts
6204
6404
  import { existsSync as existsSync7, unlinkSync as unlinkSync2 } from "fs";
6205
- import { join as join8 } from "path";
6405
+ import { join as join10 } from "path";
6206
6406
  var PickTicketStage;
6207
6407
  var init_pick_ticket = __esm({
6208
6408
  "src/autonomous/stages/pick-ticket.ts"() {
@@ -6257,7 +6457,7 @@ var init_pick_ticket = __esm({
6257
6457
  return { action: "retry", instruction: `Ticket ${ticketId} is ${ticket.status} \u2014 pick an open ticket.` };
6258
6458
  }
6259
6459
  }
6260
- const planPath = join8(ctx.dir, "plan.md");
6460
+ const planPath = join10(ctx.dir, "plan.md");
6261
6461
  try {
6262
6462
  if (existsSync7(planPath)) unlinkSync2(planPath);
6263
6463
  } catch {
@@ -6297,11 +6497,11 @@ ${ticket.description}` : "",
6297
6497
  });
6298
6498
 
6299
6499
  // src/autonomous/stages/plan.ts
6300
- import { existsSync as existsSync8, readFileSync as readFileSync3 } from "fs";
6301
- import { join as join9 } from "path";
6500
+ import { existsSync as existsSync8, readFileSync as readFileSync5 } from "fs";
6501
+ import { join as join11 } from "path";
6302
6502
  function readFileSafe(path2) {
6303
6503
  try {
6304
- return readFileSync3(path2, "utf-8");
6504
+ return readFileSync5(path2, "utf-8");
6305
6505
  } catch {
6306
6506
  return "";
6307
6507
  }
@@ -6342,7 +6542,7 @@ var init_plan = __esm({
6342
6542
  };
6343
6543
  }
6344
6544
  async report(ctx, _report) {
6345
- const planPath = join9(ctx.dir, "plan.md");
6545
+ const planPath = join11(ctx.dir, "plan.md");
6346
6546
  if (!existsSync8(planPath)) {
6347
6547
  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
6548
  }
@@ -6588,7 +6788,8 @@ var init_implement = __esm({
6588
6788
  reminders: [
6589
6789
  "Follow the plan exactly. Do NOT deviate without re-planning.",
6590
6790
  "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."
6791
+ "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.",
6792
+ "Track which files you create or modify. Only these files should be staged at commit time."
6592
6793
  ],
6593
6794
  transitionedFrom: ctx.state.previousState ?? void 0
6594
6795
  };
@@ -7285,10 +7486,10 @@ var init_finalize = __esm({
7285
7486
  "Code review passed. Time to commit.",
7286
7487
  "",
7287
7488
  ctx.state.ticket ? `1. Update ticket ${ctx.state.ticket.id} status to "complete" in .story/` : "",
7288
- "2. Stage all changed files (code + .story/ changes)",
7489
+ "2. Stage only the files you created or modified for this ticket (code + .story/ changes). Do NOT use `git add -A` or `git add .`",
7289
7490
  '3. Call me with completedAction: "files_staged"'
7290
7491
  ].filter(Boolean).join("\n"),
7291
- reminders: ["Stage both code changes and .story/ ticket update in the same commit."],
7492
+ reminders: ["Stage both code changes and .story/ ticket update in the same commit. Only stage files related to this ticket."],
7292
7493
  transitionedFrom: ctx.state.previousState ?? void 0
7293
7494
  };
7294
7495
  }
@@ -7792,7 +7993,7 @@ Impact: ${nextIssue.impact}` : ""}` : `Fix issue ${next}.`,
7792
7993
 
7793
7994
  // src/autonomous/stages/handover.ts
7794
7995
  import { writeFileSync as writeFileSync2 } from "fs";
7795
- import { join as join10 } from "path";
7996
+ import { join as join12 } from "path";
7796
7997
  var HandoverStage;
7797
7998
  var init_handover2 = __esm({
7798
7999
  "src/autonomous/stages/handover.ts"() {
@@ -7829,7 +8030,7 @@ var init_handover2 = __esm({
7829
8030
  } catch {
7830
8031
  handoverFailed = true;
7831
8032
  try {
7832
- const fallbackPath = join10(ctx.dir, "handover-fallback.md");
8033
+ const fallbackPath = join12(ctx.dir, "handover-fallback.md");
7833
8034
  writeFileSync2(fallbackPath, content, "utf-8");
7834
8035
  } catch {
7835
8036
  }
@@ -7913,8 +8114,32 @@ var init_stages = __esm({
7913
8114
  });
7914
8115
 
7915
8116
  // src/autonomous/guide.ts
7916
- import { readFileSync as readFileSync4, writeFileSync as writeFileSync3 } from "fs";
7917
- import { join as join11 } from "path";
8117
+ import { readFileSync as readFileSync6, writeFileSync as writeFileSync3, readdirSync as readdirSync4 } from "fs";
8118
+ import { join as join13 } from "path";
8119
+ function buildGuideRecommendOptions(root) {
8120
+ const opts = {};
8121
+ try {
8122
+ const handoversDir = join13(root, ".story", "handovers");
8123
+ const files = readdirSync4(handoversDir, "utf-8").filter((f) => f.endsWith(".md")).sort();
8124
+ if (files.length > 0) {
8125
+ opts.latestHandoverContent = readFileSync6(join13(handoversDir, files[files.length - 1]), "utf-8");
8126
+ }
8127
+ } catch {
8128
+ }
8129
+ try {
8130
+ const snapshotsDir = join13(root, ".story", "snapshots");
8131
+ const snapFiles = readdirSync4(snapshotsDir, "utf-8").filter((f) => f.endsWith(".json")).sort();
8132
+ if (snapFiles.length > 0) {
8133
+ const raw = readFileSync6(join13(snapshotsDir, snapFiles[snapFiles.length - 1]), "utf-8");
8134
+ const snap = JSON.parse(raw);
8135
+ if (snap.issues) {
8136
+ opts.previousOpenIssueCount = snap.issues.filter((i) => i.status !== "resolved").length;
8137
+ }
8138
+ }
8139
+ } catch {
8140
+ }
8141
+ return opts;
8142
+ }
7918
8143
  async function recoverPendingMutation(dir, state, root) {
7919
8144
  const mutation = state.pendingProjectMutation;
7920
8145
  if (!mutation || typeof mutation !== "object") return state;
@@ -8286,7 +8511,7 @@ Staged: ${stagedResult.data.join(", ")}`
8286
8511
  }
8287
8512
  }
8288
8513
  const { state: projectState, warnings } = await loadProject(root);
8289
- const handoversDir = join11(root, ".story", "handovers");
8514
+ const handoversDir = join13(root, ".story", "handovers");
8290
8515
  const ctx = { state: projectState, warnings, root, handoversDir, format: "md" };
8291
8516
  let handoverText = "";
8292
8517
  try {
@@ -8303,7 +8528,7 @@ Staged: ${stagedResult.data.join(", ")}`
8303
8528
  }
8304
8529
  } catch {
8305
8530
  }
8306
- const rulesText = readFileSafe2(join11(root, "RULES.md"));
8531
+ const rulesText = readFileSafe2(join13(root, "RULES.md"));
8307
8532
  const lessonDigest = buildLessonDigest(projectState.lessons);
8308
8533
  const digestParts = [
8309
8534
  handoverText ? `## Recent Handovers
@@ -8319,7 +8544,7 @@ ${rulesText}` : "",
8319
8544
  ].filter(Boolean);
8320
8545
  const digest = digestParts.join("\n\n---\n\n");
8321
8546
  try {
8322
- writeFileSync3(join11(dir, "context-digest.md"), digest, "utf-8");
8547
+ writeFileSync3(join13(dir, "context-digest.md"), digest, "utf-8");
8323
8548
  } catch {
8324
8549
  }
8325
8550
  if (mode !== "auto" && args.ticketId) {
@@ -8338,6 +8563,18 @@ ${rulesText}` : "",
8338
8563
  return guideError(new Error(`Ticket ${args.ticketId} is blocked by: ${ticket.blockedBy.join(", ")}.`));
8339
8564
  }
8340
8565
  }
8566
+ if (mode !== "review") {
8567
+ const claimId = ticket.claimedBySession;
8568
+ if (claimId && typeof claimId === "string" && claimId !== session.sessionId) {
8569
+ const claimingSession = findSessionById(root, claimId);
8570
+ if (claimingSession && claimingSession.state.status === "active" && !isLeaseExpired(claimingSession.state)) {
8571
+ deleteSession(root, session.sessionId);
8572
+ return guideError(new Error(
8573
+ `Ticket ${args.ticketId} is claimed by active session ${claimId}. Wait for it to finish or stop it with "claudestory session stop ${claimId}".`
8574
+ ));
8575
+ }
8576
+ }
8577
+ }
8341
8578
  let entryState;
8342
8579
  if (mode === "review") {
8343
8580
  entryState = "CODE_REVIEW";
@@ -8433,7 +8670,8 @@ ${ticket.description}` : "",
8433
8670
  } else {
8434
8671
  candidatesText = "No tickets found.";
8435
8672
  }
8436
- const recResult = recommend(projectState, 5);
8673
+ const guideRecOptions = buildGuideRecommendOptions(root);
8674
+ const recResult = recommend(projectState, 5, guideRecOptions);
8437
8675
  let recsText = "";
8438
8676
  if (recResult.recommendations.length > 0) {
8439
8677
  const ticketRecs = recResult.recommendations.filter((r) => r.kind === "ticket");
@@ -8658,26 +8896,7 @@ async function handleResume(root, args) {
8658
8896
  ));
8659
8897
  }
8660
8898
  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 };
8899
+ const mapping = RECOVERY_MAPPING[resumeState] ?? { state: "PICK_TICKET", resetPlan: false, resetCode: false };
8681
8900
  const recoveryReviews = {
8682
8901
  plan: mapping.resetPlan ? [] : info.state.reviews.plan,
8683
8902
  code: mapping.resetCode ? [] : info.state.reviews.code
@@ -9025,12 +9244,12 @@ function guideError(err) {
9025
9244
  }
9026
9245
  function readFileSafe2(path2) {
9027
9246
  try {
9028
- return readFileSync4(path2, "utf-8");
9247
+ return readFileSync6(path2, "utf-8");
9029
9248
  } catch {
9030
9249
  return "";
9031
9250
  }
9032
9251
  }
9033
- var SEVERITY_MAP, workspaceLocks, MAX_AUTO_ADVANCE_DEPTH;
9252
+ var RECOVERY_MAPPING, SEVERITY_MAP, workspaceLocks, MAX_AUTO_ADVANCE_DEPTH;
9034
9253
  var init_guide = __esm({
9035
9254
  "src/autonomous/guide.ts"() {
9036
9255
  "use strict";
@@ -9052,6 +9271,22 @@ var init_guide = __esm({
9052
9271
  init_queries();
9053
9272
  init_recommend();
9054
9273
  init_handover();
9274
+ RECOVERY_MAPPING = {
9275
+ PICK_TICKET: { state: "PICK_TICKET", resetPlan: false, resetCode: false },
9276
+ COMPLETE: { state: "PICK_TICKET", resetPlan: false, resetCode: false },
9277
+ HANDOVER: { state: "PICK_TICKET", resetPlan: false, resetCode: false },
9278
+ PLAN: { state: "PLAN", resetPlan: true, resetCode: false },
9279
+ IMPLEMENT: { state: "PLAN", resetPlan: true, resetCode: false },
9280
+ WRITE_TESTS: { state: "PLAN", resetPlan: true, resetCode: false },
9281
+ BUILD: { state: "IMPLEMENT", resetPlan: false, resetCode: true },
9282
+ VERIFY: { state: "IMPLEMENT", resetPlan: false, resetCode: true },
9283
+ PLAN_REVIEW: { state: "PLAN", resetPlan: true, resetCode: true },
9284
+ TEST: { state: "IMPLEMENT", resetPlan: false, resetCode: true },
9285
+ CODE_REVIEW: { state: "PLAN", resetPlan: true, resetCode: true },
9286
+ FINALIZE: { state: "IMPLEMENT", resetPlan: false, resetCode: true },
9287
+ LESSON_CAPTURE: { state: "PICK_TICKET", resetPlan: false, resetCode: false },
9288
+ ISSUE_SWEEP: { state: "PICK_TICKET", resetPlan: false, resetCode: false }
9289
+ };
9055
9290
  SEVERITY_MAP = {
9056
9291
  critical: "critical",
9057
9292
  major: "high",
@@ -9260,8 +9495,8 @@ var init_session_report_formatter = __esm({
9260
9495
  });
9261
9496
 
9262
9497
  // src/cli/commands/session-report.ts
9263
- import { readFileSync as readFileSync5, existsSync as existsSync10 } from "fs";
9264
- import { join as join12 } from "path";
9498
+ import { readFileSync as readFileSync7, existsSync as existsSync10 } from "fs";
9499
+ import { join as join14 } from "path";
9265
9500
  async function handleSessionReport(sessionId, root, format = "md") {
9266
9501
  if (!UUID_REGEX.test(sessionId)) {
9267
9502
  return {
@@ -9280,7 +9515,7 @@ async function handleSessionReport(sessionId, root, format = "md") {
9280
9515
  isError: true
9281
9516
  };
9282
9517
  }
9283
- const statePath2 = join12(dir, "state.json");
9518
+ const statePath2 = join14(dir, "state.json");
9284
9519
  if (!existsSync10(statePath2)) {
9285
9520
  return {
9286
9521
  output: `Error: Session ${sessionId} corrupt \u2014 state.json missing.`,
@@ -9290,7 +9525,7 @@ async function handleSessionReport(sessionId, root, format = "md") {
9290
9525
  };
9291
9526
  }
9292
9527
  try {
9293
- const rawJson = JSON.parse(readFileSync5(statePath2, "utf-8"));
9528
+ const rawJson = JSON.parse(readFileSync7(statePath2, "utf-8"));
9294
9529
  if (rawJson && typeof rawJson === "object" && "schemaVersion" in rawJson && rawJson.schemaVersion !== CURRENT_SESSION_SCHEMA_VERSION) {
9295
9530
  return {
9296
9531
  output: `Error: Session ${sessionId} \u2014 unsupported session schema version ${rawJson.schemaVersion}.`,
@@ -9319,7 +9554,7 @@ async function handleSessionReport(sessionId, root, format = "md") {
9319
9554
  const events = readEvents(dir);
9320
9555
  let planContent = null;
9321
9556
  try {
9322
- planContent = readFileSync5(join12(dir, "plan.md"), "utf-8");
9557
+ planContent = readFileSync7(join14(dir, "plan.md"), "utf-8");
9323
9558
  } catch {
9324
9559
  }
9325
9560
  let gitLog = null;
@@ -9348,7 +9583,7 @@ var init_session_report = __esm({
9348
9583
  });
9349
9584
 
9350
9585
  // src/cli/commands/phase.ts
9351
- import { join as join13, resolve as resolve6 } from "path";
9586
+ import { join as join15, resolve as resolve6 } from "path";
9352
9587
  function validatePhaseId(id) {
9353
9588
  if (id.length > PHASE_ID_MAX_LENGTH) {
9354
9589
  throw new CliValidationError("invalid_input", `Phase ID "${id}" exceeds ${PHASE_ID_MAX_LENGTH} characters`);
@@ -9537,21 +9772,21 @@ async function handlePhaseDelete(id, reassign, format, root) {
9537
9772
  const updated = { ...ticket, phase: reassign, order: maxOrder };
9538
9773
  const parsed = TicketSchema.parse(updated);
9539
9774
  const content = serializeJSON(parsed);
9540
- const target = join13(wrapDir, "tickets", `${parsed.id}.json`);
9775
+ const target = join15(wrapDir, "tickets", `${parsed.id}.json`);
9541
9776
  operations.push({ op: "write", target, content });
9542
9777
  }
9543
9778
  for (const issue of affectedIssues) {
9544
9779
  const updated = { ...issue, phase: reassign };
9545
9780
  const parsed = IssueSchema.parse(updated);
9546
9781
  const content = serializeJSON(parsed);
9547
- const target = join13(wrapDir, "issues", `${parsed.id}.json`);
9782
+ const target = join15(wrapDir, "issues", `${parsed.id}.json`);
9548
9783
  operations.push({ op: "write", target, content });
9549
9784
  }
9550
9785
  const newPhases = state.roadmap.phases.filter((p) => p.id !== id);
9551
9786
  const newRoadmap = { ...state.roadmap, phases: newPhases };
9552
9787
  const parsedRoadmap = RoadmapSchema.parse(newRoadmap);
9553
9788
  const roadmapContent = serializeJSON(parsedRoadmap);
9554
- const roadmapTarget = join13(wrapDir, "roadmap.json");
9789
+ const roadmapTarget = join15(wrapDir, "roadmap.json");
9555
9790
  operations.push({ op: "write", target: roadmapTarget, content: roadmapContent });
9556
9791
  await runTransactionUnlocked(root, operations);
9557
9792
  } else {
@@ -9584,14 +9819,14 @@ var init_phase = __esm({
9584
9819
 
9585
9820
  // src/mcp/tools.ts
9586
9821
  import { z as z10 } from "zod";
9587
- import { join as join14 } from "path";
9822
+ import { join as join16 } from "path";
9588
9823
  function formatMcpError(code, message) {
9589
9824
  return `[${code}] ${message}`;
9590
9825
  }
9591
9826
  async function runMcpReadTool(pinnedRoot, handler) {
9592
9827
  try {
9593
9828
  const { state, warnings } = await loadProject(pinnedRoot);
9594
- const handoversDir = join14(pinnedRoot, ".story", "handovers");
9829
+ const handoversDir = join16(pinnedRoot, ".story", "handovers");
9595
9830
  const ctx = { state, warnings, root: pinnedRoot, handoversDir, format: "md" };
9596
9831
  const result = await handler(ctx);
9597
9832
  if (result.errorCode && INFRASTRUCTURE_ERROR_CODES.includes(result.errorCode)) {
@@ -10178,10 +10413,10 @@ var init_tools = __esm({
10178
10413
 
10179
10414
  // src/core/init.ts
10180
10415
  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";
10416
+ import { join as join17, resolve as resolve7 } from "path";
10182
10417
  async function initProject(root, options) {
10183
10418
  const absRoot = resolve7(root);
10184
- const wrapDir = join15(absRoot, ".story");
10419
+ const wrapDir = join17(absRoot, ".story");
10185
10420
  let exists = false;
10186
10421
  try {
10187
10422
  const s = await stat2(wrapDir);
@@ -10201,11 +10436,11 @@ async function initProject(root, options) {
10201
10436
  ".story/ already exists. Use --force to overwrite config and roadmap."
10202
10437
  );
10203
10438
  }
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 });
10439
+ await mkdir4(join17(wrapDir, "tickets"), { recursive: true });
10440
+ await mkdir4(join17(wrapDir, "issues"), { recursive: true });
10441
+ await mkdir4(join17(wrapDir, "handovers"), { recursive: true });
10442
+ await mkdir4(join17(wrapDir, "notes"), { recursive: true });
10443
+ await mkdir4(join17(wrapDir, "lessons"), { recursive: true });
10209
10444
  const created = [
10210
10445
  ".story/config.json",
10211
10446
  ".story/roadmap.json",
@@ -10245,7 +10480,7 @@ async function initProject(root, options) {
10245
10480
  };
10246
10481
  await writeConfig(config, absRoot);
10247
10482
  await writeRoadmap(roadmap, absRoot);
10248
- const gitignorePath = join15(wrapDir, ".gitignore");
10483
+ const gitignorePath = join17(wrapDir, ".gitignore");
10249
10484
  await ensureGitignoreEntries(gitignorePath, STORY_GITIGNORE_ENTRIES);
10250
10485
  const warnings = [];
10251
10486
  if (options.force && exists) {
@@ -10293,7 +10528,7 @@ var init_init = __esm({
10293
10528
  // src/mcp/index.ts
10294
10529
  var mcp_exports = {};
10295
10530
  import { realpathSync as realpathSync2, existsSync as existsSync11 } from "fs";
10296
- import { resolve as resolve8, join as join16, isAbsolute } from "path";
10531
+ import { resolve as resolve8, join as join18, isAbsolute } from "path";
10297
10532
  import { z as z11 } from "zod";
10298
10533
  import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
10299
10534
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
@@ -10308,7 +10543,7 @@ function tryDiscoverRoot() {
10308
10543
  const resolved = resolve8(envRoot);
10309
10544
  try {
10310
10545
  const canonical = realpathSync2(resolved);
10311
- if (existsSync11(join16(canonical, CONFIG_PATH2))) {
10546
+ if (existsSync11(join18(canonical, CONFIG_PATH2))) {
10312
10547
  return canonical;
10313
10548
  }
10314
10549
  process.stderr.write(`Warning: No .story/config.json at ${canonical}
@@ -10411,7 +10646,7 @@ var init_mcp = __esm({
10411
10646
  init_init();
10412
10647
  ENV_VAR2 = "CLAUDESTORY_PROJECT_ROOT";
10413
10648
  CONFIG_PATH2 = ".story/config.json";
10414
- version = "0.1.35";
10649
+ version = "0.1.36";
10415
10650
  main().catch((err) => {
10416
10651
  process.stderr.write(`Fatal: ${err instanceof Error ? err.message : String(err)}
10417
10652
  `);
@@ -10447,7 +10682,7 @@ __export(run_exports, {
10447
10682
  runReadCommand: () => runReadCommand,
10448
10683
  writeOutput: () => writeOutput
10449
10684
  });
10450
- import { join as join17 } from "path";
10685
+ import { join as join19 } from "path";
10451
10686
  function writeOutput(text) {
10452
10687
  try {
10453
10688
  process.stdout.write(text + "\n");
@@ -10475,7 +10710,7 @@ async function runReadCommand(format, handler) {
10475
10710
  return;
10476
10711
  }
10477
10712
  const { state, warnings } = await loadProject(root);
10478
- const handoversDir = join17(root, ".story", "handovers");
10713
+ const handoversDir = join19(root, ".story", "handovers");
10479
10714
  const result = await handler({ state, warnings, root, handoversDir, format });
10480
10715
  writeOutput(result.output);
10481
10716
  let exitCode = result.exitCode ?? ExitCode.OK;
@@ -10510,7 +10745,7 @@ async function runDeleteCommand(format, force, handler) {
10510
10745
  return;
10511
10746
  }
10512
10747
  const { state, warnings } = await loadProject(root);
10513
- const handoversDir = join17(root, ".story", "handovers");
10748
+ const handoversDir = join19(root, ".story", "handovers");
10514
10749
  if (!force && hasIntegrityWarnings(warnings)) {
10515
10750
  writeOutput(
10516
10751
  formatError(
@@ -11024,7 +11259,7 @@ __export(setup_skill_exports, {
11024
11259
  });
11025
11260
  import { mkdir as mkdir5, writeFile as writeFile3, readFile as readFile5, rm, rename as rename2, unlink as unlink3 } from "fs/promises";
11026
11261
  import { existsSync as existsSync12 } from "fs";
11027
- import { join as join18, dirname as dirname4 } from "path";
11262
+ import { join as join20, dirname as dirname4 } from "path";
11028
11263
  import { homedir } from "os";
11029
11264
  import { execFileSync } from "child_process";
11030
11265
  import { fileURLToPath as fileURLToPath3 } from "url";
@@ -11033,10 +11268,10 @@ function log(msg) {
11033
11268
  }
11034
11269
  function resolveSkillSourceDir() {
11035
11270
  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;
11271
+ const bundledPath = join20(thisDir, "..", "src", "skill");
11272
+ if (existsSync12(join20(bundledPath, "SKILL.md"))) return bundledPath;
11273
+ const sourcePath = join20(thisDir, "..", "..", "skill");
11274
+ if (existsSync12(join20(sourcePath, "SKILL.md"))) return sourcePath;
11040
11275
  throw new Error(
11041
11276
  `Cannot find bundled skill files. Checked:
11042
11277
  ${bundledPath}
@@ -11049,7 +11284,7 @@ function isHookWithCommand(entry, command) {
11049
11284
  return e.type === "command" && typeof e.command === "string" && e.command.trim() === command;
11050
11285
  }
11051
11286
  async function registerHook(hookType, hookEntry, settingsPath, matcher) {
11052
- const path2 = settingsPath ?? join18(homedir(), ".claude", "settings.json");
11287
+ const path2 = settingsPath ?? join20(homedir(), ".claude", "settings.json");
11053
11288
  let raw = "{}";
11054
11289
  if (existsSync12(path2)) {
11055
11290
  try {
@@ -11147,7 +11382,7 @@ async function registerStopHook(settingsPath) {
11147
11382
  return registerHook("Stop", { type: "command", command: STOP_HOOK_COMMAND, async: true }, settingsPath);
11148
11383
  }
11149
11384
  async function removeHook(hookType, command, settingsPath) {
11150
- const path2 = settingsPath ?? join18(homedir(), ".claude", "settings.json");
11385
+ const path2 = settingsPath ?? join20(homedir(), ".claude", "settings.json");
11151
11386
  let raw = "{}";
11152
11387
  if (existsSync12(path2)) {
11153
11388
  try {
@@ -11194,7 +11429,7 @@ async function removeHook(hookType, command, settingsPath) {
11194
11429
  }
11195
11430
  async function handleSetupSkill(options = {}) {
11196
11431
  const { skipHooks = false } = options;
11197
- const skillDir = join18(homedir(), ".claude", "skills", "story");
11432
+ const skillDir = join20(homedir(), ".claude", "skills", "story");
11198
11433
  await mkdir5(skillDir, { recursive: true });
11199
11434
  let srcSkillDir;
11200
11435
  try {
@@ -11207,19 +11442,19 @@ async function handleSetupSkill(options = {}) {
11207
11442
  process.exitCode = 1;
11208
11443
  return;
11209
11444
  }
11210
- const oldPrimeDir = join18(homedir(), ".claude", "skills", "prime");
11445
+ const oldPrimeDir = join20(homedir(), ".claude", "skills", "prime");
11211
11446
  if (existsSync12(oldPrimeDir)) {
11212
11447
  await rm(oldPrimeDir, { recursive: true, force: true });
11213
11448
  log("Removed old /prime skill (migrated to /story)");
11214
11449
  }
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");
11450
+ const existed = existsSync12(join20(skillDir, "SKILL.md"));
11451
+ const skillContent = await readFile5(join20(srcSkillDir, "SKILL.md"), "utf-8");
11452
+ await writeFile3(join20(skillDir, "SKILL.md"), skillContent, "utf-8");
11218
11453
  let referenceWritten = false;
11219
- const refSrcPath = join18(srcSkillDir, "reference.md");
11454
+ const refSrcPath = join20(srcSkillDir, "reference.md");
11220
11455
  if (existsSync12(refSrcPath)) {
11221
11456
  const refContent = await readFile5(refSrcPath, "utf-8");
11222
- await writeFile3(join18(skillDir, "reference.md"), refContent, "utf-8");
11457
+ await writeFile3(join20(skillDir, "reference.md"), refContent, "utf-8");
11223
11458
  referenceWritten = true;
11224
11459
  }
11225
11460
  log(`${existed ? "Updated" : "Installed"} /story skill at ${skillDir}/`);
@@ -11333,8 +11568,8 @@ var hook_status_exports = {};
11333
11568
  __export(hook_status_exports, {
11334
11569
  handleHookStatus: () => handleHookStatus
11335
11570
  });
11336
- import { readFileSync as readFileSync6, writeFileSync as writeFileSync4, renameSync as renameSync2, unlinkSync as unlinkSync4 } from "fs";
11337
- import { join as join19 } from "path";
11571
+ import { readFileSync as readFileSync8, writeFileSync as writeFileSync4, renameSync as renameSync2, unlinkSync as unlinkSync4 } from "fs";
11572
+ import { join as join21 } from "path";
11338
11573
  async function readStdinSilent() {
11339
11574
  try {
11340
11575
  const chunks = [];
@@ -11384,10 +11619,10 @@ function activePayload(session) {
11384
11619
  };
11385
11620
  }
11386
11621
  function ensureGitignore(root) {
11387
- const gitignorePath = join19(root, ".story", ".gitignore");
11622
+ const gitignorePath = join21(root, ".story", ".gitignore");
11388
11623
  let existing = "";
11389
11624
  try {
11390
- existing = readFileSync6(gitignorePath, "utf-8");
11625
+ existing = readFileSync8(gitignorePath, "utf-8");
11391
11626
  } catch {
11392
11627
  }
11393
11628
  const lines = existing.split("\n").map((l) => l.trim());
@@ -11403,7 +11638,7 @@ function ensureGitignore(root) {
11403
11638
  }
11404
11639
  function writeStatus(root, payload) {
11405
11640
  ensureGitignore(root);
11406
- const statusPath = join19(root, ".story", "status.json");
11641
+ const statusPath = join21(root, ".story", "status.json");
11407
11642
  const content = JSON.stringify(payload, null, 2) + "\n";
11408
11643
  atomicWriteSync(statusPath, content);
11409
11644
  }
@@ -11462,8 +11697,8 @@ var config_update_exports = {};
11462
11697
  __export(config_update_exports, {
11463
11698
  handleConfigSetOverrides: () => handleConfigSetOverrides
11464
11699
  });
11465
- import { readFileSync as readFileSync7 } from "fs";
11466
- import { join as join20 } from "path";
11700
+ import { readFileSync as readFileSync9 } from "fs";
11701
+ import { join as join22 } from "path";
11467
11702
  async function handleConfigSetOverrides(root, format, options) {
11468
11703
  const { json: jsonArg, clear } = options;
11469
11704
  if (!clear && !jsonArg) {
@@ -11491,8 +11726,8 @@ async function handleConfigSetOverrides(root, format, options) {
11491
11726
  }
11492
11727
  let resultOverrides = null;
11493
11728
  await withProjectLock(root, { strict: false }, async () => {
11494
- const configPath = join20(root, ".story", "config.json");
11495
- const rawContent = readFileSync7(configPath, "utf-8");
11729
+ const configPath = join22(root, ".story", "config.json");
11730
+ const rawContent = readFileSync9(configPath, "utf-8");
11496
11731
  const raw = JSON.parse(rawContent);
11497
11732
  if (clear) {
11498
11733
  delete raw.recipeOverrides;
@@ -13835,7 +14070,7 @@ async function runCli() {
13835
14070
  registerSessionCommand: registerSessionCommand2,
13836
14071
  registerRepairCommand: registerRepairCommand2
13837
14072
  } = await Promise.resolve().then(() => (init_register(), register_exports));
13838
- const version2 = "0.1.35";
14073
+ const version2 = "0.1.36";
13839
14074
  class HandledError extends Error {
13840
14075
  constructor() {
13841
14076
  super("HANDLED_ERROR");