@anthropologies/claudestory 0.1.34 → 0.1.35

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.
Files changed (3) hide show
  1. package/dist/cli.js +192 -13
  2. package/dist/mcp.js +49 -11
  3. package/package.json +1 -1
package/dist/cli.js CHANGED
@@ -3382,7 +3382,20 @@ function validateParentTicket(parentId, ticketId, state) {
3382
3382
  throw new CliValidationError("invalid_input", `Parent ticket ${parentId} not found`);
3383
3383
  }
3384
3384
  }
3385
+ function buildErrorMultiset(findings) {
3386
+ const counts = /* @__PURE__ */ new Map();
3387
+ const messages = /* @__PURE__ */ new Map();
3388
+ for (const f of findings) {
3389
+ if (f.level !== "error") continue;
3390
+ const key = `${f.code}|${f.entity ?? ""}|${f.message}`;
3391
+ counts.set(key, (counts.get(key) ?? 0) + 1);
3392
+ messages.set(key, f.message);
3393
+ }
3394
+ return { counts, messages };
3395
+ }
3385
3396
  function validatePostWriteState(candidate, state, isCreate) {
3397
+ const preResult = validateProject(state);
3398
+ const { counts: preErrors } = buildErrorMultiset(preResult.findings);
3386
3399
  const existingTickets = [...state.tickets];
3387
3400
  if (isCreate) {
3388
3401
  existingTickets.push(candidate);
@@ -3399,11 +3412,17 @@ function validatePostWriteState(candidate, state, isCreate) {
3399
3412
  config: state.config,
3400
3413
  handoverFilenames: [...state.handoverFilenames]
3401
3414
  });
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}`);
3415
+ const postResult = validateProject(postState);
3416
+ const { counts: postErrors, messages: postMessages } = buildErrorMultiset(postResult.findings);
3417
+ const newErrors = [];
3418
+ for (const [key, postCount] of postErrors) {
3419
+ const preCount = preErrors.get(key) ?? 0;
3420
+ if (postCount > preCount) {
3421
+ newErrors.push(postMessages.get(key) ?? key);
3422
+ }
3423
+ }
3424
+ if (newErrors.length > 0) {
3425
+ throw new CliValidationError("validation_failed", `Write would create invalid state: ${newErrors.join("; ")}`);
3407
3426
  }
3408
3427
  }
3409
3428
  async function handleTicketCreate(args, format, root) {
@@ -3587,7 +3606,20 @@ function validateRelatedTickets(ids, state) {
3587
3606
  }
3588
3607
  }
3589
3608
  }
3609
+ function buildErrorMultiset2(findings) {
3610
+ const counts = /* @__PURE__ */ new Map();
3611
+ const messages = /* @__PURE__ */ new Map();
3612
+ for (const f of findings) {
3613
+ if (f.level !== "error") continue;
3614
+ const key = `${f.code}|${f.entity ?? ""}|${f.message}`;
3615
+ counts.set(key, (counts.get(key) ?? 0) + 1);
3616
+ messages.set(key, f.message);
3617
+ }
3618
+ return { counts, messages };
3619
+ }
3590
3620
  function validatePostWriteIssueState(candidate, state, isCreate) {
3621
+ const preResult = validateProject(state);
3622
+ const { counts: preErrors } = buildErrorMultiset2(preResult.findings);
3591
3623
  const existingIssues = [...state.issues];
3592
3624
  if (isCreate) {
3593
3625
  existingIssues.push(candidate);
@@ -3604,11 +3636,17 @@ function validatePostWriteIssueState(candidate, state, isCreate) {
3604
3636
  config: state.config,
3605
3637
  handoverFilenames: [...state.handoverFilenames]
3606
3638
  });
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}`);
3639
+ const postResult = validateProject(postState);
3640
+ const { counts: postErrors, messages: postMessages } = buildErrorMultiset2(postResult.findings);
3641
+ const newErrors = [];
3642
+ for (const [key, postCount] of postErrors) {
3643
+ const preCount = preErrors.get(key) ?? 0;
3644
+ if (postCount > preCount) {
3645
+ newErrors.push(postMessages.get(key) ?? key);
3646
+ }
3647
+ }
3648
+ if (newErrors.length > 0) {
3649
+ throw new CliValidationError("validation_failed", `Write would create invalid state: ${newErrors.join("; ")}`);
3612
3650
  }
3613
3651
  }
3614
3652
  async function handleIssueCreate(args, format, root) {
@@ -10373,7 +10411,7 @@ var init_mcp = __esm({
10373
10411
  init_init();
10374
10412
  ENV_VAR2 = "CLAUDESTORY_PROJECT_ROOT";
10375
10413
  CONFIG_PATH2 = ".story/config.json";
10376
- version = "0.1.34";
10414
+ version = "0.1.35";
10377
10415
  main().catch((err) => {
10378
10416
  process.stderr.write(`Fatal: ${err instanceof Error ? err.message : String(err)}
10379
10417
  `);
@@ -10521,6 +10559,102 @@ var init_run = __esm({
10521
10559
  }
10522
10560
  });
10523
10561
 
10562
+ // src/cli/commands/repair.ts
10563
+ function computeRepairs(state, warnings) {
10564
+ const integrityWarning = warnings.find(
10565
+ (w) => INTEGRITY_WARNING_TYPES.includes(w.type)
10566
+ );
10567
+ if (integrityWarning) {
10568
+ return {
10569
+ fixes: [],
10570
+ error: `Cannot repair: data integrity issue in ${integrityWarning.file}: ${integrityWarning.message}. Fix the corrupt file first, then retry.`,
10571
+ tickets: [],
10572
+ issues: []
10573
+ };
10574
+ }
10575
+ const fixes = [];
10576
+ const modifiedTickets = [];
10577
+ const modifiedIssues = [];
10578
+ const ticketIDs = new Set(state.tickets.map((t) => t.id));
10579
+ const phaseIDs = new Set(state.roadmap.phases.map((p) => {
10580
+ const id = p.id;
10581
+ return typeof id === "object" && id !== null ? id.rawValue ?? String(id) : String(id);
10582
+ }));
10583
+ for (const ticket of state.tickets) {
10584
+ let modified = false;
10585
+ let blockedBy = [...ticket.blockedBy];
10586
+ let parentTicket = ticket.parentTicket;
10587
+ let phase = ticket.phase;
10588
+ const validBlockedBy = blockedBy.filter((ref) => ticketIDs.has(ref));
10589
+ if (validBlockedBy.length < blockedBy.length) {
10590
+ const removed = blockedBy.filter((ref) => !ticketIDs.has(ref));
10591
+ blockedBy = validBlockedBy;
10592
+ modified = true;
10593
+ fixes.push({ entity: ticket.id, field: "blockedBy", description: `Removed stale refs: ${removed.join(", ")}` });
10594
+ }
10595
+ if (parentTicket && !ticketIDs.has(parentTicket)) {
10596
+ fixes.push({ entity: ticket.id, field: "parentTicket", description: `Cleared stale ref: ${parentTicket}` });
10597
+ parentTicket = null;
10598
+ modified = true;
10599
+ }
10600
+ const phaseRaw = typeof phase === "object" && phase !== null ? phase.rawValue ?? String(phase) : phase != null ? String(phase) : null;
10601
+ if (phaseRaw && !phaseIDs.has(phaseRaw)) {
10602
+ fixes.push({ entity: ticket.id, field: "phase", description: `Cleared stale phase: ${phaseRaw}` });
10603
+ phase = null;
10604
+ modified = true;
10605
+ }
10606
+ if (modified) {
10607
+ modifiedTickets.push({ ...ticket, blockedBy, parentTicket, phase });
10608
+ }
10609
+ }
10610
+ for (const issue of state.issues) {
10611
+ let modified = false;
10612
+ let relatedTickets = [...issue.relatedTickets];
10613
+ let phase = issue.phase;
10614
+ const validRelated = relatedTickets.filter((ref) => ticketIDs.has(ref));
10615
+ if (validRelated.length < relatedTickets.length) {
10616
+ const removed = relatedTickets.filter((ref) => !ticketIDs.has(ref));
10617
+ relatedTickets = validRelated;
10618
+ modified = true;
10619
+ fixes.push({ entity: issue.id, field: "relatedTickets", description: `Removed stale refs: ${removed.join(", ")}` });
10620
+ }
10621
+ const issuePhaseRaw = typeof phase === "object" && phase !== null ? phase.rawValue ?? String(phase) : phase != null ? String(phase) : null;
10622
+ if (issuePhaseRaw && !phaseIDs.has(issuePhaseRaw)) {
10623
+ fixes.push({ entity: issue.id, field: "phase", description: `Cleared stale phase: ${issuePhaseRaw}` });
10624
+ phase = null;
10625
+ modified = true;
10626
+ }
10627
+ if (modified) {
10628
+ modifiedIssues.push({ ...issue, relatedTickets, phase });
10629
+ }
10630
+ }
10631
+ return { fixes, tickets: modifiedTickets, issues: modifiedIssues };
10632
+ }
10633
+ function handleRepair(ctx, dryRun) {
10634
+ const { fixes, error } = computeRepairs(ctx.state, ctx.warnings);
10635
+ if (error) {
10636
+ return { output: error, errorCode: "project_corrupt" };
10637
+ }
10638
+ if (fixes.length === 0) {
10639
+ return { output: "No stale references found. Project is clean." };
10640
+ }
10641
+ const lines = [`Found ${fixes.length} stale reference(s)${dryRun ? " (dry run)" : ""}:`, ""];
10642
+ for (const fix of fixes) {
10643
+ lines.push(`- ${fix.entity}.${fix.field}: ${fix.description}`);
10644
+ }
10645
+ if (dryRun) {
10646
+ lines.push("", "Run without --dry-run to apply fixes.");
10647
+ }
10648
+ return { output: lines.join("\n") };
10649
+ }
10650
+ var init_repair = __esm({
10651
+ "src/cli/commands/repair.ts"() {
10652
+ "use strict";
10653
+ init_esm_shims();
10654
+ init_errors();
10655
+ }
10656
+ });
10657
+
10524
10658
  // src/cli/commands/init.ts
10525
10659
  import { basename as basename2 } from "path";
10526
10660
  function registerInitCommand(yargs) {
@@ -11605,6 +11739,7 @@ __export(register_exports, {
11605
11739
  registerRecapCommand: () => registerRecapCommand,
11606
11740
  registerRecommendCommand: () => registerRecommendCommand,
11607
11741
  registerReferenceCommand: () => registerReferenceCommand,
11742
+ registerRepairCommand: () => registerRepairCommand,
11608
11743
  registerSelftestCommand: () => registerSelftestCommand,
11609
11744
  registerSessionCommand: () => registerSessionCommand,
11610
11745
  registerSetupSkillCommand: () => registerSetupSkillCommand,
@@ -11635,6 +11770,47 @@ function registerValidateCommand(yargs) {
11635
11770
  }
11636
11771
  );
11637
11772
  }
11773
+ function registerRepairCommand(yargs) {
11774
+ return yargs.command(
11775
+ "repair",
11776
+ "Fix stale references in .story/ data",
11777
+ (y) => y.option("dry-run", { type: "boolean", default: false, describe: "Show what would be fixed without writing" }),
11778
+ async (argv) => {
11779
+ const dryRun = argv["dry-run"];
11780
+ if (dryRun) {
11781
+ await runReadCommand("md", (ctx) => handleRepair(ctx, true));
11782
+ } else {
11783
+ const { withProjectLock: withProjectLock2, writeTicketUnlocked: writeTicketUnlocked2, writeIssueUnlocked: writeIssueUnlocked2, runTransactionUnlocked: runTransactionUnlocked2 } = await Promise.resolve().then(() => (init_project_loader(), project_loader_exports));
11784
+ const root = (await Promise.resolve().then(() => (init_project_root_discovery(), project_root_discovery_exports))).discoverProjectRoot();
11785
+ await withProjectLock2(root, { strict: false }, async ({ state, warnings }) => {
11786
+ const result = computeRepairs(state, warnings);
11787
+ if (result.error) {
11788
+ writeOutput(result.error);
11789
+ process.exitCode = ExitCode.USER_ERROR;
11790
+ return;
11791
+ }
11792
+ if (result.fixes.length === 0) {
11793
+ writeOutput("No stale references found. Project is clean.");
11794
+ return;
11795
+ }
11796
+ await runTransactionUnlocked2(root, async () => {
11797
+ for (const ticket of result.tickets) {
11798
+ await writeTicketUnlocked2(ticket, root);
11799
+ }
11800
+ for (const issue of result.issues) {
11801
+ await writeIssueUnlocked2(issue, root);
11802
+ }
11803
+ });
11804
+ const lines = [`Fixed ${result.fixes.length} stale reference(s):`, ""];
11805
+ for (const fix of result.fixes) {
11806
+ lines.push(`- ${fix.entity}.${fix.field}: ${fix.description}`);
11807
+ }
11808
+ writeOutput(lines.join("\n"));
11809
+ });
11810
+ }
11811
+ }
11812
+ );
11813
+ }
11638
11814
  function registerHandoverCommand(yargs) {
11639
11815
  return yargs.command(
11640
11816
  "handover",
@@ -13606,6 +13782,7 @@ var init_register = __esm({
13606
13782
  init_output_formatter();
13607
13783
  init_status();
13608
13784
  init_validate();
13785
+ init_repair();
13609
13786
  init_handover();
13610
13787
  init_blocker();
13611
13788
  init_ticket2();
@@ -13655,9 +13832,10 @@ async function runCli() {
13655
13832
  registerSetupSkillCommand: registerSetupSkillCommand2,
13656
13833
  registerHookStatusCommand: registerHookStatusCommand2,
13657
13834
  registerConfigCommand: registerConfigCommand2,
13658
- registerSessionCommand: registerSessionCommand2
13835
+ registerSessionCommand: registerSessionCommand2,
13836
+ registerRepairCommand: registerRepairCommand2
13659
13837
  } = await Promise.resolve().then(() => (init_register(), register_exports));
13660
- const version2 = "0.1.34";
13838
+ const version2 = "0.1.35";
13661
13839
  class HandledError extends Error {
13662
13840
  constructor() {
13663
13841
  super("HANDLED_ERROR");
@@ -13689,6 +13867,7 @@ async function runCli() {
13689
13867
  cli = registerHandoverCommand2(cli);
13690
13868
  cli = registerBlockerCommand2(cli);
13691
13869
  cli = registerValidateCommand2(cli);
13870
+ cli = registerRepairCommand2(cli);
13692
13871
  cli = registerSnapshotCommand2(cli);
13693
13872
  cli = registerRecapCommand2(cli);
13694
13873
  cli = registerExportCommand2(cli);
package/dist/mcp.js CHANGED
@@ -2984,7 +2984,20 @@ function validateRelatedTickets(ids, state) {
2984
2984
  }
2985
2985
  }
2986
2986
  }
2987
+ function buildErrorMultiset2(findings) {
2988
+ const counts = /* @__PURE__ */ new Map();
2989
+ const messages = /* @__PURE__ */ new Map();
2990
+ for (const f of findings) {
2991
+ if (f.level !== "error") continue;
2992
+ const key = `${f.code}|${f.entity ?? ""}|${f.message}`;
2993
+ counts.set(key, (counts.get(key) ?? 0) + 1);
2994
+ messages.set(key, f.message);
2995
+ }
2996
+ return { counts, messages };
2997
+ }
2987
2998
  function validatePostWriteIssueState(candidate, state, isCreate) {
2999
+ const preResult = validateProject(state);
3000
+ const { counts: preErrors } = buildErrorMultiset2(preResult.findings);
2988
3001
  const existingIssues = [...state.issues];
2989
3002
  if (isCreate) {
2990
3003
  existingIssues.push(candidate);
@@ -3001,11 +3014,17 @@ function validatePostWriteIssueState(candidate, state, isCreate) {
3001
3014
  config: state.config,
3002
3015
  handoverFilenames: [...state.handoverFilenames]
3003
3016
  });
3004
- const result = validateProject(postState);
3005
- if (!result.valid) {
3006
- const errors = result.findings.filter((f) => f.level === "error");
3007
- const msg = errors.map((f) => f.message).join("; ");
3008
- throw new CliValidationError("validation_failed", `Write would create invalid state: ${msg}`);
3017
+ const postResult = validateProject(postState);
3018
+ const { counts: postErrors, messages: postMessages } = buildErrorMultiset2(postResult.findings);
3019
+ const newErrors = [];
3020
+ for (const [key, postCount] of postErrors) {
3021
+ const preCount = preErrors.get(key) ?? 0;
3022
+ if (postCount > preCount) {
3023
+ newErrors.push(postMessages.get(key) ?? key);
3024
+ }
3025
+ }
3026
+ if (newErrors.length > 0) {
3027
+ throw new CliValidationError("validation_failed", `Write would create invalid state: ${newErrors.join("; ")}`);
3009
3028
  }
3010
3029
  }
3011
3030
  async function handleIssueCreate(args, format, root) {
@@ -4205,7 +4224,20 @@ function validateParentTicket(parentId, ticketId, state) {
4205
4224
  throw new CliValidationError("invalid_input", `Parent ticket ${parentId} not found`);
4206
4225
  }
4207
4226
  }
4227
+ function buildErrorMultiset(findings) {
4228
+ const counts = /* @__PURE__ */ new Map();
4229
+ const messages = /* @__PURE__ */ new Map();
4230
+ for (const f of findings) {
4231
+ if (f.level !== "error") continue;
4232
+ const key = `${f.code}|${f.entity ?? ""}|${f.message}`;
4233
+ counts.set(key, (counts.get(key) ?? 0) + 1);
4234
+ messages.set(key, f.message);
4235
+ }
4236
+ return { counts, messages };
4237
+ }
4208
4238
  function validatePostWriteState(candidate, state, isCreate) {
4239
+ const preResult = validateProject(state);
4240
+ const { counts: preErrors } = buildErrorMultiset(preResult.findings);
4209
4241
  const existingTickets = [...state.tickets];
4210
4242
  if (isCreate) {
4211
4243
  existingTickets.push(candidate);
@@ -4222,11 +4254,17 @@ function validatePostWriteState(candidate, state, isCreate) {
4222
4254
  config: state.config,
4223
4255
  handoverFilenames: [...state.handoverFilenames]
4224
4256
  });
4225
- const result = validateProject(postState);
4226
- if (!result.valid) {
4227
- const errors = result.findings.filter((f) => f.level === "error");
4228
- const msg = errors.map((f) => f.message).join("; ");
4229
- throw new CliValidationError("validation_failed", `Write would create invalid state: ${msg}`);
4257
+ const postResult = validateProject(postState);
4258
+ const { counts: postErrors, messages: postMessages } = buildErrorMultiset(postResult.findings);
4259
+ const newErrors = [];
4260
+ for (const [key, postCount] of postErrors) {
4261
+ const preCount = preErrors.get(key) ?? 0;
4262
+ if (postCount > preCount) {
4263
+ newErrors.push(postMessages.get(key) ?? key);
4264
+ }
4265
+ }
4266
+ if (newErrors.length > 0) {
4267
+ throw new CliValidationError("validation_failed", `Write would create invalid state: ${newErrors.join("; ")}`);
4230
4268
  }
4231
4269
  }
4232
4270
  async function handleTicketCreate(args, format, root) {
@@ -9521,7 +9559,7 @@ async function ensureGitignoreEntries(gitignorePath, entries) {
9521
9559
  // src/mcp/index.ts
9522
9560
  var ENV_VAR2 = "CLAUDESTORY_PROJECT_ROOT";
9523
9561
  var CONFIG_PATH2 = ".story/config.json";
9524
- var version = "0.1.34";
9562
+ var version = "0.1.35";
9525
9563
  function tryDiscoverRoot() {
9526
9564
  const envRoot = process.env[ENV_VAR2];
9527
9565
  if (envRoot) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@anthropologies/claudestory",
3
- "version": "0.1.34",
3
+ "version": "0.1.35",
4
4
  "license": "UNLICENSED",
5
5
  "description": "Cross-session context persistence for AI coding projects. Tracks tickets, issues, roadmap, and handovers so every session builds on the last.",
6
6
  "keywords": [