@anthropologies/claudestory 0.1.15 → 0.1.16

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 +114 -6
  2. package/dist/mcp.js +177 -5
  3. package/package.json +1 -1
package/dist/cli.js CHANGED
@@ -562,6 +562,29 @@ var init_handover_parser = __esm({
562
562
  });
563
563
 
564
564
  // src/core/project-loader.ts
565
+ var project_loader_exports = {};
566
+ __export(project_loader_exports, {
567
+ atomicWrite: () => atomicWrite,
568
+ deleteIssue: () => deleteIssue,
569
+ deleteNote: () => deleteNote,
570
+ deleteTicket: () => deleteTicket,
571
+ guardPath: () => guardPath,
572
+ loadProject: () => loadProject,
573
+ runTransaction: () => runTransaction,
574
+ runTransactionUnlocked: () => runTransactionUnlocked,
575
+ serializeJSON: () => serializeJSON,
576
+ sortKeysDeep: () => sortKeysDeep,
577
+ withProjectLock: () => withProjectLock,
578
+ writeConfig: () => writeConfig,
579
+ writeIssue: () => writeIssue,
580
+ writeIssueUnlocked: () => writeIssueUnlocked,
581
+ writeNote: () => writeNote,
582
+ writeNoteUnlocked: () => writeNoteUnlocked,
583
+ writeRoadmap: () => writeRoadmap,
584
+ writeRoadmapUnlocked: () => writeRoadmapUnlocked,
585
+ writeTicket: () => writeTicket,
586
+ writeTicketUnlocked: () => writeTicketUnlocked
587
+ });
565
588
  import {
566
589
  readdir as readdir2,
567
590
  readFile as readFile2,
@@ -920,6 +943,12 @@ async function runTransactionUnlocked(root, operations) {
920
943
  throw new ProjectLoaderError("io_error", "Transaction failed", err);
921
944
  }
922
945
  }
946
+ async function runTransaction(root, operations) {
947
+ const wrapDir = resolve2(root, ".story");
948
+ await withLock(wrapDir, async () => {
949
+ await runTransactionUnlocked(root, operations);
950
+ });
951
+ }
923
952
  async function doRecoverTransaction(wrapDir) {
924
953
  const journalPath = join3(wrapDir, ".txn.json");
925
954
  let entries;
@@ -5237,6 +5266,19 @@ async function handleReportPlan(root, dir, state, report) {
5237
5266
  });
5238
5267
  }
5239
5268
  const risk = assessRisk(void 0, void 0);
5269
+ if (state.ticket) {
5270
+ try {
5271
+ const { withProjectLock: withProjectLock2, writeTicketUnlocked: writeTicketUnlocked2 } = await Promise.resolve().then(() => (init_project_loader(), project_loader_exports));
5272
+ await withProjectLock2(root, { strict: false }, async ({ state: projectState }) => {
5273
+ const ticket = projectState.ticketByID(state.ticket.id);
5274
+ if (ticket && ticket.status !== "inprogress") {
5275
+ const updated = { ...ticket, status: "inprogress" };
5276
+ await writeTicketUnlocked2(updated, root);
5277
+ }
5278
+ });
5279
+ } catch {
5280
+ }
5281
+ }
5240
5282
  const written = writeSessionSync(dir, {
5241
5283
  ...state,
5242
5284
  state: "PLAN_REVIEW",
@@ -5594,11 +5636,11 @@ async function handleReportComplete(root, dir, state, report) {
5594
5636
  const maxTickets = updated.config.maxTicketsPerSession;
5595
5637
  let nextState;
5596
5638
  let advice = "ok";
5597
- if (pressure === "critical") {
5639
+ if (maxTickets > 0 && ticketsDone >= maxTickets) {
5598
5640
  nextState = "HANDOVER";
5599
- advice = "compact-now";
5600
- } else if (maxTickets > 0 && ticketsDone >= maxTickets) {
5641
+ } else if (pressure === "critical") {
5601
5642
  nextState = "HANDOVER";
5643
+ advice = "compact-now";
5602
5644
  } else if (pressure === "high") {
5603
5645
  advice = "consider-compact";
5604
5646
  nextState = "PICK_TICKET";
@@ -5678,6 +5720,36 @@ async function handleReportHandover(root, dir, state, report) {
5678
5720
  } catch {
5679
5721
  }
5680
5722
  }
5723
+ const pressureLevel = state.contextPressure?.level ?? "low";
5724
+ const maxTickets = state.config.maxTicketsPerSession;
5725
+ const capReached = maxTickets > 0 && state.completedTickets.length >= maxTickets;
5726
+ let hasMoreTickets = false;
5727
+ try {
5728
+ const { state: ps } = await loadProject(root);
5729
+ hasMoreTickets = nextTickets(ps, 1).kind === "found";
5730
+ } catch {
5731
+ }
5732
+ if (pressureLevel === "critical" && hasMoreTickets && !capReached) {
5733
+ return guideResult(state, "HANDOVER", {
5734
+ instruction: [
5735
+ "# Context Compaction Needed",
5736
+ "",
5737
+ `${state.completedTickets.length} ticket(s) completed so far. Handover written. Context is large \u2014 time to compact and continue.`,
5738
+ "",
5739
+ 'Call `claudestory_autonomous_guide` with `action: "pre_compact"` now:',
5740
+ "```json",
5741
+ `{ "sessionId": "${state.sessionId}", "action": "pre_compact" }`,
5742
+ "```",
5743
+ "",
5744
+ 'After pre_compact responds, run `/compact`, then call with `action: "resume"` to continue working on more tickets.'
5745
+ ].join("\n"),
5746
+ reminders: [
5747
+ "Do NOT stop. This is a context compaction, not a session end.",
5748
+ "Call pre_compact \u2192 /compact \u2192 resume to continue autonomous work."
5749
+ ],
5750
+ contextAdvice: "compact-now"
5751
+ });
5752
+ }
5681
5753
  const written = writeSessionSync(dir, {
5682
5754
  ...state,
5683
5755
  state: "SESSION_END",
@@ -5725,6 +5797,40 @@ async function handleResume(root, args) {
5725
5797
  resumeFromRevision: null,
5726
5798
  contextPressure: { ...info.state.contextPressure, compactionCount: (info.state.contextPressure?.compactionCount ?? 0) + 1 }
5727
5799
  });
5800
+ if (resumeState === "PICK_TICKET") {
5801
+ let candidatesText = "No ticket candidates available.";
5802
+ let topCandidate = null;
5803
+ try {
5804
+ const { state: ps } = await loadProject(root);
5805
+ const result = nextTickets(ps, 5);
5806
+ if (result.kind === "found") {
5807
+ topCandidate = result.candidates[0] ?? null;
5808
+ candidatesText = result.candidates.map(
5809
+ (c, i) => `${i + 1}. **${c.ticket.id}: ${c.ticket.title}** (${c.ticket.type})`
5810
+ ).join("\n");
5811
+ }
5812
+ } catch {
5813
+ }
5814
+ return guideResult(written, "PICK_TICKET", {
5815
+ instruction: [
5816
+ "# Resumed After Compact \u2014 Continue Working",
5817
+ "",
5818
+ `${written.completedTickets.length} ticket(s) done so far. Context compacted. Pick the next ticket immediately.`,
5819
+ "",
5820
+ candidatesText,
5821
+ "",
5822
+ topCandidate ? `Pick **${topCandidate.ticket.id}** by calling \`claudestory_autonomous_guide\` now:` : "Pick a ticket now:",
5823
+ "```json",
5824
+ topCandidate ? `{ "sessionId": "${written.sessionId}", "action": "report", "report": { "completedAction": "ticket_picked", "ticketId": "${topCandidate.ticket.id}" } }` : `{ "sessionId": "${written.sessionId}", "action": "report", "report": { "completedAction": "ticket_picked", "ticketId": "T-XXX" } }`,
5825
+ "```"
5826
+ ].join("\n"),
5827
+ reminders: [
5828
+ "Do NOT stop or summarize. Pick the next ticket IMMEDIATELY.",
5829
+ "Do NOT ask the user for confirmation.",
5830
+ "You are in autonomous mode \u2014 continue working."
5831
+ ]
5832
+ });
5833
+ }
5728
5834
  return guideResult(written, resumeState, {
5729
5835
  instruction: [
5730
5836
  "# Resumed After Compact",
@@ -5736,6 +5842,7 @@ async function handleResume(root, args) {
5736
5842
  ].join("\n"),
5737
5843
  reminders: [
5738
5844
  "Do NOT use plan mode.",
5845
+ "Do NOT stop or summarize.",
5739
5846
  "Call autonomous_guide after completing each step."
5740
5847
  ]
5741
5848
  });
@@ -5751,11 +5858,12 @@ async function handlePreCompact(root, args) {
5751
5858
  return guideError(new Error(`Session ${args.sessionId} is already in COMPACT state. Call action: "resume" to continue.`));
5752
5859
  }
5753
5860
  const headResult = await gitHead(root);
5861
+ const resumeTarget = info.state.state === "HANDOVER" ? "PICK_TICKET" : info.state.state;
5754
5862
  const written = writeSessionSync(info.dir, {
5755
5863
  ...refreshLease(info.state),
5756
5864
  state: "COMPACT",
5757
5865
  previousState: info.state.state,
5758
- preCompactState: info.state.state,
5866
+ preCompactState: resumeTarget,
5759
5867
  resumeFromRevision: info.state.revision,
5760
5868
  git: {
5761
5869
  ...info.state.git,
@@ -6835,7 +6943,7 @@ var init_mcp = __esm({
6835
6943
  init_init();
6836
6944
  ENV_VAR2 = "CLAUDESTORY_PROJECT_ROOT";
6837
6945
  CONFIG_PATH2 = ".story/config.json";
6838
- version = "0.1.15";
6946
+ version = "0.1.16";
6839
6947
  main().catch((err) => {
6840
6948
  process.stderr.write(`Fatal: ${err instanceof Error ? err.message : String(err)}
6841
6949
  `);
@@ -9470,7 +9578,7 @@ async function runCli() {
9470
9578
  registerHookStatusCommand: registerHookStatusCommand2,
9471
9579
  registerConfigCommand: registerConfigCommand2
9472
9580
  } = await Promise.resolve().then(() => (init_register(), register_exports));
9473
- const version2 = "0.1.15";
9581
+ const version2 = "0.1.16";
9474
9582
  class HandledError extends Error {
9475
9583
  constructor() {
9476
9584
  super("HANDLED_ERROR");
package/dist/mcp.js CHANGED
@@ -504,6 +504,29 @@ var init_handover_parser = __esm({
504
504
  });
505
505
 
506
506
  // src/core/project-loader.ts
507
+ var project_loader_exports = {};
508
+ __export(project_loader_exports, {
509
+ atomicWrite: () => atomicWrite,
510
+ deleteIssue: () => deleteIssue,
511
+ deleteNote: () => deleteNote,
512
+ deleteTicket: () => deleteTicket,
513
+ guardPath: () => guardPath,
514
+ loadProject: () => loadProject,
515
+ runTransaction: () => runTransaction,
516
+ runTransactionUnlocked: () => runTransactionUnlocked,
517
+ serializeJSON: () => serializeJSON,
518
+ sortKeysDeep: () => sortKeysDeep,
519
+ withProjectLock: () => withProjectLock,
520
+ writeConfig: () => writeConfig,
521
+ writeIssue: () => writeIssue,
522
+ writeIssueUnlocked: () => writeIssueUnlocked,
523
+ writeNote: () => writeNote,
524
+ writeNoteUnlocked: () => writeNoteUnlocked,
525
+ writeRoadmap: () => writeRoadmap,
526
+ writeRoadmapUnlocked: () => writeRoadmapUnlocked,
527
+ writeTicket: () => writeTicket,
528
+ writeTicketUnlocked: () => writeTicketUnlocked
529
+ });
507
530
  import {
508
531
  readdir as readdir2,
509
532
  readFile as readFile2,
@@ -807,6 +830,67 @@ async function withProjectLock(root, options, handler) {
807
830
  await handler(result);
808
831
  });
809
832
  }
833
+ async function runTransactionUnlocked(root, operations) {
834
+ const wrapDir = resolve2(root, ".story");
835
+ const journalPath = join3(wrapDir, ".txn.json");
836
+ const entries = [];
837
+ let commitStarted = false;
838
+ try {
839
+ for (const op of operations) {
840
+ if (op.op === "write") {
841
+ const tempPath = `${op.target}.${process.pid}.tmp`;
842
+ entries.push({ op: "write", target: op.target, tempPath });
843
+ } else {
844
+ entries.push({ op: "delete", target: op.target });
845
+ }
846
+ }
847
+ const journal = { entries, commitStarted: false };
848
+ await fsyncWrite(journalPath, JSON.stringify(journal, null, 2));
849
+ for (const op of operations) {
850
+ if (op.op === "write") {
851
+ const tempPath = `${op.target}.${process.pid}.tmp`;
852
+ await fsyncWrite(tempPath, op.content);
853
+ }
854
+ }
855
+ journal.commitStarted = true;
856
+ await fsyncWrite(journalPath, JSON.stringify(journal, null, 2));
857
+ commitStarted = true;
858
+ for (const entry of entries) {
859
+ if (entry.op === "write" && entry.tempPath) {
860
+ await rename(entry.tempPath, entry.target);
861
+ } else if (entry.op === "delete") {
862
+ try {
863
+ await unlink(entry.target);
864
+ } catch {
865
+ }
866
+ }
867
+ }
868
+ await unlink(journalPath);
869
+ } catch (err) {
870
+ if (!commitStarted) {
871
+ for (const entry of entries) {
872
+ if (entry.tempPath) {
873
+ try {
874
+ await unlink(entry.tempPath);
875
+ } catch {
876
+ }
877
+ }
878
+ }
879
+ try {
880
+ await unlink(journalPath);
881
+ } catch {
882
+ }
883
+ }
884
+ if (err instanceof ProjectLoaderError) throw err;
885
+ throw new ProjectLoaderError("io_error", "Transaction failed", err);
886
+ }
887
+ }
888
+ async function runTransaction(root, operations) {
889
+ const wrapDir = resolve2(root, ".story");
890
+ await withLock(wrapDir, async () => {
891
+ await runTransactionUnlocked(root, operations);
892
+ });
893
+ }
810
894
  async function doRecoverTransaction(wrapDir) {
811
895
  const journalPath = join3(wrapDir, ".txn.json");
812
896
  let entries;
@@ -996,6 +1080,15 @@ async function atomicWrite(targetPath, content) {
996
1080
  );
997
1081
  }
998
1082
  }
1083
+ async function fsyncWrite(filePath, content) {
1084
+ const fh = await open(filePath, "w");
1085
+ try {
1086
+ await fh.writeFile(content, "utf-8");
1087
+ await fh.sync();
1088
+ } finally {
1089
+ await fh.close();
1090
+ }
1091
+ }
999
1092
  async function guardPath(target, root) {
1000
1093
  let resolvedRoot;
1001
1094
  try {
@@ -4739,6 +4832,19 @@ async function handleReportPlan(root, dir, state, report) {
4739
4832
  });
4740
4833
  }
4741
4834
  const risk = assessRisk(void 0, void 0);
4835
+ if (state.ticket) {
4836
+ try {
4837
+ const { withProjectLock: withProjectLock2, writeTicketUnlocked: writeTicketUnlocked2 } = await Promise.resolve().then(() => (init_project_loader(), project_loader_exports));
4838
+ await withProjectLock2(root, { strict: false }, async ({ state: projectState }) => {
4839
+ const ticket = projectState.ticketByID(state.ticket.id);
4840
+ if (ticket && ticket.status !== "inprogress") {
4841
+ const updated = { ...ticket, status: "inprogress" };
4842
+ await writeTicketUnlocked2(updated, root);
4843
+ }
4844
+ });
4845
+ } catch {
4846
+ }
4847
+ }
4742
4848
  const written = writeSessionSync(dir, {
4743
4849
  ...state,
4744
4850
  state: "PLAN_REVIEW",
@@ -5096,11 +5202,11 @@ async function handleReportComplete(root, dir, state, report) {
5096
5202
  const maxTickets = updated.config.maxTicketsPerSession;
5097
5203
  let nextState;
5098
5204
  let advice = "ok";
5099
- if (pressure === "critical") {
5205
+ if (maxTickets > 0 && ticketsDone >= maxTickets) {
5100
5206
  nextState = "HANDOVER";
5101
- advice = "compact-now";
5102
- } else if (maxTickets > 0 && ticketsDone >= maxTickets) {
5207
+ } else if (pressure === "critical") {
5103
5208
  nextState = "HANDOVER";
5209
+ advice = "compact-now";
5104
5210
  } else if (pressure === "high") {
5105
5211
  advice = "consider-compact";
5106
5212
  nextState = "PICK_TICKET";
@@ -5180,6 +5286,36 @@ async function handleReportHandover(root, dir, state, report) {
5180
5286
  } catch {
5181
5287
  }
5182
5288
  }
5289
+ const pressureLevel = state.contextPressure?.level ?? "low";
5290
+ const maxTickets = state.config.maxTicketsPerSession;
5291
+ const capReached = maxTickets > 0 && state.completedTickets.length >= maxTickets;
5292
+ let hasMoreTickets = false;
5293
+ try {
5294
+ const { state: ps } = await loadProject(root);
5295
+ hasMoreTickets = nextTickets(ps, 1).kind === "found";
5296
+ } catch {
5297
+ }
5298
+ if (pressureLevel === "critical" && hasMoreTickets && !capReached) {
5299
+ return guideResult(state, "HANDOVER", {
5300
+ instruction: [
5301
+ "# Context Compaction Needed",
5302
+ "",
5303
+ `${state.completedTickets.length} ticket(s) completed so far. Handover written. Context is large \u2014 time to compact and continue.`,
5304
+ "",
5305
+ 'Call `claudestory_autonomous_guide` with `action: "pre_compact"` now:',
5306
+ "```json",
5307
+ `{ "sessionId": "${state.sessionId}", "action": "pre_compact" }`,
5308
+ "```",
5309
+ "",
5310
+ 'After pre_compact responds, run `/compact`, then call with `action: "resume"` to continue working on more tickets.'
5311
+ ].join("\n"),
5312
+ reminders: [
5313
+ "Do NOT stop. This is a context compaction, not a session end.",
5314
+ "Call pre_compact \u2192 /compact \u2192 resume to continue autonomous work."
5315
+ ],
5316
+ contextAdvice: "compact-now"
5317
+ });
5318
+ }
5183
5319
  const written = writeSessionSync(dir, {
5184
5320
  ...state,
5185
5321
  state: "SESSION_END",
@@ -5227,6 +5363,40 @@ async function handleResume(root, args) {
5227
5363
  resumeFromRevision: null,
5228
5364
  contextPressure: { ...info.state.contextPressure, compactionCount: (info.state.contextPressure?.compactionCount ?? 0) + 1 }
5229
5365
  });
5366
+ if (resumeState === "PICK_TICKET") {
5367
+ let candidatesText = "No ticket candidates available.";
5368
+ let topCandidate = null;
5369
+ try {
5370
+ const { state: ps } = await loadProject(root);
5371
+ const result = nextTickets(ps, 5);
5372
+ if (result.kind === "found") {
5373
+ topCandidate = result.candidates[0] ?? null;
5374
+ candidatesText = result.candidates.map(
5375
+ (c, i) => `${i + 1}. **${c.ticket.id}: ${c.ticket.title}** (${c.ticket.type})`
5376
+ ).join("\n");
5377
+ }
5378
+ } catch {
5379
+ }
5380
+ return guideResult(written, "PICK_TICKET", {
5381
+ instruction: [
5382
+ "# Resumed After Compact \u2014 Continue Working",
5383
+ "",
5384
+ `${written.completedTickets.length} ticket(s) done so far. Context compacted. Pick the next ticket immediately.`,
5385
+ "",
5386
+ candidatesText,
5387
+ "",
5388
+ topCandidate ? `Pick **${topCandidate.ticket.id}** by calling \`claudestory_autonomous_guide\` now:` : "Pick a ticket now:",
5389
+ "```json",
5390
+ topCandidate ? `{ "sessionId": "${written.sessionId}", "action": "report", "report": { "completedAction": "ticket_picked", "ticketId": "${topCandidate.ticket.id}" } }` : `{ "sessionId": "${written.sessionId}", "action": "report", "report": { "completedAction": "ticket_picked", "ticketId": "T-XXX" } }`,
5391
+ "```"
5392
+ ].join("\n"),
5393
+ reminders: [
5394
+ "Do NOT stop or summarize. Pick the next ticket IMMEDIATELY.",
5395
+ "Do NOT ask the user for confirmation.",
5396
+ "You are in autonomous mode \u2014 continue working."
5397
+ ]
5398
+ });
5399
+ }
5230
5400
  return guideResult(written, resumeState, {
5231
5401
  instruction: [
5232
5402
  "# Resumed After Compact",
@@ -5238,6 +5408,7 @@ async function handleResume(root, args) {
5238
5408
  ].join("\n"),
5239
5409
  reminders: [
5240
5410
  "Do NOT use plan mode.",
5411
+ "Do NOT stop or summarize.",
5241
5412
  "Call autonomous_guide after completing each step."
5242
5413
  ]
5243
5414
  });
@@ -5253,11 +5424,12 @@ async function handlePreCompact(root, args) {
5253
5424
  return guideError(new Error(`Session ${args.sessionId} is already in COMPACT state. Call action: "resume" to continue.`));
5254
5425
  }
5255
5426
  const headResult = await gitHead(root);
5427
+ const resumeTarget = info.state.state === "HANDOVER" ? "PICK_TICKET" : info.state.state;
5256
5428
  const written = writeSessionSync(info.dir, {
5257
5429
  ...refreshLease(info.state),
5258
5430
  state: "COMPACT",
5259
5431
  previousState: info.state.state,
5260
- preCompactState: info.state.state,
5432
+ preCompactState: resumeTarget,
5261
5433
  resumeFromRevision: info.state.revision,
5262
5434
  git: {
5263
5435
  ...info.state.git,
@@ -6034,7 +6206,7 @@ async function ensureGitignoreEntries(gitignorePath, entries) {
6034
6206
  // src/mcp/index.ts
6035
6207
  var ENV_VAR2 = "CLAUDESTORY_PROJECT_ROOT";
6036
6208
  var CONFIG_PATH2 = ".story/config.json";
6037
- var version = "0.1.15";
6209
+ var version = "0.1.16";
6038
6210
  function tryDiscoverRoot() {
6039
6211
  const envRoot = process.env[ENV_VAR2];
6040
6212
  if (envRoot) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@anthropologies/claudestory",
3
- "version": "0.1.15",
3
+ "version": "0.1.16",
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": [