@anthropologies/claudestory 0.1.0 → 0.1.2

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/README.md CHANGED
@@ -5,7 +5,7 @@ Cross-session context persistence for AI coding assistants. Tracks tickets, issu
5
5
  ## Installation
6
6
 
7
7
  ```bash
8
- npm install -g claudestory
8
+ npm install -g @anthropologies/claudestory
9
9
  ```
10
10
 
11
11
  Requires Node.js 20+.
@@ -140,7 +140,7 @@ This gives the AI full project context from the first message.
140
140
  ## Library Usage
141
141
 
142
142
  ```typescript
143
- import { loadProject, ProjectState } from "claudestory";
143
+ import { loadProject, ProjectState } from "@anthropologies/claudestory";
144
144
 
145
145
  const { state, warnings } = await loadProject("/path/to/project");
146
146
  console.log(state.tickets.length); // all tickets
package/dist/cli.js CHANGED
@@ -224,9 +224,9 @@ function formatStatus(state, format) {
224
224
  const phases = phasesWithStatus(state);
225
225
  const data = {
226
226
  project: state.config.project,
227
- totalTickets: state.totalTicketCount,
228
- completeTickets: state.completeTicketCount,
229
- openTickets: state.openTicketCount,
227
+ totalTickets: state.leafTicketCount,
228
+ completeTickets: state.completeLeafTicketCount,
229
+ openTickets: state.leafTicketCount - state.completeLeafTicketCount,
230
230
  blockedTickets: state.blockedCount,
231
231
  openIssues: state.openIssueCount,
232
232
  handovers: state.handoverFilenames.length,
@@ -243,7 +243,7 @@ function formatStatus(state, format) {
243
243
  const lines = [
244
244
  `# ${escapeMarkdownInline(state.config.project)}`,
245
245
  "",
246
- `Tickets: ${state.completeTicketCount}/${state.totalTicketCount} complete, ${state.blockedCount} blocked`,
246
+ `Tickets: ${state.completeLeafTicketCount}/${state.leafTicketCount} complete, ${state.blockedCount} blocked`,
247
247
  `Issues: ${state.openIssueCount} open`,
248
248
  `Handovers: ${state.handoverFilenames.length}`,
249
249
  "",
@@ -465,7 +465,11 @@ function formatInitResult(result, format) {
465
465
  if (format === "json") {
466
466
  return JSON.stringify(successEnvelope(result), null, 2);
467
467
  }
468
- return [`Initialized .story/ at ${escapeMarkdownInline(result.root)}`, "", ...result.created.map((f) => ` ${f}`)].join("\n");
468
+ const lines = [`Initialized .story/ at ${escapeMarkdownInline(result.root)}`, "", ...result.created.map((f) => ` ${f}`)];
469
+ if (result.warnings.length > 0) {
470
+ lines.push("", `Warning: ${result.warnings.length} corrupt file(s) found. Run \`claudestory validate\` to inspect.`);
471
+ }
472
+ return lines.join("\n");
469
473
  }
470
474
  function formatHandoverList(filenames, format) {
471
475
  if (format === "json") {
@@ -509,6 +513,8 @@ var ProjectState = class _ProjectState {
509
513
  // --- Derived (public readonly) ---
510
514
  umbrellaIDs;
511
515
  leafTickets;
516
+ leafTicketCount;
517
+ completeLeafTicketCount;
512
518
  // --- Derived (private) ---
513
519
  leafTicketsByPhase;
514
520
  childrenByParent;
@@ -535,6 +541,10 @@ var ProjectState = class _ProjectState {
535
541
  }
536
542
  this.umbrellaIDs = parentIDs;
537
543
  this.leafTickets = input.tickets.filter((t) => !parentIDs.has(t.id));
544
+ this.leafTicketCount = this.leafTickets.length;
545
+ this.completeLeafTicketCount = this.leafTickets.filter(
546
+ (t) => t.status === "complete"
547
+ ).length;
538
548
  const byPhase = /* @__PURE__ */ new Map();
539
549
  for (const t of this.leafTickets) {
540
550
  const phase = t.phase;
@@ -640,7 +650,7 @@ var ProjectState = class _ProjectState {
640
650
  });
641
651
  }
642
652
  get blockedCount() {
643
- return this.tickets.filter((t) => this.isBlocked(t)).length;
653
+ return this.leafTickets.filter((t) => t.status !== "complete" && this.isBlocked(t)).length;
644
654
  }
645
655
  ticketByID(id) {
646
656
  return this.ticketsByID.get(id);
@@ -1699,7 +1709,7 @@ function dfsBlocked(id, state, visited, inStack, findings) {
1699
1709
 
1700
1710
  // src/core/init.ts
1701
1711
  init_esm_shims();
1702
- import { mkdir, stat as stat2 } from "fs/promises";
1712
+ import { mkdir, stat as stat2, readdir as readdir3, readFile as readFile3 } from "fs/promises";
1703
1713
  import { join as join4, resolve as resolve3 } from "path";
1704
1714
  init_errors();
1705
1715
  async function initProject(root, options) {
@@ -1757,6 +1767,25 @@ async function initProject(root, options) {
1757
1767
  };
1758
1768
  await writeConfig(config, absRoot);
1759
1769
  await writeRoadmap(roadmap, absRoot);
1770
+ const warnings = [];
1771
+ if (options.force && exists) {
1772
+ for (const subdir of ["tickets", "issues"]) {
1773
+ const dir = join4(wrapDir, subdir);
1774
+ try {
1775
+ const files = await readdir3(dir);
1776
+ for (const f of files) {
1777
+ if (!f.endsWith(".json")) continue;
1778
+ try {
1779
+ const content = await readFile3(join4(dir, f), "utf-8");
1780
+ JSON.parse(content);
1781
+ } catch {
1782
+ warnings.push(`.story/${subdir}/${f}`);
1783
+ }
1784
+ }
1785
+ } catch {
1786
+ }
1787
+ }
1788
+ }
1760
1789
  return {
1761
1790
  root: absRoot,
1762
1791
  created: [
@@ -1765,7 +1794,8 @@ async function initProject(root, options) {
1765
1794
  ".story/tickets/",
1766
1795
  ".story/issues/",
1767
1796
  ".story/handovers/"
1768
- ]
1797
+ ],
1798
+ warnings
1769
1799
  };
1770
1800
  }
1771
1801
 
@@ -3759,8 +3789,13 @@ function registerPhaseCommand(yargs2) {
3759
3789
  }
3760
3790
 
3761
3791
  // src/cli/index.ts
3762
- var version = "0.1.0";
3763
- var HANDLED_ERROR = /* @__PURE__ */ Symbol("HANDLED_ERROR");
3792
+ var version = "0.1.2";
3793
+ var HandledError = class extends Error {
3794
+ constructor() {
3795
+ super("HANDLED_ERROR");
3796
+ this.name = "HandledError";
3797
+ }
3798
+ };
3764
3799
  var rawArgs = hideBin(process.argv);
3765
3800
  function sniffFormat(args) {
3766
3801
  for (let i = 0; i < args.length; i++) {
@@ -3774,7 +3809,7 @@ var cli = yargs(rawArgs).scriptName("claudestory").version(version).strict().dem
3774
3809
  if (err) throw err;
3775
3810
  writeOutput(formatError("invalid_input", msg ?? "Unknown error", errorFormat));
3776
3811
  process.exitCode = ExitCode.USER_ERROR;
3777
- throw HANDLED_ERROR;
3812
+ throw new HandledError();
3778
3813
  });
3779
3814
  cli = registerInitCommand(cli);
3780
3815
  cli = registerStatusCommand(cli);
@@ -3784,9 +3819,14 @@ cli = registerIssueCommand(cli);
3784
3819
  cli = registerHandoverCommand(cli);
3785
3820
  cli = registerBlockerCommand(cli);
3786
3821
  cli = registerValidateCommand(cli);
3787
- await cli.parseAsync().catch((err) => {
3788
- if (err === HANDLED_ERROR) return;
3822
+ function handleUnexpectedError(err) {
3823
+ if (err instanceof HandledError) return;
3789
3824
  const message = err instanceof Error ? err.message : String(err);
3790
3825
  writeOutput(formatError("io_error", message, errorFormat));
3791
3826
  process.exitCode = ExitCode.USER_ERROR;
3792
- });
3827
+ }
3828
+ try {
3829
+ await cli.parseAsync().catch(handleUnexpectedError);
3830
+ } catch (err) {
3831
+ handleUnexpectedError(err);
3832
+ }
package/dist/index.d.ts CHANGED
@@ -400,6 +400,8 @@ declare class ProjectState {
400
400
  readonly handoverFilenames: readonly string[];
401
401
  readonly umbrellaIDs: ReadonlySet<string>;
402
402
  readonly leafTickets: readonly Ticket[];
403
+ readonly leafTicketCount: number;
404
+ readonly completeLeafTicketCount: number;
403
405
  private readonly leafTicketsByPhase;
404
406
  private readonly childrenByParent;
405
407
  private readonly reverseBlocksMap;
@@ -697,6 +699,7 @@ interface InitOptions {
697
699
  interface InitResult {
698
700
  readonly root: string;
699
701
  readonly created: readonly string[];
702
+ readonly warnings: readonly string[];
700
703
  }
701
704
  /**
702
705
  * Scaffolds a new .story/ directory structure.
@@ -762,6 +765,7 @@ declare function formatError(code: ErrorCode, message: string, format: OutputFor
762
765
  declare function formatInitResult(result: {
763
766
  root: string;
764
767
  created: readonly string[];
768
+ warnings: readonly string[];
765
769
  }, format: OutputFormat): string;
766
770
  declare function formatHandoverList(filenames: readonly string[], format: OutputFormat): string;
767
771
  declare function formatHandoverContent(filename: string, content: string, format: OutputFormat): string;
package/dist/index.js CHANGED
@@ -127,6 +127,8 @@ var ProjectState = class _ProjectState {
127
127
  // --- Derived (public readonly) ---
128
128
  umbrellaIDs;
129
129
  leafTickets;
130
+ leafTicketCount;
131
+ completeLeafTicketCount;
130
132
  // --- Derived (private) ---
131
133
  leafTicketsByPhase;
132
134
  childrenByParent;
@@ -153,6 +155,10 @@ var ProjectState = class _ProjectState {
153
155
  }
154
156
  this.umbrellaIDs = parentIDs;
155
157
  this.leafTickets = input.tickets.filter((t) => !parentIDs.has(t.id));
158
+ this.leafTicketCount = this.leafTickets.length;
159
+ this.completeLeafTicketCount = this.leafTickets.filter(
160
+ (t) => t.status === "complete"
161
+ ).length;
156
162
  const byPhase = /* @__PURE__ */ new Map();
157
163
  for (const t of this.leafTickets) {
158
164
  const phase = t.phase;
@@ -258,7 +264,7 @@ var ProjectState = class _ProjectState {
258
264
  });
259
265
  }
260
266
  get blockedCount() {
261
- return this.tickets.filter((t) => this.isBlocked(t)).length;
267
+ return this.leafTickets.filter((t) => t.status !== "complete" && this.isBlocked(t)).length;
262
268
  }
263
269
  ticketByID(id) {
264
270
  return this.ticketsByID.get(id);
@@ -1353,7 +1359,7 @@ function dfsBlocked(id, state, visited, inStack, findings) {
1353
1359
  }
1354
1360
 
1355
1361
  // src/core/init.ts
1356
- import { mkdir, stat as stat2 } from "fs/promises";
1362
+ import { mkdir, stat as stat2, readdir as readdir3, readFile as readFile3 } from "fs/promises";
1357
1363
  import { join as join4, resolve as resolve3 } from "path";
1358
1364
  async function initProject(root, options) {
1359
1365
  const absRoot = resolve3(root);
@@ -1410,6 +1416,25 @@ async function initProject(root, options) {
1410
1416
  };
1411
1417
  await writeConfig(config, absRoot);
1412
1418
  await writeRoadmap(roadmap, absRoot);
1419
+ const warnings = [];
1420
+ if (options.force && exists) {
1421
+ for (const subdir of ["tickets", "issues"]) {
1422
+ const dir = join4(wrapDir, subdir);
1423
+ try {
1424
+ const files = await readdir3(dir);
1425
+ for (const f of files) {
1426
+ if (!f.endsWith(".json")) continue;
1427
+ try {
1428
+ const content = await readFile3(join4(dir, f), "utf-8");
1429
+ JSON.parse(content);
1430
+ } catch {
1431
+ warnings.push(`.story/${subdir}/${f}`);
1432
+ }
1433
+ }
1434
+ } catch {
1435
+ }
1436
+ }
1437
+ }
1413
1438
  return {
1414
1439
  root: absRoot,
1415
1440
  created: [
@@ -1418,7 +1443,8 @@ async function initProject(root, options) {
1418
1443
  ".story/tickets/",
1419
1444
  ".story/issues/",
1420
1445
  ".story/handovers/"
1421
- ]
1446
+ ],
1447
+ warnings
1422
1448
  };
1423
1449
  }
1424
1450
 
@@ -1467,9 +1493,9 @@ function formatStatus(state, format) {
1467
1493
  const phases = phasesWithStatus(state);
1468
1494
  const data = {
1469
1495
  project: state.config.project,
1470
- totalTickets: state.totalTicketCount,
1471
- completeTickets: state.completeTicketCount,
1472
- openTickets: state.openTicketCount,
1496
+ totalTickets: state.leafTicketCount,
1497
+ completeTickets: state.completeLeafTicketCount,
1498
+ openTickets: state.leafTicketCount - state.completeLeafTicketCount,
1473
1499
  blockedTickets: state.blockedCount,
1474
1500
  openIssues: state.openIssueCount,
1475
1501
  handovers: state.handoverFilenames.length,
@@ -1486,7 +1512,7 @@ function formatStatus(state, format) {
1486
1512
  const lines = [
1487
1513
  `# ${escapeMarkdownInline(state.config.project)}`,
1488
1514
  "",
1489
- `Tickets: ${state.completeTicketCount}/${state.totalTicketCount} complete, ${state.blockedCount} blocked`,
1515
+ `Tickets: ${state.completeLeafTicketCount}/${state.leafTicketCount} complete, ${state.blockedCount} blocked`,
1490
1516
  `Issues: ${state.openIssueCount} open`,
1491
1517
  `Handovers: ${state.handoverFilenames.length}`,
1492
1518
  "",
@@ -1708,7 +1734,11 @@ function formatInitResult(result, format) {
1708
1734
  if (format === "json") {
1709
1735
  return JSON.stringify(successEnvelope(result), null, 2);
1710
1736
  }
1711
- return [`Initialized .story/ at ${escapeMarkdownInline(result.root)}`, "", ...result.created.map((f) => ` ${f}`)].join("\n");
1737
+ const lines = [`Initialized .story/ at ${escapeMarkdownInline(result.root)}`, "", ...result.created.map((f) => ` ${f}`)];
1738
+ if (result.warnings.length > 0) {
1739
+ lines.push("", `Warning: ${result.warnings.length} corrupt file(s) found. Run \`claudestory validate\` to inspect.`);
1740
+ }
1741
+ return lines.join("\n");
1712
1742
  }
1713
1743
  function formatHandoverList(filenames, format) {
1714
1744
  if (format === "json") {
package/dist/mcp.js CHANGED
@@ -171,6 +171,8 @@ var ProjectState = class _ProjectState {
171
171
  // --- Derived (public readonly) ---
172
172
  umbrellaIDs;
173
173
  leafTickets;
174
+ leafTicketCount;
175
+ completeLeafTicketCount;
174
176
  // --- Derived (private) ---
175
177
  leafTicketsByPhase;
176
178
  childrenByParent;
@@ -197,6 +199,10 @@ var ProjectState = class _ProjectState {
197
199
  }
198
200
  this.umbrellaIDs = parentIDs;
199
201
  this.leafTickets = input.tickets.filter((t) => !parentIDs.has(t.id));
202
+ this.leafTicketCount = this.leafTickets.length;
203
+ this.completeLeafTicketCount = this.leafTickets.filter(
204
+ (t) => t.status === "complete"
205
+ ).length;
200
206
  const byPhase = /* @__PURE__ */ new Map();
201
207
  for (const t of this.leafTickets) {
202
208
  const phase = t.phase;
@@ -302,7 +308,7 @@ var ProjectState = class _ProjectState {
302
308
  });
303
309
  }
304
310
  get blockedCount() {
305
- return this.tickets.filter((t) => this.isBlocked(t)).length;
311
+ return this.leafTickets.filter((t) => t.status !== "complete" && this.isBlocked(t)).length;
306
312
  }
307
313
  ticketByID(id) {
308
314
  return this.ticketsByID.get(id);
@@ -853,9 +859,9 @@ function formatStatus(state, format) {
853
859
  const phases = phasesWithStatus(state);
854
860
  const data = {
855
861
  project: state.config.project,
856
- totalTickets: state.totalTicketCount,
857
- completeTickets: state.completeTicketCount,
858
- openTickets: state.openTicketCount,
862
+ totalTickets: state.leafTicketCount,
863
+ completeTickets: state.completeLeafTicketCount,
864
+ openTickets: state.leafTicketCount - state.completeLeafTicketCount,
859
865
  blockedTickets: state.blockedCount,
860
866
  openIssues: state.openIssueCount,
861
867
  handovers: state.handoverFilenames.length,
@@ -872,7 +878,7 @@ function formatStatus(state, format) {
872
878
  const lines = [
873
879
  `# ${escapeMarkdownInline(state.config.project)}`,
874
880
  "",
875
- `Tickets: ${state.completeTicketCount}/${state.totalTicketCount} complete, ${state.blockedCount} blocked`,
881
+ `Tickets: ${state.completeLeafTicketCount}/${state.leafTicketCount} complete, ${state.blockedCount} blocked`,
876
882
  `Issues: ${state.openIssueCount} open`,
877
883
  `Handovers: ${state.handoverFilenames.length}`,
878
884
  "",
@@ -1702,7 +1708,7 @@ function registerAllTools(server, pinnedRoot) {
1702
1708
  // src/mcp/index.ts
1703
1709
  var ENV_VAR2 = "CLAUDESTORY_PROJECT_ROOT";
1704
1710
  var CONFIG_PATH2 = ".story/config.json";
1705
- var version = "0.1.0";
1711
+ var version = "0.1.2";
1706
1712
  function pinProjectRoot() {
1707
1713
  const envRoot = process.env[ENV_VAR2];
1708
1714
  if (envRoot) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@anthropologies/claudestory",
3
- "version": "0.1.0",
3
+ "version": "0.1.2",
4
4
  "type": "module",
5
5
  "exports": {
6
6
  ".": {