@anthropologies/claudestory 0.1.57 → 0.1.59

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
@@ -5444,8 +5444,48 @@ var init_session_types = __esm({
5444
5444
  maxTicketsPerSession: z9.number().min(0).default(5),
5445
5445
  handoverInterval: z9.number().min(0).default(3),
5446
5446
  compactThreshold: z9.string().default("high"),
5447
- reviewBackends: z9.array(z9.string()).default(["codex", "agent"])
5447
+ reviewBackends: z9.array(z9.string()).default(["codex", "agent"]),
5448
+ // T-181: Multi-lens review config
5449
+ lensConfig: z9.object({
5450
+ lenses: z9.union([z9.literal("auto"), z9.array(z9.string())]).default("auto"),
5451
+ maxLenses: z9.number().min(1).max(8).default(8),
5452
+ lensTimeout: z9.union([
5453
+ z9.number(),
5454
+ z9.object({ default: z9.number(), opus: z9.number() })
5455
+ ]).default({ default: 60, opus: 120 }),
5456
+ findingBudget: z9.number().min(1).default(10),
5457
+ confidenceFloor: z9.number().min(0).max(1).default(0.6),
5458
+ tokenBudgetPerLens: z9.number().min(1e3).default(32e3),
5459
+ hotPaths: z9.array(z9.string()).default([]),
5460
+ lensModels: z9.record(z9.string()).default({ default: "sonnet", security: "opus", concurrency: "opus" })
5461
+ }).optional(),
5462
+ blockingPolicy: z9.object({
5463
+ neverBlock: z9.array(z9.string()).default([]),
5464
+ alwaysBlock: z9.array(z9.string()).default(["injection", "auth-bypass", "hardcoded-secrets"]),
5465
+ planReviewBlockingLenses: z9.array(z9.string()).default(["security", "error-handling"])
5466
+ }).optional(),
5467
+ requireSecretsGate: z9.boolean().default(false),
5468
+ requireAccessibility: z9.boolean().default(false),
5469
+ testMapping: z9.object({
5470
+ strategy: z9.literal("convention"),
5471
+ patterns: z9.array(z9.object({
5472
+ source: z9.string(),
5473
+ test: z9.string()
5474
+ }))
5475
+ }).optional()
5448
5476
  }).default({ maxTicketsPerSession: 5, compactThreshold: "high", reviewBackends: ["codex", "agent"], handoverInterval: 3 }),
5477
+ // T-181: Lens review findings history (for lessons feedback loop)
5478
+ lensReviewHistory: z9.array(z9.object({
5479
+ ticketId: z9.string(),
5480
+ stage: z9.enum(["CODE_REVIEW", "PLAN_REVIEW"]),
5481
+ lens: z9.string(),
5482
+ category: z9.string(),
5483
+ severity: z9.string(),
5484
+ disposition: z9.enum(["open", "addressed", "contested", "deferred"]),
5485
+ description: z9.string(),
5486
+ dismissReason: z9.string().optional(),
5487
+ timestamp: z9.string()
5488
+ })).default([]),
5449
5489
  // T-123: Issue sweep tracking
5450
5490
  issueSweepState: z9.object({
5451
5491
  remaining: z9.array(z9.string()),
@@ -5474,7 +5514,8 @@ var init_session_types = __esm({
5474
5514
  resolvedDefaults: z9.object({
5475
5515
  maxTicketsPerSession: z9.number(),
5476
5516
  compactThreshold: z9.string(),
5477
- reviewBackends: z9.array(z9.string())
5517
+ reviewBackends: z9.array(z9.string()),
5518
+ handoverInterval: z9.number().optional()
5478
5519
  }).optional()
5479
5520
  }).passthrough();
5480
5521
  }
@@ -5667,7 +5708,8 @@ function refreshLease(state) {
5667
5708
  contextPressure: {
5668
5709
  ...state.contextPressure,
5669
5710
  guideCallCount: newCallCount,
5670
- ticketsCompleted: state.completedTickets?.length ?? 0
5711
+ // ISS-084: Include resolved issues in work count
5712
+ ticketsCompleted: (state.completedTickets?.length ?? 0) + (state.resolvedIssues?.length ?? 0)
5671
5713
  }
5672
5714
  };
5673
5715
  }
@@ -5885,7 +5927,7 @@ var init_state_machine = __esm({
5885
5927
  VERIFY: ["FINALIZE", "IMPLEMENT", "VERIFY"],
5886
5928
  // pass → FINALIZE, fail → IMPLEMENT, retry
5887
5929
  FINALIZE: ["COMPLETE", "PICK_TICKET"],
5888
- // PICK_TICKET for issue-fix flow (bypass COMPLETE)
5930
+ // ISS-084: issues now route through COMPLETE too; PICK_TICKET kept for in-flight session compat
5889
5931
  COMPLETE: ["PICK_TICKET", "HANDOVER", "ISSUE_SWEEP", "SESSION_END"],
5890
5932
  ISSUE_FIX: ["FINALIZE", "PICK_TICKET", "ISSUE_FIX"],
5891
5933
  // T-153: fix done → FINALIZE, cancel → PICK_TICKET, retry self
@@ -5905,7 +5947,7 @@ var init_state_machine = __esm({
5905
5947
  // src/autonomous/context-pressure.ts
5906
5948
  function evaluatePressure(state) {
5907
5949
  const calls = state.contextPressure?.guideCallCount ?? state.guideCallCount ?? 0;
5908
- const tickets = state.contextPressure?.ticketsCompleted ?? state.completedTickets?.length ?? 0;
5950
+ const tickets = (state.completedTickets?.length ?? 0) + (state.resolvedIssues?.length ?? 0);
5909
5951
  const eventsBytes = state.contextPressure?.eventsLogBytes ?? 0;
5910
5952
  const tier = state.config?.compactThreshold ?? "high";
5911
5953
  const t = THRESHOLDS[tier] ?? THRESHOLDS["high"];
@@ -6314,6 +6356,22 @@ function djb2Hash(content) {
6314
6356
  }
6315
6357
  return hash.toString(36);
6316
6358
  }
6359
+ function buildLensHistoryUpdate(findings, existing, ticketId, stage) {
6360
+ const existingKeys = new Set(
6361
+ existing.map((e) => `${e.ticketId}:${e.stage}:${e.lens}:${e.category}`)
6362
+ );
6363
+ const newEntries = findings.map((f) => ({
6364
+ ticketId,
6365
+ stage,
6366
+ lens: typeof f.lens === "string" && f.lens !== "" ? f.lens : "unknown",
6367
+ category: f.category,
6368
+ severity: f.severity,
6369
+ disposition: f.disposition ?? "open",
6370
+ description: f.description,
6371
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
6372
+ })).filter((e) => !existingKeys.has(`${e.ticketId}:${e.stage}:${e.lens}:${e.category}`));
6373
+ return newEntries.length > 0 ? [...existing, ...newEntries] : null;
6374
+ }
6317
6375
  var StageContext;
6318
6376
  var init_types2 = __esm({
6319
6377
  "src/autonomous/stages/types.ts"() {
@@ -6448,17 +6506,22 @@ var init_pick_ticket = __esm({
6448
6506
  (c, i) => `${i + 1}. **${c.ticket.id}: ${c.ticket.title}** (${c.ticket.type})`
6449
6507
  ).join("\n");
6450
6508
  }
6451
- const highIssues = projectState.issues.filter(
6452
- (i) => i.status === "open" && (i.severity === "critical" || i.severity === "high")
6453
- );
6509
+ const allOpenIssues = projectState.issues.filter((i) => i.status === "open");
6510
+ const highIssues = allOpenIssues.filter((i) => i.severity === "critical" || i.severity === "high");
6511
+ const otherIssues = allOpenIssues.filter((i) => i.severity !== "critical" && i.severity !== "high");
6454
6512
  let issuesText = "";
6455
6513
  if (highIssues.length > 0) {
6456
6514
  issuesText = "\n\n## Open Issues (high+ severity)\n\n" + highIssues.map(
6457
6515
  (i, idx) => `${idx + 1}. **${i.id}: ${i.title}** (${i.severity})`
6458
6516
  ).join("\n");
6459
6517
  }
6518
+ if (otherIssues.length > 0) {
6519
+ issuesText += "\n\n## Open Issues (medium/low)\n\n" + otherIssues.map(
6520
+ (i, idx) => `${idx + 1}. **${i.id}: ${i.title}** (${i.severity})`
6521
+ ).join("\n");
6522
+ }
6460
6523
  const topCandidate = candidates.kind === "found" ? candidates.candidates[0] : null;
6461
- const hasIssues = highIssues.length > 0;
6524
+ const hasIssues = allOpenIssues.length > 0;
6462
6525
  if (!topCandidate && candidates.kind !== "found" && !hasIssues) {
6463
6526
  return { action: "goto", target: "COMPLETE" };
6464
6527
  }
@@ -6479,7 +6542,7 @@ var init_pick_ticket = __esm({
6479
6542
  "",
6480
6543
  "Or to fix an issue:",
6481
6544
  "```json",
6482
- `{ "sessionId": "${ctx.state.sessionId}", "action": "report", "report": { "completedAction": "issue_picked", "issueId": "${highIssues[0].id}" } }`,
6545
+ `{ "sessionId": "${ctx.state.sessionId}", "action": "report", "report": { "completedAction": "issue_picked", "issueId": "${(highIssues[0] ?? allOpenIssues[0]).id}" } }`,
6483
6546
  "```"
6484
6547
  ] : []
6485
6548
  ].join("\n"),
@@ -6697,6 +6760,7 @@ var init_plan_review = __esm({
6697
6760
  "src/autonomous/stages/plan-review.ts"() {
6698
6761
  "use strict";
6699
6762
  init_esm_shims();
6763
+ init_types2();
6700
6764
  init_review_depth();
6701
6765
  PlanReviewStage = class {
6702
6766
  id = "PLAN_REVIEW";
@@ -6707,6 +6771,31 @@ var init_plan_review = __esm({
6707
6771
  const reviewer = nextReviewer(existingReviews, backends);
6708
6772
  const risk = ctx.state.ticket?.risk ?? "low";
6709
6773
  const minRounds = requiredRounds(risk);
6774
+ if (reviewer === "lenses") {
6775
+ return {
6776
+ instruction: [
6777
+ `# Multi-Lens Plan Review \u2014 Round ${roundNum} of ${Math.max(minRounds, roundNum)} minimum`,
6778
+ "",
6779
+ "This round uses the **multi-lens review orchestrator** for plan review. It fans out to specialized review agents (Clean Code, Security, Error Handling, and more) in parallel to evaluate the plan from multiple perspectives.",
6780
+ "",
6781
+ "1. Read the plan file",
6782
+ "2. Call `prepareLensReview()` with the plan text (stage: PLAN_REVIEW)",
6783
+ "3. Spawn all lens subagents in parallel",
6784
+ "4. Collect results and pass through the merger and judge pipeline",
6785
+ "5. Report the final SynthesisResult verdict and findings",
6786
+ "",
6787
+ "When done, call `claudestory_autonomous_guide` with:",
6788
+ "```json",
6789
+ `{ "sessionId": "${ctx.state.sessionId}", "action": "report", "report": { "completedAction": "plan_review_round", "verdict": "<approve|revise|reject>", "findings": [...] } }`,
6790
+ "```"
6791
+ ].join("\n"),
6792
+ reminders: [
6793
+ "Report the exact verdict and findings from the synthesizer.",
6794
+ "Lens subagents run in parallel with read-only tools (Read, Grep, Glob)."
6795
+ ],
6796
+ transitionedFrom: ctx.state.previousState ?? void 0
6797
+ };
6798
+ }
6710
6799
  return {
6711
6800
  instruction: [
6712
6801
  `# Plan Review \u2014 Round ${roundNum} of ${Math.max(minRounds, roundNum)} minimum`,
@@ -6768,9 +6857,19 @@ var init_plan_review = __esm({
6768
6857
  nextAction = "PLAN_REVIEW";
6769
6858
  }
6770
6859
  const reviewsForWrite = isReject ? { ...ctx.state.reviews, plan: [] } : { ...ctx.state.reviews, plan: planReviews };
6771
- ctx.writeState({
6860
+ const stateUpdate = {
6772
6861
  reviews: reviewsForWrite
6773
- });
6862
+ };
6863
+ if (reviewerBackend === "lenses" && findings.length > 0) {
6864
+ const updated = buildLensHistoryUpdate(
6865
+ findings,
6866
+ ctx.state.lensReviewHistory ?? [],
6867
+ ctx.state.ticket?.id ?? "unknown",
6868
+ "PLAN_REVIEW"
6869
+ );
6870
+ if (updated) stateUpdate.lensReviewHistory = updated;
6871
+ }
6872
+ ctx.writeState(stateUpdate);
6774
6873
  ctx.appendEvent("plan_review", {
6775
6874
  round: roundNum,
6776
6875
  verdict,
@@ -7134,13 +7233,42 @@ var init_test = __esm({
7134
7233
  }
7135
7234
  });
7136
7235
 
7236
+ // src/autonomous/review-lenses/schema-validator.ts
7237
+ var init_schema_validator = __esm({
7238
+ "src/autonomous/review-lenses/schema-validator.ts"() {
7239
+ "use strict";
7240
+ init_esm_shims();
7241
+ }
7242
+ });
7243
+
7244
+ // src/autonomous/review-lenses/cache.ts
7245
+ import { createHash } from "crypto";
7246
+ import { readFileSync as readFileSync6, writeFileSync as writeFileSync2, mkdirSync as mkdirSync2, existsSync as existsSync9, rmSync as rmSync2, renameSync as renameSync2 } from "fs";
7247
+ import { join as join12 } from "path";
7248
+ function clearCache(sessionDir2) {
7249
+ const dir = join12(sessionDir2, CACHE_DIR);
7250
+ if (!existsSync9(dir)) return;
7251
+ rmSync2(dir, { recursive: true, force: true });
7252
+ }
7253
+ var CACHE_DIR;
7254
+ var init_cache = __esm({
7255
+ "src/autonomous/review-lenses/cache.ts"() {
7256
+ "use strict";
7257
+ init_esm_shims();
7258
+ init_schema_validator();
7259
+ CACHE_DIR = "lens-cache";
7260
+ }
7261
+ });
7262
+
7137
7263
  // src/autonomous/stages/code-review.ts
7138
7264
  var CodeReviewStage;
7139
7265
  var init_code_review = __esm({
7140
7266
  "src/autonomous/stages/code-review.ts"() {
7141
7267
  "use strict";
7142
7268
  init_esm_shims();
7269
+ init_types2();
7143
7270
  init_review_depth();
7271
+ init_cache();
7144
7272
  CodeReviewStage = class {
7145
7273
  id = "CODE_REVIEW";
7146
7274
  async enter(ctx) {
@@ -7153,6 +7281,32 @@ var init_code_review = __esm({
7153
7281
  const mergeBase = ctx.state.git.mergeBase;
7154
7282
  const diffCommand = mergeBase ? `\`git diff ${mergeBase}\`` : `\`git diff HEAD\` AND \`git ls-files --others --exclude-standard\``;
7155
7283
  const diffReminder = mergeBase ? `Run: git diff ${mergeBase} \u2014 pass FULL output to reviewer.` : "Run: git diff HEAD + git ls-files --others --exclude-standard \u2014 pass FULL output to reviewer.";
7284
+ if (reviewer === "lenses") {
7285
+ return {
7286
+ instruction: [
7287
+ `# Multi-Lens Code Review \u2014 Round ${roundNum} of ${rounds} minimum`,
7288
+ "",
7289
+ `Capture the diff with: ${diffCommand}`,
7290
+ "",
7291
+ "This round uses the **multi-lens review orchestrator**. It fans out to specialized review agents (Clean Code, Security, Error Handling, and more) in parallel, then synthesizes findings into a single verdict.",
7292
+ "",
7293
+ "1. Capture the full diff",
7294
+ "2. Call `prepareLensReview()` with the diff and changed file list",
7295
+ "3. Spawn all lens subagents in parallel (each prompt is provided by the orchestrator)",
7296
+ "4. Collect results and pass through the merger and judge pipeline",
7297
+ "5. Report the final SynthesisResult verdict and findings",
7298
+ "",
7299
+ "When done, report verdict and findings."
7300
+ ].join("\n"),
7301
+ reminders: [
7302
+ diffReminder,
7303
+ "Do NOT compress or summarize the diff.",
7304
+ "Lens subagents run in parallel with read-only tools (Read, Grep, Glob).",
7305
+ "If the reviewer flags pre-existing issues unrelated to your changes, file them as issues using claudestory_issue_create with severity and impact. Do not fix them in this ticket."
7306
+ ],
7307
+ transitionedFrom: ctx.state.previousState ?? void 0
7308
+ };
7309
+ }
7156
7310
  return {
7157
7311
  instruction: [
7158
7312
  `# Code Review \u2014 Round ${roundNum} of ${rounds} minimum`,
@@ -7218,8 +7372,10 @@ var init_code_review = __esm({
7218
7372
  nextAction = "CODE_REVIEW";
7219
7373
  }
7220
7374
  if (nextAction === "PLAN") {
7375
+ clearCache(ctx.dir);
7221
7376
  ctx.writeState({
7222
7377
  reviews: { plan: [], code: [] },
7378
+ lensReviewHistory: [],
7223
7379
  ticket: ctx.state.ticket ? { ...ctx.state.ticket, realizedRisk: void 0 } : ctx.state.ticket
7224
7380
  });
7225
7381
  ctx.appendEvent("code_review", {
@@ -7231,9 +7387,19 @@ var init_code_review = __esm({
7231
7387
  await ctx.fileDeferredFindings(findings, "code");
7232
7388
  return { action: "back", target: "PLAN", reason: "plan_redirect" };
7233
7389
  }
7234
- ctx.writeState({
7390
+ const stateUpdate = {
7235
7391
  reviews: { ...ctx.state.reviews, code: codeReviews }
7236
- });
7392
+ };
7393
+ if (reviewerBackend === "lenses" && findings.length > 0) {
7394
+ const updated = buildLensHistoryUpdate(
7395
+ findings,
7396
+ ctx.state.lensReviewHistory ?? [],
7397
+ ctx.state.ticket?.id ?? "unknown",
7398
+ "CODE_REVIEW"
7399
+ );
7400
+ if (updated) stateUpdate.lensReviewHistory = updated;
7401
+ }
7402
+ ctx.writeState(stateUpdate);
7237
7403
  ctx.appendEvent("code_review", {
7238
7404
  round: roundNum,
7239
7405
  verdict,
@@ -7798,7 +7964,7 @@ var init_finalize = __esm({
7798
7964
  }
7799
7965
  });
7800
7966
  ctx.appendEvent("commit", { commitHash: normalizedHash, issueId: currentIssue.id });
7801
- return { action: "goto", target: "PICK_TICKET" };
7967
+ return { action: "goto", target: "COMPLETE" };
7802
7968
  }
7803
7969
  const completedTicket = ctx.state.ticket ? { id: ctx.state.ticket.id, title: ctx.state.ticket.title, commitHash: normalizedHash, risk: ctx.state.ticket.risk, realizedRisk: ctx.state.ticket.realizedRisk } : void 0;
7804
7970
  ctx.writeState({
@@ -7836,6 +8002,8 @@ var init_complete = __esm({
7836
8002
  finalizeCheckpoint: null
7837
8003
  });
7838
8004
  const ticketsDone = ctx.state.completedTickets.length;
8005
+ const issuesDone = (ctx.state.resolvedIssues ?? []).length;
8006
+ const totalWorkDone = ticketsDone + issuesDone;
7839
8007
  const maxTickets = ctx.state.config.maxTicketsPerSession;
7840
8008
  const mode = ctx.state.mode ?? "auto";
7841
8009
  if (mode !== "auto") {
@@ -7856,15 +8024,17 @@ var init_complete = __esm({
7856
8024
  };
7857
8025
  }
7858
8026
  const handoverInterval = ctx.state.config.handoverInterval ?? 5;
7859
- if (handoverInterval > 0 && ticketsDone > 0 && ticketsDone % handoverInterval === 0) {
8027
+ if (handoverInterval > 0 && totalWorkDone > 0 && totalWorkDone % handoverInterval === 0) {
7860
8028
  try {
7861
8029
  const { handleHandoverCreate: handleHandoverCreate3 } = await Promise.resolve().then(() => (init_handover(), handover_exports));
7862
8030
  const completedIds = ctx.state.completedTickets.map((t) => t.id).join(", ");
8031
+ const resolvedIds = (ctx.state.resolvedIssues ?? []).join(", ");
7863
8032
  const content = [
7864
- `# Checkpoint \u2014 ${ticketsDone} tickets completed`,
8033
+ `# Checkpoint \u2014 ${totalWorkDone} items completed`,
7865
8034
  "",
7866
8035
  `**Session:** ${ctx.state.sessionId}`,
7867
- `**Tickets:** ${completedIds}`,
8036
+ ...completedIds ? [`**Tickets:** ${completedIds}`] : [],
8037
+ ...resolvedIds ? [`**Issues resolved:** ${resolvedIds}`] : [],
7868
8038
  "",
7869
8039
  "This is an automatic mid-session checkpoint. The session is still active."
7870
8040
  ].join("\n");
@@ -7878,24 +8048,19 @@ var init_complete = __esm({
7878
8048
  await saveSnapshot2(ctx.root, loadResult);
7879
8049
  } catch {
7880
8050
  }
7881
- ctx.appendEvent("checkpoint", { ticketsDone, interval: handoverInterval });
8051
+ ctx.appendEvent("checkpoint", { ticketsDone, issuesDone, totalWorkDone, interval: handoverInterval });
7882
8052
  }
8053
+ const { state: projectState } = await ctx.loadProject();
7883
8054
  let nextTarget;
7884
- if (maxTickets > 0 && ticketsDone >= maxTickets) {
8055
+ if (maxTickets > 0 && totalWorkDone >= maxTickets) {
7885
8056
  nextTarget = "HANDOVER";
7886
8057
  } else {
7887
- nextTarget = "PICK_TICKET";
7888
- }
7889
- const { state: projectState } = await ctx.loadProject();
7890
- const nextResult = nextTickets(projectState, 1);
7891
- if (nextResult.kind !== "found") {
7892
- const highIssues = projectState.issues.filter(
7893
- (i) => i.status === "open" && (i.severity === "critical" || i.severity === "high")
7894
- );
7895
- if (highIssues.length > 0) {
8058
+ const nextResult = nextTickets(projectState, 1);
8059
+ if (nextResult.kind === "found") {
7896
8060
  nextTarget = "PICK_TICKET";
7897
8061
  } else {
7898
- nextTarget = "HANDOVER";
8062
+ const openIssues = projectState.issues.filter((i) => i.status === "open");
8063
+ nextTarget = openIssues.length > 0 ? "PICK_TICKET" : "HANDOVER";
7899
8064
  }
7900
8065
  }
7901
8066
  if (nextTarget === "HANDOVER") {
@@ -7910,7 +8075,7 @@ var init_complete = __esm({
7910
8075
  target: "HANDOVER",
7911
8076
  result: {
7912
8077
  instruction: [
7913
- `# Session Complete \u2014 ${ticketsDone} ticket(s) done`,
8078
+ `# Session Complete \u2014 ${ticketsDone} ticket(s) and ${issuesDone} issue(s) done`,
7914
8079
  "",
7915
8080
  "Write a session handover summarizing what was accomplished, decisions made, and what's next.",
7916
8081
  "",
@@ -7979,6 +8144,7 @@ var init_lesson_capture = __esm({
7979
8144
  const planReviews = ctx.state.reviews.plan ?? [];
7980
8145
  const codeReviews = ctx.state.reviews.code ?? [];
7981
8146
  const ticketsDone = ctx.state.completedTickets.length;
8147
+ const issuesDone = (ctx.state.resolvedIssues ?? []).length;
7982
8148
  const planFindings = planReviews.reduce((sum, r) => sum + (r.findingCount ?? 0), 0);
7983
8149
  const planCritical = planReviews.reduce((sum, r) => sum + (r.criticalCount ?? 0), 0);
7984
8150
  const planMajor = planReviews.reduce((sum, r) => sum + (r.majorCount ?? 0), 0);
@@ -7996,11 +8162,49 @@ var init_lesson_capture = __esm({
7996
8162
  planFindings,
7997
8163
  codeFindings
7998
8164
  });
8165
+ const lensHistory = ctx.state.lensReviewHistory ?? [];
8166
+ const dismissed = lensHistory.filter(
8167
+ (f) => f.disposition === "contested"
8168
+ );
8169
+ const tupleCounts = /* @__PURE__ */ new Map();
8170
+ for (const f of lensHistory) {
8171
+ const key = `${f.lens}:${f.category}`;
8172
+ const entry = tupleCounts.get(key) ?? { total: 0, dismissed: 0 };
8173
+ entry.total++;
8174
+ if (f.disposition === "contested") {
8175
+ entry.dismissed++;
8176
+ }
8177
+ tupleCounts.set(key, entry);
8178
+ }
8179
+ const falsePositivePatterns = [];
8180
+ for (const [key, counts] of tupleCounts) {
8181
+ if (counts.total >= 5 && counts.dismissed / counts.total >= 0.6) {
8182
+ falsePositivePatterns.push(
8183
+ `- **${key}**: ${counts.dismissed}/${counts.total} dismissed (${Math.round(counts.dismissed / counts.total * 100)}%)`
8184
+ );
8185
+ }
8186
+ }
8187
+ const lensSection = falsePositivePatterns.length > 0 ? [
8188
+ "",
8189
+ "## Lens False Positive Patterns",
8190
+ "",
8191
+ "These (lens, category) tuples have a >60% dismissal rate over 5+ reviews. Create a lesson for each:",
8192
+ ...falsePositivePatterns,
8193
+ "",
8194
+ 'For each pattern above, create a lesson: "Lens X tends to flag category Y, but this project considers it acceptable because [reason from dismiss history]."',
8195
+ 'Tag with `source: "lens-feedback"` and `tags: ["lens", lens-name]`.'
8196
+ ].join("\n") : "";
8197
+ const dismissedSection = dismissed.length > 0 && falsePositivePatterns.length === 0 ? [
8198
+ "",
8199
+ `## Dismissed Lens Findings (${dismissed.length})`,
8200
+ "",
8201
+ "Some lens findings were dismissed this session. Not enough data for automatic patterns yet (need 5+ reviews per tuple at >60% dismissal rate)."
8202
+ ].join("\n") : "";
7999
8203
  return {
8000
8204
  instruction: [
8001
8205
  "# Capture Lessons from Review Findings",
8002
8206
  "",
8003
- `This session completed ${ticketsDone} ticket(s). Review summary:`,
8207
+ `This session completed ${ticketsDone} ticket(s) and ${issuesDone} issue(s). Review summary:`,
8004
8208
  `- **Plan reviews:** ${planReviews.length} round(s), ${planCritical} critical, ${planMajor} major, ${planFindings} total findings`,
8005
8209
  `- **Code reviews:** ${codeReviews.length} round(s), ${codeCritical} critical, ${codeMajor} major, ${codeFindings} total findings`,
8006
8210
  "",
@@ -8011,15 +8215,18 @@ var init_lesson_capture = __esm({
8011
8215
  " - If it matches an existing lesson \u2192 call `claudestory_lesson_reinforce`",
8012
8216
  ' - If it\'s a new pattern \u2192 call `claudestory_lesson_create` with `source: "review"`',
8013
8217
  "3. Skip patterns that are one-off or already well-covered",
8218
+ lensSection,
8219
+ dismissedSection,
8014
8220
  `4. Call \`claudestory_autonomous_guide\` with completedAction: "lessons_captured"`,
8015
8221
  "",
8016
8222
  "```json",
8017
8223
  `{ "sessionId": "${ctx.state.sessionId}", "action": "report", "report": { "completedAction": "lessons_captured" } }`,
8018
8224
  "```"
8019
- ].join("\n"),
8225
+ ].filter(Boolean).join("\n"),
8020
8226
  reminders: [
8021
8227
  "Check existing lessons first \u2014 reinforce before creating duplicates.",
8022
- "Only capture patterns worth remembering across sessions."
8228
+ "Only capture patterns worth remembering across sessions.",
8229
+ ...falsePositivePatterns.length > 0 ? ["Lens false positive patterns MUST be captured as lessons \u2014 they improve future reviews."] : []
8023
8230
  ]
8024
8231
  };
8025
8232
  }
@@ -8218,8 +8425,8 @@ Impact: ${nextIssue.impact}` : ""}` : `Fix issue ${next}.`,
8218
8425
  });
8219
8426
 
8220
8427
  // src/autonomous/stages/handover.ts
8221
- import { writeFileSync as writeFileSync2 } from "fs";
8222
- import { join as join12 } from "path";
8428
+ import { writeFileSync as writeFileSync3 } from "fs";
8429
+ import { join as join13 } from "path";
8223
8430
  var HandoverStage;
8224
8431
  var init_handover2 = __esm({
8225
8432
  "src/autonomous/stages/handover.ts"() {
@@ -8231,9 +8438,10 @@ var init_handover2 = __esm({
8231
8438
  id = "HANDOVER";
8232
8439
  async enter(ctx) {
8233
8440
  const ticketsDone = ctx.state.completedTickets.length;
8441
+ const issuesDone = (ctx.state.resolvedIssues ?? []).length;
8234
8442
  return {
8235
8443
  instruction: [
8236
- `# Session Complete \u2014 ${ticketsDone} ticket(s) done`,
8444
+ `# Session Complete \u2014 ${ticketsDone} ticket(s) and ${issuesDone} issue(s) done`,
8237
8445
  "",
8238
8446
  "Write a session handover summarizing what was accomplished, decisions made, and what's next.",
8239
8447
  "",
@@ -8256,8 +8464,8 @@ var init_handover2 = __esm({
8256
8464
  } catch {
8257
8465
  handoverFailed = true;
8258
8466
  try {
8259
- const fallbackPath = join12(ctx.dir, "handover-fallback.md");
8260
- writeFileSync2(fallbackPath, content, "utf-8");
8467
+ const fallbackPath = join13(ctx.dir, "handover-fallback.md");
8468
+ writeFileSync3(fallbackPath, content, "utf-8");
8261
8469
  } catch {
8262
8470
  }
8263
8471
  }
@@ -8280,18 +8488,22 @@ var init_handover2 = __esm({
8280
8488
  });
8281
8489
  ctx.appendEvent("session_end", {
8282
8490
  ticketsCompleted: ctx.state.completedTickets.length,
8491
+ issuesResolved: (ctx.state.resolvedIssues ?? []).length,
8283
8492
  handoverFailed
8284
8493
  });
8285
8494
  const ticketsDone = ctx.state.completedTickets.length;
8495
+ const issuesDone = (ctx.state.resolvedIssues ?? []).length;
8496
+ const resolvedList = (ctx.state.resolvedIssues ?? []).map((id) => `- ${id} (resolved)`).join("\n");
8286
8497
  return {
8287
8498
  action: "advance",
8288
8499
  result: {
8289
8500
  instruction: [
8290
8501
  "# Session Complete",
8291
8502
  "",
8292
- `${ticketsDone} ticket(s) completed.${handoverFailed ? " Handover creation failed \u2014 fallback saved to session directory." : " Handover written."}${stashPopFailed ? " Auto-stash pop failed \u2014 run `git stash pop` manually." : ""} Session ended.`,
8503
+ `${ticketsDone} ticket(s) and ${issuesDone} issue(s) completed.${handoverFailed ? " Handover creation failed \u2014 fallback saved to session directory." : " Handover written."}${stashPopFailed ? " Auto-stash pop failed \u2014 run `git stash pop` manually." : ""} Session ended.`,
8293
8504
  "",
8294
- ctx.state.completedTickets.map((t) => `- ${t.id}${t.title ? `: ${t.title}` : ""} (${t.commitHash ?? "no commit"})`).join("\n")
8505
+ ctx.state.completedTickets.map((t) => `- ${t.id}${t.title ? `: ${t.title}` : ""} (${t.commitHash ?? "no commit"})`).join("\n"),
8506
+ ...resolvedList ? [resolvedList] : []
8295
8507
  ].join("\n"),
8296
8508
  reminders: [],
8297
8509
  transitionedFrom: "HANDOVER"
@@ -8342,8 +8554,8 @@ var init_stages = __esm({
8342
8554
  });
8343
8555
 
8344
8556
  // src/autonomous/version-check.ts
8345
- import { readFileSync as readFileSync6 } from "fs";
8346
- import { join as join13, dirname as dirname4 } from "path";
8557
+ import { readFileSync as readFileSync7 } from "fs";
8558
+ import { join as join14, dirname as dirname4 } from "path";
8347
8559
  import { fileURLToPath as fileURLToPath3 } from "url";
8348
8560
  function checkVersionMismatch(runningVersion, installedVersion) {
8349
8561
  if (!installedVersion) return null;
@@ -8355,12 +8567,12 @@ function getInstalledVersion() {
8355
8567
  try {
8356
8568
  const thisFile = fileURLToPath3(import.meta.url);
8357
8569
  const candidates = [
8358
- join13(dirname4(thisFile), "..", "..", "package.json"),
8359
- join13(dirname4(thisFile), "..", "package.json")
8570
+ join14(dirname4(thisFile), "..", "..", "package.json"),
8571
+ join14(dirname4(thisFile), "..", "package.json")
8360
8572
  ];
8361
8573
  for (const candidate of candidates) {
8362
8574
  try {
8363
- const raw = readFileSync6(candidate, "utf-8");
8575
+ const raw = readFileSync7(candidate, "utf-8");
8364
8576
  const pkg = JSON.parse(raw);
8365
8577
  if (pkg.version) return pkg.version;
8366
8578
  } catch {
@@ -8372,7 +8584,7 @@ function getInstalledVersion() {
8372
8584
  }
8373
8585
  }
8374
8586
  function getRunningVersion() {
8375
- return "0.1.57";
8587
+ return "0.1.59";
8376
8588
  }
8377
8589
  var init_version_check = __esm({
8378
8590
  "src/autonomous/version-check.ts"() {
@@ -8382,23 +8594,23 @@ var init_version_check = __esm({
8382
8594
  });
8383
8595
 
8384
8596
  // src/autonomous/guide.ts
8385
- import { readFileSync as readFileSync7, writeFileSync as writeFileSync3, readdirSync as readdirSync4 } from "fs";
8386
- import { join as join14 } from "path";
8597
+ import { readFileSync as readFileSync8, writeFileSync as writeFileSync4, readdirSync as readdirSync4 } from "fs";
8598
+ import { join as join15 } from "path";
8387
8599
  function buildGuideRecommendOptions(root) {
8388
8600
  const opts = {};
8389
8601
  try {
8390
- const handoversDir = join14(root, ".story", "handovers");
8602
+ const handoversDir = join15(root, ".story", "handovers");
8391
8603
  const files = readdirSync4(handoversDir, "utf-8").filter((f) => f.endsWith(".md")).sort();
8392
8604
  if (files.length > 0) {
8393
- opts.latestHandoverContent = readFileSync7(join14(handoversDir, files[files.length - 1]), "utf-8");
8605
+ opts.latestHandoverContent = readFileSync8(join15(handoversDir, files[files.length - 1]), "utf-8");
8394
8606
  }
8395
8607
  } catch {
8396
8608
  }
8397
8609
  try {
8398
- const snapshotsDir = join14(root, ".story", "snapshots");
8610
+ const snapshotsDir = join15(root, ".story", "snapshots");
8399
8611
  const snapFiles = readdirSync4(snapshotsDir, "utf-8").filter((f) => f.endsWith(".json")).sort();
8400
8612
  if (snapFiles.length > 0) {
8401
- const raw = readFileSync7(join14(snapshotsDir, snapFiles[snapFiles.length - 1]), "utf-8");
8613
+ const raw = readFileSync8(join15(snapshotsDir, snapFiles[snapFiles.length - 1]), "utf-8");
8402
8614
  const snap = JSON.parse(raw);
8403
8615
  if (snap.issues) {
8404
8616
  opts.previousOpenIssueCount = snap.issues.filter((i) => i.status !== "resolved").length;
@@ -8780,7 +8992,7 @@ Staged: ${stagedResult.data.join(", ")}`
8780
8992
  }
8781
8993
  }
8782
8994
  const { state: projectState, warnings } = await loadProject(root);
8783
- const handoversDir = join14(root, ".story", "handovers");
8995
+ const handoversDir = join15(root, ".story", "handovers");
8784
8996
  const ctx = { state: projectState, warnings, root, handoversDir, format: "md" };
8785
8997
  let handoverText = "";
8786
8998
  try {
@@ -8797,7 +9009,7 @@ Staged: ${stagedResult.data.join(", ")}`
8797
9009
  }
8798
9010
  } catch {
8799
9011
  }
8800
- const rulesText = readFileSafe2(join14(root, "RULES.md"));
9012
+ const rulesText = readFileSafe2(join15(root, "RULES.md"));
8801
9013
  const lessonDigest = buildLessonDigest(projectState.lessons);
8802
9014
  const digestParts = [
8803
9015
  handoverText ? `## Recent Handovers
@@ -8813,7 +9025,7 @@ ${rulesText}` : "",
8813
9025
  ].filter(Boolean);
8814
9026
  const digest = digestParts.join("\n\n---\n\n");
8815
9027
  try {
8816
- writeFileSync3(join14(dir, "context-digest.md"), digest, "utf-8");
9028
+ writeFileSync4(join15(dir, "context-digest.md"), digest, "utf-8");
8817
9029
  } catch {
8818
9030
  }
8819
9031
  if (mode !== "auto" && args.ticketId) {
@@ -9318,7 +9530,7 @@ ${driftPreamble}Recovered to state: **${mapping.state}**. Continue from here.`,
9318
9530
  instruction: [
9319
9531
  "# Resumed After Compact \u2014 Continue Working",
9320
9532
  "",
9321
- `${written.completedTickets.length} ticket(s) done so far. Context compacted. Pick the next ticket immediately.`,
9533
+ `${written.completedTickets.length} ticket(s) and ${(written.resolvedIssues ?? []).length} issue(s) done so far. Context compacted. Pick the next ticket or issue immediately.`,
9322
9534
  "",
9323
9535
  candidatesText,
9324
9536
  "",
@@ -9434,7 +9646,8 @@ async function handleCancel(root, args) {
9434
9646
  return guideError(new Error("Session already ended."));
9435
9647
  }
9436
9648
  const isAutoMode = info.state.mode === "auto" || !info.state.mode;
9437
- const hasTicketsRemaining = info.state.config.maxTicketsPerSession === 0 || info.state.completedTickets.length < info.state.config.maxTicketsPerSession;
9649
+ const totalDone = info.state.completedTickets.length + (info.state.resolvedIssues?.length ?? 0);
9650
+ const hasTicketsRemaining = info.state.config.maxTicketsPerSession === 0 || totalDone < info.state.config.maxTicketsPerSession;
9438
9651
  const isWorkingState = !["SESSION_END", "HANDOVER", "COMPACT"].includes(info.state.state);
9439
9652
  if (isAutoMode && hasTicketsRemaining && isWorkingState) {
9440
9653
  return {
@@ -9443,7 +9656,7 @@ async function handleCancel(root, args) {
9443
9656
  text: [
9444
9657
  "# Cancel Rejected \u2014 Session Still Active",
9445
9658
  "",
9446
- `You have completed ${info.state.completedTickets.length} ticket(s) with more work remaining.`,
9659
+ `You have completed ${info.state.completedTickets.length} ticket(s) and ${(info.state.resolvedIssues ?? []).length} issue(s) with more work remaining.`,
9447
9660
  "Do NOT cancel an autonomous session due to context size.",
9448
9661
  "If you need to manage context, Claude Code handles compaction automatically.",
9449
9662
  "",
@@ -9511,14 +9724,14 @@ async function handleCancel(root, args) {
9511
9724
  });
9512
9725
  const stashNote = stashPopFailed ? " Auto-stash pop failed \u2014 run `git stash pop` manually." : "";
9513
9726
  return {
9514
- content: [{ type: "text", text: `Session ${args.sessionId} cancelled. ${written.completedTickets.length} ticket(s) were completed.${stashNote}` }]
9727
+ content: [{ type: "text", text: `Session ${args.sessionId} cancelled. ${written.completedTickets.length} ticket(s) and ${(written.resolvedIssues ?? []).length} issue(s) were completed.${stashNote}` }]
9515
9728
  };
9516
9729
  }
9517
9730
  function guideResult(state, currentState, opts) {
9518
9731
  const summary = {
9519
9732
  ticket: state.ticket ? `${state.ticket.id}: ${state.ticket.title}` : "none",
9520
9733
  risk: state.ticket?.risk ?? "unknown",
9521
- completed: state.completedTickets.map((t) => t.id),
9734
+ completed: [...state.completedTickets.map((t) => t.id), ...state.resolvedIssues ?? []],
9522
9735
  currentStep: currentState,
9523
9736
  contextPressure: state.contextPressure?.level ?? "low",
9524
9737
  branch: state.git?.branch ?? null
@@ -9564,7 +9777,7 @@ function guideError(err) {
9564
9777
  }
9565
9778
  function readFileSafe2(path2) {
9566
9779
  try {
9567
- return readFileSync7(path2, "utf-8");
9780
+ return readFileSync8(path2, "utf-8");
9568
9781
  } catch {
9569
9782
  return "";
9570
9783
  }
@@ -9817,8 +10030,8 @@ var init_session_report_formatter = __esm({
9817
10030
  });
9818
10031
 
9819
10032
  // src/cli/commands/session-report.ts
9820
- import { readFileSync as readFileSync8, existsSync as existsSync10 } from "fs";
9821
- import { join as join15 } from "path";
10033
+ import { readFileSync as readFileSync9, existsSync as existsSync11 } from "fs";
10034
+ import { join as join16 } from "path";
9822
10035
  async function handleSessionReport(sessionId, root, format = "md") {
9823
10036
  if (!UUID_REGEX.test(sessionId)) {
9824
10037
  return {
@@ -9829,7 +10042,7 @@ async function handleSessionReport(sessionId, root, format = "md") {
9829
10042
  };
9830
10043
  }
9831
10044
  const dir = sessionDir(root, sessionId);
9832
- if (!existsSync10(dir)) {
10045
+ if (!existsSync11(dir)) {
9833
10046
  return {
9834
10047
  output: `Error: Session ${sessionId} not found.`,
9835
10048
  exitCode: 1,
@@ -9837,8 +10050,8 @@ async function handleSessionReport(sessionId, root, format = "md") {
9837
10050
  isError: true
9838
10051
  };
9839
10052
  }
9840
- const statePath2 = join15(dir, "state.json");
9841
- if (!existsSync10(statePath2)) {
10053
+ const statePath2 = join16(dir, "state.json");
10054
+ if (!existsSync11(statePath2)) {
9842
10055
  return {
9843
10056
  output: `Error: Session ${sessionId} corrupt \u2014 state.json missing.`,
9844
10057
  exitCode: 1,
@@ -9847,7 +10060,7 @@ async function handleSessionReport(sessionId, root, format = "md") {
9847
10060
  };
9848
10061
  }
9849
10062
  try {
9850
- const rawJson = JSON.parse(readFileSync8(statePath2, "utf-8"));
10063
+ const rawJson = JSON.parse(readFileSync9(statePath2, "utf-8"));
9851
10064
  if (rawJson && typeof rawJson === "object" && "schemaVersion" in rawJson && rawJson.schemaVersion !== CURRENT_SESSION_SCHEMA_VERSION) {
9852
10065
  return {
9853
10066
  output: `Error: Session ${sessionId} \u2014 unsupported session schema version ${rawJson.schemaVersion}.`,
@@ -9876,7 +10089,7 @@ async function handleSessionReport(sessionId, root, format = "md") {
9876
10089
  const events = readEvents(dir);
9877
10090
  let planContent = null;
9878
10091
  try {
9879
- planContent = readFileSync8(join15(dir, "plan.md"), "utf-8");
10092
+ planContent = readFileSync9(join16(dir, "plan.md"), "utf-8");
9880
10093
  } catch {
9881
10094
  }
9882
10095
  let gitLog = null;
@@ -9905,7 +10118,7 @@ var init_session_report = __esm({
9905
10118
  });
9906
10119
 
9907
10120
  // src/cli/commands/phase.ts
9908
- import { join as join16, resolve as resolve6 } from "path";
10121
+ import { join as join17, resolve as resolve6 } from "path";
9909
10122
  function validatePhaseId(id) {
9910
10123
  if (id.length > PHASE_ID_MAX_LENGTH) {
9911
10124
  throw new CliValidationError("invalid_input", `Phase ID "${id}" exceeds ${PHASE_ID_MAX_LENGTH} characters`);
@@ -10094,21 +10307,21 @@ async function handlePhaseDelete(id, reassign, format, root) {
10094
10307
  const updated = { ...ticket, phase: reassign, order: maxOrder };
10095
10308
  const parsed = TicketSchema.parse(updated);
10096
10309
  const content = serializeJSON(parsed);
10097
- const target = join16(wrapDir, "tickets", `${parsed.id}.json`);
10310
+ const target = join17(wrapDir, "tickets", `${parsed.id}.json`);
10098
10311
  operations.push({ op: "write", target, content });
10099
10312
  }
10100
10313
  for (const issue of affectedIssues) {
10101
10314
  const updated = { ...issue, phase: reassign };
10102
10315
  const parsed = IssueSchema.parse(updated);
10103
10316
  const content = serializeJSON(parsed);
10104
- const target = join16(wrapDir, "issues", `${parsed.id}.json`);
10317
+ const target = join17(wrapDir, "issues", `${parsed.id}.json`);
10105
10318
  operations.push({ op: "write", target, content });
10106
10319
  }
10107
10320
  const newPhases = state.roadmap.phases.filter((p) => p.id !== id);
10108
10321
  const newRoadmap = { ...state.roadmap, phases: newPhases };
10109
10322
  const parsedRoadmap = RoadmapSchema.parse(newRoadmap);
10110
10323
  const roadmapContent = serializeJSON(parsedRoadmap);
10111
- const roadmapTarget = join16(wrapDir, "roadmap.json");
10324
+ const roadmapTarget = join17(wrapDir, "roadmap.json");
10112
10325
  operations.push({ op: "write", target: roadmapTarget, content: roadmapContent });
10113
10326
  await runTransactionUnlocked(root, operations);
10114
10327
  } else {
@@ -10141,14 +10354,14 @@ var init_phase = __esm({
10141
10354
 
10142
10355
  // src/mcp/tools.ts
10143
10356
  import { z as z10 } from "zod";
10144
- import { join as join17 } from "path";
10357
+ import { join as join18 } from "path";
10145
10358
  function formatMcpError(code, message) {
10146
10359
  return `[${code}] ${message}`;
10147
10360
  }
10148
10361
  async function runMcpReadTool(pinnedRoot, handler) {
10149
10362
  try {
10150
10363
  const { state, warnings } = await loadProject(pinnedRoot);
10151
- const handoversDir = join17(pinnedRoot, ".story", "handovers");
10364
+ const handoversDir = join18(pinnedRoot, ".story", "handovers");
10152
10365
  const ctx = { state, warnings, root: pinnedRoot, handoversDir, format: "md" };
10153
10366
  const result = await handler(ctx);
10154
10367
  if (result.errorCode && INFRASTRUCTURE_ERROR_CODES.includes(result.errorCode)) {
@@ -10736,10 +10949,10 @@ var init_tools = __esm({
10736
10949
 
10737
10950
  // src/core/init.ts
10738
10951
  import { mkdir as mkdir4, stat as stat2, readFile as readFile4, writeFile as writeFile2 } from "fs/promises";
10739
- import { join as join18, resolve as resolve7 } from "path";
10952
+ import { join as join19, resolve as resolve7 } from "path";
10740
10953
  async function initProject(root, options) {
10741
10954
  const absRoot = resolve7(root);
10742
- const wrapDir = join18(absRoot, ".story");
10955
+ const wrapDir = join19(absRoot, ".story");
10743
10956
  let exists = false;
10744
10957
  try {
10745
10958
  const s = await stat2(wrapDir);
@@ -10759,11 +10972,11 @@ async function initProject(root, options) {
10759
10972
  ".story/ already exists. Use --force to overwrite config and roadmap."
10760
10973
  );
10761
10974
  }
10762
- await mkdir4(join18(wrapDir, "tickets"), { recursive: true });
10763
- await mkdir4(join18(wrapDir, "issues"), { recursive: true });
10764
- await mkdir4(join18(wrapDir, "handovers"), { recursive: true });
10765
- await mkdir4(join18(wrapDir, "notes"), { recursive: true });
10766
- await mkdir4(join18(wrapDir, "lessons"), { recursive: true });
10975
+ await mkdir4(join19(wrapDir, "tickets"), { recursive: true });
10976
+ await mkdir4(join19(wrapDir, "issues"), { recursive: true });
10977
+ await mkdir4(join19(wrapDir, "handovers"), { recursive: true });
10978
+ await mkdir4(join19(wrapDir, "notes"), { recursive: true });
10979
+ await mkdir4(join19(wrapDir, "lessons"), { recursive: true });
10767
10980
  const created = [
10768
10981
  ".story/config.json",
10769
10982
  ".story/roadmap.json",
@@ -10803,7 +11016,7 @@ async function initProject(root, options) {
10803
11016
  };
10804
11017
  await writeConfig(config, absRoot);
10805
11018
  await writeRoadmap(roadmap, absRoot);
10806
- const gitignorePath = join18(wrapDir, ".gitignore");
11019
+ const gitignorePath = join19(wrapDir, ".gitignore");
10807
11020
  await ensureGitignoreEntries(gitignorePath, STORY_GITIGNORE_ENTRIES);
10808
11021
  const warnings = [];
10809
11022
  if (options.force && exists) {
@@ -10850,8 +11063,8 @@ var init_init = __esm({
10850
11063
 
10851
11064
  // src/mcp/index.ts
10852
11065
  var mcp_exports = {};
10853
- import { realpathSync as realpathSync2, existsSync as existsSync11 } from "fs";
10854
- import { resolve as resolve8, join as join19, isAbsolute } from "path";
11066
+ import { realpathSync as realpathSync2, existsSync as existsSync12 } from "fs";
11067
+ import { resolve as resolve8, join as join20, isAbsolute } from "path";
10855
11068
  import { z as z11 } from "zod";
10856
11069
  import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
10857
11070
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
@@ -10866,7 +11079,7 @@ function tryDiscoverRoot() {
10866
11079
  const resolved = resolve8(envRoot);
10867
11080
  try {
10868
11081
  const canonical = realpathSync2(resolved);
10869
- if (existsSync11(join19(canonical, CONFIG_PATH2))) {
11082
+ if (existsSync12(join20(canonical, CONFIG_PATH2))) {
10870
11083
  return canonical;
10871
11084
  }
10872
11085
  process.stderr.write(`Warning: No .story/config.json at ${canonical}
@@ -10969,7 +11182,7 @@ var init_mcp = __esm({
10969
11182
  init_init();
10970
11183
  ENV_VAR2 = "CLAUDESTORY_PROJECT_ROOT";
10971
11184
  CONFIG_PATH2 = ".story/config.json";
10972
- version = "0.1.57";
11185
+ version = "0.1.59";
10973
11186
  main().catch((err) => {
10974
11187
  process.stderr.write(`Fatal: ${err instanceof Error ? err.message : String(err)}
10975
11188
  `);
@@ -11005,7 +11218,7 @@ __export(run_exports, {
11005
11218
  runReadCommand: () => runReadCommand,
11006
11219
  writeOutput: () => writeOutput
11007
11220
  });
11008
- import { join as join20 } from "path";
11221
+ import { join as join21 } from "path";
11009
11222
  function writeOutput(text) {
11010
11223
  try {
11011
11224
  process.stdout.write(text + "\n");
@@ -11033,7 +11246,7 @@ async function runReadCommand(format, handler) {
11033
11246
  return;
11034
11247
  }
11035
11248
  const { state, warnings } = await loadProject(root);
11036
- const handoversDir = join20(root, ".story", "handovers");
11249
+ const handoversDir = join21(root, ".story", "handovers");
11037
11250
  const result = await handler({ state, warnings, root, handoversDir, format });
11038
11251
  writeOutput(result.output);
11039
11252
  let exitCode = result.exitCode ?? ExitCode.OK;
@@ -11068,7 +11281,7 @@ async function runDeleteCommand(format, force, handler) {
11068
11281
  return;
11069
11282
  }
11070
11283
  const { state, warnings } = await loadProject(root);
11071
- const handoversDir = join20(root, ".story", "handovers");
11284
+ const handoversDir = join21(root, ".story", "handovers");
11072
11285
  if (!force && hasIntegrityWarnings(warnings)) {
11073
11286
  writeOutput(
11074
11287
  formatError(
@@ -11582,8 +11795,8 @@ __export(setup_skill_exports, {
11582
11795
  resolveSkillSourceDir: () => resolveSkillSourceDir
11583
11796
  });
11584
11797
  import { mkdir as mkdir5, writeFile as writeFile3, readFile as readFile5, readdir as readdir4, copyFile, rm, rename as rename2, unlink as unlink3 } from "fs/promises";
11585
- import { existsSync as existsSync12 } from "fs";
11586
- import { join as join21, dirname as dirname5 } from "path";
11798
+ import { existsSync as existsSync13 } from "fs";
11799
+ import { join as join22, dirname as dirname5 } from "path";
11587
11800
  import { homedir } from "os";
11588
11801
  import { execFileSync } from "child_process";
11589
11802
  import { fileURLToPath as fileURLToPath4 } from "url";
@@ -11592,10 +11805,10 @@ function log(msg) {
11592
11805
  }
11593
11806
  function resolveSkillSourceDir() {
11594
11807
  const thisDir = dirname5(fileURLToPath4(import.meta.url));
11595
- const bundledPath = join21(thisDir, "..", "src", "skill");
11596
- if (existsSync12(join21(bundledPath, "SKILL.md"))) return bundledPath;
11597
- const sourcePath = join21(thisDir, "..", "..", "skill");
11598
- if (existsSync12(join21(sourcePath, "SKILL.md"))) return sourcePath;
11808
+ const bundledPath = join22(thisDir, "..", "src", "skill");
11809
+ if (existsSync13(join22(bundledPath, "SKILL.md"))) return bundledPath;
11810
+ const sourcePath = join22(thisDir, "..", "..", "skill");
11811
+ if (existsSync13(join22(sourcePath, "SKILL.md"))) return sourcePath;
11599
11812
  throw new Error(
11600
11813
  `Cannot find bundled skill files. Checked:
11601
11814
  ${bundledPath}
@@ -11605,31 +11818,31 @@ function resolveSkillSourceDir() {
11605
11818
  async function copyDirRecursive(srcDir, destDir) {
11606
11819
  const tmpDir = destDir + ".tmp";
11607
11820
  const bakDir = destDir + ".bak";
11608
- if (!existsSync12(destDir) && existsSync12(bakDir)) {
11821
+ if (!existsSync13(destDir) && existsSync13(bakDir)) {
11609
11822
  await rename2(bakDir, destDir);
11610
11823
  }
11611
- if (existsSync12(tmpDir)) await rm(tmpDir, { recursive: true, force: true });
11612
- if (existsSync12(bakDir)) await rm(bakDir, { recursive: true, force: true });
11824
+ if (existsSync13(tmpDir)) await rm(tmpDir, { recursive: true, force: true });
11825
+ if (existsSync13(bakDir)) await rm(bakDir, { recursive: true, force: true });
11613
11826
  await mkdir5(tmpDir, { recursive: true });
11614
11827
  const entries = await readdir4(srcDir, { withFileTypes: true, recursive: true });
11615
11828
  const written = [];
11616
11829
  for (const entry of entries) {
11617
11830
  if (!entry.isFile()) continue;
11618
11831
  const parent = entry.parentPath ?? entry.path ?? srcDir;
11619
- const relativePath = join21(parent, entry.name).slice(srcDir.length + 1);
11620
- const srcPath = join21(srcDir, relativePath);
11621
- const destPath = join21(tmpDir, relativePath);
11832
+ const relativePath = join22(parent, entry.name).slice(srcDir.length + 1);
11833
+ const srcPath = join22(srcDir, relativePath);
11834
+ const destPath = join22(tmpDir, relativePath);
11622
11835
  await mkdir5(dirname5(destPath), { recursive: true });
11623
11836
  await copyFile(srcPath, destPath);
11624
11837
  written.push(relativePath);
11625
11838
  }
11626
- if (existsSync12(destDir)) {
11839
+ if (existsSync13(destDir)) {
11627
11840
  await rename2(destDir, bakDir);
11628
11841
  }
11629
11842
  try {
11630
11843
  await rename2(tmpDir, destDir);
11631
11844
  } catch (err) {
11632
- if (existsSync12(bakDir)) await rename2(bakDir, destDir).catch(() => {
11845
+ if (existsSync13(bakDir)) await rename2(bakDir, destDir).catch(() => {
11633
11846
  });
11634
11847
  await rm(tmpDir, { recursive: true, force: true }).catch(() => {
11635
11848
  });
@@ -11645,9 +11858,9 @@ function isHookWithCommand(entry, command) {
11645
11858
  return e.type === "command" && typeof e.command === "string" && e.command.trim() === command;
11646
11859
  }
11647
11860
  async function registerHook(hookType, hookEntry, settingsPath, matcher) {
11648
- const path2 = settingsPath ?? join21(homedir(), ".claude", "settings.json");
11861
+ const path2 = settingsPath ?? join22(homedir(), ".claude", "settings.json");
11649
11862
  let raw = "{}";
11650
- if (existsSync12(path2)) {
11863
+ if (existsSync13(path2)) {
11651
11864
  try {
11652
11865
  raw = await readFile5(path2, "utf-8");
11653
11866
  } catch {
@@ -11743,9 +11956,9 @@ async function registerStopHook(settingsPath) {
11743
11956
  return registerHook("Stop", { type: "command", command: STOP_HOOK_COMMAND, async: true }, settingsPath);
11744
11957
  }
11745
11958
  async function removeHook(hookType, command, settingsPath) {
11746
- const path2 = settingsPath ?? join21(homedir(), ".claude", "settings.json");
11959
+ const path2 = settingsPath ?? join22(homedir(), ".claude", "settings.json");
11747
11960
  let raw = "{}";
11748
- if (existsSync12(path2)) {
11961
+ if (existsSync13(path2)) {
11749
11962
  try {
11750
11963
  raw = await readFile5(path2, "utf-8");
11751
11964
  } catch {
@@ -11790,7 +12003,7 @@ async function removeHook(hookType, command, settingsPath) {
11790
12003
  }
11791
12004
  async function handleSetupSkill(options = {}) {
11792
12005
  const { skipHooks = false } = options;
11793
- const skillDir = join21(homedir(), ".claude", "skills", "story");
12006
+ const skillDir = join22(homedir(), ".claude", "skills", "story");
11794
12007
  await mkdir5(skillDir, { recursive: true });
11795
12008
  let srcSkillDir;
11796
12009
  try {
@@ -11803,30 +12016,30 @@ async function handleSetupSkill(options = {}) {
11803
12016
  process.exitCode = 1;
11804
12017
  return;
11805
12018
  }
11806
- const oldPrimeDir = join21(homedir(), ".claude", "skills", "prime");
11807
- if (existsSync12(oldPrimeDir)) {
12019
+ const oldPrimeDir = join22(homedir(), ".claude", "skills", "prime");
12020
+ if (existsSync13(oldPrimeDir)) {
11808
12021
  await rm(oldPrimeDir, { recursive: true, force: true });
11809
12022
  log("Removed old /prime skill (migrated to /story)");
11810
12023
  }
11811
- const existed = existsSync12(join21(skillDir, "SKILL.md"));
11812
- const skillContent = await readFile5(join21(srcSkillDir, "SKILL.md"), "utf-8");
11813
- await writeFile3(join21(skillDir, "SKILL.md"), skillContent, "utf-8");
12024
+ const existed = existsSync13(join22(skillDir, "SKILL.md"));
12025
+ const skillContent = await readFile5(join22(srcSkillDir, "SKILL.md"), "utf-8");
12026
+ await writeFile3(join22(skillDir, "SKILL.md"), skillContent, "utf-8");
11814
12027
  const supportFiles = ["setup-flow.md", "autonomous-mode.md", "reference.md"];
11815
12028
  const writtenFiles = ["SKILL.md"];
11816
12029
  const missingFiles = [];
11817
12030
  for (const filename of supportFiles) {
11818
- const srcPath = join21(srcSkillDir, filename);
11819
- if (existsSync12(srcPath)) {
12031
+ const srcPath = join22(srcSkillDir, filename);
12032
+ if (existsSync13(srcPath)) {
11820
12033
  const content = await readFile5(srcPath, "utf-8");
11821
- await writeFile3(join21(skillDir, filename), content, "utf-8");
12034
+ await writeFile3(join22(skillDir, filename), content, "utf-8");
11822
12035
  writtenFiles.push(filename);
11823
12036
  } else {
11824
12037
  missingFiles.push(filename);
11825
12038
  }
11826
12039
  }
11827
- const designSrcDir = join21(srcSkillDir, "design");
11828
- if (existsSync12(designSrcDir)) {
11829
- const designDestDir = join21(skillDir, "design");
12040
+ const designSrcDir = join22(srcSkillDir, "design");
12041
+ if (existsSync13(designSrcDir)) {
12042
+ const designDestDir = join22(skillDir, "design");
11830
12043
  try {
11831
12044
  const designFiles = await copyDirRecursive(designSrcDir, designDestDir);
11832
12045
  for (const f of designFiles) writtenFiles.push(`design/${f}`);
@@ -11949,8 +12162,8 @@ var hook_status_exports = {};
11949
12162
  __export(hook_status_exports, {
11950
12163
  handleHookStatus: () => handleHookStatus
11951
12164
  });
11952
- import { readFileSync as readFileSync9, writeFileSync as writeFileSync4, renameSync as renameSync2, unlinkSync as unlinkSync4 } from "fs";
11953
- import { join as join22 } from "path";
12165
+ import { readFileSync as readFileSync10, writeFileSync as writeFileSync5, renameSync as renameSync3, unlinkSync as unlinkSync4 } from "fs";
12166
+ import { join as join23 } from "path";
11954
12167
  async function readStdinSilent() {
11955
12168
  try {
11956
12169
  const chunks = [];
@@ -11967,8 +12180,8 @@ async function readStdinSilent() {
11967
12180
  function atomicWriteSync(targetPath, content) {
11968
12181
  const tmp = `${targetPath}.${process.pid}.tmp`;
11969
12182
  try {
11970
- writeFileSync4(tmp, content, "utf-8");
11971
- renameSync2(tmp, targetPath);
12183
+ writeFileSync5(tmp, content, "utf-8");
12184
+ renameSync3(tmp, targetPath);
11972
12185
  return true;
11973
12186
  } catch {
11974
12187
  try {
@@ -12000,10 +12213,10 @@ function activePayload(session) {
12000
12213
  };
12001
12214
  }
12002
12215
  function ensureGitignore(root) {
12003
- const gitignorePath = join22(root, ".story", ".gitignore");
12216
+ const gitignorePath = join23(root, ".story", ".gitignore");
12004
12217
  let existing = "";
12005
12218
  try {
12006
- existing = readFileSync9(gitignorePath, "utf-8");
12219
+ existing = readFileSync10(gitignorePath, "utf-8");
12007
12220
  } catch {
12008
12221
  }
12009
12222
  const lines = existing.split("\n").map((l) => l.trim());
@@ -12013,13 +12226,13 @@ function ensureGitignore(root) {
12013
12226
  if (content.length > 0 && !content.endsWith("\n")) content += "\n";
12014
12227
  content += missing.join("\n") + "\n";
12015
12228
  try {
12016
- writeFileSync4(gitignorePath, content, "utf-8");
12229
+ writeFileSync5(gitignorePath, content, "utf-8");
12017
12230
  } catch {
12018
12231
  }
12019
12232
  }
12020
12233
  function writeStatus(root, payload) {
12021
12234
  ensureGitignore(root);
12022
- const statusPath = join22(root, ".story", "status.json");
12235
+ const statusPath = join23(root, ".story", "status.json");
12023
12236
  const content = JSON.stringify(payload, null, 2) + "\n";
12024
12237
  atomicWriteSync(statusPath, content);
12025
12238
  }
@@ -12078,8 +12291,8 @@ var config_update_exports = {};
12078
12291
  __export(config_update_exports, {
12079
12292
  handleConfigSetOverrides: () => handleConfigSetOverrides
12080
12293
  });
12081
- import { readFileSync as readFileSync10 } from "fs";
12082
- import { join as join23 } from "path";
12294
+ import { readFileSync as readFileSync11 } from "fs";
12295
+ import { join as join24 } from "path";
12083
12296
  async function handleConfigSetOverrides(root, format, options) {
12084
12297
  const { json: jsonArg, clear } = options;
12085
12298
  if (!clear && !jsonArg) {
@@ -12107,8 +12320,8 @@ async function handleConfigSetOverrides(root, format, options) {
12107
12320
  }
12108
12321
  let resultOverrides = null;
12109
12322
  await withProjectLock(root, { strict: false }, async () => {
12110
- const configPath = join23(root, ".story", "config.json");
12111
- const rawContent = readFileSync10(configPath, "utf-8");
12323
+ const configPath = join24(root, ".story", "config.json");
12324
+ const rawContent = readFileSync11(configPath, "utf-8");
12112
12325
  const raw = JSON.parse(rawContent);
12113
12326
  if (clear) {
12114
12327
  delete raw.recipeOverrides;
@@ -14451,7 +14664,7 @@ async function runCli() {
14451
14664
  registerSessionCommand: registerSessionCommand2,
14452
14665
  registerRepairCommand: registerRepairCommand2
14453
14666
  } = await Promise.resolve().then(() => (init_register(), register_exports));
14454
- const version2 = "0.1.57";
14667
+ const version2 = "0.1.59";
14455
14668
  class HandledError extends Error {
14456
14669
  constructor() {
14457
14670
  super("HANDLED_ERROR");