@anthropologies/claudestory 0.1.18 → 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 +43 -16
- package/dist/index.d.ts +40 -40
- package/dist/mcp.js +192 -152
- 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);
|
|
@@ -7219,21 +7229,38 @@ var init_complete = __esm({
|
|
|
7219
7229
|
'Call me with completedAction: "handover_written" and include the content in handoverContent.'
|
|
7220
7230
|
].join("\n"),
|
|
7221
7231
|
reminders: [],
|
|
7222
|
-
transitionedFrom: "COMPLETE"
|
|
7223
|
-
contextAdvice: "ok"
|
|
7232
|
+
transitionedFrom: "COMPLETE"
|
|
7224
7233
|
}
|
|
7225
7234
|
};
|
|
7226
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
|
+
}
|
|
7227
7261
|
let nextTarget;
|
|
7228
|
-
let advice = "ok";
|
|
7229
7262
|
if (maxTickets > 0 && ticketsDone >= maxTickets) {
|
|
7230
7263
|
nextTarget = "HANDOVER";
|
|
7231
|
-
} else if (pressure === "critical") {
|
|
7232
|
-
advice = "compact-now";
|
|
7233
|
-
nextTarget = "PICK_TICKET";
|
|
7234
|
-
} else if (pressure === "high") {
|
|
7235
|
-
advice = "consider-compact";
|
|
7236
|
-
nextTarget = "PICK_TICKET";
|
|
7237
7264
|
} else {
|
|
7238
7265
|
nextTarget = "PICK_TICKET";
|
|
7239
7266
|
}
|
|
@@ -7262,7 +7289,7 @@ var init_complete = __esm({
|
|
|
7262
7289
|
].join("\n"),
|
|
7263
7290
|
reminders: [],
|
|
7264
7291
|
transitionedFrom: "COMPLETE",
|
|
7265
|
-
contextAdvice:
|
|
7292
|
+
contextAdvice: "ok"
|
|
7266
7293
|
}
|
|
7267
7294
|
};
|
|
7268
7295
|
}
|
|
@@ -7730,6 +7757,7 @@ async function handleStart(root, args) {
|
|
|
7730
7757
|
if (typeof overrides.maxTicketsPerSession === "number") sessionConfig.maxTicketsPerSession = overrides.maxTicketsPerSession;
|
|
7731
7758
|
if (typeof overrides.compactThreshold === "string") sessionConfig.compactThreshold = overrides.compactThreshold;
|
|
7732
7759
|
if (Array.isArray(overrides.reviewBackends)) sessionConfig.reviewBackends = overrides.reviewBackends;
|
|
7760
|
+
if (typeof overrides.handoverInterval === "number") sessionConfig.handoverInterval = overrides.handoverInterval;
|
|
7733
7761
|
}
|
|
7734
7762
|
} catch {
|
|
7735
7763
|
}
|
|
@@ -8580,7 +8608,7 @@ function guideResult(state, currentState, opts) {
|
|
|
8580
8608
|
transitionedFrom: opts.transitionedFrom,
|
|
8581
8609
|
instruction: opts.instruction,
|
|
8582
8610
|
reminders: opts.reminders ?? [],
|
|
8583
|
-
contextAdvice:
|
|
8611
|
+
contextAdvice: "ok",
|
|
8584
8612
|
sessionSummary: summary
|
|
8585
8613
|
};
|
|
8586
8614
|
const parts = [
|
|
@@ -8594,7 +8622,6 @@ function guideResult(state, currentState, opts) {
|
|
|
8594
8622
|
`**Completed:** ${summary.completed.length > 0 ? summary.completed.join(", ") : "none"}`,
|
|
8595
8623
|
`**Tickets done:** ${summary.completed.length}`,
|
|
8596
8624
|
summary.branch ? `**Branch:** ${summary.branch}` : "",
|
|
8597
|
-
output.contextAdvice !== "ok" ? `**Context:** ${output.contextAdvice}` : "",
|
|
8598
8625
|
output.reminders.length > 0 ? `
|
|
8599
8626
|
**Reminders:**
|
|
8600
8627
|
${output.reminders.map((r) => `- ${r}`).join("\n")}` : ""
|
|
@@ -9996,7 +10023,7 @@ var init_mcp = __esm({
|
|
|
9996
10023
|
init_init();
|
|
9997
10024
|
ENV_VAR2 = "CLAUDESTORY_PROJECT_ROOT";
|
|
9998
10025
|
CONFIG_PATH2 = ".story/config.json";
|
|
9999
|
-
version = "0.1.
|
|
10026
|
+
version = "0.1.20";
|
|
10000
10027
|
main().catch((err) => {
|
|
10001
10028
|
process.stderr.write(`Fatal: ${err instanceof Error ? err.message : String(err)}
|
|
10002
10029
|
`);
|
|
@@ -13274,7 +13301,7 @@ async function runCli() {
|
|
|
13274
13301
|
registerConfigCommand: registerConfigCommand2,
|
|
13275
13302
|
registerSessionCommand: registerSessionCommand2
|
|
13276
13303
|
} = await Promise.resolve().then(() => (init_register(), register_exports));
|
|
13277
|
-
const version2 = "0.1.
|
|
13304
|
+
const version2 = "0.1.20";
|
|
13278
13305
|
class HandledError extends Error {
|
|
13279
13306
|
constructor() {
|
|
13280
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();
|
|
@@ -6722,21 +6743,38 @@ var CompleteStage = class {
|
|
|
6722
6743
|
'Call me with completedAction: "handover_written" and include the content in handoverContent.'
|
|
6723
6744
|
].join("\n"),
|
|
6724
6745
|
reminders: [],
|
|
6725
|
-
transitionedFrom: "COMPLETE"
|
|
6726
|
-
contextAdvice: "ok"
|
|
6746
|
+
transitionedFrom: "COMPLETE"
|
|
6727
6747
|
}
|
|
6728
6748
|
};
|
|
6729
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
|
+
}
|
|
6730
6775
|
let nextTarget;
|
|
6731
|
-
let advice = "ok";
|
|
6732
6776
|
if (maxTickets > 0 && ticketsDone >= maxTickets) {
|
|
6733
6777
|
nextTarget = "HANDOVER";
|
|
6734
|
-
} else if (pressure === "critical") {
|
|
6735
|
-
advice = "compact-now";
|
|
6736
|
-
nextTarget = "PICK_TICKET";
|
|
6737
|
-
} else if (pressure === "high") {
|
|
6738
|
-
advice = "consider-compact";
|
|
6739
|
-
nextTarget = "PICK_TICKET";
|
|
6740
6778
|
} else {
|
|
6741
6779
|
nextTarget = "PICK_TICKET";
|
|
6742
6780
|
}
|
|
@@ -6765,7 +6803,7 @@ var CompleteStage = class {
|
|
|
6765
6803
|
].join("\n"),
|
|
6766
6804
|
reminders: [],
|
|
6767
6805
|
transitionedFrom: "COMPLETE",
|
|
6768
|
-
contextAdvice:
|
|
6806
|
+
contextAdvice: "ok"
|
|
6769
6807
|
}
|
|
6770
6808
|
};
|
|
6771
6809
|
}
|
|
@@ -6911,6 +6949,7 @@ Impact: ${nextIssue.impact}` : ""}` : `Fix issue ${next}.`,
|
|
|
6911
6949
|
|
|
6912
6950
|
// src/autonomous/stages/handover.ts
|
|
6913
6951
|
init_esm_shims();
|
|
6952
|
+
init_handover();
|
|
6914
6953
|
import { writeFileSync as writeFileSync2 } from "fs";
|
|
6915
6954
|
import { join as join10 } from "path";
|
|
6916
6955
|
var HandoverStage = class {
|
|
@@ -7005,6 +7044,7 @@ init_project_loader();
|
|
|
7005
7044
|
init_snapshot();
|
|
7006
7045
|
init_snapshot();
|
|
7007
7046
|
init_queries();
|
|
7047
|
+
init_handover();
|
|
7008
7048
|
async function recoverPendingMutation(dir, state, root) {
|
|
7009
7049
|
const mutation = state.pendingProjectMutation;
|
|
7010
7050
|
if (!mutation || typeof mutation !== "object") return state;
|
|
@@ -7206,6 +7246,7 @@ async function handleStart(root, args) {
|
|
|
7206
7246
|
if (typeof overrides.maxTicketsPerSession === "number") sessionConfig.maxTicketsPerSession = overrides.maxTicketsPerSession;
|
|
7207
7247
|
if (typeof overrides.compactThreshold === "string") sessionConfig.compactThreshold = overrides.compactThreshold;
|
|
7208
7248
|
if (Array.isArray(overrides.reviewBackends)) sessionConfig.reviewBackends = overrides.reviewBackends;
|
|
7249
|
+
if (typeof overrides.handoverInterval === "number") sessionConfig.handoverInterval = overrides.handoverInterval;
|
|
7209
7250
|
}
|
|
7210
7251
|
} catch {
|
|
7211
7252
|
}
|
|
@@ -8057,7 +8098,7 @@ function guideResult(state, currentState, opts) {
|
|
|
8057
8098
|
transitionedFrom: opts.transitionedFrom,
|
|
8058
8099
|
instruction: opts.instruction,
|
|
8059
8100
|
reminders: opts.reminders ?? [],
|
|
8060
|
-
contextAdvice:
|
|
8101
|
+
contextAdvice: "ok",
|
|
8061
8102
|
sessionSummary: summary
|
|
8062
8103
|
};
|
|
8063
8104
|
const parts = [
|
|
@@ -8071,7 +8112,6 @@ function guideResult(state, currentState, opts) {
|
|
|
8071
8112
|
`**Completed:** ${summary.completed.length > 0 ? summary.completed.join(", ") : "none"}`,
|
|
8072
8113
|
`**Tickets done:** ${summary.completed.length}`,
|
|
8073
8114
|
summary.branch ? `**Branch:** ${summary.branch}` : "",
|
|
8074
|
-
output.contextAdvice !== "ok" ? `**Context:** ${output.contextAdvice}` : "",
|
|
8075
8115
|
output.reminders.length > 0 ? `
|
|
8076
8116
|
**Reminders:**
|
|
8077
8117
|
${output.reminders.map((r) => `- ${r}`).join("\n")}` : ""
|
|
@@ -9148,7 +9188,7 @@ async function ensureGitignoreEntries(gitignorePath, entries) {
|
|
|
9148
9188
|
// src/mcp/index.ts
|
|
9149
9189
|
var ENV_VAR2 = "CLAUDESTORY_PROJECT_ROOT";
|
|
9150
9190
|
var CONFIG_PATH2 = ".story/config.json";
|
|
9151
|
-
var version = "0.1.
|
|
9191
|
+
var version = "0.1.20";
|
|
9152
9192
|
function tryDiscoverRoot() {
|
|
9153
9193
|
const envRoot = process.env[ENV_VAR2];
|
|
9154
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": [
|