@anthropologies/claudestory 0.1.19 → 0.1.20
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/cli.js +40 -4
- package/dist/index.d.ts +40 -40
- package/dist/mcp.js +189 -140
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -2993,6 +2993,14 @@ var init_validate = __esm({
|
|
|
2993
2993
|
});
|
|
2994
2994
|
|
|
2995
2995
|
// src/cli/commands/handover.ts
|
|
2996
|
+
var handover_exports = {};
|
|
2997
|
+
__export(handover_exports, {
|
|
2998
|
+
handleHandoverCreate: () => handleHandoverCreate,
|
|
2999
|
+
handleHandoverGet: () => handleHandoverGet,
|
|
3000
|
+
handleHandoverLatest: () => handleHandoverLatest,
|
|
3001
|
+
handleHandoverList: () => handleHandoverList,
|
|
3002
|
+
normalizeSlug: () => normalizeSlug
|
|
3003
|
+
});
|
|
2996
3004
|
import { existsSync as existsSync4 } from "fs";
|
|
2997
3005
|
import { mkdir as mkdir2 } from "fs/promises";
|
|
2998
3006
|
import { join as join4, resolve as resolve4 } from "path";
|
|
@@ -5144,9 +5152,10 @@ var init_session_types = __esm({
|
|
|
5144
5152
|
// Recipe overrides (maxTicketsPerSession: 0 = no limit)
|
|
5145
5153
|
config: z9.object({
|
|
5146
5154
|
maxTicketsPerSession: z9.number().min(0).default(3),
|
|
5155
|
+
handoverInterval: z9.number().min(0).default(5),
|
|
5147
5156
|
compactThreshold: z9.string().default("high"),
|
|
5148
5157
|
reviewBackends: z9.array(z9.string()).default(["codex", "agent"])
|
|
5149
|
-
}).default({ maxTicketsPerSession: 3, compactThreshold: "high", reviewBackends: ["codex", "agent"] }),
|
|
5158
|
+
}).default({ maxTicketsPerSession: 3, compactThreshold: "high", reviewBackends: ["codex", "agent"], handoverInterval: 5 }),
|
|
5150
5159
|
// T-123: Issue sweep tracking
|
|
5151
5160
|
issueSweepState: z9.object({
|
|
5152
5161
|
remaining: z9.array(z9.string()),
|
|
@@ -5269,7 +5278,8 @@ function createSession(root, recipe, workspaceId, configOverrides) {
|
|
|
5269
5278
|
config: {
|
|
5270
5279
|
maxTicketsPerSession: configOverrides?.maxTicketsPerSession ?? 3,
|
|
5271
5280
|
compactThreshold: configOverrides?.compactThreshold ?? "high",
|
|
5272
|
-
reviewBackends: configOverrides?.reviewBackends ?? ["codex", "agent"]
|
|
5281
|
+
reviewBackends: configOverrides?.reviewBackends ?? ["codex", "agent"],
|
|
5282
|
+
handoverInterval: configOverrides?.handoverInterval ?? 5
|
|
5273
5283
|
}
|
|
5274
5284
|
};
|
|
5275
5285
|
writeSessionSync(dir, state);
|
|
@@ -7223,6 +7233,31 @@ var init_complete = __esm({
|
|
|
7223
7233
|
}
|
|
7224
7234
|
};
|
|
7225
7235
|
}
|
|
7236
|
+
const handoverInterval = ctx.state.config.handoverInterval ?? 5;
|
|
7237
|
+
if (handoverInterval > 0 && ticketsDone > 0 && ticketsDone % handoverInterval === 0) {
|
|
7238
|
+
try {
|
|
7239
|
+
const { handleHandoverCreate: handleHandoverCreate3 } = await Promise.resolve().then(() => (init_handover(), handover_exports));
|
|
7240
|
+
const completedIds = ctx.state.completedTickets.map((t) => t.id).join(", ");
|
|
7241
|
+
const content = [
|
|
7242
|
+
`# Checkpoint \u2014 ${ticketsDone} tickets completed`,
|
|
7243
|
+
"",
|
|
7244
|
+
`**Session:** ${ctx.state.sessionId}`,
|
|
7245
|
+
`**Tickets:** ${completedIds}`,
|
|
7246
|
+
"",
|
|
7247
|
+
"This is an automatic mid-session checkpoint. The session is still active."
|
|
7248
|
+
].join("\n");
|
|
7249
|
+
await handleHandoverCreate3(content, "checkpoint", "md", ctx.root);
|
|
7250
|
+
} catch {
|
|
7251
|
+
}
|
|
7252
|
+
try {
|
|
7253
|
+
const { loadProject: loadProject2 } = await Promise.resolve().then(() => (init_project_loader(), project_loader_exports));
|
|
7254
|
+
const { saveSnapshot: saveSnapshot2 } = await Promise.resolve().then(() => (init_snapshot(), snapshot_exports));
|
|
7255
|
+
const loadResult = await loadProject2(ctx.root);
|
|
7256
|
+
await saveSnapshot2(ctx.root, loadResult);
|
|
7257
|
+
} catch {
|
|
7258
|
+
}
|
|
7259
|
+
ctx.appendEvent("checkpoint", { ticketsDone, interval: handoverInterval });
|
|
7260
|
+
}
|
|
7226
7261
|
let nextTarget;
|
|
7227
7262
|
if (maxTickets > 0 && ticketsDone >= maxTickets) {
|
|
7228
7263
|
nextTarget = "HANDOVER";
|
|
@@ -7722,6 +7757,7 @@ async function handleStart(root, args) {
|
|
|
7722
7757
|
if (typeof overrides.maxTicketsPerSession === "number") sessionConfig.maxTicketsPerSession = overrides.maxTicketsPerSession;
|
|
7723
7758
|
if (typeof overrides.compactThreshold === "string") sessionConfig.compactThreshold = overrides.compactThreshold;
|
|
7724
7759
|
if (Array.isArray(overrides.reviewBackends)) sessionConfig.reviewBackends = overrides.reviewBackends;
|
|
7760
|
+
if (typeof overrides.handoverInterval === "number") sessionConfig.handoverInterval = overrides.handoverInterval;
|
|
7725
7761
|
}
|
|
7726
7762
|
} catch {
|
|
7727
7763
|
}
|
|
@@ -9987,7 +10023,7 @@ var init_mcp = __esm({
|
|
|
9987
10023
|
init_init();
|
|
9988
10024
|
ENV_VAR2 = "CLAUDESTORY_PROJECT_ROOT";
|
|
9989
10025
|
CONFIG_PATH2 = ".story/config.json";
|
|
9990
|
-
version = "0.1.
|
|
10026
|
+
version = "0.1.20";
|
|
9991
10027
|
main().catch((err) => {
|
|
9992
10028
|
process.stderr.write(`Fatal: ${err instanceof Error ? err.message : String(err)}
|
|
9993
10029
|
`);
|
|
@@ -13265,7 +13301,7 @@ async function runCli() {
|
|
|
13265
13301
|
registerConfigCommand: registerConfigCommand2,
|
|
13266
13302
|
registerSessionCommand: registerSessionCommand2
|
|
13267
13303
|
} = await Promise.resolve().then(() => (init_register(), register_exports));
|
|
13268
|
-
const version2 = "0.1.
|
|
13304
|
+
const version2 = "0.1.20";
|
|
13269
13305
|
class HandledError extends Error {
|
|
13270
13306
|
constructor() {
|
|
13271
13307
|
super("HANDLED_ERROR");
|
package/dist/index.d.ts
CHANGED
|
@@ -1331,15 +1331,40 @@ declare const SnapshotV1Schema: z.ZodObject<{
|
|
|
1331
1331
|
file: z.ZodString;
|
|
1332
1332
|
message: z.ZodString;
|
|
1333
1333
|
}, "strip", z.ZodTypeAny, {
|
|
1334
|
-
message: string;
|
|
1335
1334
|
type: string;
|
|
1335
|
+
message: string;
|
|
1336
1336
|
file: string;
|
|
1337
1337
|
}, {
|
|
1338
|
-
message: string;
|
|
1339
1338
|
type: string;
|
|
1339
|
+
message: string;
|
|
1340
1340
|
file: string;
|
|
1341
1341
|
}>, "many">>;
|
|
1342
1342
|
}, "strip", z.ZodTypeAny, {
|
|
1343
|
+
version: 1;
|
|
1344
|
+
config: {
|
|
1345
|
+
version: number;
|
|
1346
|
+
type: string;
|
|
1347
|
+
language: string;
|
|
1348
|
+
project: string;
|
|
1349
|
+
features: {
|
|
1350
|
+
issues: boolean;
|
|
1351
|
+
tickets: boolean;
|
|
1352
|
+
handovers: boolean;
|
|
1353
|
+
roadmap: boolean;
|
|
1354
|
+
reviews: boolean;
|
|
1355
|
+
} & {
|
|
1356
|
+
[k: string]: unknown;
|
|
1357
|
+
};
|
|
1358
|
+
schemaVersion?: number | undefined;
|
|
1359
|
+
recipe?: string | undefined;
|
|
1360
|
+
recipeOverrides?: {
|
|
1361
|
+
maxTicketsPerSession?: number | undefined;
|
|
1362
|
+
compactThreshold?: string | undefined;
|
|
1363
|
+
reviewBackends?: string[] | undefined;
|
|
1364
|
+
} | undefined;
|
|
1365
|
+
} & {
|
|
1366
|
+
[k: string]: unknown;
|
|
1367
|
+
};
|
|
1343
1368
|
issues: z.objectOutputType<{
|
|
1344
1369
|
id: z.ZodString;
|
|
1345
1370
|
title: z.ZodString;
|
|
@@ -1376,8 +1401,8 @@ declare const SnapshotV1Schema: z.ZodObject<{
|
|
|
1376
1401
|
claimedBySession: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
1377
1402
|
}, z.ZodTypeAny, "passthrough">[];
|
|
1378
1403
|
roadmap: {
|
|
1379
|
-
title: string;
|
|
1380
1404
|
date: string;
|
|
1405
|
+
title: string;
|
|
1381
1406
|
phases: z.objectOutputType<{
|
|
1382
1407
|
id: z.ZodString;
|
|
1383
1408
|
label: z.ZodString;
|
|
@@ -1395,7 +1420,6 @@ declare const SnapshotV1Schema: z.ZodObject<{
|
|
|
1395
1420
|
} & {
|
|
1396
1421
|
[k: string]: unknown;
|
|
1397
1422
|
};
|
|
1398
|
-
version: 1;
|
|
1399
1423
|
project: string;
|
|
1400
1424
|
notes: z.objectOutputType<{
|
|
1401
1425
|
id: z.ZodString;
|
|
@@ -1421,11 +1445,19 @@ declare const SnapshotV1Schema: z.ZodObject<{
|
|
|
1421
1445
|
status: z.ZodEnum<["active", "deprecated", "superseded"]>;
|
|
1422
1446
|
}, z.ZodTypeAny, "passthrough">[];
|
|
1423
1447
|
createdAt: string;
|
|
1424
|
-
|
|
1448
|
+
handoverFilenames: string[];
|
|
1449
|
+
warnings?: {
|
|
1425
1450
|
type: string;
|
|
1451
|
+
message: string;
|
|
1452
|
+
file: string;
|
|
1453
|
+
}[] | undefined;
|
|
1454
|
+
}, {
|
|
1455
|
+
version: 1;
|
|
1456
|
+
config: {
|
|
1426
1457
|
version: number;
|
|
1427
|
-
|
|
1458
|
+
type: string;
|
|
1428
1459
|
language: string;
|
|
1460
|
+
project: string;
|
|
1429
1461
|
features: {
|
|
1430
1462
|
issues: boolean;
|
|
1431
1463
|
tickets: boolean;
|
|
@@ -1445,13 +1477,6 @@ declare const SnapshotV1Schema: z.ZodObject<{
|
|
|
1445
1477
|
} & {
|
|
1446
1478
|
[k: string]: unknown;
|
|
1447
1479
|
};
|
|
1448
|
-
handoverFilenames: string[];
|
|
1449
|
-
warnings?: {
|
|
1450
|
-
message: string;
|
|
1451
|
-
type: string;
|
|
1452
|
-
file: string;
|
|
1453
|
-
}[] | undefined;
|
|
1454
|
-
}, {
|
|
1455
1480
|
issues: z.objectInputType<{
|
|
1456
1481
|
id: z.ZodString;
|
|
1457
1482
|
title: z.ZodString;
|
|
@@ -1488,8 +1513,8 @@ declare const SnapshotV1Schema: z.ZodObject<{
|
|
|
1488
1513
|
claimedBySession: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
1489
1514
|
}, z.ZodTypeAny, "passthrough">[];
|
|
1490
1515
|
roadmap: {
|
|
1491
|
-
title: string;
|
|
1492
1516
|
date: string;
|
|
1517
|
+
title: string;
|
|
1493
1518
|
phases: z.objectInputType<{
|
|
1494
1519
|
id: z.ZodString;
|
|
1495
1520
|
label: z.ZodString;
|
|
@@ -1507,33 +1532,8 @@ declare const SnapshotV1Schema: z.ZodObject<{
|
|
|
1507
1532
|
} & {
|
|
1508
1533
|
[k: string]: unknown;
|
|
1509
1534
|
};
|
|
1510
|
-
version: 1;
|
|
1511
1535
|
project: string;
|
|
1512
1536
|
createdAt: string;
|
|
1513
|
-
config: {
|
|
1514
|
-
type: string;
|
|
1515
|
-
version: number;
|
|
1516
|
-
project: string;
|
|
1517
|
-
language: string;
|
|
1518
|
-
features: {
|
|
1519
|
-
issues: boolean;
|
|
1520
|
-
tickets: boolean;
|
|
1521
|
-
handovers: boolean;
|
|
1522
|
-
roadmap: boolean;
|
|
1523
|
-
reviews: boolean;
|
|
1524
|
-
} & {
|
|
1525
|
-
[k: string]: unknown;
|
|
1526
|
-
};
|
|
1527
|
-
schemaVersion?: number | undefined;
|
|
1528
|
-
recipe?: string | undefined;
|
|
1529
|
-
recipeOverrides?: {
|
|
1530
|
-
maxTicketsPerSession?: number | undefined;
|
|
1531
|
-
compactThreshold?: string | undefined;
|
|
1532
|
-
reviewBackends?: string[] | undefined;
|
|
1533
|
-
} | undefined;
|
|
1534
|
-
} & {
|
|
1535
|
-
[k: string]: unknown;
|
|
1536
|
-
};
|
|
1537
1537
|
notes?: z.objectInputType<{
|
|
1538
1538
|
id: z.ZodString;
|
|
1539
1539
|
title: z.ZodNullable<z.ZodString>;
|
|
@@ -1558,8 +1558,8 @@ declare const SnapshotV1Schema: z.ZodObject<{
|
|
|
1558
1558
|
status: z.ZodEnum<["active", "deprecated", "superseded"]>;
|
|
1559
1559
|
}, z.ZodTypeAny, "passthrough">[] | undefined;
|
|
1560
1560
|
warnings?: {
|
|
1561
|
-
message: string;
|
|
1562
1561
|
type: string;
|
|
1562
|
+
message: string;
|
|
1563
1563
|
file: string;
|
|
1564
1564
|
}[] | undefined;
|
|
1565
1565
|
handoverFilenames?: string[] | undefined;
|
package/dist/mcp.js
CHANGED
|
@@ -2689,6 +2689,157 @@ var init_validation = __esm({
|
|
|
2689
2689
|
}
|
|
2690
2690
|
});
|
|
2691
2691
|
|
|
2692
|
+
// src/cli/commands/handover.ts
|
|
2693
|
+
var handover_exports = {};
|
|
2694
|
+
__export(handover_exports, {
|
|
2695
|
+
handleHandoverCreate: () => handleHandoverCreate,
|
|
2696
|
+
handleHandoverGet: () => handleHandoverGet,
|
|
2697
|
+
handleHandoverLatest: () => handleHandoverLatest,
|
|
2698
|
+
handleHandoverList: () => handleHandoverList,
|
|
2699
|
+
normalizeSlug: () => normalizeSlug
|
|
2700
|
+
});
|
|
2701
|
+
import { existsSync as existsSync4 } from "fs";
|
|
2702
|
+
import { mkdir as mkdir2 } from "fs/promises";
|
|
2703
|
+
import { join as join4, resolve as resolve4 } from "path";
|
|
2704
|
+
function handleHandoverList(ctx) {
|
|
2705
|
+
return { output: formatHandoverList(ctx.state.handoverFilenames, ctx.format) };
|
|
2706
|
+
}
|
|
2707
|
+
async function handleHandoverLatest(ctx, count = 1) {
|
|
2708
|
+
if (ctx.state.handoverFilenames.length === 0) {
|
|
2709
|
+
return {
|
|
2710
|
+
output: formatError("not_found", "No handovers found", ctx.format),
|
|
2711
|
+
exitCode: ExitCode.USER_ERROR,
|
|
2712
|
+
errorCode: "not_found"
|
|
2713
|
+
};
|
|
2714
|
+
}
|
|
2715
|
+
const filenames = ctx.state.handoverFilenames.slice(0, count);
|
|
2716
|
+
const parts = [];
|
|
2717
|
+
for (const filename of filenames) {
|
|
2718
|
+
await parseHandoverFilename(filename, ctx.handoversDir);
|
|
2719
|
+
try {
|
|
2720
|
+
const content = await readHandover(ctx.handoversDir, filename);
|
|
2721
|
+
parts.push(formatHandoverContent(filename, content, ctx.format));
|
|
2722
|
+
} catch (err) {
|
|
2723
|
+
if (err.code === "ENOENT") {
|
|
2724
|
+
if (count > 1) continue;
|
|
2725
|
+
return {
|
|
2726
|
+
output: formatError("not_found", `Handover file not found: ${filename}`, ctx.format),
|
|
2727
|
+
exitCode: ExitCode.USER_ERROR,
|
|
2728
|
+
errorCode: "not_found"
|
|
2729
|
+
};
|
|
2730
|
+
}
|
|
2731
|
+
return {
|
|
2732
|
+
output: formatError("io_error", `Cannot read handover: ${err.message}`, ctx.format),
|
|
2733
|
+
exitCode: ExitCode.USER_ERROR,
|
|
2734
|
+
errorCode: "io_error"
|
|
2735
|
+
};
|
|
2736
|
+
}
|
|
2737
|
+
}
|
|
2738
|
+
if (parts.length === 0) {
|
|
2739
|
+
return {
|
|
2740
|
+
output: formatError("not_found", "No handovers found", ctx.format),
|
|
2741
|
+
exitCode: ExitCode.USER_ERROR,
|
|
2742
|
+
errorCode: "not_found"
|
|
2743
|
+
};
|
|
2744
|
+
}
|
|
2745
|
+
const separator = ctx.format === "json" ? "\n" : "\n\n---\n\n";
|
|
2746
|
+
return { output: parts.join(separator) };
|
|
2747
|
+
}
|
|
2748
|
+
async function handleHandoverGet(filename, ctx) {
|
|
2749
|
+
await parseHandoverFilename(filename, ctx.handoversDir);
|
|
2750
|
+
try {
|
|
2751
|
+
const content = await readHandover(ctx.handoversDir, filename);
|
|
2752
|
+
return { output: formatHandoverContent(filename, content, ctx.format) };
|
|
2753
|
+
} catch (err) {
|
|
2754
|
+
if (err.code === "ENOENT") {
|
|
2755
|
+
return {
|
|
2756
|
+
output: formatError("not_found", `Handover not found: ${filename}`, ctx.format),
|
|
2757
|
+
exitCode: ExitCode.USER_ERROR,
|
|
2758
|
+
errorCode: "not_found"
|
|
2759
|
+
};
|
|
2760
|
+
}
|
|
2761
|
+
return {
|
|
2762
|
+
output: formatError("io_error", `Cannot read handover: ${err.message}`, ctx.format),
|
|
2763
|
+
exitCode: ExitCode.USER_ERROR,
|
|
2764
|
+
errorCode: "io_error"
|
|
2765
|
+
};
|
|
2766
|
+
}
|
|
2767
|
+
}
|
|
2768
|
+
function normalizeSlug(raw) {
|
|
2769
|
+
let slug = raw.trim().toLowerCase().replace(/\s+/g, "-").replace(/[^a-z0-9-]/g, "").replace(/-+/g, "-").replace(/^-|-$/g, "");
|
|
2770
|
+
if (slug.length > 60) slug = slug.slice(0, 60).replace(/-$/, "");
|
|
2771
|
+
if (!slug) {
|
|
2772
|
+
throw new CliValidationError(
|
|
2773
|
+
"invalid_input",
|
|
2774
|
+
`Slug is empty after normalization: "${raw}"`
|
|
2775
|
+
);
|
|
2776
|
+
}
|
|
2777
|
+
return slug;
|
|
2778
|
+
}
|
|
2779
|
+
async function handleHandoverCreate(content, slugRaw, format, root) {
|
|
2780
|
+
if (!content.trim()) {
|
|
2781
|
+
throw new CliValidationError("invalid_input", "Handover content is empty");
|
|
2782
|
+
}
|
|
2783
|
+
const slug = normalizeSlug(slugRaw);
|
|
2784
|
+
const date = todayISO();
|
|
2785
|
+
let filename;
|
|
2786
|
+
await withProjectLock(root, { strict: false }, async () => {
|
|
2787
|
+
const absRoot = resolve4(root);
|
|
2788
|
+
const handoversDir = join4(absRoot, ".story", "handovers");
|
|
2789
|
+
await mkdir2(handoversDir, { recursive: true });
|
|
2790
|
+
const wrapDir = join4(absRoot, ".story");
|
|
2791
|
+
const datePrefix = `${date}-`;
|
|
2792
|
+
const seqRegex = new RegExp(`^${date}-(\\d{2})-`);
|
|
2793
|
+
let maxSeq = 0;
|
|
2794
|
+
const { readdirSync: readdirSync2 } = await import("fs");
|
|
2795
|
+
try {
|
|
2796
|
+
for (const f of readdirSync2(handoversDir)) {
|
|
2797
|
+
const m = f.match(seqRegex);
|
|
2798
|
+
if (m) {
|
|
2799
|
+
const n = parseInt(m[1], 10);
|
|
2800
|
+
if (n > maxSeq) maxSeq = n;
|
|
2801
|
+
}
|
|
2802
|
+
}
|
|
2803
|
+
} catch {
|
|
2804
|
+
}
|
|
2805
|
+
let nextSeq = maxSeq + 1;
|
|
2806
|
+
if (nextSeq > 99) {
|
|
2807
|
+
throw new CliValidationError(
|
|
2808
|
+
"conflict",
|
|
2809
|
+
`Too many handovers for ${date}; limit is 99 per day`
|
|
2810
|
+
);
|
|
2811
|
+
}
|
|
2812
|
+
let candidate = `${date}-${String(nextSeq).padStart(2, "0")}-${slug}.md`;
|
|
2813
|
+
let candidatePath = join4(handoversDir, candidate);
|
|
2814
|
+
while (existsSync4(candidatePath)) {
|
|
2815
|
+
nextSeq++;
|
|
2816
|
+
if (nextSeq > 99) {
|
|
2817
|
+
throw new CliValidationError(
|
|
2818
|
+
"conflict",
|
|
2819
|
+
`Too many handovers for ${date}; limit is 99 per day`
|
|
2820
|
+
);
|
|
2821
|
+
}
|
|
2822
|
+
candidate = `${date}-${String(nextSeq).padStart(2, "0")}-${slug}.md`;
|
|
2823
|
+
candidatePath = join4(handoversDir, candidate);
|
|
2824
|
+
}
|
|
2825
|
+
await parseHandoverFilename(candidate, handoversDir);
|
|
2826
|
+
await guardPath(candidatePath, wrapDir);
|
|
2827
|
+
await atomicWrite(candidatePath, content);
|
|
2828
|
+
filename = candidate;
|
|
2829
|
+
});
|
|
2830
|
+
return { output: formatHandoverCreateResult(filename, format) };
|
|
2831
|
+
}
|
|
2832
|
+
var init_handover = __esm({
|
|
2833
|
+
"src/cli/commands/handover.ts"() {
|
|
2834
|
+
"use strict";
|
|
2835
|
+
init_esm_shims();
|
|
2836
|
+
init_handover_parser();
|
|
2837
|
+
init_output_formatter();
|
|
2838
|
+
init_project_loader();
|
|
2839
|
+
init_helpers();
|
|
2840
|
+
}
|
|
2841
|
+
});
|
|
2842
|
+
|
|
2692
2843
|
// src/core/id-allocation.ts
|
|
2693
2844
|
function nextTicketID(tickets) {
|
|
2694
2845
|
let max = 0;
|
|
@@ -3445,9 +3596,10 @@ var init_session_types = __esm({
|
|
|
3445
3596
|
// Recipe overrides (maxTicketsPerSession: 0 = no limit)
|
|
3446
3597
|
config: z9.object({
|
|
3447
3598
|
maxTicketsPerSession: z9.number().min(0).default(3),
|
|
3599
|
+
handoverInterval: z9.number().min(0).default(5),
|
|
3448
3600
|
compactThreshold: z9.string().default("high"),
|
|
3449
3601
|
reviewBackends: z9.array(z9.string()).default(["codex", "agent"])
|
|
3450
|
-
}).default({ maxTicketsPerSession: 3, compactThreshold: "high", reviewBackends: ["codex", "agent"] }),
|
|
3602
|
+
}).default({ maxTicketsPerSession: 3, compactThreshold: "high", reviewBackends: ["codex", "agent"], handoverInterval: 5 }),
|
|
3451
3603
|
// T-123: Issue sweep tracking
|
|
3452
3604
|
issueSweepState: z9.object({
|
|
3453
3605
|
remaining: z9.array(z9.string()),
|
|
@@ -3570,7 +3722,8 @@ function createSession(root, recipe, workspaceId, configOverrides) {
|
|
|
3570
3722
|
config: {
|
|
3571
3723
|
maxTicketsPerSession: configOverrides?.maxTicketsPerSession ?? 3,
|
|
3572
3724
|
compactThreshold: configOverrides?.compactThreshold ?? "high",
|
|
3573
|
-
reviewBackends: configOverrides?.reviewBackends ?? ["codex", "agent"]
|
|
3725
|
+
reviewBackends: configOverrides?.reviewBackends ?? ["codex", "agent"],
|
|
3726
|
+
handoverInterval: configOverrides?.handoverInterval ?? 5
|
|
3574
3727
|
}
|
|
3575
3728
|
};
|
|
3576
3729
|
writeSessionSync(dir, state);
|
|
@@ -3926,143 +4079,8 @@ function handleValidate(ctx) {
|
|
|
3926
4079
|
};
|
|
3927
4080
|
}
|
|
3928
4081
|
|
|
3929
|
-
// src/
|
|
3930
|
-
|
|
3931
|
-
init_handover_parser();
|
|
3932
|
-
init_output_formatter();
|
|
3933
|
-
init_project_loader();
|
|
3934
|
-
init_helpers();
|
|
3935
|
-
import { existsSync as existsSync4 } from "fs";
|
|
3936
|
-
import { mkdir as mkdir2 } from "fs/promises";
|
|
3937
|
-
import { join as join4, resolve as resolve4 } from "path";
|
|
3938
|
-
function handleHandoverList(ctx) {
|
|
3939
|
-
return { output: formatHandoverList(ctx.state.handoverFilenames, ctx.format) };
|
|
3940
|
-
}
|
|
3941
|
-
async function handleHandoverLatest(ctx, count = 1) {
|
|
3942
|
-
if (ctx.state.handoverFilenames.length === 0) {
|
|
3943
|
-
return {
|
|
3944
|
-
output: formatError("not_found", "No handovers found", ctx.format),
|
|
3945
|
-
exitCode: ExitCode.USER_ERROR,
|
|
3946
|
-
errorCode: "not_found"
|
|
3947
|
-
};
|
|
3948
|
-
}
|
|
3949
|
-
const filenames = ctx.state.handoverFilenames.slice(0, count);
|
|
3950
|
-
const parts = [];
|
|
3951
|
-
for (const filename of filenames) {
|
|
3952
|
-
await parseHandoverFilename(filename, ctx.handoversDir);
|
|
3953
|
-
try {
|
|
3954
|
-
const content = await readHandover(ctx.handoversDir, filename);
|
|
3955
|
-
parts.push(formatHandoverContent(filename, content, ctx.format));
|
|
3956
|
-
} catch (err) {
|
|
3957
|
-
if (err.code === "ENOENT") {
|
|
3958
|
-
if (count > 1) continue;
|
|
3959
|
-
return {
|
|
3960
|
-
output: formatError("not_found", `Handover file not found: ${filename}`, ctx.format),
|
|
3961
|
-
exitCode: ExitCode.USER_ERROR,
|
|
3962
|
-
errorCode: "not_found"
|
|
3963
|
-
};
|
|
3964
|
-
}
|
|
3965
|
-
return {
|
|
3966
|
-
output: formatError("io_error", `Cannot read handover: ${err.message}`, ctx.format),
|
|
3967
|
-
exitCode: ExitCode.USER_ERROR,
|
|
3968
|
-
errorCode: "io_error"
|
|
3969
|
-
};
|
|
3970
|
-
}
|
|
3971
|
-
}
|
|
3972
|
-
if (parts.length === 0) {
|
|
3973
|
-
return {
|
|
3974
|
-
output: formatError("not_found", "No handovers found", ctx.format),
|
|
3975
|
-
exitCode: ExitCode.USER_ERROR,
|
|
3976
|
-
errorCode: "not_found"
|
|
3977
|
-
};
|
|
3978
|
-
}
|
|
3979
|
-
const separator = ctx.format === "json" ? "\n" : "\n\n---\n\n";
|
|
3980
|
-
return { output: parts.join(separator) };
|
|
3981
|
-
}
|
|
3982
|
-
async function handleHandoverGet(filename, ctx) {
|
|
3983
|
-
await parseHandoverFilename(filename, ctx.handoversDir);
|
|
3984
|
-
try {
|
|
3985
|
-
const content = await readHandover(ctx.handoversDir, filename);
|
|
3986
|
-
return { output: formatHandoverContent(filename, content, ctx.format) };
|
|
3987
|
-
} catch (err) {
|
|
3988
|
-
if (err.code === "ENOENT") {
|
|
3989
|
-
return {
|
|
3990
|
-
output: formatError("not_found", `Handover not found: ${filename}`, ctx.format),
|
|
3991
|
-
exitCode: ExitCode.USER_ERROR,
|
|
3992
|
-
errorCode: "not_found"
|
|
3993
|
-
};
|
|
3994
|
-
}
|
|
3995
|
-
return {
|
|
3996
|
-
output: formatError("io_error", `Cannot read handover: ${err.message}`, ctx.format),
|
|
3997
|
-
exitCode: ExitCode.USER_ERROR,
|
|
3998
|
-
errorCode: "io_error"
|
|
3999
|
-
};
|
|
4000
|
-
}
|
|
4001
|
-
}
|
|
4002
|
-
function normalizeSlug(raw) {
|
|
4003
|
-
let slug = raw.trim().toLowerCase().replace(/\s+/g, "-").replace(/[^a-z0-9-]/g, "").replace(/-+/g, "-").replace(/^-|-$/g, "");
|
|
4004
|
-
if (slug.length > 60) slug = slug.slice(0, 60).replace(/-$/, "");
|
|
4005
|
-
if (!slug) {
|
|
4006
|
-
throw new CliValidationError(
|
|
4007
|
-
"invalid_input",
|
|
4008
|
-
`Slug is empty after normalization: "${raw}"`
|
|
4009
|
-
);
|
|
4010
|
-
}
|
|
4011
|
-
return slug;
|
|
4012
|
-
}
|
|
4013
|
-
async function handleHandoverCreate(content, slugRaw, format, root) {
|
|
4014
|
-
if (!content.trim()) {
|
|
4015
|
-
throw new CliValidationError("invalid_input", "Handover content is empty");
|
|
4016
|
-
}
|
|
4017
|
-
const slug = normalizeSlug(slugRaw);
|
|
4018
|
-
const date = todayISO();
|
|
4019
|
-
let filename;
|
|
4020
|
-
await withProjectLock(root, { strict: false }, async () => {
|
|
4021
|
-
const absRoot = resolve4(root);
|
|
4022
|
-
const handoversDir = join4(absRoot, ".story", "handovers");
|
|
4023
|
-
await mkdir2(handoversDir, { recursive: true });
|
|
4024
|
-
const wrapDir = join4(absRoot, ".story");
|
|
4025
|
-
const datePrefix = `${date}-`;
|
|
4026
|
-
const seqRegex = new RegExp(`^${date}-(\\d{2})-`);
|
|
4027
|
-
let maxSeq = 0;
|
|
4028
|
-
const { readdirSync: readdirSync2 } = await import("fs");
|
|
4029
|
-
try {
|
|
4030
|
-
for (const f of readdirSync2(handoversDir)) {
|
|
4031
|
-
const m = f.match(seqRegex);
|
|
4032
|
-
if (m) {
|
|
4033
|
-
const n = parseInt(m[1], 10);
|
|
4034
|
-
if (n > maxSeq) maxSeq = n;
|
|
4035
|
-
}
|
|
4036
|
-
}
|
|
4037
|
-
} catch {
|
|
4038
|
-
}
|
|
4039
|
-
let nextSeq = maxSeq + 1;
|
|
4040
|
-
if (nextSeq > 99) {
|
|
4041
|
-
throw new CliValidationError(
|
|
4042
|
-
"conflict",
|
|
4043
|
-
`Too many handovers for ${date}; limit is 99 per day`
|
|
4044
|
-
);
|
|
4045
|
-
}
|
|
4046
|
-
let candidate = `${date}-${String(nextSeq).padStart(2, "0")}-${slug}.md`;
|
|
4047
|
-
let candidatePath = join4(handoversDir, candidate);
|
|
4048
|
-
while (existsSync4(candidatePath)) {
|
|
4049
|
-
nextSeq++;
|
|
4050
|
-
if (nextSeq > 99) {
|
|
4051
|
-
throw new CliValidationError(
|
|
4052
|
-
"conflict",
|
|
4053
|
-
`Too many handovers for ${date}; limit is 99 per day`
|
|
4054
|
-
);
|
|
4055
|
-
}
|
|
4056
|
-
candidate = `${date}-${String(nextSeq).padStart(2, "0")}-${slug}.md`;
|
|
4057
|
-
candidatePath = join4(handoversDir, candidate);
|
|
4058
|
-
}
|
|
4059
|
-
await parseHandoverFilename(candidate, handoversDir);
|
|
4060
|
-
await guardPath(candidatePath, wrapDir);
|
|
4061
|
-
await atomicWrite(candidatePath, content);
|
|
4062
|
-
filename = candidate;
|
|
4063
|
-
});
|
|
4064
|
-
return { output: formatHandoverCreateResult(filename, format) };
|
|
4065
|
-
}
|
|
4082
|
+
// src/mcp/tools.ts
|
|
4083
|
+
init_handover();
|
|
4066
4084
|
|
|
4067
4085
|
// src/cli/commands/blocker.ts
|
|
4068
4086
|
init_esm_shims();
|
|
@@ -5149,6 +5167,9 @@ function errMsg(err) {
|
|
|
5149
5167
|
return err instanceof Error ? err.message : String(err);
|
|
5150
5168
|
}
|
|
5151
5169
|
|
|
5170
|
+
// src/mcp/tools.ts
|
|
5171
|
+
init_handover();
|
|
5172
|
+
|
|
5152
5173
|
// src/autonomous/guide.ts
|
|
5153
5174
|
init_esm_shims();
|
|
5154
5175
|
init_session_types();
|
|
@@ -6726,6 +6747,31 @@ var CompleteStage = class {
|
|
|
6726
6747
|
}
|
|
6727
6748
|
};
|
|
6728
6749
|
}
|
|
6750
|
+
const handoverInterval = ctx.state.config.handoverInterval ?? 5;
|
|
6751
|
+
if (handoverInterval > 0 && ticketsDone > 0 && ticketsDone % handoverInterval === 0) {
|
|
6752
|
+
try {
|
|
6753
|
+
const { handleHandoverCreate: handleHandoverCreate3 } = await Promise.resolve().then(() => (init_handover(), handover_exports));
|
|
6754
|
+
const completedIds = ctx.state.completedTickets.map((t) => t.id).join(", ");
|
|
6755
|
+
const content = [
|
|
6756
|
+
`# Checkpoint \u2014 ${ticketsDone} tickets completed`,
|
|
6757
|
+
"",
|
|
6758
|
+
`**Session:** ${ctx.state.sessionId}`,
|
|
6759
|
+
`**Tickets:** ${completedIds}`,
|
|
6760
|
+
"",
|
|
6761
|
+
"This is an automatic mid-session checkpoint. The session is still active."
|
|
6762
|
+
].join("\n");
|
|
6763
|
+
await handleHandoverCreate3(content, "checkpoint", "md", ctx.root);
|
|
6764
|
+
} catch {
|
|
6765
|
+
}
|
|
6766
|
+
try {
|
|
6767
|
+
const { loadProject: loadProject2 } = await Promise.resolve().then(() => (init_project_loader(), project_loader_exports));
|
|
6768
|
+
const { saveSnapshot: saveSnapshot2 } = await Promise.resolve().then(() => (init_snapshot(), snapshot_exports));
|
|
6769
|
+
const loadResult = await loadProject2(ctx.root);
|
|
6770
|
+
await saveSnapshot2(ctx.root, loadResult);
|
|
6771
|
+
} catch {
|
|
6772
|
+
}
|
|
6773
|
+
ctx.appendEvent("checkpoint", { ticketsDone, interval: handoverInterval });
|
|
6774
|
+
}
|
|
6729
6775
|
let nextTarget;
|
|
6730
6776
|
if (maxTickets > 0 && ticketsDone >= maxTickets) {
|
|
6731
6777
|
nextTarget = "HANDOVER";
|
|
@@ -6903,6 +6949,7 @@ Impact: ${nextIssue.impact}` : ""}` : `Fix issue ${next}.`,
|
|
|
6903
6949
|
|
|
6904
6950
|
// src/autonomous/stages/handover.ts
|
|
6905
6951
|
init_esm_shims();
|
|
6952
|
+
init_handover();
|
|
6906
6953
|
import { writeFileSync as writeFileSync2 } from "fs";
|
|
6907
6954
|
import { join as join10 } from "path";
|
|
6908
6955
|
var HandoverStage = class {
|
|
@@ -6997,6 +7044,7 @@ init_project_loader();
|
|
|
6997
7044
|
init_snapshot();
|
|
6998
7045
|
init_snapshot();
|
|
6999
7046
|
init_queries();
|
|
7047
|
+
init_handover();
|
|
7000
7048
|
async function recoverPendingMutation(dir, state, root) {
|
|
7001
7049
|
const mutation = state.pendingProjectMutation;
|
|
7002
7050
|
if (!mutation || typeof mutation !== "object") return state;
|
|
@@ -7198,6 +7246,7 @@ async function handleStart(root, args) {
|
|
|
7198
7246
|
if (typeof overrides.maxTicketsPerSession === "number") sessionConfig.maxTicketsPerSession = overrides.maxTicketsPerSession;
|
|
7199
7247
|
if (typeof overrides.compactThreshold === "string") sessionConfig.compactThreshold = overrides.compactThreshold;
|
|
7200
7248
|
if (Array.isArray(overrides.reviewBackends)) sessionConfig.reviewBackends = overrides.reviewBackends;
|
|
7249
|
+
if (typeof overrides.handoverInterval === "number") sessionConfig.handoverInterval = overrides.handoverInterval;
|
|
7201
7250
|
}
|
|
7202
7251
|
} catch {
|
|
7203
7252
|
}
|
|
@@ -9139,7 +9188,7 @@ async function ensureGitignoreEntries(gitignorePath, entries) {
|
|
|
9139
9188
|
// src/mcp/index.ts
|
|
9140
9189
|
var ENV_VAR2 = "CLAUDESTORY_PROJECT_ROOT";
|
|
9141
9190
|
var CONFIG_PATH2 = ".story/config.json";
|
|
9142
|
-
var version = "0.1.
|
|
9191
|
+
var version = "0.1.20";
|
|
9143
9192
|
function tryDiscoverRoot() {
|
|
9144
9193
|
const envRoot = process.env[ENV_VAR2];
|
|
9145
9194
|
if (envRoot) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@anthropologies/claudestory",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.20",
|
|
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": [
|