@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/README.md +6 -6
- package/dist/cli.js +3094 -2321
- package/dist/index.d.ts +3 -1
- package/dist/index.js +39 -9
- package/dist/mcp.js +153 -42
- package/package.json +1 -1
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) =>
|
|
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
|
-
|
|
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
|
-
|
|
980
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
26
|
-
|
|
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
|
|
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) =>
|
|
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
|
|
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 =
|
|
1908
|
-
const snapshotsDir =
|
|
1909
|
-
await
|
|
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 =
|
|
1931
|
-
const wrapDir =
|
|
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 =
|
|
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(
|
|
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(
|
|
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
|
|
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 =
|
|
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.
|
|
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 =
|
|
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(
|
|
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);
|