@anthropologies/claudestory 0.1.4 → 0.1.6

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/index.d.ts CHANGED
@@ -573,6 +573,7 @@ declare function guardPath(target: string, root: string): Promise<void>;
573
573
  *
574
574
  * CLAUDESTORY_PROJECT_ROOT env var overrides walk-up discovery.
575
575
  * Returns the canonical absolute path, or null if not found.
576
+ * Throws ProjectLoaderError if .story/ exists but is unreadable.
576
577
  */
577
578
  declare function discoverProjectRoot(startDir?: string): string | null;
578
579
 
@@ -1364,6 +1365,7 @@ declare function formatInitResult(result: {
1364
1365
  }, format: OutputFormat): string;
1365
1366
  declare function formatHandoverList(filenames: readonly string[], format: OutputFormat): string;
1366
1367
  declare function formatHandoverContent(filename: string, content: string, format: OutputFormat): string;
1368
+ declare function formatHandoverCreateResult(filename: string, format: OutputFormat): string;
1367
1369
 
1368
1370
  declare function formatSnapshotResult(result: {
1369
1371
  filename: string;
@@ -1373,4 +1375,4 @@ declare function formatSnapshotResult(result: {
1373
1375
  declare function formatRecap(recap: RecapResult, state: ProjectState, format: OutputFormat): string;
1374
1376
  declare function formatExport(state: ProjectState, mode: "all" | "phase", phaseId: string | null, format: OutputFormat): string;
1375
1377
 
1376
- export { type Blocker, BlockerSchema, CURRENT_SCHEMA_VERSION, type Config, ConfigSchema, DATE_REGEX, DateSchema, ERROR_CODES, type ErrorCode, type ErrorEnvelope, ExitCode, type ExitCodeValue, type Features, FeaturesSchema, INTEGRITY_WARNING_TYPES, ISSUE_ID_REGEX, ISSUE_SEVERITIES, ISSUE_STATUSES, type InitOptions, type InitResult, type Issue, IssueIdSchema, IssueSchema, type IssueSeverity, type IssueStatus, type LoadOptions, type LoadResult, type LoadWarning, type LoadWarningType, type NextTicketAllBlocked, type NextTicketAllComplete, type NextTicketEmpty, type NextTicketOutcome, type NextTicketResult, OUTPUT_FORMATS, type OutputFormat, type PartialEnvelope, type Phase, PhaseSchema, type PhaseStatus, type PhaseWithStatus, ProjectLoaderError, ProjectState, type RecapResult, type Roadmap, RoadmapSchema, type SnapshotDiff, type SnapshotV1, SnapshotV1Schema, type SuccessEnvelope, TICKET_ID_REGEX, TICKET_STATUSES, TICKET_TYPES, type Ticket, TicketIdSchema, TicketSchema, type TicketStatus, type TicketType, type UmbrellaProgress, type UnblockImpact, type ValidationFinding, type ValidationLevel, type ValidationResult, type WithProjectLockOptions, atomicWrite, blockedTickets, buildRecap, currentPhase, deleteIssue, deleteTicket, diffStates, discoverProjectRoot, errorEnvelope, escapeMarkdownInline, extractHandoverDate, fencedBlock, formatBlockedTickets, formatBlockerList, formatError, formatExport, formatHandoverContent, formatHandoverList, formatInitResult, formatIssue, formatIssueList, formatNextTicketOutcome, formatPhaseList, formatPhaseTickets, formatRecap, formatSnapshotResult, formatStatus, formatTicket, formatTicketList, formatValidation, guardPath, initProject, isBlockerCleared, listHandovers, loadLatestSnapshot, loadProject, mergeValidation, nextIssueID, nextOrder, nextTicket, nextTicketID, partialEnvelope, phasesWithStatus, readHandover, runTransaction, runTransactionUnlocked, saveSnapshot, serializeJSON, sortKeysDeep, successEnvelope, ticketsUnblockedBy, umbrellaProgress, validateProject, withProjectLock, writeConfig, writeIssue, writeIssueUnlocked, writeRoadmap, writeRoadmapUnlocked, writeTicket, writeTicketUnlocked };
1378
+ export { type Blocker, BlockerSchema, CURRENT_SCHEMA_VERSION, type Config, ConfigSchema, DATE_REGEX, DateSchema, ERROR_CODES, type ErrorCode, type ErrorEnvelope, ExitCode, type ExitCodeValue, type Features, FeaturesSchema, INTEGRITY_WARNING_TYPES, ISSUE_ID_REGEX, ISSUE_SEVERITIES, ISSUE_STATUSES, type InitOptions, type InitResult, type Issue, IssueIdSchema, IssueSchema, type IssueSeverity, type IssueStatus, type LoadOptions, type LoadResult, type LoadWarning, type LoadWarningType, type NextTicketAllBlocked, type NextTicketAllComplete, type NextTicketEmpty, type NextTicketOutcome, type NextTicketResult, OUTPUT_FORMATS, type OutputFormat, type PartialEnvelope, type Phase, PhaseSchema, type PhaseStatus, type PhaseWithStatus, ProjectLoaderError, ProjectState, type RecapResult, type Roadmap, RoadmapSchema, type SnapshotDiff, type SnapshotV1, SnapshotV1Schema, type SuccessEnvelope, TICKET_ID_REGEX, TICKET_STATUSES, TICKET_TYPES, type Ticket, TicketIdSchema, TicketSchema, type TicketStatus, type TicketType, type UmbrellaProgress, type UnblockImpact, type ValidationFinding, type ValidationLevel, type ValidationResult, type WithProjectLockOptions, atomicWrite, blockedTickets, buildRecap, currentPhase, deleteIssue, deleteTicket, diffStates, discoverProjectRoot, errorEnvelope, escapeMarkdownInline, extractHandoverDate, fencedBlock, formatBlockedTickets, formatBlockerList, formatError, formatExport, formatHandoverContent, formatHandoverCreateResult, formatHandoverList, formatInitResult, formatIssue, formatIssueList, formatNextTicketOutcome, formatPhaseList, formatPhaseTickets, formatRecap, formatSnapshotResult, formatStatus, formatTicket, formatTicketList, formatValidation, guardPath, initProject, isBlockerCleared, listHandovers, loadLatestSnapshot, loadProject, mergeValidation, nextIssueID, nextOrder, nextTicket, nextTicketID, partialEnvelope, phasesWithStatus, readHandover, runTransaction, runTransactionUnlocked, saveSnapshot, serializeJSON, sortKeysDeep, successEnvelope, ticketsUnblockedBy, umbrellaProgress, validateProject, withProjectLock, writeConfig, writeIssue, writeIssueUnlocked, writeRoadmap, writeRoadmapUnlocked, writeTicket, writeTicketUnlocked };
package/dist/index.js CHANGED
@@ -358,6 +358,7 @@ import { readdir, readFile } from "fs/promises";
358
358
  import { existsSync } from "fs";
359
359
  import { join, relative, extname } from "path";
360
360
  var HANDOVER_DATE_REGEX = /^\d{4}-\d{2}-\d{2}/;
361
+ var HANDOVER_SEQ_REGEX = /^(\d{4}-\d{2}-\d{2})-(\d{2})-/;
361
362
  async function listHandovers(handoversDir, root, warnings) {
362
363
  if (!existsSync(handoversDir)) return [];
363
364
  let entries;
@@ -387,7 +388,16 @@ async function listHandovers(handoversDir, root, warnings) {
387
388
  });
388
389
  }
389
390
  }
390
- conforming.sort((a, b) => b.localeCompare(a));
391
+ conforming.sort((a, b) => {
392
+ const dateA = a.slice(0, 10);
393
+ const dateB = b.slice(0, 10);
394
+ if (dateA !== dateB) return dateB.localeCompare(dateA);
395
+ const seqA = a.match(HANDOVER_SEQ_REGEX);
396
+ const seqB = b.match(HANDOVER_SEQ_REGEX);
397
+ if (seqA && !seqB) return -1;
398
+ if (!seqA && seqB) return 1;
399
+ return b.localeCompare(a);
400
+ });
391
401
  return [...conforming, ...nonConforming];
392
402
  }
393
403
  async function readHandover(handoversDir, filename) {
@@ -961,30 +971,43 @@ async function withLock(wrapDir, fn) {
961
971
  }
962
972
 
963
973
  // src/core/project-root-discovery.ts
964
- import { existsSync as existsSync3 } from "fs";
974
+ import { existsSync as existsSync3, accessSync, constants } from "fs";
965
975
  import { resolve as resolve2, dirname as dirname2, join as join3 } from "path";
966
976
  var ENV_VAR = "CLAUDESTORY_PROJECT_ROOT";
977
+ var STORY_DIR = ".story";
967
978
  var CONFIG_PATH = ".story/config.json";
968
979
  function discoverProjectRoot(startDir) {
969
980
  const envRoot = process.env[ENV_VAR];
970
981
  if (envRoot) {
971
982
  const resolved = resolve2(envRoot);
972
- if (existsSync3(join3(resolved, CONFIG_PATH))) {
973
- return resolved;
974
- }
975
- return null;
983
+ return checkRoot(resolved);
976
984
  }
977
985
  let current = resolve2(startDir ?? process.cwd());
978
986
  for (; ; ) {
979
- if (existsSync3(join3(current, CONFIG_PATH))) {
980
- return current;
981
- }
987
+ const result = checkRoot(current);
988
+ if (result) return result;
982
989
  const parent = dirname2(current);
983
990
  if (parent === current) break;
984
991
  current = parent;
985
992
  }
986
993
  return null;
987
994
  }
995
+ function checkRoot(candidate) {
996
+ if (existsSync3(join3(candidate, CONFIG_PATH))) {
997
+ return candidate;
998
+ }
999
+ if (existsSync3(join3(candidate, STORY_DIR))) {
1000
+ try {
1001
+ accessSync(join3(candidate, STORY_DIR), constants.R_OK);
1002
+ } catch {
1003
+ throw new ProjectLoaderError(
1004
+ "io_error",
1005
+ `Permission denied: cannot read .story/ in ${candidate}`
1006
+ );
1007
+ }
1008
+ }
1009
+ return null;
1010
+ }
988
1011
 
989
1012
  // src/core/queries.ts
990
1013
  function nextTicket(state) {
@@ -1974,6 +1997,12 @@ function formatHandoverContent(filename, content, format) {
1974
1997
  }
1975
1998
  return content;
1976
1999
  }
2000
+ function formatHandoverCreateResult(filename, format) {
2001
+ if (format === "json") {
2002
+ return JSON.stringify(successEnvelope({ filename }), null, 2);
2003
+ }
2004
+ return `Created handover: ${filename}`;
2005
+ }
1977
2006
  function formatSnapshotResult(result, format) {
1978
2007
  if (format === "json") {
1979
2008
  return JSON.stringify(successEnvelope(result), null, 2);
@@ -2298,6 +2327,7 @@ export {
2298
2327
  formatError,
2299
2328
  formatExport,
2300
2329
  formatHandoverContent,
2330
+ formatHandoverCreateResult,
2301
2331
  formatHandoverList,
2302
2332
  formatInitResult,
2303
2333
  formatIssue,
package/dist/mcp.js CHANGED
@@ -2,39 +2,70 @@
2
2
 
3
3
  // src/mcp/index.ts
4
4
  import { realpathSync, existsSync as existsSync5 } from "fs";
5
- import { resolve as resolve6, join as join7, isAbsolute } from "path";
5
+ import { resolve as resolve7, join as join8, isAbsolute } from "path";
6
6
  import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
7
7
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
8
8
 
9
9
  // src/core/project-root-discovery.ts
10
- import { existsSync } from "fs";
10
+ import { existsSync, accessSync, constants } from "fs";
11
11
  import { resolve, dirname, join } from "path";
12
+
13
+ // src/core/errors.ts
14
+ var CURRENT_SCHEMA_VERSION = 1;
15
+ var ProjectLoaderError = class extends Error {
16
+ constructor(code, message, cause) {
17
+ super(message);
18
+ this.code = code;
19
+ this.cause = cause;
20
+ }
21
+ name = "ProjectLoaderError";
22
+ };
23
+ var INTEGRITY_WARNING_TYPES = [
24
+ "parse_error",
25
+ "schema_error",
26
+ "duplicate_id"
27
+ ];
28
+
29
+ // src/core/project-root-discovery.ts
12
30
  var ENV_VAR = "CLAUDESTORY_PROJECT_ROOT";
31
+ var STORY_DIR = ".story";
13
32
  var CONFIG_PATH = ".story/config.json";
14
33
  function discoverProjectRoot(startDir) {
15
34
  const envRoot = process.env[ENV_VAR];
16
35
  if (envRoot) {
17
36
  const resolved = resolve(envRoot);
18
- if (existsSync(join(resolved, CONFIG_PATH))) {
19
- return resolved;
20
- }
21
- return null;
37
+ return checkRoot(resolved);
22
38
  }
23
39
  let current = resolve(startDir ?? process.cwd());
24
40
  for (; ; ) {
25
- if (existsSync(join(current, CONFIG_PATH))) {
26
- return current;
27
- }
41
+ const result = checkRoot(current);
42
+ if (result) return result;
28
43
  const parent = dirname(current);
29
44
  if (parent === current) break;
30
45
  current = parent;
31
46
  }
32
47
  return null;
33
48
  }
49
+ function checkRoot(candidate) {
50
+ if (existsSync(join(candidate, CONFIG_PATH))) {
51
+ return candidate;
52
+ }
53
+ if (existsSync(join(candidate, STORY_DIR))) {
54
+ try {
55
+ accessSync(join(candidate, STORY_DIR), constants.R_OK);
56
+ } catch {
57
+ throw new ProjectLoaderError(
58
+ "io_error",
59
+ `Permission denied: cannot read .story/ in ${candidate}`
60
+ );
61
+ }
62
+ }
63
+ return null;
64
+ }
34
65
 
35
66
  // src/mcp/tools.ts
36
67
  import { z as z7 } from "zod";
37
- import { join as join6 } from "path";
68
+ import { join as join7 } from "path";
38
69
 
39
70
  // src/core/project-loader.ts
40
71
  import {
@@ -365,27 +396,12 @@ var ProjectState = class _ProjectState {
365
396
  }
366
397
  };
367
398
 
368
- // src/core/errors.ts
369
- var CURRENT_SCHEMA_VERSION = 1;
370
- var ProjectLoaderError = class extends Error {
371
- constructor(code, message, cause) {
372
- super(message);
373
- this.code = code;
374
- this.cause = cause;
375
- }
376
- name = "ProjectLoaderError";
377
- };
378
- var INTEGRITY_WARNING_TYPES = [
379
- "parse_error",
380
- "schema_error",
381
- "duplicate_id"
382
- ];
383
-
384
399
  // src/core/handover-parser.ts
385
400
  import { readdir, readFile } from "fs/promises";
386
401
  import { existsSync as existsSync2 } from "fs";
387
402
  import { join as join2, relative, extname } from "path";
388
403
  var HANDOVER_DATE_REGEX = /^\d{4}-\d{2}-\d{2}/;
404
+ var HANDOVER_SEQ_REGEX = /^(\d{4}-\d{2}-\d{2})-(\d{2})-/;
389
405
  async function listHandovers(handoversDir, root, warnings) {
390
406
  if (!existsSync2(handoversDir)) return [];
391
407
  let entries;
@@ -415,7 +431,16 @@ async function listHandovers(handoversDir, root, warnings) {
415
431
  });
416
432
  }
417
433
  }
418
- conforming.sort((a, b) => b.localeCompare(a));
434
+ conforming.sort((a, b) => {
435
+ const dateA = a.slice(0, 10);
436
+ const dateB = b.slice(0, 10);
437
+ if (dateA !== dateB) return dateB.localeCompare(dateA);
438
+ const seqA = a.match(HANDOVER_SEQ_REGEX);
439
+ const seqB = b.match(HANDOVER_SEQ_REGEX);
440
+ if (seqA && !seqB) return -1;
441
+ if (!seqA && seqB) return 1;
442
+ return b.localeCompare(a);
443
+ });
419
444
  return [...conforming, ...nonConforming];
420
445
  }
421
446
  async function readHandover(handoversDir, filename) {
@@ -778,6 +803,13 @@ var CliValidationError = class extends Error {
778
803
  this.name = "CliValidationError";
779
804
  }
780
805
  };
806
+ function todayISO() {
807
+ const d = /* @__PURE__ */ new Date();
808
+ const y = d.getFullYear();
809
+ const m = String(d.getMonth() + 1).padStart(2, "0");
810
+ const day = String(d.getDate()).padStart(2, "0");
811
+ return `${y}-${m}-${day}`;
812
+ }
781
813
  async function parseHandoverFilename(raw, handoversDir) {
782
814
  if (raw.includes("/") || raw.includes("\\") || raw.includes("..") || raw.includes("\0")) {
783
815
  throw new CliValidationError(
@@ -1201,6 +1233,12 @@ function formatHandoverContent(filename, content, format) {
1201
1233
  }
1202
1234
  return content;
1203
1235
  }
1236
+ function formatHandoverCreateResult(filename, format) {
1237
+ if (format === "json") {
1238
+ return JSON.stringify(successEnvelope({ filename }), null, 2);
1239
+ }
1240
+ return `Created handover: ${filename}`;
1241
+ }
1204
1242
  function formatSnapshotResult(result, format) {
1205
1243
  if (format === "json") {
1206
1244
  return JSON.stringify(successEnvelope(result), null, 2);
@@ -1740,6 +1778,8 @@ function handleValidate(ctx) {
1740
1778
  }
1741
1779
 
1742
1780
  // src/cli/commands/handover.ts
1781
+ import { mkdir } from "fs/promises";
1782
+ import { join as join4, resolve as resolve4 } from "path";
1743
1783
  function handleHandoverList(ctx) {
1744
1784
  return { output: formatHandoverList(ctx.state.handoverFilenames, ctx.format) };
1745
1785
  }
@@ -1791,6 +1831,59 @@ async function handleHandoverGet(filename, ctx) {
1791
1831
  };
1792
1832
  }
1793
1833
  }
1834
+ function normalizeSlug(raw) {
1835
+ let slug = raw.trim().toLowerCase().replace(/\s+/g, "-").replace(/[^a-z0-9-]/g, "").replace(/-+/g, "-").replace(/^-|-$/g, "");
1836
+ if (slug.length > 60) slug = slug.slice(0, 60).replace(/-$/, "");
1837
+ if (!slug) {
1838
+ throw new CliValidationError(
1839
+ "invalid_input",
1840
+ `Slug is empty after normalization: "${raw}"`
1841
+ );
1842
+ }
1843
+ return slug;
1844
+ }
1845
+ async function handleHandoverCreate(content, slugRaw, format, root) {
1846
+ if (!content.trim()) {
1847
+ throw new CliValidationError("invalid_input", "Handover content is empty");
1848
+ }
1849
+ const slug = normalizeSlug(slugRaw);
1850
+ const date = todayISO();
1851
+ let filename;
1852
+ await withProjectLock(root, { strict: false }, async () => {
1853
+ const absRoot = resolve4(root);
1854
+ const handoversDir = join4(absRoot, ".story", "handovers");
1855
+ await mkdir(handoversDir, { recursive: true });
1856
+ const wrapDir = join4(absRoot, ".story");
1857
+ const datePrefix = `${date}-`;
1858
+ const seqRegex = new RegExp(`^${date}-(\\d{2})-`);
1859
+ let maxSeq = 0;
1860
+ const { readdirSync } = await import("fs");
1861
+ try {
1862
+ for (const f of readdirSync(handoversDir)) {
1863
+ const m = f.match(seqRegex);
1864
+ if (m) {
1865
+ const n = parseInt(m[1], 10);
1866
+ if (n > maxSeq) maxSeq = n;
1867
+ }
1868
+ }
1869
+ } catch {
1870
+ }
1871
+ const nextSeq = maxSeq + 1;
1872
+ if (nextSeq > 99) {
1873
+ throw new CliValidationError(
1874
+ "conflict",
1875
+ `Too many handovers for ${date}; limit is 99 per day`
1876
+ );
1877
+ }
1878
+ const candidate = `${date}-${String(nextSeq).padStart(2, "0")}-${slug}.md`;
1879
+ const candidatePath = join4(handoversDir, candidate);
1880
+ await parseHandoverFilename(candidate, handoversDir);
1881
+ await guardPath(candidatePath, wrapDir);
1882
+ await atomicWrite(candidatePath, content);
1883
+ filename = candidate;
1884
+ });
1885
+ return { output: formatHandoverCreateResult(filename, format) };
1886
+ }
1794
1887
 
1795
1888
  // src/cli/commands/blocker.ts
1796
1889
  function handleBlockerList(ctx) {
@@ -1883,9 +1976,9 @@ function handleIssueGet(id, ctx) {
1883
1976
  }
1884
1977
 
1885
1978
  // src/core/snapshot.ts
1886
- import { readdir as readdir3, readFile as readFile3, mkdir, unlink as unlink2 } from "fs/promises";
1979
+ import { readdir as readdir3, readFile as readFile3, mkdir as mkdir2, unlink as unlink2 } from "fs/promises";
1887
1980
  import { existsSync as existsSync4 } from "fs";
1888
- import { join as join4, resolve as resolve4 } from "path";
1981
+ import { join as join5, resolve as resolve5 } from "path";
1889
1982
  import { z as z6 } from "zod";
1890
1983
  var LoadWarningSchema = z6.object({
1891
1984
  type: z6.string(),
@@ -1904,9 +1997,9 @@ var SnapshotV1Schema = z6.object({
1904
1997
  });
1905
1998
  var MAX_SNAPSHOTS = 20;
1906
1999
  async function saveSnapshot(root, loadResult) {
1907
- const absRoot = resolve4(root);
1908
- const snapshotsDir = join4(absRoot, ".story", "snapshots");
1909
- await mkdir(snapshotsDir, { recursive: true });
2000
+ const absRoot = resolve5(root);
2001
+ const snapshotsDir = join5(absRoot, ".story", "snapshots");
2002
+ await mkdir2(snapshotsDir, { recursive: true });
1910
2003
  const { state, warnings } = loadResult;
1911
2004
  const now = /* @__PURE__ */ new Date();
1912
2005
  const filename = formatSnapshotFilename(now);
@@ -1927,8 +2020,8 @@ async function saveSnapshot(root, loadResult) {
1927
2020
  } : {}
1928
2021
  };
1929
2022
  const json = JSON.stringify(snapshot, null, 2) + "\n";
1930
- const targetPath = join4(snapshotsDir, filename);
1931
- const wrapDir = join4(absRoot, ".story");
2023
+ const targetPath = join5(snapshotsDir, filename);
2024
+ const wrapDir = join5(absRoot, ".story");
1932
2025
  await guardPath(targetPath, wrapDir);
1933
2026
  await atomicWrite(targetPath, json);
1934
2027
  const pruned = await pruneSnapshots(snapshotsDir);
@@ -1936,13 +2029,13 @@ async function saveSnapshot(root, loadResult) {
1936
2029
  return { filename, retained: entries.length, pruned };
1937
2030
  }
1938
2031
  async function loadLatestSnapshot(root) {
1939
- const snapshotsDir = join4(resolve4(root), ".story", "snapshots");
2032
+ const snapshotsDir = join5(resolve5(root), ".story", "snapshots");
1940
2033
  if (!existsSync4(snapshotsDir)) return null;
1941
2034
  const files = await listSnapshotFiles(snapshotsDir);
1942
2035
  if (files.length === 0) return null;
1943
2036
  for (const filename of files) {
1944
2037
  try {
1945
- const content = await readFile3(join4(snapshotsDir, filename), "utf-8");
2038
+ const content = await readFile3(join5(snapshotsDir, filename), "utf-8");
1946
2039
  const parsed = JSON.parse(content);
1947
2040
  const snapshot = SnapshotV1Schema.parse(parsed);
1948
2041
  return { snapshot, filename };
@@ -2103,7 +2196,7 @@ async function pruneSnapshots(dir) {
2103
2196
  const toRemove = files.slice(MAX_SNAPSHOTS);
2104
2197
  for (const f of toRemove) {
2105
2198
  try {
2106
- await unlink2(join4(dir, f));
2199
+ await unlink2(join5(dir, f));
2107
2200
  } catch {
2108
2201
  }
2109
2202
  }
@@ -2141,7 +2234,7 @@ function handleExport(ctx, mode, phaseId) {
2141
2234
  }
2142
2235
 
2143
2236
  // src/cli/commands/phase.ts
2144
- import { join as join5, resolve as resolve5 } from "path";
2237
+ import { join as join6, resolve as resolve6 } from "path";
2145
2238
  function handlePhaseList(ctx) {
2146
2239
  return { output: formatPhaseList(ctx.state, ctx.format) };
2147
2240
  }
@@ -2200,7 +2293,7 @@ function formatMcpError(code, message) {
2200
2293
  async function runMcpReadTool(pinnedRoot, handler) {
2201
2294
  try {
2202
2295
  const { state, warnings } = await loadProject(pinnedRoot);
2203
- const handoversDir = join6(pinnedRoot, ".story", "handovers");
2296
+ const handoversDir = join7(pinnedRoot, ".story", "handovers");
2204
2297
  const ctx = { state, warnings, root: pinnedRoot, handoversDir, format: "md" };
2205
2298
  const result = await handler(ctx);
2206
2299
  if (result.errorCode && INFRASTRUCTURE_ERROR_CODES.includes(result.errorCode)) {
@@ -2375,12 +2468,30 @@ function registerAllTools(server, pinnedRoot) {
2375
2468
  const phaseId = args.phase ?? null;
2376
2469
  return runMcpReadTool(pinnedRoot, (ctx) => handleExport(ctx, mode, phaseId));
2377
2470
  });
2471
+ server.registerTool("claudestory_handover_create", {
2472
+ description: "Create a handover document from markdown content",
2473
+ inputSchema: {
2474
+ content: z7.string().describe("Markdown content of the handover"),
2475
+ slug: z7.string().optional().describe("Slug for filename (e.g. phase5b-wrapup). Default: session")
2476
+ }
2477
+ }, (args) => {
2478
+ if (!args.content?.trim()) {
2479
+ return Promise.resolve({
2480
+ content: [{ type: "text", text: formatMcpError("invalid_input", "Handover content is empty") }],
2481
+ isError: true
2482
+ });
2483
+ }
2484
+ return runMcpWriteTool(
2485
+ pinnedRoot,
2486
+ (root) => handleHandoverCreate(args.content, args.slug ?? "session", "md", root)
2487
+ );
2488
+ });
2378
2489
  }
2379
2490
 
2380
2491
  // src/mcp/index.ts
2381
2492
  var ENV_VAR2 = "CLAUDESTORY_PROJECT_ROOT";
2382
2493
  var CONFIG_PATH2 = ".story/config.json";
2383
- var version = "0.1.4";
2494
+ var version = "0.1.6";
2384
2495
  function pinProjectRoot() {
2385
2496
  const envRoot = process.env[ENV_VAR2];
2386
2497
  if (envRoot) {
@@ -2389,7 +2500,7 @@ function pinProjectRoot() {
2389
2500
  `);
2390
2501
  process.exit(1);
2391
2502
  }
2392
- const resolved = resolve6(envRoot);
2503
+ const resolved = resolve7(envRoot);
2393
2504
  let canonical;
2394
2505
  try {
2395
2506
  canonical = realpathSync(resolved);
@@ -2398,7 +2509,7 @@ function pinProjectRoot() {
2398
2509
  `);
2399
2510
  process.exit(1);
2400
2511
  }
2401
- if (!existsSync5(join7(canonical, CONFIG_PATH2))) {
2512
+ if (!existsSync5(join8(canonical, CONFIG_PATH2))) {
2402
2513
  process.stderr.write(`Error: No .story/config.json at ${canonical}
2403
2514
  `);
2404
2515
  process.exit(1);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@anthropologies/claudestory",
3
- "version": "0.1.4",
3
+ "version": "0.1.6",
4
4
  "type": "module",
5
5
  "exports": {
6
6
  ".": {