@anthropologies/claudestory 0.1.22 → 0.1.24
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 +83 -17
- package/dist/index.d.ts +40 -40
- package/dist/mcp.js +82 -16
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -5798,6 +5798,13 @@ async function gitStashPop(cwd, commitHash) {
|
|
|
5798
5798
|
}
|
|
5799
5799
|
return git(cwd, ["stash", "pop", match.ref], () => void 0);
|
|
5800
5800
|
}
|
|
5801
|
+
async function gitDiffTreeNames(cwd, commitHash) {
|
|
5802
|
+
return git(
|
|
5803
|
+
cwd,
|
|
5804
|
+
["diff-tree", "--name-only", "--no-commit-id", "-r", commitHash],
|
|
5805
|
+
(out) => out.split("\n").filter((l) => l.trim().length > 0)
|
|
5806
|
+
);
|
|
5807
|
+
}
|
|
5801
5808
|
async function gitLogRange(cwd, from, to, limit = 20) {
|
|
5802
5809
|
if (from && !SAFE_REF.test(from)) {
|
|
5803
5810
|
return { ok: false, reason: "invalid_ref", message: `Invalid git ref: ${from}` };
|
|
@@ -6372,8 +6379,10 @@ var init_plan_review = __esm({
|
|
|
6372
6379
|
const isRevise = verdict === "revise" || verdict === "request_changes";
|
|
6373
6380
|
const isReject = verdict === "reject";
|
|
6374
6381
|
let nextAction;
|
|
6375
|
-
if (isReject
|
|
6382
|
+
if (isReject) {
|
|
6376
6383
|
nextAction = "PLAN";
|
|
6384
|
+
} else if (isRevise) {
|
|
6385
|
+
nextAction = "PLAN_REVIEW";
|
|
6377
6386
|
} else if (verdict === "approve" || !hasCriticalOrMajor && roundNum >= minRounds) {
|
|
6378
6387
|
nextAction = "IMPLEMENT";
|
|
6379
6388
|
} else if (roundNum >= 5) {
|
|
@@ -6395,7 +6404,21 @@ var init_plan_review = __esm({
|
|
|
6395
6404
|
return {
|
|
6396
6405
|
action: "back",
|
|
6397
6406
|
target: "PLAN",
|
|
6398
|
-
reason:
|
|
6407
|
+
reason: "reject"
|
|
6408
|
+
};
|
|
6409
|
+
}
|
|
6410
|
+
if (isRevise) {
|
|
6411
|
+
const findingSummary = findings.length > 0 ? findings.slice(0, 5).map((f) => `- [${f.severity}] ${f.description}`).join("\n") : "Address the reviewer's concerns.";
|
|
6412
|
+
return {
|
|
6413
|
+
action: "retry",
|
|
6414
|
+
instruction: [
|
|
6415
|
+
`# Plan Review \u2014 Round ${roundNum} requested changes`,
|
|
6416
|
+
"",
|
|
6417
|
+
'Update the plan to address these findings, then call me with completedAction: "plan_review_round" and the new review verdict.',
|
|
6418
|
+
"",
|
|
6419
|
+
findingSummary
|
|
6420
|
+
].join("\n"),
|
|
6421
|
+
reminders: ["Update the plan file, then re-review. Do NOT rewrite from scratch."]
|
|
6399
6422
|
};
|
|
6400
6423
|
}
|
|
6401
6424
|
if (nextAction === "IMPLEMENT") {
|
|
@@ -6501,7 +6524,7 @@ function exhaustionAction(ctx) {
|
|
|
6501
6524
|
return {
|
|
6502
6525
|
action: "back",
|
|
6503
6526
|
target: "PLAN",
|
|
6504
|
-
reason: "
|
|
6527
|
+
reason: "TDD exhausted: could not verify new failing tests after 3 attempts. Revise the plan to make the test expectations clearer or simpler."
|
|
6505
6528
|
};
|
|
6506
6529
|
}
|
|
6507
6530
|
var MAX_WRITE_TESTS_RETRIES, EXIT_CODE_REGEX, FAIL_COUNT_REGEX, WriteTestsStage;
|
|
@@ -6664,6 +6687,13 @@ var init_test = __esm({
|
|
|
6664
6687
|
const retryCount = ctx.state.testRetryCount ?? 0;
|
|
6665
6688
|
const exitCodeMatch = notes.match(/exit\s*(?:code[:\s]*)?\s*(\d+)/i);
|
|
6666
6689
|
if (!exitCodeMatch) {
|
|
6690
|
+
const nextRetry = retryCount + 1;
|
|
6691
|
+
if (nextRetry >= MAX_TEST_RETRIES) {
|
|
6692
|
+
ctx.writeState({ testRetryCount: 0 });
|
|
6693
|
+
ctx.appendEvent("tests_parse_exhausted", { retryCount: nextRetry });
|
|
6694
|
+
return { action: "advance" };
|
|
6695
|
+
}
|
|
6696
|
+
ctx.writeState({ testRetryCount: nextRetry });
|
|
6667
6697
|
return { action: "retry", instruction: 'Could not parse exit code from notes. Include "exit code: 0" (or non-zero) in your notes.' };
|
|
6668
6698
|
}
|
|
6669
6699
|
const exitCode = parseInt(exitCodeMatch[1], 10);
|
|
@@ -7092,6 +7122,22 @@ var init_finalize = __esm({
|
|
|
7092
7122
|
async handleStage(ctx, report) {
|
|
7093
7123
|
const stagedResult = await gitDiffCachedNames(ctx.root);
|
|
7094
7124
|
if (!stagedResult.ok || stagedResult.data.length === 0) {
|
|
7125
|
+
const headResult = await gitHead(ctx.root);
|
|
7126
|
+
const previousHead = ctx.state.git.expectedHead ?? ctx.state.git.initHead;
|
|
7127
|
+
if (headResult.ok && previousHead && headResult.data.hash !== previousHead) {
|
|
7128
|
+
const ticketId2 = ctx.state.ticket?.id;
|
|
7129
|
+
if (ticketId2) {
|
|
7130
|
+
const treeResult = await gitDiffTreeNames(ctx.root, headResult.data.hash);
|
|
7131
|
+
const ticketPath = `.story/tickets/${ticketId2}.json`;
|
|
7132
|
+
if (treeResult.ok && !treeResult.data.includes(ticketPath)) {
|
|
7133
|
+
return {
|
|
7134
|
+
action: "retry",
|
|
7135
|
+
instruction: `Commit detected (${headResult.data.hash.slice(0, 7)}) but ticket file ${ticketPath} is not in the commit. Amend the commit to include it: \`git add ${ticketPath} && git commit --amend --no-edit\`, then report completedAction: "commit_done" with the new hash.`
|
|
7136
|
+
};
|
|
7137
|
+
}
|
|
7138
|
+
}
|
|
7139
|
+
return this.handleCommit(ctx, { ...report, commitHash: headResult.data.hash });
|
|
7140
|
+
}
|
|
7095
7141
|
return { action: "retry", instruction: 'No files are staged. Stage your changes and call me again with completedAction: "files_staged".' };
|
|
7096
7142
|
}
|
|
7097
7143
|
const baselineUntracked = ctx.state.git.baseline?.untrackedPaths ?? [];
|
|
@@ -7109,6 +7155,16 @@ var init_finalize = __esm({
|
|
|
7109
7155
|
}
|
|
7110
7156
|
}
|
|
7111
7157
|
}
|
|
7158
|
+
const ticketId = ctx.state.ticket?.id;
|
|
7159
|
+
if (ticketId) {
|
|
7160
|
+
const ticketPath = `.story/tickets/${ticketId}.json`;
|
|
7161
|
+
if (!stagedResult.data.includes(ticketPath)) {
|
|
7162
|
+
return {
|
|
7163
|
+
action: "retry",
|
|
7164
|
+
instruction: `Ticket file ${ticketPath} is not staged. Run \`git add ${ticketPath}\` and call me again with completedAction: "files_staged".`
|
|
7165
|
+
};
|
|
7166
|
+
}
|
|
7167
|
+
}
|
|
7112
7168
|
ctx.writeState({
|
|
7113
7169
|
finalizeCheckpoint: overlapOverridden ? "staged_override" : "staged"
|
|
7114
7170
|
});
|
|
@@ -7169,27 +7225,29 @@ var init_finalize = __esm({
|
|
|
7169
7225
|
}
|
|
7170
7226
|
const headResult = await gitHead(ctx.root);
|
|
7171
7227
|
const previousHead = ctx.state.git.expectedHead ?? ctx.state.git.initHead;
|
|
7172
|
-
|
|
7228
|
+
const fullHead = headResult.ok ? headResult.data.hash : null;
|
|
7229
|
+
if (!fullHead || !fullHead.startsWith(commitHash) && commitHash !== fullHead) {
|
|
7173
7230
|
return {
|
|
7174
7231
|
action: "retry",
|
|
7175
|
-
instruction: `Commit hash mismatch: reported ${commitHash} but HEAD is ${
|
|
7232
|
+
instruction: `Commit hash mismatch: reported ${commitHash} but HEAD is ${fullHead ?? "unknown"}. Verify the commit succeeded and report the correct hash.`
|
|
7176
7233
|
};
|
|
7177
7234
|
}
|
|
7178
|
-
|
|
7179
|
-
|
|
7235
|
+
const normalizedHash = fullHead;
|
|
7236
|
+
if (previousHead && normalizedHash === previousHead) {
|
|
7237
|
+
return { action: "retry", instruction: `No new commit detected: HEAD (${normalizedHash}) has not changed. Create a commit first, then report the new hash.` };
|
|
7180
7238
|
}
|
|
7181
|
-
const completedTicket = ctx.state.ticket ? { id: ctx.state.ticket.id, title: ctx.state.ticket.title, commitHash, risk: ctx.state.ticket.risk } : void 0;
|
|
7239
|
+
const completedTicket = ctx.state.ticket ? { id: ctx.state.ticket.id, title: ctx.state.ticket.title, commitHash: normalizedHash, risk: ctx.state.ticket.risk, realizedRisk: ctx.state.ticket.realizedRisk } : void 0;
|
|
7182
7240
|
ctx.writeState({
|
|
7183
7241
|
finalizeCheckpoint: "committed",
|
|
7184
7242
|
completedTickets: completedTicket ? [...ctx.state.completedTickets, completedTicket] : ctx.state.completedTickets,
|
|
7185
7243
|
ticket: void 0,
|
|
7186
7244
|
git: {
|
|
7187
7245
|
...ctx.state.git,
|
|
7188
|
-
mergeBase:
|
|
7189
|
-
expectedHead:
|
|
7246
|
+
mergeBase: normalizedHash,
|
|
7247
|
+
expectedHead: normalizedHash
|
|
7190
7248
|
}
|
|
7191
7249
|
});
|
|
7192
|
-
ctx.appendEvent("commit", { commitHash, ticketId: completedTicket?.id });
|
|
7250
|
+
ctx.appendEvent("commit", { commitHash: normalizedHash, ticketId: completedTicket?.id });
|
|
7193
7251
|
return { action: "advance" };
|
|
7194
7252
|
}
|
|
7195
7253
|
};
|
|
@@ -7323,7 +7381,7 @@ var init_complete = __esm({
|
|
|
7323
7381
|
"You are in autonomous mode \u2014 continue working until all tickets are done or the session limit is reached."
|
|
7324
7382
|
],
|
|
7325
7383
|
transitionedFrom: "COMPLETE",
|
|
7326
|
-
contextAdvice:
|
|
7384
|
+
contextAdvice: "ok"
|
|
7327
7385
|
}
|
|
7328
7386
|
};
|
|
7329
7387
|
}
|
|
@@ -8097,10 +8155,14 @@ ${ticket.description}` : "",
|
|
|
8097
8155
|
data: { recipe, branch: written.git.branch, head: written.git.initHead, mode: "auto" }
|
|
8098
8156
|
});
|
|
8099
8157
|
const topCandidate = nextResult.kind === "found" ? nextResult.candidates[0] : null;
|
|
8158
|
+
const maxTickets = updated.config.maxTicketsPerSession;
|
|
8159
|
+
const interval = updated.config.handoverInterval ?? 5;
|
|
8160
|
+
const sessionDesc = maxTickets > 0 ? `Work continuously until all tickets are done or you reach ${maxTickets} tickets.` : "Work continuously until all tickets are done.";
|
|
8161
|
+
const checkpointDesc = interval > 0 ? ` A checkpoint handover will be saved every ${interval} tickets.` : "";
|
|
8100
8162
|
const instruction = [
|
|
8101
8163
|
"# Autonomous Session Started",
|
|
8102
8164
|
"",
|
|
8103
|
-
|
|
8165
|
+
`You are now in autonomous mode. ${sessionDesc}${checkpointDesc}`,
|
|
8104
8166
|
"Do NOT stop to summarize. Do NOT ask the user. Pick a ticket and start working immediately.",
|
|
8105
8167
|
"",
|
|
8106
8168
|
"## Ticket Candidates",
|
|
@@ -8530,11 +8592,15 @@ async function handleCancel(root, args) {
|
|
|
8530
8592
|
}
|
|
8531
8593
|
const info = findSessionById(root, args.sessionId);
|
|
8532
8594
|
if (!info) return guideError(new Error(`Session ${args.sessionId} not found`));
|
|
8533
|
-
if (info.state.
|
|
8595
|
+
if (info.state.state === "SESSION_END" || info.state.status === "completed") {
|
|
8596
|
+
return guideError(new Error("Session already ended."));
|
|
8597
|
+
}
|
|
8598
|
+
const CANCELLABLE_STATES = /* @__PURE__ */ new Set(["PICK_TICKET", "COMPLETE", "HANDOVER"]);
|
|
8599
|
+
if (info.state.recipe === "coding" && !CANCELLABLE_STATES.has(info.state.state)) {
|
|
8534
8600
|
const sessionMode = info.state.mode ?? "auto";
|
|
8535
8601
|
const modeGuidance = sessionMode === "plan" ? "Plan mode sessions end after plan review approval \u2014 continue to that step." : sessionMode === "review" ? "Review mode sessions end after code review approval \u2014 continue to that step." : sessionMode === "guided" ? "Guided mode sessions end after ticket completion \u2014 continue to FINALIZE." : "Complete the current ticket and write a handover to end the session.";
|
|
8536
8602
|
return guideError(new Error(
|
|
8537
|
-
`Cannot cancel a coding session. ${modeGuidance}`
|
|
8603
|
+
`Cannot cancel a coding session from ${info.state.state}. ${modeGuidance}`
|
|
8538
8604
|
));
|
|
8539
8605
|
}
|
|
8540
8606
|
await recoverPendingMutation(info.dir, info.state, root);
|
|
@@ -10024,7 +10090,7 @@ var init_mcp = __esm({
|
|
|
10024
10090
|
init_init();
|
|
10025
10091
|
ENV_VAR2 = "CLAUDESTORY_PROJECT_ROOT";
|
|
10026
10092
|
CONFIG_PATH2 = ".story/config.json";
|
|
10027
|
-
version = "0.1.
|
|
10093
|
+
version = "0.1.24";
|
|
10028
10094
|
main().catch((err) => {
|
|
10029
10095
|
process.stderr.write(`Fatal: ${err instanceof Error ? err.message : String(err)}
|
|
10030
10096
|
`);
|
|
@@ -13302,7 +13368,7 @@ async function runCli() {
|
|
|
13302
13368
|
registerConfigCommand: registerConfigCommand2,
|
|
13303
13369
|
registerSessionCommand: registerSessionCommand2
|
|
13304
13370
|
} = await Promise.resolve().then(() => (init_register(), register_exports));
|
|
13305
|
-
const version2 = "0.1.
|
|
13371
|
+
const version2 = "0.1.24";
|
|
13306
13372
|
class HandledError extends Error {
|
|
13307
13373
|
constructor() {
|
|
13308
13374
|
super("HANDLED_ERROR");
|
package/dist/index.d.ts
CHANGED
|
@@ -1331,40 +1331,15 @@ declare const SnapshotV1Schema: z.ZodObject<{
|
|
|
1331
1331
|
file: z.ZodString;
|
|
1332
1332
|
message: z.ZodString;
|
|
1333
1333
|
}, "strip", z.ZodTypeAny, {
|
|
1334
|
-
type: string;
|
|
1335
1334
|
message: string;
|
|
1335
|
+
type: string;
|
|
1336
1336
|
file: string;
|
|
1337
1337
|
}, {
|
|
1338
|
-
type: string;
|
|
1339
1338
|
message: string;
|
|
1339
|
+
type: 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
|
-
};
|
|
1368
1343
|
issues: z.objectOutputType<{
|
|
1369
1344
|
id: z.ZodString;
|
|
1370
1345
|
title: z.ZodString;
|
|
@@ -1401,8 +1376,8 @@ declare const SnapshotV1Schema: z.ZodObject<{
|
|
|
1401
1376
|
claimedBySession: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
1402
1377
|
}, z.ZodTypeAny, "passthrough">[];
|
|
1403
1378
|
roadmap: {
|
|
1404
|
-
date: string;
|
|
1405
1379
|
title: string;
|
|
1380
|
+
date: string;
|
|
1406
1381
|
phases: z.objectOutputType<{
|
|
1407
1382
|
id: z.ZodString;
|
|
1408
1383
|
label: z.ZodString;
|
|
@@ -1420,6 +1395,7 @@ declare const SnapshotV1Schema: z.ZodObject<{
|
|
|
1420
1395
|
} & {
|
|
1421
1396
|
[k: string]: unknown;
|
|
1422
1397
|
};
|
|
1398
|
+
version: 1;
|
|
1423
1399
|
project: string;
|
|
1424
1400
|
notes: z.objectOutputType<{
|
|
1425
1401
|
id: z.ZodString;
|
|
@@ -1445,19 +1421,11 @@ declare const SnapshotV1Schema: z.ZodObject<{
|
|
|
1445
1421
|
status: z.ZodEnum<["active", "deprecated", "superseded"]>;
|
|
1446
1422
|
}, z.ZodTypeAny, "passthrough">[];
|
|
1447
1423
|
createdAt: string;
|
|
1448
|
-
handoverFilenames: string[];
|
|
1449
|
-
warnings?: {
|
|
1450
|
-
type: string;
|
|
1451
|
-
message: string;
|
|
1452
|
-
file: string;
|
|
1453
|
-
}[] | undefined;
|
|
1454
|
-
}, {
|
|
1455
|
-
version: 1;
|
|
1456
1424
|
config: {
|
|
1457
|
-
version: number;
|
|
1458
1425
|
type: string;
|
|
1459
|
-
|
|
1426
|
+
version: number;
|
|
1460
1427
|
project: string;
|
|
1428
|
+
language: string;
|
|
1461
1429
|
features: {
|
|
1462
1430
|
issues: boolean;
|
|
1463
1431
|
tickets: boolean;
|
|
@@ -1477,6 +1445,13 @@ declare const SnapshotV1Schema: z.ZodObject<{
|
|
|
1477
1445
|
} & {
|
|
1478
1446
|
[k: string]: unknown;
|
|
1479
1447
|
};
|
|
1448
|
+
handoverFilenames: string[];
|
|
1449
|
+
warnings?: {
|
|
1450
|
+
message: string;
|
|
1451
|
+
type: string;
|
|
1452
|
+
file: string;
|
|
1453
|
+
}[] | undefined;
|
|
1454
|
+
}, {
|
|
1480
1455
|
issues: z.objectInputType<{
|
|
1481
1456
|
id: z.ZodString;
|
|
1482
1457
|
title: z.ZodString;
|
|
@@ -1513,8 +1488,8 @@ declare const SnapshotV1Schema: z.ZodObject<{
|
|
|
1513
1488
|
claimedBySession: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
1514
1489
|
}, z.ZodTypeAny, "passthrough">[];
|
|
1515
1490
|
roadmap: {
|
|
1516
|
-
date: string;
|
|
1517
1491
|
title: string;
|
|
1492
|
+
date: string;
|
|
1518
1493
|
phases: z.objectInputType<{
|
|
1519
1494
|
id: z.ZodString;
|
|
1520
1495
|
label: z.ZodString;
|
|
@@ -1532,8 +1507,33 @@ declare const SnapshotV1Schema: z.ZodObject<{
|
|
|
1532
1507
|
} & {
|
|
1533
1508
|
[k: string]: unknown;
|
|
1534
1509
|
};
|
|
1510
|
+
version: 1;
|
|
1535
1511
|
project: string;
|
|
1536
1512
|
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
|
-
type: string;
|
|
1562
1561
|
message: string;
|
|
1562
|
+
type: string;
|
|
1563
1563
|
file: string;
|
|
1564
1564
|
}[] | undefined;
|
|
1565
1565
|
handoverFilenames?: string[] | undefined;
|
package/dist/mcp.js
CHANGED
|
@@ -5402,6 +5402,13 @@ async function gitStashPop(cwd, commitHash) {
|
|
|
5402
5402
|
}
|
|
5403
5403
|
return git(cwd, ["stash", "pop", match.ref], () => void 0);
|
|
5404
5404
|
}
|
|
5405
|
+
async function gitDiffTreeNames(cwd, commitHash) {
|
|
5406
|
+
return git(
|
|
5407
|
+
cwd,
|
|
5408
|
+
["diff-tree", "--name-only", "--no-commit-id", "-r", commitHash],
|
|
5409
|
+
(out) => out.split("\n").filter((l) => l.trim().length > 0)
|
|
5410
|
+
);
|
|
5411
|
+
}
|
|
5405
5412
|
var SAFE_REF = /^[0-9a-f]{4,40}$/i;
|
|
5406
5413
|
async function gitLogRange(cwd, from, to, limit = 20) {
|
|
5407
5414
|
if (from && !SAFE_REF.test(from)) {
|
|
@@ -5935,8 +5942,10 @@ var PlanReviewStage = class {
|
|
|
5935
5942
|
const isRevise = verdict === "revise" || verdict === "request_changes";
|
|
5936
5943
|
const isReject = verdict === "reject";
|
|
5937
5944
|
let nextAction;
|
|
5938
|
-
if (isReject
|
|
5945
|
+
if (isReject) {
|
|
5939
5946
|
nextAction = "PLAN";
|
|
5947
|
+
} else if (isRevise) {
|
|
5948
|
+
nextAction = "PLAN_REVIEW";
|
|
5940
5949
|
} else if (verdict === "approve" || !hasCriticalOrMajor && roundNum >= minRounds) {
|
|
5941
5950
|
nextAction = "IMPLEMENT";
|
|
5942
5951
|
} else if (roundNum >= 5) {
|
|
@@ -5958,7 +5967,21 @@ var PlanReviewStage = class {
|
|
|
5958
5967
|
return {
|
|
5959
5968
|
action: "back",
|
|
5960
5969
|
target: "PLAN",
|
|
5961
|
-
reason:
|
|
5970
|
+
reason: "reject"
|
|
5971
|
+
};
|
|
5972
|
+
}
|
|
5973
|
+
if (isRevise) {
|
|
5974
|
+
const findingSummary = findings.length > 0 ? findings.slice(0, 5).map((f) => `- [${f.severity}] ${f.description}`).join("\n") : "Address the reviewer's concerns.";
|
|
5975
|
+
return {
|
|
5976
|
+
action: "retry",
|
|
5977
|
+
instruction: [
|
|
5978
|
+
`# Plan Review \u2014 Round ${roundNum} requested changes`,
|
|
5979
|
+
"",
|
|
5980
|
+
'Update the plan to address these findings, then call me with completedAction: "plan_review_round" and the new review verdict.',
|
|
5981
|
+
"",
|
|
5982
|
+
findingSummary
|
|
5983
|
+
].join("\n"),
|
|
5984
|
+
reminders: ["Update the plan file, then re-review. Do NOT rewrite from scratch."]
|
|
5962
5985
|
};
|
|
5963
5986
|
}
|
|
5964
5987
|
if (nextAction === "IMPLEMENT") {
|
|
@@ -6168,7 +6191,7 @@ function exhaustionAction(ctx) {
|
|
|
6168
6191
|
return {
|
|
6169
6192
|
action: "back",
|
|
6170
6193
|
target: "PLAN",
|
|
6171
|
-
reason: "
|
|
6194
|
+
reason: "TDD exhausted: could not verify new failing tests after 3 attempts. Revise the plan to make the test expectations clearer or simpler."
|
|
6172
6195
|
};
|
|
6173
6196
|
}
|
|
6174
6197
|
|
|
@@ -6207,6 +6230,13 @@ var TestStage = class {
|
|
|
6207
6230
|
const retryCount = ctx.state.testRetryCount ?? 0;
|
|
6208
6231
|
const exitCodeMatch = notes.match(/exit\s*(?:code[:\s]*)?\s*(\d+)/i);
|
|
6209
6232
|
if (!exitCodeMatch) {
|
|
6233
|
+
const nextRetry = retryCount + 1;
|
|
6234
|
+
if (nextRetry >= MAX_TEST_RETRIES) {
|
|
6235
|
+
ctx.writeState({ testRetryCount: 0 });
|
|
6236
|
+
ctx.appendEvent("tests_parse_exhausted", { retryCount: nextRetry });
|
|
6237
|
+
return { action: "advance" };
|
|
6238
|
+
}
|
|
6239
|
+
ctx.writeState({ testRetryCount: nextRetry });
|
|
6210
6240
|
return { action: "retry", instruction: 'Could not parse exit code from notes. Include "exit code: 0" (or non-zero) in your notes.' };
|
|
6211
6241
|
}
|
|
6212
6242
|
const exitCode = parseInt(exitCodeMatch[1], 10);
|
|
@@ -6614,6 +6644,22 @@ var FinalizeStage = class {
|
|
|
6614
6644
|
async handleStage(ctx, report) {
|
|
6615
6645
|
const stagedResult = await gitDiffCachedNames(ctx.root);
|
|
6616
6646
|
if (!stagedResult.ok || stagedResult.data.length === 0) {
|
|
6647
|
+
const headResult = await gitHead(ctx.root);
|
|
6648
|
+
const previousHead = ctx.state.git.expectedHead ?? ctx.state.git.initHead;
|
|
6649
|
+
if (headResult.ok && previousHead && headResult.data.hash !== previousHead) {
|
|
6650
|
+
const ticketId2 = ctx.state.ticket?.id;
|
|
6651
|
+
if (ticketId2) {
|
|
6652
|
+
const treeResult = await gitDiffTreeNames(ctx.root, headResult.data.hash);
|
|
6653
|
+
const ticketPath = `.story/tickets/${ticketId2}.json`;
|
|
6654
|
+
if (treeResult.ok && !treeResult.data.includes(ticketPath)) {
|
|
6655
|
+
return {
|
|
6656
|
+
action: "retry",
|
|
6657
|
+
instruction: `Commit detected (${headResult.data.hash.slice(0, 7)}) but ticket file ${ticketPath} is not in the commit. Amend the commit to include it: \`git add ${ticketPath} && git commit --amend --no-edit\`, then report completedAction: "commit_done" with the new hash.`
|
|
6658
|
+
};
|
|
6659
|
+
}
|
|
6660
|
+
}
|
|
6661
|
+
return this.handleCommit(ctx, { ...report, commitHash: headResult.data.hash });
|
|
6662
|
+
}
|
|
6617
6663
|
return { action: "retry", instruction: 'No files are staged. Stage your changes and call me again with completedAction: "files_staged".' };
|
|
6618
6664
|
}
|
|
6619
6665
|
const baselineUntracked = ctx.state.git.baseline?.untrackedPaths ?? [];
|
|
@@ -6631,6 +6677,16 @@ var FinalizeStage = class {
|
|
|
6631
6677
|
}
|
|
6632
6678
|
}
|
|
6633
6679
|
}
|
|
6680
|
+
const ticketId = ctx.state.ticket?.id;
|
|
6681
|
+
if (ticketId) {
|
|
6682
|
+
const ticketPath = `.story/tickets/${ticketId}.json`;
|
|
6683
|
+
if (!stagedResult.data.includes(ticketPath)) {
|
|
6684
|
+
return {
|
|
6685
|
+
action: "retry",
|
|
6686
|
+
instruction: `Ticket file ${ticketPath} is not staged. Run \`git add ${ticketPath}\` and call me again with completedAction: "files_staged".`
|
|
6687
|
+
};
|
|
6688
|
+
}
|
|
6689
|
+
}
|
|
6634
6690
|
ctx.writeState({
|
|
6635
6691
|
finalizeCheckpoint: overlapOverridden ? "staged_override" : "staged"
|
|
6636
6692
|
});
|
|
@@ -6691,27 +6747,29 @@ var FinalizeStage = class {
|
|
|
6691
6747
|
}
|
|
6692
6748
|
const headResult = await gitHead(ctx.root);
|
|
6693
6749
|
const previousHead = ctx.state.git.expectedHead ?? ctx.state.git.initHead;
|
|
6694
|
-
|
|
6750
|
+
const fullHead = headResult.ok ? headResult.data.hash : null;
|
|
6751
|
+
if (!fullHead || !fullHead.startsWith(commitHash) && commitHash !== fullHead) {
|
|
6695
6752
|
return {
|
|
6696
6753
|
action: "retry",
|
|
6697
|
-
instruction: `Commit hash mismatch: reported ${commitHash} but HEAD is ${
|
|
6754
|
+
instruction: `Commit hash mismatch: reported ${commitHash} but HEAD is ${fullHead ?? "unknown"}. Verify the commit succeeded and report the correct hash.`
|
|
6698
6755
|
};
|
|
6699
6756
|
}
|
|
6700
|
-
|
|
6701
|
-
|
|
6757
|
+
const normalizedHash = fullHead;
|
|
6758
|
+
if (previousHead && normalizedHash === previousHead) {
|
|
6759
|
+
return { action: "retry", instruction: `No new commit detected: HEAD (${normalizedHash}) has not changed. Create a commit first, then report the new hash.` };
|
|
6702
6760
|
}
|
|
6703
|
-
const completedTicket = ctx.state.ticket ? { id: ctx.state.ticket.id, title: ctx.state.ticket.title, commitHash, risk: ctx.state.ticket.risk } : void 0;
|
|
6761
|
+
const completedTicket = ctx.state.ticket ? { id: ctx.state.ticket.id, title: ctx.state.ticket.title, commitHash: normalizedHash, risk: ctx.state.ticket.risk, realizedRisk: ctx.state.ticket.realizedRisk } : void 0;
|
|
6704
6762
|
ctx.writeState({
|
|
6705
6763
|
finalizeCheckpoint: "committed",
|
|
6706
6764
|
completedTickets: completedTicket ? [...ctx.state.completedTickets, completedTicket] : ctx.state.completedTickets,
|
|
6707
6765
|
ticket: void 0,
|
|
6708
6766
|
git: {
|
|
6709
6767
|
...ctx.state.git,
|
|
6710
|
-
mergeBase:
|
|
6711
|
-
expectedHead:
|
|
6768
|
+
mergeBase: normalizedHash,
|
|
6769
|
+
expectedHead: normalizedHash
|
|
6712
6770
|
}
|
|
6713
6771
|
});
|
|
6714
|
-
ctx.appendEvent("commit", { commitHash, ticketId: completedTicket?.id });
|
|
6772
|
+
ctx.appendEvent("commit", { commitHash: normalizedHash, ticketId: completedTicket?.id });
|
|
6715
6773
|
return { action: "advance" };
|
|
6716
6774
|
}
|
|
6717
6775
|
};
|
|
@@ -6837,7 +6895,7 @@ var CompleteStage = class {
|
|
|
6837
6895
|
"You are in autonomous mode \u2014 continue working until all tickets are done or the session limit is reached."
|
|
6838
6896
|
],
|
|
6839
6897
|
transitionedFrom: "COMPLETE",
|
|
6840
|
-
contextAdvice:
|
|
6898
|
+
contextAdvice: "ok"
|
|
6841
6899
|
}
|
|
6842
6900
|
};
|
|
6843
6901
|
}
|
|
@@ -7586,10 +7644,14 @@ ${ticket.description}` : "",
|
|
|
7586
7644
|
data: { recipe, branch: written.git.branch, head: written.git.initHead, mode: "auto" }
|
|
7587
7645
|
});
|
|
7588
7646
|
const topCandidate = nextResult.kind === "found" ? nextResult.candidates[0] : null;
|
|
7647
|
+
const maxTickets = updated.config.maxTicketsPerSession;
|
|
7648
|
+
const interval = updated.config.handoverInterval ?? 5;
|
|
7649
|
+
const sessionDesc = maxTickets > 0 ? `Work continuously until all tickets are done or you reach ${maxTickets} tickets.` : "Work continuously until all tickets are done.";
|
|
7650
|
+
const checkpointDesc = interval > 0 ? ` A checkpoint handover will be saved every ${interval} tickets.` : "";
|
|
7589
7651
|
const instruction = [
|
|
7590
7652
|
"# Autonomous Session Started",
|
|
7591
7653
|
"",
|
|
7592
|
-
|
|
7654
|
+
`You are now in autonomous mode. ${sessionDesc}${checkpointDesc}`,
|
|
7593
7655
|
"Do NOT stop to summarize. Do NOT ask the user. Pick a ticket and start working immediately.",
|
|
7594
7656
|
"",
|
|
7595
7657
|
"## Ticket Candidates",
|
|
@@ -8020,11 +8082,15 @@ async function handleCancel(root, args) {
|
|
|
8020
8082
|
}
|
|
8021
8083
|
const info = findSessionById(root, args.sessionId);
|
|
8022
8084
|
if (!info) return guideError(new Error(`Session ${args.sessionId} not found`));
|
|
8023
|
-
if (info.state.
|
|
8085
|
+
if (info.state.state === "SESSION_END" || info.state.status === "completed") {
|
|
8086
|
+
return guideError(new Error("Session already ended."));
|
|
8087
|
+
}
|
|
8088
|
+
const CANCELLABLE_STATES = /* @__PURE__ */ new Set(["PICK_TICKET", "COMPLETE", "HANDOVER"]);
|
|
8089
|
+
if (info.state.recipe === "coding" && !CANCELLABLE_STATES.has(info.state.state)) {
|
|
8024
8090
|
const sessionMode = info.state.mode ?? "auto";
|
|
8025
8091
|
const modeGuidance = sessionMode === "plan" ? "Plan mode sessions end after plan review approval \u2014 continue to that step." : sessionMode === "review" ? "Review mode sessions end after code review approval \u2014 continue to that step." : sessionMode === "guided" ? "Guided mode sessions end after ticket completion \u2014 continue to FINALIZE." : "Complete the current ticket and write a handover to end the session.";
|
|
8026
8092
|
return guideError(new Error(
|
|
8027
|
-
`Cannot cancel a coding session. ${modeGuidance}`
|
|
8093
|
+
`Cannot cancel a coding session from ${info.state.state}. ${modeGuidance}`
|
|
8028
8094
|
));
|
|
8029
8095
|
}
|
|
8030
8096
|
await recoverPendingMutation(info.dir, info.state, root);
|
|
@@ -9189,7 +9255,7 @@ async function ensureGitignoreEntries(gitignorePath, entries) {
|
|
|
9189
9255
|
// src/mcp/index.ts
|
|
9190
9256
|
var ENV_VAR2 = "CLAUDESTORY_PROJECT_ROOT";
|
|
9191
9257
|
var CONFIG_PATH2 = ".story/config.json";
|
|
9192
|
-
var version = "0.1.
|
|
9258
|
+
var version = "0.1.24";
|
|
9193
9259
|
function tryDiscoverRoot() {
|
|
9194
9260
|
const envRoot = process.env[ENV_VAR2];
|
|
9195
9261
|
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.24",
|
|
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": [
|