@anthropologies/claudestory 0.1.34 → 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);
@@ -3382,7 +3476,20 @@ function validateParentTicket(parentId, ticketId, state) {
3382
3476
  throw new CliValidationError("invalid_input", `Parent ticket ${parentId} not found`);
3383
3477
  }
3384
3478
  }
3479
+ function buildErrorMultiset(findings) {
3480
+ const counts = /* @__PURE__ */ new Map();
3481
+ const messages = /* @__PURE__ */ new Map();
3482
+ for (const f of findings) {
3483
+ if (f.level !== "error") continue;
3484
+ const key = `${f.code}|${f.entity ?? ""}|${f.message}`;
3485
+ counts.set(key, (counts.get(key) ?? 0) + 1);
3486
+ messages.set(key, f.message);
3487
+ }
3488
+ return { counts, messages };
3489
+ }
3385
3490
  function validatePostWriteState(candidate, state, isCreate) {
3491
+ const preResult = validateProject(state);
3492
+ const { counts: preErrors } = buildErrorMultiset(preResult.findings);
3386
3493
  const existingTickets = [...state.tickets];
3387
3494
  if (isCreate) {
3388
3495
  existingTickets.push(candidate);
@@ -3399,11 +3506,17 @@ function validatePostWriteState(candidate, state, isCreate) {
3399
3506
  config: state.config,
3400
3507
  handoverFilenames: [...state.handoverFilenames]
3401
3508
  });
3402
- const result = validateProject(postState);
3403
- if (!result.valid) {
3404
- const errors = result.findings.filter((f) => f.level === "error");
3405
- const msg = errors.map((f) => f.message).join("; ");
3406
- throw new CliValidationError("validation_failed", `Write would create invalid state: ${msg}`);
3509
+ const postResult = validateProject(postState);
3510
+ const { counts: postErrors, messages: postMessages } = buildErrorMultiset(postResult.findings);
3511
+ const newErrors = [];
3512
+ for (const [key, postCount] of postErrors) {
3513
+ const preCount = preErrors.get(key) ?? 0;
3514
+ if (postCount > preCount) {
3515
+ newErrors.push(postMessages.get(key) ?? key);
3516
+ }
3517
+ }
3518
+ if (newErrors.length > 0) {
3519
+ throw new CliValidationError("validation_failed", `Write would create invalid state: ${newErrors.join("; ")}`);
3407
3520
  }
3408
3521
  }
3409
3522
  async function handleTicketCreate(args, format, root) {
@@ -3587,7 +3700,20 @@ function validateRelatedTickets(ids, state) {
3587
3700
  }
3588
3701
  }
3589
3702
  }
3703
+ function buildErrorMultiset2(findings) {
3704
+ const counts = /* @__PURE__ */ new Map();
3705
+ const messages = /* @__PURE__ */ new Map();
3706
+ for (const f of findings) {
3707
+ if (f.level !== "error") continue;
3708
+ const key = `${f.code}|${f.entity ?? ""}|${f.message}`;
3709
+ counts.set(key, (counts.get(key) ?? 0) + 1);
3710
+ messages.set(key, f.message);
3711
+ }
3712
+ return { counts, messages };
3713
+ }
3590
3714
  function validatePostWriteIssueState(candidate, state, isCreate) {
3715
+ const preResult = validateProject(state);
3716
+ const { counts: preErrors } = buildErrorMultiset2(preResult.findings);
3591
3717
  const existingIssues = [...state.issues];
3592
3718
  if (isCreate) {
3593
3719
  existingIssues.push(candidate);
@@ -3604,11 +3730,17 @@ function validatePostWriteIssueState(candidate, state, isCreate) {
3604
3730
  config: state.config,
3605
3731
  handoverFilenames: [...state.handoverFilenames]
3606
3732
  });
3607
- const result = validateProject(postState);
3608
- if (!result.valid) {
3609
- const errors = result.findings.filter((f) => f.level === "error");
3610
- const msg = errors.map((f) => f.message).join("; ");
3611
- throw new CliValidationError("validation_failed", `Write would create invalid state: ${msg}`);
3733
+ const postResult = validateProject(postState);
3734
+ const { counts: postErrors, messages: postMessages } = buildErrorMultiset2(postResult.findings);
3735
+ const newErrors = [];
3736
+ for (const [key, postCount] of postErrors) {
3737
+ const preCount = preErrors.get(key) ?? 0;
3738
+ if (postCount > preCount) {
3739
+ newErrors.push(postMessages.get(key) ?? key);
3740
+ }
3741
+ }
3742
+ if (newErrors.length > 0) {
3743
+ throw new CliValidationError("validation_failed", `Write would create invalid state: ${newErrors.join("; ")}`);
3612
3744
  }
3613
3745
  }
3614
3746
  async function handleIssueCreate(args, format, root) {
@@ -3742,11 +3874,11 @@ __export(snapshot_exports, {
3742
3874
  });
3743
3875
  import { readdir as readdir3, readFile as readFile3, mkdir as mkdir3, unlink as unlink2 } from "fs/promises";
3744
3876
  import { existsSync as existsSync5 } from "fs";
3745
- import { join as join5, resolve as resolve5 } from "path";
3877
+ import { join as join6, resolve as resolve5 } from "path";
3746
3878
  import { z as z8 } from "zod";
3747
3879
  async function saveSnapshot(root, loadResult) {
3748
3880
  const absRoot = resolve5(root);
3749
- const snapshotsDir = join5(absRoot, ".story", "snapshots");
3881
+ const snapshotsDir = join6(absRoot, ".story", "snapshots");
3750
3882
  await mkdir3(snapshotsDir, { recursive: true });
3751
3883
  const { state, warnings } = loadResult;
3752
3884
  const now = /* @__PURE__ */ new Date();
@@ -3771,8 +3903,8 @@ async function saveSnapshot(root, loadResult) {
3771
3903
  } : {}
3772
3904
  };
3773
3905
  const json = JSON.stringify(snapshot, null, 2) + "\n";
3774
- const targetPath = join5(snapshotsDir, filename);
3775
- const wrapDir = join5(absRoot, ".story");
3906
+ const targetPath = join6(snapshotsDir, filename);
3907
+ const wrapDir = join6(absRoot, ".story");
3776
3908
  await guardPath(targetPath, wrapDir);
3777
3909
  await atomicWrite(targetPath, json);
3778
3910
  const pruned = await pruneSnapshots(snapshotsDir);
@@ -3780,13 +3912,13 @@ async function saveSnapshot(root, loadResult) {
3780
3912
  return { filename, retained: entries.length, pruned };
3781
3913
  }
3782
3914
  async function loadLatestSnapshot(root) {
3783
- const snapshotsDir = join5(resolve5(root), ".story", "snapshots");
3915
+ const snapshotsDir = join6(resolve5(root), ".story", "snapshots");
3784
3916
  if (!existsSync5(snapshotsDir)) return null;
3785
3917
  const files = await listSnapshotFiles(snapshotsDir);
3786
3918
  if (files.length === 0) return null;
3787
3919
  for (const filename of files) {
3788
3920
  try {
3789
- const content = await readFile3(join5(snapshotsDir, filename), "utf-8");
3921
+ const content = await readFile3(join6(snapshotsDir, filename), "utf-8");
3790
3922
  const parsed = JSON.parse(content);
3791
3923
  const snapshot = SnapshotV1Schema.parse(parsed);
3792
3924
  return { snapshot, filename };
@@ -4030,7 +4162,7 @@ async function pruneSnapshots(dir) {
4030
4162
  const toRemove = files.slice(MAX_SNAPSHOTS);
4031
4163
  for (const f of toRemove) {
4032
4164
  try {
4033
- await unlink2(join5(dir, f));
4165
+ await unlink2(join6(dir, f));
4034
4166
  } catch {
4035
4167
  }
4036
4168
  }
@@ -4449,7 +4581,7 @@ var init_lesson2 = __esm({
4449
4581
  });
4450
4582
 
4451
4583
  // src/core/recommend.ts
4452
- function recommend(state, count) {
4584
+ function recommend(state, count, options) {
4453
4585
  const effectiveCount = Math.max(1, Math.min(10, count));
4454
4586
  const dedup = /* @__PURE__ */ new Map();
4455
4587
  const phaseIndex = buildPhaseIndex(state);
@@ -4461,7 +4593,8 @@ function recommend(state, count) {
4461
4593
  () => generateNearCompleteUmbrellas(state, phaseIndex),
4462
4594
  () => generatePhaseMomentum(state),
4463
4595
  () => generateQuickWins(state, phaseIndex),
4464
- () => generateOpenIssues(state)
4596
+ () => generateOpenIssues(state),
4597
+ () => generateDebtTrend(state, options)
4465
4598
  ];
4466
4599
  for (const gen of generators) {
4467
4600
  for (const rec of gen()) {
@@ -4471,6 +4604,7 @@ function recommend(state, count) {
4471
4604
  }
4472
4605
  }
4473
4606
  }
4607
+ applyHandoverBoost(state, dedup, options);
4474
4608
  const curPhase = currentPhase(state);
4475
4609
  const curPhaseIdx = curPhase ? phaseIndex.get(curPhase.id) ?? 0 : 0;
4476
4610
  for (const [id, rec] of dedup) {
@@ -4660,7 +4794,76 @@ function sortByPhaseAndOrder(tickets, phaseIndex) {
4660
4794
  return a.order - b.order;
4661
4795
  });
4662
4796
  }
4663
- 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;
4664
4867
  var init_recommend = __esm({
4665
4868
  "src/core/recommend.ts"() {
4666
4869
  "use strict";
@@ -4682,17 +4885,52 @@ var init_recommend = __esm({
4682
4885
  high_impact_unblock: 4,
4683
4886
  near_complete_umbrella: 5,
4684
4887
  phase_momentum: 6,
4685
- quick_win: 7,
4686
- open_issue: 8
4888
+ debt_trend: 7,
4889
+ quick_win: 8,
4890
+ handover_context: 9,
4891
+ open_issue: 10
4687
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;
4688
4900
  }
4689
4901
  });
4690
4902
 
4691
4903
  // src/cli/commands/recommend.ts
4904
+ import { readFileSync as readFileSync2, readdirSync as readdirSync2 } from "fs";
4905
+ import { join as join7 } from "path";
4692
4906
  function handleRecommend(ctx, count) {
4693
- const result = recommend(ctx.state, count);
4907
+ const options = buildRecommendOptions(ctx);
4908
+ const result = recommend(ctx.state, count, options);
4694
4909
  return { output: formatRecommendations(result, ctx.state, ctx.format) };
4695
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
+ }
4696
4934
  var init_recommend2 = __esm({
4697
4935
  "src/cli/commands/recommend.ts"() {
4698
4936
  "use strict";
@@ -5242,27 +5480,27 @@ __export(session_exports, {
5242
5480
  import { randomUUID } from "crypto";
5243
5481
  import {
5244
5482
  mkdirSync,
5245
- readdirSync,
5246
- readFileSync,
5483
+ readdirSync as readdirSync3,
5484
+ readFileSync as readFileSync3,
5247
5485
  writeFileSync,
5248
5486
  renameSync,
5249
5487
  unlinkSync,
5250
5488
  existsSync as existsSync6,
5251
5489
  rmSync
5252
5490
  } from "fs";
5253
- import { join as join6 } from "path";
5491
+ import { join as join8 } from "path";
5254
5492
  import lockfile2 from "proper-lockfile";
5255
5493
  function sessionsRoot(root) {
5256
- return join6(root, ".story", SESSIONS_DIR);
5494
+ return join8(root, ".story", SESSIONS_DIR);
5257
5495
  }
5258
5496
  function sessionDir(root, sessionId) {
5259
- return join6(sessionsRoot(root), sessionId);
5497
+ return join8(sessionsRoot(root), sessionId);
5260
5498
  }
5261
5499
  function statePath(dir) {
5262
- return join6(dir, "state.json");
5500
+ return join8(dir, "state.json");
5263
5501
  }
5264
5502
  function eventsPath(dir) {
5265
- return join6(dir, "events.log");
5503
+ return join8(dir, "events.log");
5266
5504
  }
5267
5505
  function createSession(root, recipe, workspaceId, configOverrides) {
5268
5506
  const id = randomUUID();
@@ -5318,7 +5556,7 @@ function readSession(dir) {
5318
5556
  const path2 = statePath(dir);
5319
5557
  let raw;
5320
5558
  try {
5321
- raw = readFileSync(path2, "utf-8");
5559
+ raw = readFileSync3(path2, "utf-8");
5322
5560
  } catch {
5323
5561
  return null;
5324
5562
  }
@@ -5361,7 +5599,7 @@ function readEvents(dir) {
5361
5599
  const path2 = eventsPath(dir);
5362
5600
  let raw;
5363
5601
  try {
5364
- raw = readFileSync(path2, "utf-8");
5602
+ raw = readFileSync3(path2, "utf-8");
5365
5603
  } catch {
5366
5604
  return { events: [], malformedCount: 0 };
5367
5605
  }
@@ -5418,7 +5656,7 @@ function findActiveSessionFull(root) {
5418
5656
  const sessDir = sessionsRoot(root);
5419
5657
  let entries;
5420
5658
  try {
5421
- entries = readdirSync(sessDir, { withFileTypes: true });
5659
+ entries = readdirSync3(sessDir, { withFileTypes: true });
5422
5660
  } catch {
5423
5661
  return null;
5424
5662
  }
@@ -5432,7 +5670,7 @@ function findActiveSessionFull(root) {
5432
5670
  let bestGuideCall = 0;
5433
5671
  for (const entry of entries) {
5434
5672
  if (!entry.isDirectory()) continue;
5435
- const dir = join6(sessDir, entry.name);
5673
+ const dir = join8(sessDir, entry.name);
5436
5674
  const session = readSession(dir);
5437
5675
  if (!session) continue;
5438
5676
  if (session.status !== "active") continue;
@@ -5455,7 +5693,7 @@ function findStaleSessions(root) {
5455
5693
  const sessDir = sessionsRoot(root);
5456
5694
  let entries;
5457
5695
  try {
5458
- entries = readdirSync(sessDir, { withFileTypes: true });
5696
+ entries = readdirSync3(sessDir, { withFileTypes: true });
5459
5697
  } catch {
5460
5698
  return [];
5461
5699
  }
@@ -5468,7 +5706,7 @@ function findStaleSessions(root) {
5468
5706
  const results = [];
5469
5707
  for (const entry of entries) {
5470
5708
  if (!entry.isDirectory()) continue;
5471
- const dir = join6(sessDir, entry.name);
5709
+ const dir = join8(sessDir, entry.name);
5472
5710
  const session = readSession(dir);
5473
5711
  if (!session) continue;
5474
5712
  if (session.status !== "active") continue;
@@ -5524,7 +5762,7 @@ function findResumableSession(root) {
5524
5762
  const sessDir = sessionsRoot(root);
5525
5763
  let entries;
5526
5764
  try {
5527
- entries = readdirSync(sessDir, { withFileTypes: true });
5765
+ entries = readdirSync3(sessDir, { withFileTypes: true });
5528
5766
  } catch {
5529
5767
  return null;
5530
5768
  }
@@ -5539,7 +5777,7 @@ function findResumableSession(root) {
5539
5777
  let bestPreparedAt = 0;
5540
5778
  for (const entry of entries) {
5541
5779
  if (!entry.isDirectory()) continue;
5542
- const dir = join6(sessDir, entry.name);
5780
+ const dir = join8(sessDir, entry.name);
5543
5781
  const session = readSession(dir);
5544
5782
  if (!session) continue;
5545
5783
  if (session.status !== "active") continue;
@@ -5563,7 +5801,7 @@ async function withSessionLock(root, fn) {
5563
5801
  release = await lockfile2.lock(sessDir, {
5564
5802
  retries: { retries: 3, minTimeout: 100, maxTimeout: 1e3 },
5565
5803
  stale: 3e4,
5566
- lockfilePath: join6(sessDir, ".lock")
5804
+ lockfilePath: join8(sessDir, ".lock")
5567
5805
  });
5568
5806
  return await fn();
5569
5807
  } finally {
@@ -5879,16 +6117,16 @@ var init_git_inspector = __esm({
5879
6117
  });
5880
6118
 
5881
6119
  // src/autonomous/recipes/loader.ts
5882
- import { readFileSync as readFileSync2 } from "fs";
5883
- 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";
5884
6122
  import { fileURLToPath as fileURLToPath2 } from "url";
5885
6123
  function loadRecipe(recipeName) {
5886
6124
  if (!/^[A-Za-z0-9_-]+$/.test(recipeName)) {
5887
6125
  throw new Error(`Invalid recipe name: ${recipeName}`);
5888
6126
  }
5889
- const recipesDir = join7(dirname3(fileURLToPath2(import.meta.url)), "..", "recipes");
5890
- const path2 = join7(recipesDir, `${recipeName}.json`);
5891
- 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");
5892
6130
  return JSON.parse(raw);
5893
6131
  }
5894
6132
  function resolveRecipe(recipeName, projectOverrides) {
@@ -6164,7 +6402,7 @@ var init_types2 = __esm({
6164
6402
 
6165
6403
  // src/autonomous/stages/pick-ticket.ts
6166
6404
  import { existsSync as existsSync7, unlinkSync as unlinkSync2 } from "fs";
6167
- import { join as join8 } from "path";
6405
+ import { join as join10 } from "path";
6168
6406
  var PickTicketStage;
6169
6407
  var init_pick_ticket = __esm({
6170
6408
  "src/autonomous/stages/pick-ticket.ts"() {
@@ -6219,7 +6457,7 @@ var init_pick_ticket = __esm({
6219
6457
  return { action: "retry", instruction: `Ticket ${ticketId} is ${ticket.status} \u2014 pick an open ticket.` };
6220
6458
  }
6221
6459
  }
6222
- const planPath = join8(ctx.dir, "plan.md");
6460
+ const planPath = join10(ctx.dir, "plan.md");
6223
6461
  try {
6224
6462
  if (existsSync7(planPath)) unlinkSync2(planPath);
6225
6463
  } catch {
@@ -6259,11 +6497,11 @@ ${ticket.description}` : "",
6259
6497
  });
6260
6498
 
6261
6499
  // src/autonomous/stages/plan.ts
6262
- import { existsSync as existsSync8, readFileSync as readFileSync3 } from "fs";
6263
- import { join as join9 } from "path";
6500
+ import { existsSync as existsSync8, readFileSync as readFileSync5 } from "fs";
6501
+ import { join as join11 } from "path";
6264
6502
  function readFileSafe(path2) {
6265
6503
  try {
6266
- return readFileSync3(path2, "utf-8");
6504
+ return readFileSync5(path2, "utf-8");
6267
6505
  } catch {
6268
6506
  return "";
6269
6507
  }
@@ -6304,7 +6542,7 @@ var init_plan = __esm({
6304
6542
  };
6305
6543
  }
6306
6544
  async report(ctx, _report) {
6307
- const planPath = join9(ctx.dir, "plan.md");
6545
+ const planPath = join11(ctx.dir, "plan.md");
6308
6546
  if (!existsSync8(planPath)) {
6309
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"] };
6310
6548
  }
@@ -6550,7 +6788,8 @@ var init_implement = __esm({
6550
6788
  reminders: [
6551
6789
  "Follow the plan exactly. Do NOT deviate without re-planning.",
6552
6790
  "Do NOT ask the user for confirmation.",
6553
- "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."
6554
6793
  ],
6555
6794
  transitionedFrom: ctx.state.previousState ?? void 0
6556
6795
  };
@@ -7247,10 +7486,10 @@ var init_finalize = __esm({
7247
7486
  "Code review passed. Time to commit.",
7248
7487
  "",
7249
7488
  ctx.state.ticket ? `1. Update ticket ${ctx.state.ticket.id} status to "complete" in .story/` : "",
7250
- "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 .`",
7251
7490
  '3. Call me with completedAction: "files_staged"'
7252
7491
  ].filter(Boolean).join("\n"),
7253
- 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."],
7254
7493
  transitionedFrom: ctx.state.previousState ?? void 0
7255
7494
  };
7256
7495
  }
@@ -7754,7 +7993,7 @@ Impact: ${nextIssue.impact}` : ""}` : `Fix issue ${next}.`,
7754
7993
 
7755
7994
  // src/autonomous/stages/handover.ts
7756
7995
  import { writeFileSync as writeFileSync2 } from "fs";
7757
- import { join as join10 } from "path";
7996
+ import { join as join12 } from "path";
7758
7997
  var HandoverStage;
7759
7998
  var init_handover2 = __esm({
7760
7999
  "src/autonomous/stages/handover.ts"() {
@@ -7791,7 +8030,7 @@ var init_handover2 = __esm({
7791
8030
  } catch {
7792
8031
  handoverFailed = true;
7793
8032
  try {
7794
- const fallbackPath = join10(ctx.dir, "handover-fallback.md");
8033
+ const fallbackPath = join12(ctx.dir, "handover-fallback.md");
7795
8034
  writeFileSync2(fallbackPath, content, "utf-8");
7796
8035
  } catch {
7797
8036
  }
@@ -7875,8 +8114,32 @@ var init_stages = __esm({
7875
8114
  });
7876
8115
 
7877
8116
  // src/autonomous/guide.ts
7878
- import { readFileSync as readFileSync4, writeFileSync as writeFileSync3 } from "fs";
7879
- 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
+ }
7880
8143
  async function recoverPendingMutation(dir, state, root) {
7881
8144
  const mutation = state.pendingProjectMutation;
7882
8145
  if (!mutation || typeof mutation !== "object") return state;
@@ -8248,7 +8511,7 @@ Staged: ${stagedResult.data.join(", ")}`
8248
8511
  }
8249
8512
  }
8250
8513
  const { state: projectState, warnings } = await loadProject(root);
8251
- const handoversDir = join11(root, ".story", "handovers");
8514
+ const handoversDir = join13(root, ".story", "handovers");
8252
8515
  const ctx = { state: projectState, warnings, root, handoversDir, format: "md" };
8253
8516
  let handoverText = "";
8254
8517
  try {
@@ -8265,7 +8528,7 @@ Staged: ${stagedResult.data.join(", ")}`
8265
8528
  }
8266
8529
  } catch {
8267
8530
  }
8268
- const rulesText = readFileSafe2(join11(root, "RULES.md"));
8531
+ const rulesText = readFileSafe2(join13(root, "RULES.md"));
8269
8532
  const lessonDigest = buildLessonDigest(projectState.lessons);
8270
8533
  const digestParts = [
8271
8534
  handoverText ? `## Recent Handovers
@@ -8281,7 +8544,7 @@ ${rulesText}` : "",
8281
8544
  ].filter(Boolean);
8282
8545
  const digest = digestParts.join("\n\n---\n\n");
8283
8546
  try {
8284
- writeFileSync3(join11(dir, "context-digest.md"), digest, "utf-8");
8547
+ writeFileSync3(join13(dir, "context-digest.md"), digest, "utf-8");
8285
8548
  } catch {
8286
8549
  }
8287
8550
  if (mode !== "auto" && args.ticketId) {
@@ -8300,6 +8563,18 @@ ${rulesText}` : "",
8300
8563
  return guideError(new Error(`Ticket ${args.ticketId} is blocked by: ${ticket.blockedBy.join(", ")}.`));
8301
8564
  }
8302
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
+ }
8303
8578
  let entryState;
8304
8579
  if (mode === "review") {
8305
8580
  entryState = "CODE_REVIEW";
@@ -8395,7 +8670,8 @@ ${ticket.description}` : "",
8395
8670
  } else {
8396
8671
  candidatesText = "No tickets found.";
8397
8672
  }
8398
- const recResult = recommend(projectState, 5);
8673
+ const guideRecOptions = buildGuideRecommendOptions(root);
8674
+ const recResult = recommend(projectState, 5, guideRecOptions);
8399
8675
  let recsText = "";
8400
8676
  if (recResult.recommendations.length > 0) {
8401
8677
  const ticketRecs = recResult.recommendations.filter((r) => r.kind === "ticket");
@@ -8620,26 +8896,7 @@ async function handleResume(root, args) {
8620
8896
  ));
8621
8897
  }
8622
8898
  if (expectedHead && headResult.data.hash !== expectedHead) {
8623
- const recoveryMapping = {
8624
- PICK_TICKET: { state: "PICK_TICKET", resetPlan: false, resetCode: false },
8625
- COMPLETE: { state: "PICK_TICKET", resetPlan: false, resetCode: false },
8626
- HANDOVER: { state: "PICK_TICKET", resetPlan: false, resetCode: false },
8627
- PLAN: { state: "PLAN", resetPlan: true, resetCode: false },
8628
- IMPLEMENT: { state: "PLAN", resetPlan: true, resetCode: false },
8629
- WRITE_TESTS: { state: "PLAN", resetPlan: true, resetCode: false },
8630
- // T-139: baseline stale after HEAD change
8631
- VERIFY: { state: "IMPLEMENT", resetPlan: false, resetCode: true },
8632
- // T-131: reviewed code stale after HEAD drift
8633
- PLAN_REVIEW: { state: "PLAN", resetPlan: true, resetCode: true },
8634
- TEST: { state: "IMPLEMENT", resetPlan: false, resetCode: true },
8635
- // T-128: tests invalidated by HEAD change
8636
- CODE_REVIEW: { state: "PLAN", resetPlan: true, resetCode: true },
8637
- FINALIZE: { state: "IMPLEMENT", resetPlan: false, resetCode: true },
8638
- LESSON_CAPTURE: { state: "PICK_TICKET", resetPlan: false, resetCode: false },
8639
- ISSUE_SWEEP: { state: "PICK_TICKET", resetPlan: false, resetCode: false }
8640
- // T-128: post-complete, restart sweep
8641
- };
8642
- const mapping = recoveryMapping[resumeState] ?? { state: "PICK_TICKET", resetPlan: false, resetCode: false };
8899
+ const mapping = RECOVERY_MAPPING[resumeState] ?? { state: "PICK_TICKET", resetPlan: false, resetCode: false };
8643
8900
  const recoveryReviews = {
8644
8901
  plan: mapping.resetPlan ? [] : info.state.reviews.plan,
8645
8902
  code: mapping.resetCode ? [] : info.state.reviews.code
@@ -8987,12 +9244,12 @@ function guideError(err) {
8987
9244
  }
8988
9245
  function readFileSafe2(path2) {
8989
9246
  try {
8990
- return readFileSync4(path2, "utf-8");
9247
+ return readFileSync6(path2, "utf-8");
8991
9248
  } catch {
8992
9249
  return "";
8993
9250
  }
8994
9251
  }
8995
- var SEVERITY_MAP, workspaceLocks, MAX_AUTO_ADVANCE_DEPTH;
9252
+ var RECOVERY_MAPPING, SEVERITY_MAP, workspaceLocks, MAX_AUTO_ADVANCE_DEPTH;
8996
9253
  var init_guide = __esm({
8997
9254
  "src/autonomous/guide.ts"() {
8998
9255
  "use strict";
@@ -9014,6 +9271,22 @@ var init_guide = __esm({
9014
9271
  init_queries();
9015
9272
  init_recommend();
9016
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
+ };
9017
9290
  SEVERITY_MAP = {
9018
9291
  critical: "critical",
9019
9292
  major: "high",
@@ -9222,8 +9495,8 @@ var init_session_report_formatter = __esm({
9222
9495
  });
9223
9496
 
9224
9497
  // src/cli/commands/session-report.ts
9225
- import { readFileSync as readFileSync5, existsSync as existsSync10 } from "fs";
9226
- import { join as join12 } from "path";
9498
+ import { readFileSync as readFileSync7, existsSync as existsSync10 } from "fs";
9499
+ import { join as join14 } from "path";
9227
9500
  async function handleSessionReport(sessionId, root, format = "md") {
9228
9501
  if (!UUID_REGEX.test(sessionId)) {
9229
9502
  return {
@@ -9242,7 +9515,7 @@ async function handleSessionReport(sessionId, root, format = "md") {
9242
9515
  isError: true
9243
9516
  };
9244
9517
  }
9245
- const statePath2 = join12(dir, "state.json");
9518
+ const statePath2 = join14(dir, "state.json");
9246
9519
  if (!existsSync10(statePath2)) {
9247
9520
  return {
9248
9521
  output: `Error: Session ${sessionId} corrupt \u2014 state.json missing.`,
@@ -9252,7 +9525,7 @@ async function handleSessionReport(sessionId, root, format = "md") {
9252
9525
  };
9253
9526
  }
9254
9527
  try {
9255
- const rawJson = JSON.parse(readFileSync5(statePath2, "utf-8"));
9528
+ const rawJson = JSON.parse(readFileSync7(statePath2, "utf-8"));
9256
9529
  if (rawJson && typeof rawJson === "object" && "schemaVersion" in rawJson && rawJson.schemaVersion !== CURRENT_SESSION_SCHEMA_VERSION) {
9257
9530
  return {
9258
9531
  output: `Error: Session ${sessionId} \u2014 unsupported session schema version ${rawJson.schemaVersion}.`,
@@ -9281,7 +9554,7 @@ async function handleSessionReport(sessionId, root, format = "md") {
9281
9554
  const events = readEvents(dir);
9282
9555
  let planContent = null;
9283
9556
  try {
9284
- planContent = readFileSync5(join12(dir, "plan.md"), "utf-8");
9557
+ planContent = readFileSync7(join14(dir, "plan.md"), "utf-8");
9285
9558
  } catch {
9286
9559
  }
9287
9560
  let gitLog = null;
@@ -9310,7 +9583,7 @@ var init_session_report = __esm({
9310
9583
  });
9311
9584
 
9312
9585
  // src/cli/commands/phase.ts
9313
- import { join as join13, resolve as resolve6 } from "path";
9586
+ import { join as join15, resolve as resolve6 } from "path";
9314
9587
  function validatePhaseId(id) {
9315
9588
  if (id.length > PHASE_ID_MAX_LENGTH) {
9316
9589
  throw new CliValidationError("invalid_input", `Phase ID "${id}" exceeds ${PHASE_ID_MAX_LENGTH} characters`);
@@ -9499,21 +9772,21 @@ async function handlePhaseDelete(id, reassign, format, root) {
9499
9772
  const updated = { ...ticket, phase: reassign, order: maxOrder };
9500
9773
  const parsed = TicketSchema.parse(updated);
9501
9774
  const content = serializeJSON(parsed);
9502
- const target = join13(wrapDir, "tickets", `${parsed.id}.json`);
9775
+ const target = join15(wrapDir, "tickets", `${parsed.id}.json`);
9503
9776
  operations.push({ op: "write", target, content });
9504
9777
  }
9505
9778
  for (const issue of affectedIssues) {
9506
9779
  const updated = { ...issue, phase: reassign };
9507
9780
  const parsed = IssueSchema.parse(updated);
9508
9781
  const content = serializeJSON(parsed);
9509
- const target = join13(wrapDir, "issues", `${parsed.id}.json`);
9782
+ const target = join15(wrapDir, "issues", `${parsed.id}.json`);
9510
9783
  operations.push({ op: "write", target, content });
9511
9784
  }
9512
9785
  const newPhases = state.roadmap.phases.filter((p) => p.id !== id);
9513
9786
  const newRoadmap = { ...state.roadmap, phases: newPhases };
9514
9787
  const parsedRoadmap = RoadmapSchema.parse(newRoadmap);
9515
9788
  const roadmapContent = serializeJSON(parsedRoadmap);
9516
- const roadmapTarget = join13(wrapDir, "roadmap.json");
9789
+ const roadmapTarget = join15(wrapDir, "roadmap.json");
9517
9790
  operations.push({ op: "write", target: roadmapTarget, content: roadmapContent });
9518
9791
  await runTransactionUnlocked(root, operations);
9519
9792
  } else {
@@ -9546,14 +9819,14 @@ var init_phase = __esm({
9546
9819
 
9547
9820
  // src/mcp/tools.ts
9548
9821
  import { z as z10 } from "zod";
9549
- import { join as join14 } from "path";
9822
+ import { join as join16 } from "path";
9550
9823
  function formatMcpError(code, message) {
9551
9824
  return `[${code}] ${message}`;
9552
9825
  }
9553
9826
  async function runMcpReadTool(pinnedRoot, handler) {
9554
9827
  try {
9555
9828
  const { state, warnings } = await loadProject(pinnedRoot);
9556
- const handoversDir = join14(pinnedRoot, ".story", "handovers");
9829
+ const handoversDir = join16(pinnedRoot, ".story", "handovers");
9557
9830
  const ctx = { state, warnings, root: pinnedRoot, handoversDir, format: "md" };
9558
9831
  const result = await handler(ctx);
9559
9832
  if (result.errorCode && INFRASTRUCTURE_ERROR_CODES.includes(result.errorCode)) {
@@ -10140,10 +10413,10 @@ var init_tools = __esm({
10140
10413
 
10141
10414
  // src/core/init.ts
10142
10415
  import { mkdir as mkdir4, stat as stat2, readFile as readFile4, writeFile as writeFile2 } from "fs/promises";
10143
- import { join as join15, resolve as resolve7 } from "path";
10416
+ import { join as join17, resolve as resolve7 } from "path";
10144
10417
  async function initProject(root, options) {
10145
10418
  const absRoot = resolve7(root);
10146
- const wrapDir = join15(absRoot, ".story");
10419
+ const wrapDir = join17(absRoot, ".story");
10147
10420
  let exists = false;
10148
10421
  try {
10149
10422
  const s = await stat2(wrapDir);
@@ -10163,11 +10436,11 @@ async function initProject(root, options) {
10163
10436
  ".story/ already exists. Use --force to overwrite config and roadmap."
10164
10437
  );
10165
10438
  }
10166
- await mkdir4(join15(wrapDir, "tickets"), { recursive: true });
10167
- await mkdir4(join15(wrapDir, "issues"), { recursive: true });
10168
- await mkdir4(join15(wrapDir, "handovers"), { recursive: true });
10169
- await mkdir4(join15(wrapDir, "notes"), { recursive: true });
10170
- 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 });
10171
10444
  const created = [
10172
10445
  ".story/config.json",
10173
10446
  ".story/roadmap.json",
@@ -10207,7 +10480,7 @@ async function initProject(root, options) {
10207
10480
  };
10208
10481
  await writeConfig(config, absRoot);
10209
10482
  await writeRoadmap(roadmap, absRoot);
10210
- const gitignorePath = join15(wrapDir, ".gitignore");
10483
+ const gitignorePath = join17(wrapDir, ".gitignore");
10211
10484
  await ensureGitignoreEntries(gitignorePath, STORY_GITIGNORE_ENTRIES);
10212
10485
  const warnings = [];
10213
10486
  if (options.force && exists) {
@@ -10255,7 +10528,7 @@ var init_init = __esm({
10255
10528
  // src/mcp/index.ts
10256
10529
  var mcp_exports = {};
10257
10530
  import { realpathSync as realpathSync2, existsSync as existsSync11 } from "fs";
10258
- import { resolve as resolve8, join as join16, isAbsolute } from "path";
10531
+ import { resolve as resolve8, join as join18, isAbsolute } from "path";
10259
10532
  import { z as z11 } from "zod";
10260
10533
  import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
10261
10534
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
@@ -10270,7 +10543,7 @@ function tryDiscoverRoot() {
10270
10543
  const resolved = resolve8(envRoot);
10271
10544
  try {
10272
10545
  const canonical = realpathSync2(resolved);
10273
- if (existsSync11(join16(canonical, CONFIG_PATH2))) {
10546
+ if (existsSync11(join18(canonical, CONFIG_PATH2))) {
10274
10547
  return canonical;
10275
10548
  }
10276
10549
  process.stderr.write(`Warning: No .story/config.json at ${canonical}
@@ -10373,7 +10646,7 @@ var init_mcp = __esm({
10373
10646
  init_init();
10374
10647
  ENV_VAR2 = "CLAUDESTORY_PROJECT_ROOT";
10375
10648
  CONFIG_PATH2 = ".story/config.json";
10376
- version = "0.1.34";
10649
+ version = "0.1.36";
10377
10650
  main().catch((err) => {
10378
10651
  process.stderr.write(`Fatal: ${err instanceof Error ? err.message : String(err)}
10379
10652
  `);
@@ -10409,7 +10682,7 @@ __export(run_exports, {
10409
10682
  runReadCommand: () => runReadCommand,
10410
10683
  writeOutput: () => writeOutput
10411
10684
  });
10412
- import { join as join17 } from "path";
10685
+ import { join as join19 } from "path";
10413
10686
  function writeOutput(text) {
10414
10687
  try {
10415
10688
  process.stdout.write(text + "\n");
@@ -10437,7 +10710,7 @@ async function runReadCommand(format, handler) {
10437
10710
  return;
10438
10711
  }
10439
10712
  const { state, warnings } = await loadProject(root);
10440
- const handoversDir = join17(root, ".story", "handovers");
10713
+ const handoversDir = join19(root, ".story", "handovers");
10441
10714
  const result = await handler({ state, warnings, root, handoversDir, format });
10442
10715
  writeOutput(result.output);
10443
10716
  let exitCode = result.exitCode ?? ExitCode.OK;
@@ -10472,7 +10745,7 @@ async function runDeleteCommand(format, force, handler) {
10472
10745
  return;
10473
10746
  }
10474
10747
  const { state, warnings } = await loadProject(root);
10475
- const handoversDir = join17(root, ".story", "handovers");
10748
+ const handoversDir = join19(root, ".story", "handovers");
10476
10749
  if (!force && hasIntegrityWarnings(warnings)) {
10477
10750
  writeOutput(
10478
10751
  formatError(
@@ -10521,6 +10794,102 @@ var init_run = __esm({
10521
10794
  }
10522
10795
  });
10523
10796
 
10797
+ // src/cli/commands/repair.ts
10798
+ function computeRepairs(state, warnings) {
10799
+ const integrityWarning = warnings.find(
10800
+ (w) => INTEGRITY_WARNING_TYPES.includes(w.type)
10801
+ );
10802
+ if (integrityWarning) {
10803
+ return {
10804
+ fixes: [],
10805
+ error: `Cannot repair: data integrity issue in ${integrityWarning.file}: ${integrityWarning.message}. Fix the corrupt file first, then retry.`,
10806
+ tickets: [],
10807
+ issues: []
10808
+ };
10809
+ }
10810
+ const fixes = [];
10811
+ const modifiedTickets = [];
10812
+ const modifiedIssues = [];
10813
+ const ticketIDs = new Set(state.tickets.map((t) => t.id));
10814
+ const phaseIDs = new Set(state.roadmap.phases.map((p) => {
10815
+ const id = p.id;
10816
+ return typeof id === "object" && id !== null ? id.rawValue ?? String(id) : String(id);
10817
+ }));
10818
+ for (const ticket of state.tickets) {
10819
+ let modified = false;
10820
+ let blockedBy = [...ticket.blockedBy];
10821
+ let parentTicket = ticket.parentTicket;
10822
+ let phase = ticket.phase;
10823
+ const validBlockedBy = blockedBy.filter((ref) => ticketIDs.has(ref));
10824
+ if (validBlockedBy.length < blockedBy.length) {
10825
+ const removed = blockedBy.filter((ref) => !ticketIDs.has(ref));
10826
+ blockedBy = validBlockedBy;
10827
+ modified = true;
10828
+ fixes.push({ entity: ticket.id, field: "blockedBy", description: `Removed stale refs: ${removed.join(", ")}` });
10829
+ }
10830
+ if (parentTicket && !ticketIDs.has(parentTicket)) {
10831
+ fixes.push({ entity: ticket.id, field: "parentTicket", description: `Cleared stale ref: ${parentTicket}` });
10832
+ parentTicket = null;
10833
+ modified = true;
10834
+ }
10835
+ const phaseRaw = typeof phase === "object" && phase !== null ? phase.rawValue ?? String(phase) : phase != null ? String(phase) : null;
10836
+ if (phaseRaw && !phaseIDs.has(phaseRaw)) {
10837
+ fixes.push({ entity: ticket.id, field: "phase", description: `Cleared stale phase: ${phaseRaw}` });
10838
+ phase = null;
10839
+ modified = true;
10840
+ }
10841
+ if (modified) {
10842
+ modifiedTickets.push({ ...ticket, blockedBy, parentTicket, phase });
10843
+ }
10844
+ }
10845
+ for (const issue of state.issues) {
10846
+ let modified = false;
10847
+ let relatedTickets = [...issue.relatedTickets];
10848
+ let phase = issue.phase;
10849
+ const validRelated = relatedTickets.filter((ref) => ticketIDs.has(ref));
10850
+ if (validRelated.length < relatedTickets.length) {
10851
+ const removed = relatedTickets.filter((ref) => !ticketIDs.has(ref));
10852
+ relatedTickets = validRelated;
10853
+ modified = true;
10854
+ fixes.push({ entity: issue.id, field: "relatedTickets", description: `Removed stale refs: ${removed.join(", ")}` });
10855
+ }
10856
+ const issuePhaseRaw = typeof phase === "object" && phase !== null ? phase.rawValue ?? String(phase) : phase != null ? String(phase) : null;
10857
+ if (issuePhaseRaw && !phaseIDs.has(issuePhaseRaw)) {
10858
+ fixes.push({ entity: issue.id, field: "phase", description: `Cleared stale phase: ${issuePhaseRaw}` });
10859
+ phase = null;
10860
+ modified = true;
10861
+ }
10862
+ if (modified) {
10863
+ modifiedIssues.push({ ...issue, relatedTickets, phase });
10864
+ }
10865
+ }
10866
+ return { fixes, tickets: modifiedTickets, issues: modifiedIssues };
10867
+ }
10868
+ function handleRepair(ctx, dryRun) {
10869
+ const { fixes, error } = computeRepairs(ctx.state, ctx.warnings);
10870
+ if (error) {
10871
+ return { output: error, errorCode: "project_corrupt" };
10872
+ }
10873
+ if (fixes.length === 0) {
10874
+ return { output: "No stale references found. Project is clean." };
10875
+ }
10876
+ const lines = [`Found ${fixes.length} stale reference(s)${dryRun ? " (dry run)" : ""}:`, ""];
10877
+ for (const fix of fixes) {
10878
+ lines.push(`- ${fix.entity}.${fix.field}: ${fix.description}`);
10879
+ }
10880
+ if (dryRun) {
10881
+ lines.push("", "Run without --dry-run to apply fixes.");
10882
+ }
10883
+ return { output: lines.join("\n") };
10884
+ }
10885
+ var init_repair = __esm({
10886
+ "src/cli/commands/repair.ts"() {
10887
+ "use strict";
10888
+ init_esm_shims();
10889
+ init_errors();
10890
+ }
10891
+ });
10892
+
10524
10893
  // src/cli/commands/init.ts
10525
10894
  import { basename as basename2 } from "path";
10526
10895
  function registerInitCommand(yargs) {
@@ -10890,7 +11259,7 @@ __export(setup_skill_exports, {
10890
11259
  });
10891
11260
  import { mkdir as mkdir5, writeFile as writeFile3, readFile as readFile5, rm, rename as rename2, unlink as unlink3 } from "fs/promises";
10892
11261
  import { existsSync as existsSync12 } from "fs";
10893
- import { join as join18, dirname as dirname4 } from "path";
11262
+ import { join as join20, dirname as dirname4 } from "path";
10894
11263
  import { homedir } from "os";
10895
11264
  import { execFileSync } from "child_process";
10896
11265
  import { fileURLToPath as fileURLToPath3 } from "url";
@@ -10899,10 +11268,10 @@ function log(msg) {
10899
11268
  }
10900
11269
  function resolveSkillSourceDir() {
10901
11270
  const thisDir = dirname4(fileURLToPath3(import.meta.url));
10902
- const bundledPath = join18(thisDir, "..", "src", "skill");
10903
- if (existsSync12(join18(bundledPath, "SKILL.md"))) return bundledPath;
10904
- const sourcePath = join18(thisDir, "..", "..", "skill");
10905
- 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;
10906
11275
  throw new Error(
10907
11276
  `Cannot find bundled skill files. Checked:
10908
11277
  ${bundledPath}
@@ -10915,7 +11284,7 @@ function isHookWithCommand(entry, command) {
10915
11284
  return e.type === "command" && typeof e.command === "string" && e.command.trim() === command;
10916
11285
  }
10917
11286
  async function registerHook(hookType, hookEntry, settingsPath, matcher) {
10918
- const path2 = settingsPath ?? join18(homedir(), ".claude", "settings.json");
11287
+ const path2 = settingsPath ?? join20(homedir(), ".claude", "settings.json");
10919
11288
  let raw = "{}";
10920
11289
  if (existsSync12(path2)) {
10921
11290
  try {
@@ -11013,7 +11382,7 @@ async function registerStopHook(settingsPath) {
11013
11382
  return registerHook("Stop", { type: "command", command: STOP_HOOK_COMMAND, async: true }, settingsPath);
11014
11383
  }
11015
11384
  async function removeHook(hookType, command, settingsPath) {
11016
- const path2 = settingsPath ?? join18(homedir(), ".claude", "settings.json");
11385
+ const path2 = settingsPath ?? join20(homedir(), ".claude", "settings.json");
11017
11386
  let raw = "{}";
11018
11387
  if (existsSync12(path2)) {
11019
11388
  try {
@@ -11060,7 +11429,7 @@ async function removeHook(hookType, command, settingsPath) {
11060
11429
  }
11061
11430
  async function handleSetupSkill(options = {}) {
11062
11431
  const { skipHooks = false } = options;
11063
- const skillDir = join18(homedir(), ".claude", "skills", "story");
11432
+ const skillDir = join20(homedir(), ".claude", "skills", "story");
11064
11433
  await mkdir5(skillDir, { recursive: true });
11065
11434
  let srcSkillDir;
11066
11435
  try {
@@ -11073,19 +11442,19 @@ async function handleSetupSkill(options = {}) {
11073
11442
  process.exitCode = 1;
11074
11443
  return;
11075
11444
  }
11076
- const oldPrimeDir = join18(homedir(), ".claude", "skills", "prime");
11445
+ const oldPrimeDir = join20(homedir(), ".claude", "skills", "prime");
11077
11446
  if (existsSync12(oldPrimeDir)) {
11078
11447
  await rm(oldPrimeDir, { recursive: true, force: true });
11079
11448
  log("Removed old /prime skill (migrated to /story)");
11080
11449
  }
11081
- const existed = existsSync12(join18(skillDir, "SKILL.md"));
11082
- const skillContent = await readFile5(join18(srcSkillDir, "SKILL.md"), "utf-8");
11083
- 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");
11084
11453
  let referenceWritten = false;
11085
- const refSrcPath = join18(srcSkillDir, "reference.md");
11454
+ const refSrcPath = join20(srcSkillDir, "reference.md");
11086
11455
  if (existsSync12(refSrcPath)) {
11087
11456
  const refContent = await readFile5(refSrcPath, "utf-8");
11088
- await writeFile3(join18(skillDir, "reference.md"), refContent, "utf-8");
11457
+ await writeFile3(join20(skillDir, "reference.md"), refContent, "utf-8");
11089
11458
  referenceWritten = true;
11090
11459
  }
11091
11460
  log(`${existed ? "Updated" : "Installed"} /story skill at ${skillDir}/`);
@@ -11199,8 +11568,8 @@ var hook_status_exports = {};
11199
11568
  __export(hook_status_exports, {
11200
11569
  handleHookStatus: () => handleHookStatus
11201
11570
  });
11202
- import { readFileSync as readFileSync6, writeFileSync as writeFileSync4, renameSync as renameSync2, unlinkSync as unlinkSync4 } from "fs";
11203
- 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";
11204
11573
  async function readStdinSilent() {
11205
11574
  try {
11206
11575
  const chunks = [];
@@ -11250,10 +11619,10 @@ function activePayload(session) {
11250
11619
  };
11251
11620
  }
11252
11621
  function ensureGitignore(root) {
11253
- const gitignorePath = join19(root, ".story", ".gitignore");
11622
+ const gitignorePath = join21(root, ".story", ".gitignore");
11254
11623
  let existing = "";
11255
11624
  try {
11256
- existing = readFileSync6(gitignorePath, "utf-8");
11625
+ existing = readFileSync8(gitignorePath, "utf-8");
11257
11626
  } catch {
11258
11627
  }
11259
11628
  const lines = existing.split("\n").map((l) => l.trim());
@@ -11269,7 +11638,7 @@ function ensureGitignore(root) {
11269
11638
  }
11270
11639
  function writeStatus(root, payload) {
11271
11640
  ensureGitignore(root);
11272
- const statusPath = join19(root, ".story", "status.json");
11641
+ const statusPath = join21(root, ".story", "status.json");
11273
11642
  const content = JSON.stringify(payload, null, 2) + "\n";
11274
11643
  atomicWriteSync(statusPath, content);
11275
11644
  }
@@ -11328,8 +11697,8 @@ var config_update_exports = {};
11328
11697
  __export(config_update_exports, {
11329
11698
  handleConfigSetOverrides: () => handleConfigSetOverrides
11330
11699
  });
11331
- import { readFileSync as readFileSync7 } from "fs";
11332
- import { join as join20 } from "path";
11700
+ import { readFileSync as readFileSync9 } from "fs";
11701
+ import { join as join22 } from "path";
11333
11702
  async function handleConfigSetOverrides(root, format, options) {
11334
11703
  const { json: jsonArg, clear } = options;
11335
11704
  if (!clear && !jsonArg) {
@@ -11357,8 +11726,8 @@ async function handleConfigSetOverrides(root, format, options) {
11357
11726
  }
11358
11727
  let resultOverrides = null;
11359
11728
  await withProjectLock(root, { strict: false }, async () => {
11360
- const configPath = join20(root, ".story", "config.json");
11361
- const rawContent = readFileSync7(configPath, "utf-8");
11729
+ const configPath = join22(root, ".story", "config.json");
11730
+ const rawContent = readFileSync9(configPath, "utf-8");
11362
11731
  const raw = JSON.parse(rawContent);
11363
11732
  if (clear) {
11364
11733
  delete raw.recipeOverrides;
@@ -11605,6 +11974,7 @@ __export(register_exports, {
11605
11974
  registerRecapCommand: () => registerRecapCommand,
11606
11975
  registerRecommendCommand: () => registerRecommendCommand,
11607
11976
  registerReferenceCommand: () => registerReferenceCommand,
11977
+ registerRepairCommand: () => registerRepairCommand,
11608
11978
  registerSelftestCommand: () => registerSelftestCommand,
11609
11979
  registerSessionCommand: () => registerSessionCommand,
11610
11980
  registerSetupSkillCommand: () => registerSetupSkillCommand,
@@ -11635,6 +12005,47 @@ function registerValidateCommand(yargs) {
11635
12005
  }
11636
12006
  );
11637
12007
  }
12008
+ function registerRepairCommand(yargs) {
12009
+ return yargs.command(
12010
+ "repair",
12011
+ "Fix stale references in .story/ data",
12012
+ (y) => y.option("dry-run", { type: "boolean", default: false, describe: "Show what would be fixed without writing" }),
12013
+ async (argv) => {
12014
+ const dryRun = argv["dry-run"];
12015
+ if (dryRun) {
12016
+ await runReadCommand("md", (ctx) => handleRepair(ctx, true));
12017
+ } else {
12018
+ const { withProjectLock: withProjectLock2, writeTicketUnlocked: writeTicketUnlocked2, writeIssueUnlocked: writeIssueUnlocked2, runTransactionUnlocked: runTransactionUnlocked2 } = await Promise.resolve().then(() => (init_project_loader(), project_loader_exports));
12019
+ const root = (await Promise.resolve().then(() => (init_project_root_discovery(), project_root_discovery_exports))).discoverProjectRoot();
12020
+ await withProjectLock2(root, { strict: false }, async ({ state, warnings }) => {
12021
+ const result = computeRepairs(state, warnings);
12022
+ if (result.error) {
12023
+ writeOutput(result.error);
12024
+ process.exitCode = ExitCode.USER_ERROR;
12025
+ return;
12026
+ }
12027
+ if (result.fixes.length === 0) {
12028
+ writeOutput("No stale references found. Project is clean.");
12029
+ return;
12030
+ }
12031
+ await runTransactionUnlocked2(root, async () => {
12032
+ for (const ticket of result.tickets) {
12033
+ await writeTicketUnlocked2(ticket, root);
12034
+ }
12035
+ for (const issue of result.issues) {
12036
+ await writeIssueUnlocked2(issue, root);
12037
+ }
12038
+ });
12039
+ const lines = [`Fixed ${result.fixes.length} stale reference(s):`, ""];
12040
+ for (const fix of result.fixes) {
12041
+ lines.push(`- ${fix.entity}.${fix.field}: ${fix.description}`);
12042
+ }
12043
+ writeOutput(lines.join("\n"));
12044
+ });
12045
+ }
12046
+ }
12047
+ );
12048
+ }
11638
12049
  function registerHandoverCommand(yargs) {
11639
12050
  return yargs.command(
11640
12051
  "handover",
@@ -13606,6 +14017,7 @@ var init_register = __esm({
13606
14017
  init_output_formatter();
13607
14018
  init_status();
13608
14019
  init_validate();
14020
+ init_repair();
13609
14021
  init_handover();
13610
14022
  init_blocker();
13611
14023
  init_ticket2();
@@ -13655,9 +14067,10 @@ async function runCli() {
13655
14067
  registerSetupSkillCommand: registerSetupSkillCommand2,
13656
14068
  registerHookStatusCommand: registerHookStatusCommand2,
13657
14069
  registerConfigCommand: registerConfigCommand2,
13658
- registerSessionCommand: registerSessionCommand2
14070
+ registerSessionCommand: registerSessionCommand2,
14071
+ registerRepairCommand: registerRepairCommand2
13659
14072
  } = await Promise.resolve().then(() => (init_register(), register_exports));
13660
- const version2 = "0.1.34";
14073
+ const version2 = "0.1.36";
13661
14074
  class HandledError extends Error {
13662
14075
  constructor() {
13663
14076
  super("HANDLED_ERROR");
@@ -13689,6 +14102,7 @@ async function runCli() {
13689
14102
  cli = registerHandoverCommand2(cli);
13690
14103
  cli = registerBlockerCommand2(cli);
13691
14104
  cli = registerValidateCommand2(cli);
14105
+ cli = registerRepairCommand2(cli);
13692
14106
  cli = registerSnapshotCommand2(cli);
13693
14107
  cli = registerRecapCommand2(cli);
13694
14108
  cli = registerExportCommand2(cli);