@anthropologies/claudestory 0.1.5 → 0.1.7

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
 
package/dist/index.js CHANGED
@@ -971,30 +971,43 @@ async function withLock(wrapDir, fn) {
971
971
  }
972
972
 
973
973
  // src/core/project-root-discovery.ts
974
- import { existsSync as existsSync3 } from "fs";
974
+ import { existsSync as existsSync3, accessSync, constants } from "fs";
975
975
  import { resolve as resolve2, dirname as dirname2, join as join3 } from "path";
976
976
  var ENV_VAR = "CLAUDESTORY_PROJECT_ROOT";
977
+ var STORY_DIR = ".story";
977
978
  var CONFIG_PATH = ".story/config.json";
978
979
  function discoverProjectRoot(startDir) {
979
980
  const envRoot = process.env[ENV_VAR];
980
981
  if (envRoot) {
981
982
  const resolved = resolve2(envRoot);
982
- if (existsSync3(join3(resolved, CONFIG_PATH))) {
983
- return resolved;
984
- }
985
- return null;
983
+ return checkRoot(resolved);
986
984
  }
987
985
  let current = resolve2(startDir ?? process.cwd());
988
986
  for (; ; ) {
989
- if (existsSync3(join3(current, CONFIG_PATH))) {
990
- return current;
991
- }
987
+ const result = checkRoot(current);
988
+ if (result) return result;
992
989
  const parent = dirname2(current);
993
990
  if (parent === current) break;
994
991
  current = parent;
995
992
  }
996
993
  return null;
997
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
+ }
998
1011
 
999
1012
  // src/core/queries.ts
1000
1013
  function nextTicket(state) {
package/dist/mcp.js CHANGED
@@ -7,30 +7,61 @@ 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";
@@ -365,22 +396,6 @@ 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";
@@ -2476,51 +2491,59 @@ function registerAllTools(server, pinnedRoot) {
2476
2491
  // src/mcp/index.ts
2477
2492
  var ENV_VAR2 = "CLAUDESTORY_PROJECT_ROOT";
2478
2493
  var CONFIG_PATH2 = ".story/config.json";
2479
- var version = "0.1.5";
2480
- function pinProjectRoot() {
2494
+ var version = "0.1.7";
2495
+ function tryDiscoverRoot() {
2481
2496
  const envRoot = process.env[ENV_VAR2];
2482
2497
  if (envRoot) {
2483
2498
  if (!isAbsolute(envRoot)) {
2484
- process.stderr.write(`Error: ${ENV_VAR2} must be an absolute path, got: ${envRoot}
2499
+ process.stderr.write(`Warning: ${ENV_VAR2} must be an absolute path, got: ${envRoot}
2485
2500
  `);
2486
- process.exit(1);
2501
+ return null;
2487
2502
  }
2488
2503
  const resolved = resolve7(envRoot);
2489
- let canonical;
2490
2504
  try {
2491
- canonical = realpathSync(resolved);
2492
- } catch {
2493
- process.stderr.write(`Error: ${ENV_VAR2} path does not exist: ${resolved}
2505
+ const canonical = realpathSync(resolved);
2506
+ if (existsSync5(join8(canonical, CONFIG_PATH2))) {
2507
+ return canonical;
2508
+ }
2509
+ process.stderr.write(`Warning: No .story/config.json at ${canonical}
2494
2510
  `);
2495
- process.exit(1);
2496
- }
2497
- if (!existsSync5(join8(canonical, CONFIG_PATH2))) {
2498
- process.stderr.write(`Error: No .story/config.json at ${canonical}
2511
+ } catch {
2512
+ process.stderr.write(`Warning: ${ENV_VAR2} path does not exist: ${resolved}
2499
2513
  `);
2500
- process.exit(1);
2501
2514
  }
2502
- return canonical;
2515
+ return null;
2503
2516
  }
2504
- const root = discoverProjectRoot();
2505
- if (!root) {
2506
- process.stderr.write("Error: No .story/ project found. Set CLAUDESTORY_PROJECT_ROOT or run from a project directory.\n");
2507
- process.exit(1);
2517
+ try {
2518
+ const root = discoverProjectRoot();
2519
+ return root ? realpathSync(root) : null;
2520
+ } catch {
2521
+ return null;
2508
2522
  }
2509
- return realpathSync(root);
2510
2523
  }
2511
2524
  async function main() {
2512
- const root = pinProjectRoot();
2525
+ const root = tryDiscoverRoot();
2513
2526
  const server = new McpServer(
2514
2527
  { name: "claudestory", version },
2515
2528
  {
2516
- instructions: "Start with claudestory_status for a project overview, then claudestory_ticket_next for the highest-priority work, then claudestory_handover_latest for session context."
2529
+ instructions: root ? "Start with claudestory_status for a project overview, then claudestory_ticket_next for the highest-priority work, then claudestory_handover_latest for session context." : "No .story/ project found in the current directory. Navigate to a project with a .story/ directory, or set CLAUDESTORY_PROJECT_ROOT."
2517
2530
  }
2518
2531
  );
2519
- registerAllTools(server, root);
2532
+ if (root) {
2533
+ registerAllTools(server, root);
2534
+ process.stderr.write(`claudestory MCP server running (root: ${root})
2535
+ `);
2536
+ } else {
2537
+ server.registerTool("claudestory_status", {
2538
+ description: "Project summary \u2014 returns error if no .story/ project found"
2539
+ }, () => Promise.resolve({
2540
+ content: [{ type: "text", text: "No .story/ project found. Navigate to a directory containing .story/ or set CLAUDESTORY_PROJECT_ROOT." }],
2541
+ isError: true
2542
+ }));
2543
+ process.stderr.write("claudestory MCP server running (no project found \u2014 tools will report errors)\n");
2544
+ }
2520
2545
  const transport = new StdioServerTransport();
2521
2546
  await server.connect(transport);
2522
- process.stderr.write(`claudestory MCP server running (root: ${root})
2523
- `);
2524
2547
  }
2525
2548
  main().catch((err) => {
2526
2549
  process.stderr.write(`Fatal: ${err instanceof Error ? err.message : String(err)}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@anthropologies/claudestory",
3
- "version": "0.1.5",
3
+ "version": "0.1.7",
4
4
  "type": "module",
5
5
  "exports": {
6
6
  ".": {