@anthropologies/claudestory 0.1.62 → 0.1.63
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 +1957 -875
- package/dist/index.d.ts +20 -3
- package/dist/index.js +137 -14
- package/dist/mcp.js +1708 -652
- package/package.json +3 -2
package/dist/cli.js
CHANGED
|
@@ -221,8 +221,12 @@ var init_session_types = __esm({
|
|
|
221
221
|
title: z.string().optional(),
|
|
222
222
|
commitHash: z.string().optional(),
|
|
223
223
|
risk: z.string().optional(),
|
|
224
|
-
realizedRisk: z.string().optional()
|
|
224
|
+
realizedRisk: z.string().optional(),
|
|
225
|
+
startedAt: z.string().optional(),
|
|
226
|
+
completedAt: z.string().optional()
|
|
225
227
|
})).default([]),
|
|
228
|
+
// T-187: Per-ticket timing -- set when ticket is picked, cleared on commit
|
|
229
|
+
ticketStartedAt: z.string().nullable().default(null),
|
|
226
230
|
// FINALIZE checkpoint
|
|
227
231
|
finalizeCheckpoint: z.enum(["staged", "staged_override", "precommit_passed", "committed"]).nullable().default(null),
|
|
228
232
|
// Git state
|
|
@@ -284,6 +288,10 @@ var init_session_types = __esm({
|
|
|
284
288
|
lastGuideCall: z.string().optional(),
|
|
285
289
|
startedAt: z.string(),
|
|
286
290
|
guideCallCount: z.number().default(0),
|
|
291
|
+
// ISS-098: Codex availability cache -- skip codex after failure
|
|
292
|
+
// ISS-110: Changed from boolean to ISO timestamp with 10-minute TTL
|
|
293
|
+
codexUnavailable: z.boolean().optional(),
|
|
294
|
+
codexUnavailableSince: z.string().optional(),
|
|
287
295
|
// Supersession tracking
|
|
288
296
|
supersededBy: z.string().optional(),
|
|
289
297
|
supersededSession: z.string().optional(),
|
|
@@ -1831,6 +1839,9 @@ function validateFindings(raw, lensName) {
|
|
|
1831
1839
|
continue;
|
|
1832
1840
|
}
|
|
1833
1841
|
const normalized = normalizeFields(item);
|
|
1842
|
+
if (typeof normalized.lens !== "string" && typeof lensName === "string") {
|
|
1843
|
+
normalized.lens = lensName;
|
|
1844
|
+
}
|
|
1834
1845
|
const reason = checkFinding(normalized, lensName);
|
|
1835
1846
|
if (reason) {
|
|
1836
1847
|
invalid.push({ raw: item, reason });
|
|
@@ -2609,6 +2620,63 @@ var init_orchestrator = __esm({
|
|
|
2609
2620
|
}
|
|
2610
2621
|
});
|
|
2611
2622
|
|
|
2623
|
+
// src/autonomous/review-lenses/diff-scope.ts
|
|
2624
|
+
function parseDiffScope(diff) {
|
|
2625
|
+
const changedFiles = /* @__PURE__ */ new Set();
|
|
2626
|
+
const addedLines = /* @__PURE__ */ new Map();
|
|
2627
|
+
if (!diff) return { changedFiles, addedLines };
|
|
2628
|
+
const lines = diff.split("\n");
|
|
2629
|
+
let currentFile = null;
|
|
2630
|
+
let currentLineNum = 0;
|
|
2631
|
+
for (const line of lines) {
|
|
2632
|
+
if (line.startsWith("+++ ")) {
|
|
2633
|
+
if (line.startsWith("+++ /dev/null")) {
|
|
2634
|
+
currentFile = null;
|
|
2635
|
+
continue;
|
|
2636
|
+
}
|
|
2637
|
+
const rawPath = line.startsWith("+++ b/") ? line.slice(6) : line.slice(4);
|
|
2638
|
+
currentFile = normalizePath(rawPath);
|
|
2639
|
+
changedFiles.add(currentFile);
|
|
2640
|
+
if (!addedLines.has(currentFile)) {
|
|
2641
|
+
addedLines.set(currentFile, /* @__PURE__ */ new Set());
|
|
2642
|
+
}
|
|
2643
|
+
currentLineNum = 0;
|
|
2644
|
+
continue;
|
|
2645
|
+
}
|
|
2646
|
+
const hunkMatch = line.match(/^@@ -\d+(?:,\d+)? \+(\d+)/);
|
|
2647
|
+
if (hunkMatch) {
|
|
2648
|
+
currentLineNum = parseInt(hunkMatch[1], 10) - 1;
|
|
2649
|
+
continue;
|
|
2650
|
+
}
|
|
2651
|
+
if (!currentFile) continue;
|
|
2652
|
+
if (line.startsWith("-")) continue;
|
|
2653
|
+
currentLineNum++;
|
|
2654
|
+
if (line.startsWith("+")) {
|
|
2655
|
+
addedLines.get(currentFile).add(currentLineNum);
|
|
2656
|
+
}
|
|
2657
|
+
}
|
|
2658
|
+
return { changedFiles, addedLines };
|
|
2659
|
+
}
|
|
2660
|
+
function normalizePath(p) {
|
|
2661
|
+
return p.startsWith("./") ? p.slice(2) : p;
|
|
2662
|
+
}
|
|
2663
|
+
function classifyOrigin(finding, scope, stage) {
|
|
2664
|
+
if (stage === "PLAN_REVIEW") return "introduced";
|
|
2665
|
+
if (!finding.file) return "introduced";
|
|
2666
|
+
const file = normalizePath(finding.file);
|
|
2667
|
+
if (!scope.changedFiles.has(file)) return "pre-existing";
|
|
2668
|
+
if (finding.line == null) return "introduced";
|
|
2669
|
+
const fileLines = scope.addedLines.get(file);
|
|
2670
|
+
if (!fileLines || !fileLines.has(finding.line)) return "pre-existing";
|
|
2671
|
+
return "introduced";
|
|
2672
|
+
}
|
|
2673
|
+
var init_diff_scope = __esm({
|
|
2674
|
+
"src/autonomous/review-lenses/diff-scope.ts"() {
|
|
2675
|
+
"use strict";
|
|
2676
|
+
init_esm_shims();
|
|
2677
|
+
}
|
|
2678
|
+
});
|
|
2679
|
+
|
|
2612
2680
|
// src/autonomous/review-lenses/mcp-handlers.ts
|
|
2613
2681
|
import { readFileSync as readFileSync3 } from "fs";
|
|
2614
2682
|
import { join as join6 } from "path";
|
|
@@ -2707,6 +2775,7 @@ function handleSynthesize(input) {
|
|
|
2707
2775
|
}
|
|
2708
2776
|
}
|
|
2709
2777
|
const stage = input.stage ?? "CODE_REVIEW";
|
|
2778
|
+
const diffScope = input.diff && input.changedFiles && stage === "CODE_REVIEW" ? parseDiffScope(input.diff) : null;
|
|
2710
2779
|
const lensesCompleted = [];
|
|
2711
2780
|
const lensesFailed = [];
|
|
2712
2781
|
const lensesInsufficientContext = [];
|
|
@@ -2739,7 +2808,8 @@ function handleSynthesize(input) {
|
|
|
2739
2808
|
const enriched = {
|
|
2740
2809
|
...f,
|
|
2741
2810
|
issueKey: generateIssueKey(f),
|
|
2742
|
-
blocking: computeBlocking(f, stage, policy)
|
|
2811
|
+
blocking: computeBlocking(f, stage, policy),
|
|
2812
|
+
origin: diffScope ? classifyOrigin(f, diffScope, stage) : void 0
|
|
2743
2813
|
};
|
|
2744
2814
|
allFindings.push(enriched);
|
|
2745
2815
|
}
|
|
@@ -2754,6 +2824,9 @@ function handleSynthesize(input) {
|
|
|
2754
2824
|
lensesFailed.push(lens);
|
|
2755
2825
|
}
|
|
2756
2826
|
}
|
|
2827
|
+
const preExistingFindings = allFindings.filter(
|
|
2828
|
+
(f) => f.origin === "pre-existing" && f.severity !== "suggestion"
|
|
2829
|
+
);
|
|
2757
2830
|
const lensMetadata = buildLensMetadata(lensesCompleted, lensesFailed, lensesInsufficientContext);
|
|
2758
2831
|
const mergerPrompt = buildMergerPrompt(allFindings, lensMetadata, stage);
|
|
2759
2832
|
return {
|
|
@@ -2763,7 +2836,9 @@ function handleSynthesize(input) {
|
|
|
2763
2836
|
lensesFailed,
|
|
2764
2837
|
lensesInsufficientContext,
|
|
2765
2838
|
droppedFindings: droppedTotal,
|
|
2766
|
-
droppedDetails: dropReasons.slice(0, 5)
|
|
2839
|
+
droppedDetails: dropReasons.slice(0, 5),
|
|
2840
|
+
preExistingFindings,
|
|
2841
|
+
preExistingCount: preExistingFindings.length
|
|
2767
2842
|
};
|
|
2768
2843
|
}
|
|
2769
2844
|
function handleJudge(input) {
|
|
@@ -2788,8 +2863,8 @@ function handleJudge(input) {
|
|
|
2788
2863
|
[...input.lensesSkipped]
|
|
2789
2864
|
);
|
|
2790
2865
|
if (input.convergenceHistory && input.convergenceHistory.length > 0) {
|
|
2791
|
-
const
|
|
2792
|
-
const historyTable = input.convergenceHistory.map((h) => `| R${h.round} | ${
|
|
2866
|
+
const sanitize2 = (s) => s.replace(/[|\n\r#>`*_~\[\]]/g, " ").slice(0, 50);
|
|
2867
|
+
const historyTable = input.convergenceHistory.map((h) => `| R${h.round} | ${sanitize2(h.verdict)} | ${h.blocking} | ${h.important} | ${sanitize2(h.newCode)} |`).join("\n");
|
|
2793
2868
|
judgePrompt += `
|
|
2794
2869
|
|
|
2795
2870
|
## Convergence History
|
|
@@ -2816,6 +2891,7 @@ var init_mcp_handlers = __esm({
|
|
|
2816
2891
|
init_schema_validator();
|
|
2817
2892
|
init_issue_key();
|
|
2818
2893
|
init_blocking_policy();
|
|
2894
|
+
init_diff_scope();
|
|
2819
2895
|
init_merger();
|
|
2820
2896
|
init_judge();
|
|
2821
2897
|
init_types();
|
|
@@ -3094,12 +3170,13 @@ var init_project_state = __esm({
|
|
|
3094
3170
|
totalTicketCount;
|
|
3095
3171
|
openTicketCount;
|
|
3096
3172
|
completeTicketCount;
|
|
3097
|
-
|
|
3173
|
+
activeIssueCount;
|
|
3098
3174
|
issuesBySeverity;
|
|
3099
3175
|
activeNoteCount;
|
|
3100
3176
|
archivedNoteCount;
|
|
3101
3177
|
activeLessonCount;
|
|
3102
3178
|
deprecatedLessonCount;
|
|
3179
|
+
lessonTags;
|
|
3103
3180
|
constructor(input) {
|
|
3104
3181
|
this.tickets = input.tickets;
|
|
3105
3182
|
this.issues = input.issues;
|
|
@@ -3180,19 +3257,19 @@ var init_project_state = __esm({
|
|
|
3180
3257
|
lByID.set(l.id, l);
|
|
3181
3258
|
}
|
|
3182
3259
|
this.lessonsByID = lByID;
|
|
3183
|
-
this.totalTicketCount =
|
|
3184
|
-
this.openTicketCount =
|
|
3260
|
+
this.totalTicketCount = this.leafTickets.length;
|
|
3261
|
+
this.openTicketCount = this.leafTickets.filter(
|
|
3185
3262
|
(t) => t.status !== "complete"
|
|
3186
3263
|
).length;
|
|
3187
|
-
this.completeTicketCount =
|
|
3264
|
+
this.completeTicketCount = this.leafTickets.filter(
|
|
3188
3265
|
(t) => t.status === "complete"
|
|
3189
3266
|
).length;
|
|
3190
|
-
this.
|
|
3191
|
-
(i) => i.status
|
|
3267
|
+
this.activeIssueCount = input.issues.filter(
|
|
3268
|
+
(i) => i.status !== "resolved"
|
|
3192
3269
|
).length;
|
|
3193
3270
|
const bySev = /* @__PURE__ */ new Map();
|
|
3194
3271
|
for (const i of input.issues) {
|
|
3195
|
-
if (i.status
|
|
3272
|
+
if (i.status !== "resolved") {
|
|
3196
3273
|
bySev.set(i.severity, (bySev.get(i.severity) ?? 0) + 1);
|
|
3197
3274
|
}
|
|
3198
3275
|
}
|
|
@@ -3209,6 +3286,7 @@ var init_project_state = __esm({
|
|
|
3209
3286
|
this.deprecatedLessonCount = this.lessons.filter(
|
|
3210
3287
|
(l) => l.status === "deprecated" || l.status === "superseded"
|
|
3211
3288
|
).length;
|
|
3289
|
+
this.lessonTags = [...new Set(this.lessons.flatMap((l) => l.tags ?? []))].sort();
|
|
3212
3290
|
}
|
|
3213
3291
|
// --- Query Methods ---
|
|
3214
3292
|
isUmbrella(ticket) {
|
|
@@ -4525,7 +4603,7 @@ function formatStatus(state, format, activeSessions = []) {
|
|
|
4525
4603
|
completeTickets: state.completeLeafTicketCount,
|
|
4526
4604
|
openTickets: state.leafTicketCount - state.completeLeafTicketCount,
|
|
4527
4605
|
blockedTickets: state.blockedCount,
|
|
4528
|
-
openIssues: state.
|
|
4606
|
+
openIssues: state.activeIssueCount,
|
|
4529
4607
|
activeNotes: state.activeNoteCount,
|
|
4530
4608
|
archivedNotes: state.archivedNoteCount,
|
|
4531
4609
|
activeLessons: state.activeLessonCount,
|
|
@@ -4547,7 +4625,7 @@ function formatStatus(state, format, activeSessions = []) {
|
|
|
4547
4625
|
`# ${escapeMarkdownInline(state.config.project)}`,
|
|
4548
4626
|
"",
|
|
4549
4627
|
`Tickets: ${state.completeLeafTicketCount}/${state.leafTicketCount} complete, ${state.blockedCount} blocked`,
|
|
4550
|
-
`Issues: ${state.
|
|
4628
|
+
`Issues: ${state.activeIssueCount} open`,
|
|
4551
4629
|
`Notes: ${state.activeNoteCount} active, ${state.archivedNoteCount} archived`,
|
|
4552
4630
|
`Lessons: ${state.activeLessonCount} active, ${state.deprecatedLessonCount} deprecated`,
|
|
4553
4631
|
`Handovers: ${state.handoverFilenames.length}`,
|
|
@@ -4996,7 +5074,7 @@ function formatRecap(recap, state, format) {
|
|
|
4996
5074
|
lines.push("No snapshot found. Run `claudestory snapshot` to enable session diffs.");
|
|
4997
5075
|
lines.push("");
|
|
4998
5076
|
lines.push(`Tickets: ${state.completeLeafTicketCount}/${state.leafTicketCount} complete, ${state.blockedCount} blocked`);
|
|
4999
|
-
lines.push(`Issues: ${state.
|
|
5077
|
+
lines.push(`Issues: ${state.activeIssueCount} open`);
|
|
5000
5078
|
} else {
|
|
5001
5079
|
lines.push(`# ${escapeMarkdownInline(state.config.project)} \u2014 Recap`);
|
|
5002
5080
|
lines.push("");
|
|
@@ -5004,6 +5082,13 @@ function formatRecap(recap, state, format) {
|
|
|
5004
5082
|
if (recap.partial) {
|
|
5005
5083
|
lines.push("**Note:** Snapshot was taken from a project with integrity warnings. Diff may be incomplete.");
|
|
5006
5084
|
}
|
|
5085
|
+
if (recap.staleness) {
|
|
5086
|
+
if (recap.staleness.status === "diverged") {
|
|
5087
|
+
lines.push("**Warning:** Snapshot commit is not an ancestor of current HEAD (history diverged; possible rebase, force-push, or branch switch).");
|
|
5088
|
+
} else if (recap.staleness.status === "behind" && recap.staleness.commitsBehind) {
|
|
5089
|
+
lines.push(`**Warning:** Snapshot is ${recap.staleness.commitsBehind} commit(s) behind HEAD -- context may be stale.`);
|
|
5090
|
+
}
|
|
5091
|
+
}
|
|
5007
5092
|
const changes = recap.changes;
|
|
5008
5093
|
const hasChanges = hasAnyChanges(changes);
|
|
5009
5094
|
if (!hasChanges) {
|
|
@@ -5265,7 +5350,7 @@ function formatFullExport(state, format) {
|
|
|
5265
5350
|
lines.push(`# ${escapeMarkdownInline(state.config.project)} \u2014 Full Export`);
|
|
5266
5351
|
lines.push("");
|
|
5267
5352
|
lines.push(`Tickets: ${state.completeLeafTicketCount}/${state.leafTicketCount} complete`);
|
|
5268
|
-
lines.push(`Issues: ${state.
|
|
5353
|
+
lines.push(`Issues: ${state.activeIssueCount} open`);
|
|
5269
5354
|
lines.push(`Notes: ${state.activeNoteCount} active, ${state.archivedNoteCount} archived`);
|
|
5270
5355
|
lines.push(`Lessons: ${state.activeLessonCount} active, ${state.deprecatedLessonCount} deprecated`);
|
|
5271
5356
|
lines.push("");
|
|
@@ -6633,6 +6718,204 @@ var init_issue2 = __esm({
|
|
|
6633
6718
|
}
|
|
6634
6719
|
});
|
|
6635
6720
|
|
|
6721
|
+
// src/autonomous/git-inspector.ts
|
|
6722
|
+
import { execFile } from "child_process";
|
|
6723
|
+
async function git(cwd, args, parse) {
|
|
6724
|
+
return new Promise((resolve10) => {
|
|
6725
|
+
execFile("git", args, { cwd, timeout: GIT_TIMEOUT, maxBuffer: 10 * 1024 * 1024 }, (err, stdout, stderr) => {
|
|
6726
|
+
if (err) {
|
|
6727
|
+
const message = stderr?.trim() || err.message || "unknown git error";
|
|
6728
|
+
resolve10({ ok: false, reason: "git_error", message });
|
|
6729
|
+
return;
|
|
6730
|
+
}
|
|
6731
|
+
try {
|
|
6732
|
+
resolve10({ ok: true, data: parse(stdout) });
|
|
6733
|
+
} catch (parseErr) {
|
|
6734
|
+
resolve10({ ok: false, reason: "parse_error", message: parseErr.message });
|
|
6735
|
+
}
|
|
6736
|
+
});
|
|
6737
|
+
});
|
|
6738
|
+
}
|
|
6739
|
+
async function gitStatus(cwd) {
|
|
6740
|
+
return git(
|
|
6741
|
+
cwd,
|
|
6742
|
+
["status", "--porcelain"],
|
|
6743
|
+
(out) => out.split("\n").filter((l) => l.length > 0)
|
|
6744
|
+
);
|
|
6745
|
+
}
|
|
6746
|
+
async function gitHead(cwd) {
|
|
6747
|
+
const hashResult = await git(cwd, ["rev-parse", "HEAD"], (out) => out.trim());
|
|
6748
|
+
if (!hashResult.ok) return hashResult;
|
|
6749
|
+
const branchResult = await gitBranch(cwd);
|
|
6750
|
+
return {
|
|
6751
|
+
ok: true,
|
|
6752
|
+
data: {
|
|
6753
|
+
hash: hashResult.data,
|
|
6754
|
+
branch: branchResult.ok ? branchResult.data : null
|
|
6755
|
+
}
|
|
6756
|
+
};
|
|
6757
|
+
}
|
|
6758
|
+
async function gitBranch(cwd) {
|
|
6759
|
+
return git(cwd, ["symbolic-ref", "--short", "HEAD"], (out) => out.trim());
|
|
6760
|
+
}
|
|
6761
|
+
async function gitMergeBase(cwd, base) {
|
|
6762
|
+
return git(cwd, ["merge-base", "HEAD", base], (out) => out.trim());
|
|
6763
|
+
}
|
|
6764
|
+
async function gitDiffStat(cwd, base) {
|
|
6765
|
+
return git(cwd, ["diff", "--numstat", base], parseDiffNumstat);
|
|
6766
|
+
}
|
|
6767
|
+
async function gitDiffNames(cwd, base) {
|
|
6768
|
+
return git(
|
|
6769
|
+
cwd,
|
|
6770
|
+
["diff", "--name-only", base],
|
|
6771
|
+
(out) => out.split("\n").filter((l) => l.length > 0)
|
|
6772
|
+
);
|
|
6773
|
+
}
|
|
6774
|
+
async function gitBlobHash(cwd, file) {
|
|
6775
|
+
return git(cwd, ["hash-object", file], (out) => out.trim());
|
|
6776
|
+
}
|
|
6777
|
+
async function gitDiffCachedNames(cwd) {
|
|
6778
|
+
return git(
|
|
6779
|
+
cwd,
|
|
6780
|
+
["diff", "--cached", "--name-only"],
|
|
6781
|
+
(out) => out.split("\n").filter((l) => l.length > 0)
|
|
6782
|
+
);
|
|
6783
|
+
}
|
|
6784
|
+
async function gitStash(cwd, message) {
|
|
6785
|
+
const pushResult = await git(cwd, ["stash", "push", "-m", message], () => void 0);
|
|
6786
|
+
if (!pushResult.ok) return { ok: false, reason: pushResult.reason, message: pushResult.message };
|
|
6787
|
+
const hashResult = await git(cwd, ["rev-parse", "stash@{0}"], (out) => out.trim());
|
|
6788
|
+
if (!hashResult.ok) {
|
|
6789
|
+
const listResult = await git(
|
|
6790
|
+
cwd,
|
|
6791
|
+
["stash", "list", "--format=%gd %s"],
|
|
6792
|
+
(out) => out.split("\n").filter((l) => l.includes(message))
|
|
6793
|
+
);
|
|
6794
|
+
if (listResult.ok && listResult.data.length > 0) {
|
|
6795
|
+
const ref = listResult.data[0].split(" ")[0];
|
|
6796
|
+
const refHash = await git(cwd, ["rev-parse", ref], (out) => out.trim());
|
|
6797
|
+
if (refHash.ok) return { ok: true, data: refHash.data };
|
|
6798
|
+
}
|
|
6799
|
+
return { ok: false, reason: "stash_hash_failed", message: "Stash created but could not be identified. Run `git stash list` to find and pop it manually." };
|
|
6800
|
+
}
|
|
6801
|
+
return { ok: true, data: hashResult.data };
|
|
6802
|
+
}
|
|
6803
|
+
async function gitStashPop(cwd, commitHash) {
|
|
6804
|
+
if (!commitHash) {
|
|
6805
|
+
return git(cwd, ["stash", "pop"], () => void 0);
|
|
6806
|
+
}
|
|
6807
|
+
const listResult = await git(
|
|
6808
|
+
cwd,
|
|
6809
|
+
["stash", "list", "--format=%gd %H"],
|
|
6810
|
+
(out) => out.split("\n").filter((l) => l.length > 0).map((l) => {
|
|
6811
|
+
const [ref, hash] = l.split(" ", 2);
|
|
6812
|
+
return { ref, hash };
|
|
6813
|
+
})
|
|
6814
|
+
);
|
|
6815
|
+
if (!listResult.ok) {
|
|
6816
|
+
return { ok: false, reason: "stash_list_failed", message: `Cannot list stash entries to find ${commitHash}. Run \`git stash list\` and pop manually.` };
|
|
6817
|
+
}
|
|
6818
|
+
const match = listResult.data.find((e) => e.hash === commitHash);
|
|
6819
|
+
if (!match) {
|
|
6820
|
+
return { ok: false, reason: "stash_not_found", message: `No stash entry with commit hash ${commitHash}` };
|
|
6821
|
+
}
|
|
6822
|
+
return git(cwd, ["stash", "pop", match.ref], () => void 0);
|
|
6823
|
+
}
|
|
6824
|
+
async function gitDiffTreeNames(cwd, commitHash) {
|
|
6825
|
+
return git(
|
|
6826
|
+
cwd,
|
|
6827
|
+
["diff-tree", "--name-only", "--no-commit-id", "-r", commitHash],
|
|
6828
|
+
(out) => out.split("\n").filter((l) => l.trim().length > 0)
|
|
6829
|
+
);
|
|
6830
|
+
}
|
|
6831
|
+
async function gitIsAncestor(cwd, ancestor, descendant) {
|
|
6832
|
+
if (!SAFE_REF.test(ancestor) || !SAFE_REF.test(descendant)) {
|
|
6833
|
+
return { ok: false, reason: "git_error", message: "invalid ref format" };
|
|
6834
|
+
}
|
|
6835
|
+
return new Promise((resolve10) => {
|
|
6836
|
+
execFile(
|
|
6837
|
+
"git",
|
|
6838
|
+
["merge-base", "--is-ancestor", ancestor, descendant],
|
|
6839
|
+
{ cwd, timeout: GIT_TIMEOUT },
|
|
6840
|
+
(err) => {
|
|
6841
|
+
if (!err) {
|
|
6842
|
+
resolve10({ ok: true, data: true });
|
|
6843
|
+
return;
|
|
6844
|
+
}
|
|
6845
|
+
const code = err.code;
|
|
6846
|
+
if (code === 1) {
|
|
6847
|
+
resolve10({ ok: true, data: false });
|
|
6848
|
+
return;
|
|
6849
|
+
}
|
|
6850
|
+
resolve10({ ok: false, reason: "git_error", message: err.message });
|
|
6851
|
+
}
|
|
6852
|
+
);
|
|
6853
|
+
});
|
|
6854
|
+
}
|
|
6855
|
+
async function gitLogRange(cwd, from, to, limit = 20) {
|
|
6856
|
+
if (from && !SAFE_REF.test(from)) {
|
|
6857
|
+
return { ok: false, reason: "invalid_ref", message: `Invalid git ref: ${from}` };
|
|
6858
|
+
}
|
|
6859
|
+
if (to && !SAFE_REF.test(to)) {
|
|
6860
|
+
return { ok: false, reason: "invalid_ref", message: `Invalid git ref: ${to}` };
|
|
6861
|
+
}
|
|
6862
|
+
if (!from || !to) {
|
|
6863
|
+
return { ok: true, data: [] };
|
|
6864
|
+
}
|
|
6865
|
+
return git(
|
|
6866
|
+
cwd,
|
|
6867
|
+
["log", "--oneline", `-${limit}`, `${from}..${to}`],
|
|
6868
|
+
(out) => out.split("\n").filter((l) => l.trim().length > 0)
|
|
6869
|
+
);
|
|
6870
|
+
}
|
|
6871
|
+
async function gitHeadHash(cwd) {
|
|
6872
|
+
return new Promise((resolve10) => {
|
|
6873
|
+
execFile("git", ["rev-parse", "HEAD"], { cwd, timeout: 3e3 }, (err, stdout) => {
|
|
6874
|
+
if (err) {
|
|
6875
|
+
const message = err.stderr?.trim() || err.message || "unknown git error";
|
|
6876
|
+
resolve10({ ok: false, reason: "git_error", message });
|
|
6877
|
+
return;
|
|
6878
|
+
}
|
|
6879
|
+
resolve10({ ok: true, data: stdout.trim() });
|
|
6880
|
+
});
|
|
6881
|
+
});
|
|
6882
|
+
}
|
|
6883
|
+
async function gitCommitDistance(cwd, fromSha, toSha) {
|
|
6884
|
+
if (!SAFE_REF.test(fromSha) || !SAFE_REF.test(toSha)) {
|
|
6885
|
+
return { ok: false, reason: "git_error", message: "invalid ref format" };
|
|
6886
|
+
}
|
|
6887
|
+
return git(cwd, ["rev-list", "--count", `${fromSha}..${toSha}`], (out) => {
|
|
6888
|
+
const n = parseInt(out.trim(), 10);
|
|
6889
|
+
if (Number.isNaN(n)) throw new Error(`unexpected rev-list output: ${out}`);
|
|
6890
|
+
return n;
|
|
6891
|
+
});
|
|
6892
|
+
}
|
|
6893
|
+
function parseDiffNumstat(out) {
|
|
6894
|
+
const lines = out.split("\n").filter((l) => l.length > 0);
|
|
6895
|
+
let insertions = 0;
|
|
6896
|
+
let deletions = 0;
|
|
6897
|
+
let filesChanged = 0;
|
|
6898
|
+
for (const line of lines) {
|
|
6899
|
+
const parts = line.split(" ");
|
|
6900
|
+
if (parts.length < 3) continue;
|
|
6901
|
+
const added = parseInt(parts[0], 10);
|
|
6902
|
+
const removed = parseInt(parts[1], 10);
|
|
6903
|
+
if (!Number.isNaN(added)) insertions += added;
|
|
6904
|
+
if (!Number.isNaN(removed)) deletions += removed;
|
|
6905
|
+
filesChanged++;
|
|
6906
|
+
}
|
|
6907
|
+
return { filesChanged, insertions, deletions, totalLines: insertions + deletions };
|
|
6908
|
+
}
|
|
6909
|
+
var GIT_TIMEOUT, SAFE_REF;
|
|
6910
|
+
var init_git_inspector = __esm({
|
|
6911
|
+
"src/autonomous/git-inspector.ts"() {
|
|
6912
|
+
"use strict";
|
|
6913
|
+
init_esm_shims();
|
|
6914
|
+
GIT_TIMEOUT = 1e4;
|
|
6915
|
+
SAFE_REF = /^[0-9a-f]{4,40}$/i;
|
|
6916
|
+
}
|
|
6917
|
+
});
|
|
6918
|
+
|
|
6636
6919
|
// src/core/snapshot.ts
|
|
6637
6920
|
var snapshot_exports = {};
|
|
6638
6921
|
__export(snapshot_exports, {
|
|
@@ -6672,6 +6955,10 @@ async function saveSnapshot(root, loadResult) {
|
|
|
6672
6955
|
}))
|
|
6673
6956
|
} : {}
|
|
6674
6957
|
};
|
|
6958
|
+
const headResult = await gitHeadHash(absRoot);
|
|
6959
|
+
if (headResult.ok) {
|
|
6960
|
+
snapshot.gitHead = headResult.data;
|
|
6961
|
+
}
|
|
6675
6962
|
const json = JSON.stringify(snapshot, null, 2) + "\n";
|
|
6676
6963
|
const targetPath = join11(snapshotsDir, filename);
|
|
6677
6964
|
const wrapDir = join11(absRoot, ".story");
|
|
@@ -6867,7 +7154,7 @@ function diffStates(snapshotState, currentState) {
|
|
|
6867
7154
|
handovers: { added: handoversAdded, removed: handoversRemoved }
|
|
6868
7155
|
};
|
|
6869
7156
|
}
|
|
6870
|
-
function buildRecap(currentState, snapshotInfo) {
|
|
7157
|
+
async function buildRecap(currentState, snapshotInfo, root) {
|
|
6871
7158
|
const next = nextTicket(currentState);
|
|
6872
7159
|
const nextTicketAction = next.kind === "found" ? { id: next.ticket.id, title: next.ticket.title, phase: next.ticket.phase } : null;
|
|
6873
7160
|
const highSeverityIssues = currentState.issues.filter(
|
|
@@ -6897,6 +7184,30 @@ function buildRecap(currentState, snapshotInfo) {
|
|
|
6897
7184
|
});
|
|
6898
7185
|
const changes = diffStates(snapshotState, currentState);
|
|
6899
7186
|
const recentlyClearedBlockers = changes.blockers.cleared;
|
|
7187
|
+
let staleness;
|
|
7188
|
+
if (snapshot.gitHead) {
|
|
7189
|
+
const currentHeadResult = await gitHeadHash(root);
|
|
7190
|
+
if (currentHeadResult.ok) {
|
|
7191
|
+
const snapshotSha = snapshot.gitHead;
|
|
7192
|
+
const currentSha = currentHeadResult.data;
|
|
7193
|
+
if (snapshotSha !== currentSha) {
|
|
7194
|
+
const ancestorResult = await gitIsAncestor(root, snapshotSha, currentSha);
|
|
7195
|
+
if (ancestorResult.ok && ancestorResult.data) {
|
|
7196
|
+
const distResult = await gitCommitDistance(root, snapshotSha, currentSha);
|
|
7197
|
+
if (distResult.ok) {
|
|
7198
|
+
staleness = {
|
|
7199
|
+
status: "behind",
|
|
7200
|
+
snapshotSha,
|
|
7201
|
+
currentSha,
|
|
7202
|
+
commitsBehind: distResult.data
|
|
7203
|
+
};
|
|
7204
|
+
}
|
|
7205
|
+
} else if (ancestorResult.ok && !ancestorResult.data) {
|
|
7206
|
+
staleness = { status: "diverged", snapshotSha, currentSha };
|
|
7207
|
+
}
|
|
7208
|
+
}
|
|
7209
|
+
}
|
|
7210
|
+
}
|
|
6900
7211
|
return {
|
|
6901
7212
|
snapshot: { filename, createdAt: snapshot.createdAt },
|
|
6902
7213
|
changes,
|
|
@@ -6905,7 +7216,8 @@ function buildRecap(currentState, snapshotInfo) {
|
|
|
6905
7216
|
highSeverityIssues,
|
|
6906
7217
|
recentlyClearedBlockers
|
|
6907
7218
|
},
|
|
6908
|
-
partial: (snapshot.warnings ?? []).length > 0
|
|
7219
|
+
partial: (snapshot.warnings ?? []).length > 0,
|
|
7220
|
+
...staleness ? { staleness } : {}
|
|
6909
7221
|
};
|
|
6910
7222
|
}
|
|
6911
7223
|
function formatSnapshotFilename(date) {
|
|
@@ -6952,6 +7264,7 @@ var init_snapshot = __esm({
|
|
|
6952
7264
|
init_project_state();
|
|
6953
7265
|
init_queries();
|
|
6954
7266
|
init_project_loader();
|
|
7267
|
+
init_git_inspector();
|
|
6955
7268
|
LoadWarningSchema = z9.object({
|
|
6956
7269
|
type: z9.string(),
|
|
6957
7270
|
file: z9.string(),
|
|
@@ -6968,7 +7281,8 @@ var init_snapshot = __esm({
|
|
|
6968
7281
|
notes: z9.array(NoteSchema).optional().default([]),
|
|
6969
7282
|
lessons: z9.array(LessonSchema).optional().default([]),
|
|
6970
7283
|
handoverFilenames: z9.array(z9.string()).optional().default([]),
|
|
6971
|
-
warnings: z9.array(LoadWarningSchema).optional()
|
|
7284
|
+
warnings: z9.array(LoadWarningSchema).optional(),
|
|
7285
|
+
gitHead: z9.string().optional()
|
|
6972
7286
|
});
|
|
6973
7287
|
MAX_SNAPSHOTS = 20;
|
|
6974
7288
|
}
|
|
@@ -6977,7 +7291,7 @@ var init_snapshot = __esm({
|
|
|
6977
7291
|
// src/cli/commands/recap.ts
|
|
6978
7292
|
async function handleRecap(ctx) {
|
|
6979
7293
|
const snapshotInfo = await loadLatestSnapshot(ctx.root);
|
|
6980
|
-
const recap = buildRecap(ctx.state, snapshotInfo);
|
|
7294
|
+
const recap = await buildRecap(ctx.state, snapshotInfo, ctx.root);
|
|
6981
7295
|
return { output: formatRecap(recap, ctx.state, ctx.format) };
|
|
6982
7296
|
}
|
|
6983
7297
|
var init_recap = __esm({
|
|
@@ -8407,15 +8721,17 @@ var init_state_machine = __esm({
|
|
|
8407
8721
|
// advance → IMPLEMENT, retry stays, exhaustion → PLAN, no-op → COMPLETE (ISS-069)
|
|
8408
8722
|
TEST: ["CODE_REVIEW", "IMPLEMENT", "TEST"],
|
|
8409
8723
|
// pass → CODE_REVIEW, fail → IMPLEMENT, retry
|
|
8410
|
-
CODE_REVIEW: ["VERIFY", "FINALIZE", "IMPLEMENT", "PLAN", "CODE_REVIEW", "SESSION_END"],
|
|
8411
|
-
// approve → VERIFY/FINALIZE, reject → IMPLEMENT/PLAN, stay for next round; SESSION_END for tiered exit
|
|
8412
|
-
VERIFY: ["FINALIZE", "IMPLEMENT", "VERIFY"],
|
|
8724
|
+
CODE_REVIEW: ["VERIFY", "BUILD", "FINALIZE", "IMPLEMENT", "PLAN", "CODE_REVIEW", "SESSION_END", "ISSUE_FIX"],
|
|
8725
|
+
// approve → VERIFY/BUILD/FINALIZE, reject → IMPLEMENT/PLAN, stay for next round; SESSION_END for tiered exit; T-208: ISSUE_FIX for issue-fix reviews
|
|
8726
|
+
VERIFY: ["BUILD", "FINALIZE", "IMPLEMENT", "VERIFY"],
|
|
8727
|
+
// pass → BUILD/FINALIZE, fail → IMPLEMENT, retry
|
|
8728
|
+
BUILD: ["FINALIZE", "IMPLEMENT", "BUILD"],
|
|
8413
8729
|
// pass → FINALIZE, fail → IMPLEMENT, retry
|
|
8414
8730
|
FINALIZE: ["COMPLETE", "PICK_TICKET"],
|
|
8415
8731
|
// ISS-084: issues now route through COMPLETE too; PICK_TICKET kept for in-flight session compat
|
|
8416
8732
|
COMPLETE: ["PICK_TICKET", "HANDOVER", "ISSUE_SWEEP", "SESSION_END"],
|
|
8417
|
-
ISSUE_FIX: ["FINALIZE", "PICK_TICKET", "ISSUE_FIX"],
|
|
8418
|
-
// T-153: fix done → FINALIZE, cancel → PICK_TICKET, retry self
|
|
8733
|
+
ISSUE_FIX: ["FINALIZE", "PICK_TICKET", "ISSUE_FIX", "CODE_REVIEW"],
|
|
8734
|
+
// T-153: fix done → FINALIZE, cancel → PICK_TICKET, retry self; T-208: optional code review
|
|
8419
8735
|
LESSON_CAPTURE: ["ISSUE_SWEEP", "HANDOVER", "LESSON_CAPTURE"],
|
|
8420
8736
|
// advance → ISSUE_SWEEP, retry self, done → HANDOVER
|
|
8421
8737
|
ISSUE_SWEEP: ["ISSUE_SWEEP", "HANDOVER", "PICK_TICKET"],
|
|
@@ -8494,16 +8810,24 @@ function requiredRounds(risk) {
|
|
|
8494
8810
|
return 3;
|
|
8495
8811
|
}
|
|
8496
8812
|
}
|
|
8497
|
-
function
|
|
8498
|
-
if (
|
|
8499
|
-
|
|
8500
|
-
if (
|
|
8813
|
+
function isCodexUnavailable(codexUnavailableSince) {
|
|
8814
|
+
if (!codexUnavailableSince) return false;
|
|
8815
|
+
const since = new Date(codexUnavailableSince).getTime();
|
|
8816
|
+
if (Number.isNaN(since)) return false;
|
|
8817
|
+
return Date.now() - since < CODEX_CACHE_TTL_MS;
|
|
8818
|
+
}
|
|
8819
|
+
function nextReviewer(previousRounds, backends, codexUnavailable, codexUnavailableSince) {
|
|
8820
|
+
const unavailable = codexUnavailableSince ? isCodexUnavailable(codexUnavailableSince) : !!codexUnavailable;
|
|
8821
|
+
const effective = unavailable ? backends.filter((b) => b !== "codex") : backends;
|
|
8822
|
+
if (effective.length === 0) return "agent";
|
|
8823
|
+
if (effective.length === 1) return effective[0];
|
|
8824
|
+
if (previousRounds.length === 0) return effective[0];
|
|
8501
8825
|
const lastReviewer = previousRounds[previousRounds.length - 1].reviewer;
|
|
8502
|
-
const lastIndex =
|
|
8503
|
-
if (lastIndex === -1) return
|
|
8504
|
-
return
|
|
8826
|
+
const lastIndex = effective.indexOf(lastReviewer);
|
|
8827
|
+
if (lastIndex === -1) return effective[0];
|
|
8828
|
+
return effective[(lastIndex + 1) % effective.length];
|
|
8505
8829
|
}
|
|
8506
|
-
var SENSITIVE_PATTERNS;
|
|
8830
|
+
var SENSITIVE_PATTERNS, CODEX_CACHE_TTL_MS;
|
|
8507
8831
|
var init_review_depth = __esm({
|
|
8508
8832
|
"src/autonomous/review-depth.ts"() {
|
|
8509
8833
|
"use strict";
|
|
@@ -8516,173 +8840,22 @@ var init_review_depth = __esm({
|
|
|
8516
8840
|
/\bmiddleware\b/i,
|
|
8517
8841
|
/\.env/i
|
|
8518
8842
|
];
|
|
8843
|
+
CODEX_CACHE_TTL_MS = 10 * 60 * 1e3;
|
|
8519
8844
|
}
|
|
8520
8845
|
});
|
|
8521
8846
|
|
|
8522
|
-
// src/autonomous/
|
|
8523
|
-
import {
|
|
8524
|
-
|
|
8525
|
-
|
|
8526
|
-
|
|
8527
|
-
|
|
8528
|
-
|
|
8529
|
-
|
|
8530
|
-
|
|
8531
|
-
|
|
8532
|
-
|
|
8533
|
-
|
|
8534
|
-
} catch (parseErr) {
|
|
8535
|
-
resolve10({ ok: false, reason: "parse_error", message: parseErr.message });
|
|
8536
|
-
}
|
|
8537
|
-
});
|
|
8538
|
-
});
|
|
8539
|
-
}
|
|
8540
|
-
async function gitStatus(cwd) {
|
|
8541
|
-
return git(
|
|
8542
|
-
cwd,
|
|
8543
|
-
["status", "--porcelain"],
|
|
8544
|
-
(out) => out.split("\n").filter((l) => l.length > 0)
|
|
8545
|
-
);
|
|
8546
|
-
}
|
|
8547
|
-
async function gitHead(cwd) {
|
|
8548
|
-
const hashResult = await git(cwd, ["rev-parse", "HEAD"], (out) => out.trim());
|
|
8549
|
-
if (!hashResult.ok) return hashResult;
|
|
8550
|
-
const branchResult = await gitBranch(cwd);
|
|
8551
|
-
return {
|
|
8552
|
-
ok: true,
|
|
8553
|
-
data: {
|
|
8554
|
-
hash: hashResult.data,
|
|
8555
|
-
branch: branchResult.ok ? branchResult.data : null
|
|
8556
|
-
}
|
|
8557
|
-
};
|
|
8558
|
-
}
|
|
8559
|
-
async function gitBranch(cwd) {
|
|
8560
|
-
return git(cwd, ["symbolic-ref", "--short", "HEAD"], (out) => out.trim());
|
|
8561
|
-
}
|
|
8562
|
-
async function gitMergeBase(cwd, base) {
|
|
8563
|
-
return git(cwd, ["merge-base", "HEAD", base], (out) => out.trim());
|
|
8564
|
-
}
|
|
8565
|
-
async function gitDiffStat(cwd, base) {
|
|
8566
|
-
return git(cwd, ["diff", "--numstat", base], parseDiffNumstat);
|
|
8567
|
-
}
|
|
8568
|
-
async function gitDiffNames(cwd, base) {
|
|
8569
|
-
return git(
|
|
8570
|
-
cwd,
|
|
8571
|
-
["diff", "--name-only", base],
|
|
8572
|
-
(out) => out.split("\n").filter((l) => l.length > 0)
|
|
8573
|
-
);
|
|
8574
|
-
}
|
|
8575
|
-
async function gitBlobHash(cwd, file) {
|
|
8576
|
-
return git(cwd, ["hash-object", file], (out) => out.trim());
|
|
8577
|
-
}
|
|
8578
|
-
async function gitDiffCachedNames(cwd) {
|
|
8579
|
-
return git(
|
|
8580
|
-
cwd,
|
|
8581
|
-
["diff", "--cached", "--name-only"],
|
|
8582
|
-
(out) => out.split("\n").filter((l) => l.length > 0)
|
|
8583
|
-
);
|
|
8584
|
-
}
|
|
8585
|
-
async function gitStash(cwd, message) {
|
|
8586
|
-
const pushResult = await git(cwd, ["stash", "push", "-m", message], () => void 0);
|
|
8587
|
-
if (!pushResult.ok) return { ok: false, reason: pushResult.reason, message: pushResult.message };
|
|
8588
|
-
const hashResult = await git(cwd, ["rev-parse", "stash@{0}"], (out) => out.trim());
|
|
8589
|
-
if (!hashResult.ok) {
|
|
8590
|
-
const listResult = await git(
|
|
8591
|
-
cwd,
|
|
8592
|
-
["stash", "list", "--format=%gd %s"],
|
|
8593
|
-
(out) => out.split("\n").filter((l) => l.includes(message))
|
|
8594
|
-
);
|
|
8595
|
-
if (listResult.ok && listResult.data.length > 0) {
|
|
8596
|
-
const ref = listResult.data[0].split(" ")[0];
|
|
8597
|
-
const refHash = await git(cwd, ["rev-parse", ref], (out) => out.trim());
|
|
8598
|
-
if (refHash.ok) return { ok: true, data: refHash.data };
|
|
8599
|
-
}
|
|
8600
|
-
return { ok: false, reason: "stash_hash_failed", message: "Stash created but could not be identified. Run `git stash list` to find and pop it manually." };
|
|
8601
|
-
}
|
|
8602
|
-
return { ok: true, data: hashResult.data };
|
|
8603
|
-
}
|
|
8604
|
-
async function gitStashPop(cwd, commitHash) {
|
|
8605
|
-
if (!commitHash) {
|
|
8606
|
-
return git(cwd, ["stash", "pop"], () => void 0);
|
|
8607
|
-
}
|
|
8608
|
-
const listResult = await git(
|
|
8609
|
-
cwd,
|
|
8610
|
-
["stash", "list", "--format=%gd %H"],
|
|
8611
|
-
(out) => out.split("\n").filter((l) => l.length > 0).map((l) => {
|
|
8612
|
-
const [ref, hash] = l.split(" ", 2);
|
|
8613
|
-
return { ref, hash };
|
|
8614
|
-
})
|
|
8615
|
-
);
|
|
8616
|
-
if (!listResult.ok) {
|
|
8617
|
-
return { ok: false, reason: "stash_list_failed", message: `Cannot list stash entries to find ${commitHash}. Run \`git stash list\` and pop manually.` };
|
|
8618
|
-
}
|
|
8619
|
-
const match = listResult.data.find((e) => e.hash === commitHash);
|
|
8620
|
-
if (!match) {
|
|
8621
|
-
return { ok: false, reason: "stash_not_found", message: `No stash entry with commit hash ${commitHash}` };
|
|
8622
|
-
}
|
|
8623
|
-
return git(cwd, ["stash", "pop", match.ref], () => void 0);
|
|
8624
|
-
}
|
|
8625
|
-
async function gitDiffTreeNames(cwd, commitHash) {
|
|
8626
|
-
return git(
|
|
8627
|
-
cwd,
|
|
8628
|
-
["diff-tree", "--name-only", "--no-commit-id", "-r", commitHash],
|
|
8629
|
-
(out) => out.split("\n").filter((l) => l.trim().length > 0)
|
|
8630
|
-
);
|
|
8631
|
-
}
|
|
8632
|
-
async function gitLogRange(cwd, from, to, limit = 20) {
|
|
8633
|
-
if (from && !SAFE_REF.test(from)) {
|
|
8634
|
-
return { ok: false, reason: "invalid_ref", message: `Invalid git ref: ${from}` };
|
|
8635
|
-
}
|
|
8636
|
-
if (to && !SAFE_REF.test(to)) {
|
|
8637
|
-
return { ok: false, reason: "invalid_ref", message: `Invalid git ref: ${to}` };
|
|
8638
|
-
}
|
|
8639
|
-
if (!from || !to) {
|
|
8640
|
-
return { ok: true, data: [] };
|
|
8641
|
-
}
|
|
8642
|
-
return git(
|
|
8643
|
-
cwd,
|
|
8644
|
-
["log", "--oneline", `-${limit}`, `${from}..${to}`],
|
|
8645
|
-
(out) => out.split("\n").filter((l) => l.trim().length > 0)
|
|
8646
|
-
);
|
|
8647
|
-
}
|
|
8648
|
-
function parseDiffNumstat(out) {
|
|
8649
|
-
const lines = out.split("\n").filter((l) => l.length > 0);
|
|
8650
|
-
let insertions = 0;
|
|
8651
|
-
let deletions = 0;
|
|
8652
|
-
let filesChanged = 0;
|
|
8653
|
-
for (const line of lines) {
|
|
8654
|
-
const parts = line.split(" ");
|
|
8655
|
-
if (parts.length < 3) continue;
|
|
8656
|
-
const added = parseInt(parts[0], 10);
|
|
8657
|
-
const removed = parseInt(parts[1], 10);
|
|
8658
|
-
if (!Number.isNaN(added)) insertions += added;
|
|
8659
|
-
if (!Number.isNaN(removed)) deletions += removed;
|
|
8660
|
-
filesChanged++;
|
|
8661
|
-
}
|
|
8662
|
-
return { filesChanged, insertions, deletions, totalLines: insertions + deletions };
|
|
8663
|
-
}
|
|
8664
|
-
var GIT_TIMEOUT, SAFE_REF;
|
|
8665
|
-
var init_git_inspector = __esm({
|
|
8666
|
-
"src/autonomous/git-inspector.ts"() {
|
|
8667
|
-
"use strict";
|
|
8668
|
-
init_esm_shims();
|
|
8669
|
-
GIT_TIMEOUT = 1e4;
|
|
8670
|
-
SAFE_REF = /^[0-9a-f]{4,40}$/i;
|
|
8671
|
-
}
|
|
8672
|
-
});
|
|
8673
|
-
|
|
8674
|
-
// src/autonomous/recipes/loader.ts
|
|
8675
|
-
import { readFileSync as readFileSync7 } from "fs";
|
|
8676
|
-
import { join as join14, dirname as dirname3 } from "path";
|
|
8677
|
-
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
8678
|
-
function loadRecipe(recipeName) {
|
|
8679
|
-
if (!/^[A-Za-z0-9_-]+$/.test(recipeName)) {
|
|
8680
|
-
throw new Error(`Invalid recipe name: ${recipeName}`);
|
|
8681
|
-
}
|
|
8682
|
-
const recipesDir = join14(dirname3(fileURLToPath2(import.meta.url)), "..", "recipes");
|
|
8683
|
-
const path2 = join14(recipesDir, `${recipeName}.json`);
|
|
8684
|
-
const raw = readFileSync7(path2, "utf-8");
|
|
8685
|
-
return JSON.parse(raw);
|
|
8847
|
+
// src/autonomous/recipes/loader.ts
|
|
8848
|
+
import { readFileSync as readFileSync7 } from "fs";
|
|
8849
|
+
import { join as join14, dirname as dirname3 } from "path";
|
|
8850
|
+
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
8851
|
+
function loadRecipe(recipeName) {
|
|
8852
|
+
if (!/^[A-Za-z0-9_-]+$/.test(recipeName)) {
|
|
8853
|
+
throw new Error(`Invalid recipe name: ${recipeName}`);
|
|
8854
|
+
}
|
|
8855
|
+
const recipesDir = join14(dirname3(fileURLToPath2(import.meta.url)), "..", "recipes");
|
|
8856
|
+
const path2 = join14(recipesDir, `${recipeName}.json`);
|
|
8857
|
+
const raw = readFileSync7(path2, "utf-8");
|
|
8858
|
+
return JSON.parse(raw);
|
|
8686
8859
|
}
|
|
8687
8860
|
function resolveRecipe(recipeName, projectOverrides) {
|
|
8688
8861
|
let raw;
|
|
@@ -8736,6 +8909,13 @@ function resolveRecipe(recipeName, projectOverrides) {
|
|
|
8736
8909
|
pipeline.splice(implementIdx + 1, 0, "TEST");
|
|
8737
8910
|
}
|
|
8738
8911
|
}
|
|
8912
|
+
if (stages2.ISSUE_FIX?.enableCodeReview) {
|
|
8913
|
+
if (pipeline.includes("VERIFY") || pipeline.includes("BUILD")) {
|
|
8914
|
+
throw new Error(
|
|
8915
|
+
"ISSUE_FIX.enableCodeReview is incompatible with VERIFY/BUILD in the pipeline (issue fixes use goto transitions, not pipeline walker)"
|
|
8916
|
+
);
|
|
8917
|
+
}
|
|
8918
|
+
}
|
|
8739
8919
|
const postComplete = raw.postComplete ? [...raw.postComplete] : [];
|
|
8740
8920
|
const recipeDefaults = raw.defaults ?? {};
|
|
8741
8921
|
const defaults = {
|
|
@@ -9072,7 +9252,15 @@ var init_pick_ticket = __esm({
|
|
|
9072
9252
|
PickTicketStage = class {
|
|
9073
9253
|
id = "PICK_TICKET";
|
|
9074
9254
|
async enter(ctx) {
|
|
9075
|
-
|
|
9255
|
+
let projectState;
|
|
9256
|
+
try {
|
|
9257
|
+
({ state: projectState } = await ctx.loadProject());
|
|
9258
|
+
} catch (err) {
|
|
9259
|
+
return {
|
|
9260
|
+
action: "retry",
|
|
9261
|
+
instruction: `Failed to load project state: ${err instanceof Error ? err.message : String(err)}. Check .story/ files for corruption, then call autonomous_guide with action "report" again.`
|
|
9262
|
+
};
|
|
9263
|
+
}
|
|
9076
9264
|
if (isTargetedMode(ctx.state)) {
|
|
9077
9265
|
const remaining = getRemainingTargets(ctx.state);
|
|
9078
9266
|
if (remaining.length === 0) {
|
|
@@ -9172,7 +9360,12 @@ var init_pick_ticket = __esm({
|
|
|
9172
9360
|
}
|
|
9173
9361
|
const targetReject = this.enforceTargetList(ctx, ticketId);
|
|
9174
9362
|
if (targetReject) return targetReject;
|
|
9175
|
-
|
|
9363
|
+
let projectState;
|
|
9364
|
+
try {
|
|
9365
|
+
({ state: projectState } = await ctx.loadProject());
|
|
9366
|
+
} catch (err) {
|
|
9367
|
+
return { action: "retry", instruction: `Failed to load project state: ${err instanceof Error ? err.message : String(err)}. Check .story/ files for corruption.` };
|
|
9368
|
+
}
|
|
9176
9369
|
const ticket = projectState.ticketByID(ticketId);
|
|
9177
9370
|
if (!ticket) {
|
|
9178
9371
|
return { action: "retry", instruction: `Ticket ${ticketId} not found. Pick a valid ticket.` };
|
|
@@ -9194,7 +9387,8 @@ var init_pick_ticket = __esm({
|
|
|
9194
9387
|
ctx.updateDraft({
|
|
9195
9388
|
ticket: { id: ticket.id, title: ticket.title, claimed: true },
|
|
9196
9389
|
reviews: { plan: [], code: [] },
|
|
9197
|
-
finalizeCheckpoint: null
|
|
9390
|
+
finalizeCheckpoint: null,
|
|
9391
|
+
ticketStartedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
9198
9392
|
});
|
|
9199
9393
|
return {
|
|
9200
9394
|
action: "advance",
|
|
@@ -9225,7 +9419,12 @@ ${ticket.description}` : "",
|
|
|
9225
9419
|
async handleIssuePick(ctx, issueId) {
|
|
9226
9420
|
const targetReject = this.enforceTargetList(ctx, issueId);
|
|
9227
9421
|
if (targetReject) return targetReject;
|
|
9228
|
-
|
|
9422
|
+
let projectState;
|
|
9423
|
+
try {
|
|
9424
|
+
({ state: projectState } = await ctx.loadProject());
|
|
9425
|
+
} catch (err) {
|
|
9426
|
+
return { action: "retry", instruction: `Failed to load project state: ${err instanceof Error ? err.message : String(err)}. Check .story/ files for corruption.` };
|
|
9427
|
+
}
|
|
9229
9428
|
const issue = projectState.issues.find((i) => i.id === issueId);
|
|
9230
9429
|
if (!issue) {
|
|
9231
9430
|
return { action: "retry", instruction: `Issue ${issueId} not found. Pick a valid issue or ticket.` };
|
|
@@ -9234,11 +9433,16 @@ ${ticket.description}` : "",
|
|
|
9234
9433
|
if (issue.status !== "open" && !(targeted && issue.status === "inprogress")) {
|
|
9235
9434
|
return { action: "retry", instruction: `Issue ${issueId} is ${issue.status}. Pick an open issue.` };
|
|
9236
9435
|
}
|
|
9436
|
+
const transitionId = `issue-pick-${issueId}-${Date.now()}`;
|
|
9437
|
+
ctx.writeState({
|
|
9438
|
+
pendingProjectMutation: { type: "issue_update", target: issueId, field: "status", value: "inprogress", expectedCurrent: issue.status, transitionId }
|
|
9439
|
+
});
|
|
9237
9440
|
try {
|
|
9238
9441
|
const { handleIssueUpdate: handleIssueUpdate2 } = await Promise.resolve().then(() => (init_issue2(), issue_exports));
|
|
9239
|
-
await handleIssueUpdate2(
|
|
9442
|
+
await handleIssueUpdate2(issueId, { status: "inprogress" }, "json", ctx.root);
|
|
9240
9443
|
} catch {
|
|
9241
9444
|
}
|
|
9445
|
+
ctx.writeState({ pendingProjectMutation: null });
|
|
9242
9446
|
ctx.updateDraft({
|
|
9243
9447
|
currentIssue: { id: issue.id, title: issue.title, severity: issue.severity },
|
|
9244
9448
|
ticket: void 0,
|
|
@@ -9394,7 +9598,7 @@ var init_plan_review = __esm({
|
|
|
9394
9598
|
const backends = ctx.state.config.reviewBackends;
|
|
9395
9599
|
const existingReviews = ctx.state.reviews.plan;
|
|
9396
9600
|
const roundNum = existingReviews.length + 1;
|
|
9397
|
-
const reviewer = nextReviewer(existingReviews, backends);
|
|
9601
|
+
const reviewer = nextReviewer(existingReviews, backends, ctx.state.codexUnavailable, ctx.state.codexUnavailableSince);
|
|
9398
9602
|
const risk = ctx.state.ticket?.risk ?? "low";
|
|
9399
9603
|
const minRounds = requiredRounds(risk);
|
|
9400
9604
|
if (reviewer === "lenses") {
|
|
@@ -9405,10 +9609,11 @@ var init_plan_review = __esm({
|
|
|
9405
9609
|
"This round uses the **multi-lens review orchestrator** for plan review. It fans out to specialized review agents (Clean Code, Security, Error Handling, and more) in parallel to evaluate the plan from multiple perspectives.",
|
|
9406
9610
|
"",
|
|
9407
9611
|
"1. Read the plan file",
|
|
9408
|
-
"2. Call `
|
|
9409
|
-
"3. Spawn all lens subagents in parallel",
|
|
9410
|
-
"4. Collect results and
|
|
9411
|
-
"5.
|
|
9612
|
+
"2. Call `claudestory_review_lenses_prepare` with the plan text as diff, stage: PLAN_REVIEW, and ticketDescription",
|
|
9613
|
+
"3. Spawn all lens subagents in parallel (each prompt is returned by the prepare tool)",
|
|
9614
|
+
"4. Collect results and call `claudestory_review_lenses_synthesize` with the lens results",
|
|
9615
|
+
"5. Run the merger agent with the returned mergerPrompt, then call `claudestory_review_lenses_judge`",
|
|
9616
|
+
"6. Run the judge agent and report the final SynthesisResult verdict and findings",
|
|
9412
9617
|
"",
|
|
9413
9618
|
"When done, call `claudestory_autonomous_guide` with:",
|
|
9414
9619
|
"```json",
|
|
@@ -9435,7 +9640,11 @@ var init_plan_review = __esm({
|
|
|
9435
9640
|
`{ "sessionId": "${ctx.state.sessionId}", "action": "report", "report": { "completedAction": "plan_review_round", "verdict": "<approve|revise|request_changes|reject>", "findings": [...] } }`,
|
|
9436
9641
|
"```"
|
|
9437
9642
|
].join("\n"),
|
|
9438
|
-
reminders: [
|
|
9643
|
+
reminders: [
|
|
9644
|
+
"Report the exact verdict and findings from the reviewer.",
|
|
9645
|
+
"IMPORTANT: After the review, file ANY pre-existing issues discovered using claudestory_issue_create with severity and impact. Do NOT skip this step.",
|
|
9646
|
+
...reviewer === "codex" ? ["If codex is unavailable (usage limit, error, etc.), fall back to agent review and include 'codex unavailable' in your report notes."] : []
|
|
9647
|
+
],
|
|
9439
9648
|
transitionedFrom: ctx.state.previousState ?? void 0
|
|
9440
9649
|
};
|
|
9441
9650
|
}
|
|
@@ -9448,7 +9657,8 @@ var init_plan_review = __esm({
|
|
|
9448
9657
|
const roundNum = planReviews.length + 1;
|
|
9449
9658
|
const findings = report.findings ?? [];
|
|
9450
9659
|
const backends = ctx.state.config.reviewBackends;
|
|
9451
|
-
const
|
|
9660
|
+
const computedReviewer = nextReviewer(planReviews, backends, ctx.state.codexUnavailable, ctx.state.codexUnavailableSince);
|
|
9661
|
+
const reviewerBackend = report.reviewer ?? (computedReviewer === "codex" && report.notes && /codex\b.*\b(unavail|limit|failed|down|error|usage)/i.test(report.notes) ? "agent" : null) ?? computedReviewer;
|
|
9452
9662
|
planReviews.push({
|
|
9453
9663
|
round: roundNum,
|
|
9454
9664
|
reviewer: reviewerBackend,
|
|
@@ -9460,6 +9670,9 @@ var init_plan_review = __esm({
|
|
|
9460
9670
|
codexSessionId: report.reviewerSessionId,
|
|
9461
9671
|
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
9462
9672
|
});
|
|
9673
|
+
if (report.notes && /codex\b.*\b(unavail|limit|failed|down|error|usage)/i.test(report.notes)) {
|
|
9674
|
+
ctx.writeState({ codexUnavailable: true, codexUnavailableSince: (/* @__PURE__ */ new Date()).toISOString() });
|
|
9675
|
+
}
|
|
9463
9676
|
const risk = ctx.state.ticket?.risk ?? "low";
|
|
9464
9677
|
const minRounds = requiredRounds(risk);
|
|
9465
9678
|
const hasCriticalOrMajor = findings.some(
|
|
@@ -9547,7 +9760,7 @@ var init_plan_review = __esm({
|
|
|
9547
9760
|
}
|
|
9548
9761
|
return { action: "advance" };
|
|
9549
9762
|
}
|
|
9550
|
-
const nextReviewerName = nextReviewer(planReviews, backends);
|
|
9763
|
+
const nextReviewerName = nextReviewer(planReviews, backends, ctx.state.codexUnavailable, ctx.state.codexUnavailableSince);
|
|
9551
9764
|
return {
|
|
9552
9765
|
action: "retry",
|
|
9553
9766
|
instruction: [
|
|
@@ -9697,7 +9910,7 @@ var init_write_tests = __esm({
|
|
|
9697
9910
|
const exitMatch = notes.match(EXIT_CODE_REGEX);
|
|
9698
9911
|
const exitCode = exitMatch ? parseInt(exitMatch[1], 10) : -1;
|
|
9699
9912
|
const failMatch = notes.match(FAIL_COUNT_REGEX);
|
|
9700
|
-
const currentFailCount = failMatch ? parseInt(failMatch[1], 10) : -1;
|
|
9913
|
+
const currentFailCount = failMatch ? parseInt(failMatch[1], 10) : exitCode === 0 ? 0 : -1;
|
|
9701
9914
|
const baseline = ctx.state.testBaseline;
|
|
9702
9915
|
const baselineFailCount = baseline?.failCount ?? -1;
|
|
9703
9916
|
const nextRetry = retryCount + 1;
|
|
@@ -9874,26 +10087,29 @@ var init_code_review = __esm({
|
|
|
9874
10087
|
const backends = ctx.state.config.reviewBackends;
|
|
9875
10088
|
const codeReviews = ctx.state.reviews.code;
|
|
9876
10089
|
const roundNum = codeReviews.length + 1;
|
|
9877
|
-
const reviewer = nextReviewer(codeReviews, backends);
|
|
10090
|
+
const reviewer = nextReviewer(codeReviews, backends, ctx.state.codexUnavailable, ctx.state.codexUnavailableSince);
|
|
9878
10091
|
const risk = ctx.state.ticket?.realizedRisk ?? ctx.state.ticket?.risk ?? "low";
|
|
9879
10092
|
const rounds = requiredRounds(risk);
|
|
9880
10093
|
const mergeBase = ctx.state.git.mergeBase;
|
|
10094
|
+
const isIssueFix = !!ctx.state.currentIssue;
|
|
10095
|
+
const issueHeader = isIssueFix ? `Issue Fix Code Review (${ctx.state.currentIssue.id})` : "Code Review";
|
|
9881
10096
|
const diffCommand = mergeBase ? `\`git diff ${mergeBase}\`` : `\`git diff HEAD\` AND \`git ls-files --others --exclude-standard\``;
|
|
9882
10097
|
const diffReminder = mergeBase ? `Run: git diff ${mergeBase} \u2014 pass FULL output to reviewer.` : "Run: git diff HEAD + git ls-files --others --exclude-standard \u2014 pass FULL output to reviewer.";
|
|
9883
10098
|
if (reviewer === "lenses") {
|
|
9884
10099
|
return {
|
|
9885
10100
|
instruction: [
|
|
9886
|
-
`# Multi-Lens
|
|
10101
|
+
`# Multi-Lens ${issueHeader} \u2014 Round ${roundNum} of ${rounds} minimum`,
|
|
9887
10102
|
"",
|
|
9888
10103
|
`Capture the diff with: ${diffCommand}`,
|
|
9889
10104
|
"",
|
|
9890
10105
|
"This round uses the **multi-lens review orchestrator**. It fans out to specialized review agents (Clean Code, Security, Error Handling, and more) in parallel, then synthesizes findings into a single verdict.",
|
|
9891
10106
|
"",
|
|
9892
|
-
"1. Capture the full diff",
|
|
9893
|
-
"2. Call `
|
|
9894
|
-
"3. Spawn all lens subagents in parallel (each prompt is
|
|
9895
|
-
"4. Collect results and
|
|
9896
|
-
"5.
|
|
10107
|
+
"1. Capture the full diff and changed file list (`git diff --name-only`)",
|
|
10108
|
+
"2. Call `claudestory_review_lenses_prepare` with the diff, changedFiles, stage: CODE_REVIEW, and ticketDescription",
|
|
10109
|
+
"3. Spawn all lens subagents in parallel (each prompt is returned by the prepare tool)",
|
|
10110
|
+
"4. Collect results and call `claudestory_review_lenses_synthesize` with the lens results, plus the diff and changedFiles from step 1 and the sessionId (enables automatic origin classification and issue filing for pre-existing findings)",
|
|
10111
|
+
"5. Run the merger agent with the returned mergerPrompt, then call `claudestory_review_lenses_judge`",
|
|
10112
|
+
"6. Run the judge agent and report the final SynthesisResult verdict and findings",
|
|
9897
10113
|
"",
|
|
9898
10114
|
"When done, report verdict and findings."
|
|
9899
10115
|
].join("\n"),
|
|
@@ -9901,14 +10117,14 @@ var init_code_review = __esm({
|
|
|
9901
10117
|
diffReminder,
|
|
9902
10118
|
"Do NOT compress or summarize the diff.",
|
|
9903
10119
|
"Lens subagents run in parallel with read-only tools (Read, Grep, Glob).",
|
|
9904
|
-
"
|
|
10120
|
+
"Pre-existing issues in surrounding code are automatically classified and filed by the synthesize tool when you pass diff, changedFiles, and sessionId. Check filedIssues in the synthesize response."
|
|
9905
10121
|
],
|
|
9906
10122
|
transitionedFrom: ctx.state.previousState ?? void 0
|
|
9907
10123
|
};
|
|
9908
10124
|
}
|
|
9909
10125
|
return {
|
|
9910
10126
|
instruction: [
|
|
9911
|
-
`#
|
|
10127
|
+
`# ${issueHeader} \u2014 Round ${roundNum} of ${rounds} minimum`,
|
|
9912
10128
|
"",
|
|
9913
10129
|
`Capture the diff with: ${diffCommand}`,
|
|
9914
10130
|
"",
|
|
@@ -9920,7 +10136,8 @@ var init_code_review = __esm({
|
|
|
9920
10136
|
reminders: [
|
|
9921
10137
|
diffReminder,
|
|
9922
10138
|
"Do NOT compress or summarize the diff.",
|
|
9923
|
-
"If the reviewer flags pre-existing issues unrelated to your changes, file them as issues using claudestory_issue_create with severity and impact. Do not fix them in this ticket."
|
|
10139
|
+
"If the reviewer flags pre-existing issues unrelated to your changes, file them as issues using claudestory_issue_create with severity and impact. Do not fix them in this ticket.",
|
|
10140
|
+
...reviewer === "codex" ? ["If codex is unavailable (usage limit, error, etc.), fall back to agent review and include 'codex unavailable' in your report notes."] : []
|
|
9924
10141
|
],
|
|
9925
10142
|
transitionedFrom: ctx.state.previousState ?? void 0
|
|
9926
10143
|
};
|
|
@@ -9934,7 +10151,8 @@ var init_code_review = __esm({
|
|
|
9934
10151
|
const roundNum = codeReviews.length + 1;
|
|
9935
10152
|
const findings = report.findings ?? [];
|
|
9936
10153
|
const backends = ctx.state.config.reviewBackends;
|
|
9937
|
-
const
|
|
10154
|
+
const computedReviewer = nextReviewer(codeReviews, backends, ctx.state.codexUnavailable, ctx.state.codexUnavailableSince);
|
|
10155
|
+
const reviewerBackend = report.reviewer ?? (computedReviewer === "codex" && report.notes && /codex\b.*\b(unavail|limit|failed|down|error|usage)/i.test(report.notes) ? "agent" : null) ?? computedReviewer;
|
|
9938
10156
|
codeReviews.push({
|
|
9939
10157
|
round: roundNum,
|
|
9940
10158
|
reviewer: reviewerBackend,
|
|
@@ -9946,6 +10164,9 @@ var init_code_review = __esm({
|
|
|
9946
10164
|
codexSessionId: report.reviewerSessionId,
|
|
9947
10165
|
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
9948
10166
|
});
|
|
10167
|
+
if (report.notes && /codex\b.*\b(unavail|limit|failed|down|error|usage)/i.test(report.notes)) {
|
|
10168
|
+
ctx.writeState({ codexUnavailable: true, codexUnavailableSince: (/* @__PURE__ */ new Date()).toISOString() });
|
|
10169
|
+
}
|
|
9949
10170
|
const risk = ctx.state.ticket?.realizedRisk ?? ctx.state.ticket?.risk ?? "low";
|
|
9950
10171
|
const minRounds = requiredRounds(risk);
|
|
9951
10172
|
const hasCriticalOrMajor = findings.some(
|
|
@@ -9970,6 +10191,7 @@ var init_code_review = __esm({
|
|
|
9970
10191
|
} else {
|
|
9971
10192
|
nextAction = "CODE_REVIEW";
|
|
9972
10193
|
}
|
|
10194
|
+
const isIssueFix = !!ctx.state.currentIssue;
|
|
9973
10195
|
if (nextAction === "PLAN") {
|
|
9974
10196
|
clearCache(ctx.dir);
|
|
9975
10197
|
ctx.writeState({
|
|
@@ -9981,9 +10203,12 @@ var init_code_review = __esm({
|
|
|
9981
10203
|
round: roundNum,
|
|
9982
10204
|
verdict,
|
|
9983
10205
|
findingCount: findings.length,
|
|
9984
|
-
redirectedTo: "PLAN"
|
|
10206
|
+
redirectedTo: isIssueFix ? "ISSUE_FIX" : "PLAN"
|
|
9985
10207
|
});
|
|
9986
10208
|
await ctx.fileDeferredFindings(findings, "code");
|
|
10209
|
+
if (isIssueFix) {
|
|
10210
|
+
return { action: "goto", target: "ISSUE_FIX" };
|
|
10211
|
+
}
|
|
9987
10212
|
return { action: "back", target: "PLAN", reason: "plan_redirect" };
|
|
9988
10213
|
}
|
|
9989
10214
|
const stateUpdate = {
|
|
@@ -10006,6 +10231,9 @@ var init_code_review = __esm({
|
|
|
10006
10231
|
});
|
|
10007
10232
|
await ctx.fileDeferredFindings(findings, "code");
|
|
10008
10233
|
if (nextAction === "IMPLEMENT") {
|
|
10234
|
+
if (isIssueFix) {
|
|
10235
|
+
return { action: "goto", target: "ISSUE_FIX" };
|
|
10236
|
+
}
|
|
10009
10237
|
return { action: "back", target: "IMPLEMENT", reason: "request_changes" };
|
|
10010
10238
|
}
|
|
10011
10239
|
if (nextAction === "FINALIZE") {
|
|
@@ -10032,7 +10260,7 @@ var init_code_review = __esm({
|
|
|
10032
10260
|
}
|
|
10033
10261
|
return { action: "advance" };
|
|
10034
10262
|
}
|
|
10035
|
-
const nextReviewerName = nextReviewer(codeReviews, backends);
|
|
10263
|
+
const nextReviewerName = nextReviewer(codeReviews, backends, ctx.state.codexUnavailable, ctx.state.codexUnavailableSince);
|
|
10036
10264
|
const mergeBase = ctx.state.git.mergeBase;
|
|
10037
10265
|
return {
|
|
10038
10266
|
action: "retry",
|
|
@@ -10336,16 +10564,49 @@ var init_finalize = __esm({
|
|
|
10336
10564
|
FinalizeStage = class {
|
|
10337
10565
|
id = "FINALIZE";
|
|
10338
10566
|
async enter(ctx) {
|
|
10567
|
+
if (ctx.state.finalizeCheckpoint === "committed") {
|
|
10568
|
+
return { action: "advance" };
|
|
10569
|
+
}
|
|
10570
|
+
const previousHead = ctx.state.git.expectedHead ?? ctx.state.git.initHead;
|
|
10571
|
+
if (previousHead) {
|
|
10572
|
+
const headResult = await gitHead(ctx.root);
|
|
10573
|
+
if (headResult.ok && headResult.data.hash !== previousHead) {
|
|
10574
|
+
const treeResult = await gitDiffTreeNames(ctx.root, headResult.data.hash);
|
|
10575
|
+
const ticketId = ctx.state.ticket?.id;
|
|
10576
|
+
if (ticketId) {
|
|
10577
|
+
const ticketPath = `.story/tickets/${ticketId}.json`;
|
|
10578
|
+
if (treeResult.ok && !treeResult.data.includes(ticketPath)) {
|
|
10579
|
+
} else {
|
|
10580
|
+
ctx.writeState({ finalizeCheckpoint: "precommit_passed" });
|
|
10581
|
+
return this.handleCommit(ctx, { completedAction: "commit_done", commitHash: headResult.data.hash });
|
|
10582
|
+
}
|
|
10583
|
+
}
|
|
10584
|
+
const issueId = ctx.state.currentIssue?.id;
|
|
10585
|
+
if (issueId) {
|
|
10586
|
+
const issuePath = `.story/issues/${issueId}.json`;
|
|
10587
|
+
if (treeResult.ok && !treeResult.data.includes(issuePath)) {
|
|
10588
|
+
} else {
|
|
10589
|
+
ctx.writeState({ finalizeCheckpoint: "precommit_passed" });
|
|
10590
|
+
return this.handleCommit(ctx, { completedAction: "commit_done", commitHash: headResult.data.hash });
|
|
10591
|
+
}
|
|
10592
|
+
}
|
|
10593
|
+
if (!ticketId && !issueId) {
|
|
10594
|
+
ctx.writeState({ finalizeCheckpoint: "precommit_passed" });
|
|
10595
|
+
return this.handleCommit(ctx, { completedAction: "commit_done", commitHash: headResult.data.hash });
|
|
10596
|
+
}
|
|
10597
|
+
}
|
|
10598
|
+
}
|
|
10339
10599
|
return {
|
|
10340
10600
|
instruction: [
|
|
10341
10601
|
"# Finalize",
|
|
10342
10602
|
"",
|
|
10343
10603
|
"Code review passed. Time to commit.",
|
|
10344
10604
|
"",
|
|
10345
|
-
|
|
10346
|
-
ctx.state.
|
|
10347
|
-
|
|
10348
|
-
|
|
10605
|
+
"1. Run `git reset` to clear the staging area (ensures no stale files from prior operations)",
|
|
10606
|
+
ctx.state.ticket ? `2. Update ticket ${ctx.state.ticket.id} status to "complete" in .story/` : "",
|
|
10607
|
+
ctx.state.currentIssue ? `2. Ensure .story/issues/${ctx.state.currentIssue.id}.json is updated with status: "resolved"` : "",
|
|
10608
|
+
"3. Stage only the files you modified for this fix (code + .story/ changes). Do NOT use `git add -A` or `git add .`",
|
|
10609
|
+
'4. Call me with completedAction: "files_staged"'
|
|
10349
10610
|
].filter(Boolean).join("\n"),
|
|
10350
10611
|
reminders: [
|
|
10351
10612
|
ctx.state.currentIssue ? "Stage both code changes and .story/ issue update in the same commit. Only stage files related to this fix." : "Stage both code changes and .story/ ticket update in the same commit. Only stage files related to this ticket."
|
|
@@ -10366,6 +10627,9 @@ var init_finalize = __esm({
|
|
|
10366
10627
|
return this.handlePrecommit(ctx);
|
|
10367
10628
|
}
|
|
10368
10629
|
if (action === "commit_done") {
|
|
10630
|
+
if (!checkpoint) {
|
|
10631
|
+
ctx.writeState({ finalizeCheckpoint: "precommit_passed" });
|
|
10632
|
+
}
|
|
10369
10633
|
return this.handleCommit(ctx, report);
|
|
10370
10634
|
}
|
|
10371
10635
|
return {
|
|
@@ -10379,12 +10643,12 @@ var init_finalize = __esm({
|
|
|
10379
10643
|
return {
|
|
10380
10644
|
action: "retry",
|
|
10381
10645
|
instruction: [
|
|
10382
|
-
"Files staged. Now
|
|
10646
|
+
"Files staged. Now commit.",
|
|
10383
10647
|
"",
|
|
10384
|
-
|
|
10385
|
-
|
|
10386
|
-
|
|
10387
|
-
|
|
10648
|
+
ctx.state.ticket ? `Commit with message: "feat: <description> (${ctx.state.ticket.id})"` : "Commit with a descriptive message.",
|
|
10649
|
+
"",
|
|
10650
|
+
'Call me with completedAction: "commit_done" and include the commitHash.'
|
|
10651
|
+
].join("\n")
|
|
10388
10652
|
};
|
|
10389
10653
|
}
|
|
10390
10654
|
const stagedResult = await gitDiffCachedNames(ctx.root);
|
|
@@ -10419,15 +10683,14 @@ var init_finalize = __esm({
|
|
|
10419
10683
|
return { action: "retry", instruction: 'No files are staged. Stage your changes and call me again with completedAction: "files_staged".' };
|
|
10420
10684
|
}
|
|
10421
10685
|
const baselineUntracked = ctx.state.git.baseline?.untrackedPaths ?? [];
|
|
10422
|
-
let overlapOverridden = false;
|
|
10423
10686
|
if (baselineUntracked.length > 0) {
|
|
10424
10687
|
const sessionTicketPath = ctx.state.ticket?.id ? `.story/tickets/${ctx.state.ticket.id}.json` : null;
|
|
10688
|
+
const sessionIssuePath = ctx.state.currentIssue?.id ? `.story/issues/${ctx.state.currentIssue.id}.json` : null;
|
|
10425
10689
|
const overlap = stagedResult.data.filter(
|
|
10426
|
-
(f) => baselineUntracked.includes(f) && f !== sessionTicketPath
|
|
10690
|
+
(f) => baselineUntracked.includes(f) && f !== sessionTicketPath && f !== sessionIssuePath
|
|
10427
10691
|
);
|
|
10428
10692
|
if (overlap.length > 0) {
|
|
10429
10693
|
if (report.overrideOverlap) {
|
|
10430
|
-
overlapOverridden = true;
|
|
10431
10694
|
} else {
|
|
10432
10695
|
return {
|
|
10433
10696
|
action: "retry",
|
|
@@ -10457,17 +10720,17 @@ var init_finalize = __esm({
|
|
|
10457
10720
|
}
|
|
10458
10721
|
}
|
|
10459
10722
|
ctx.writeState({
|
|
10460
|
-
finalizeCheckpoint:
|
|
10723
|
+
finalizeCheckpoint: "precommit_passed"
|
|
10461
10724
|
});
|
|
10462
10725
|
return {
|
|
10463
10726
|
action: "retry",
|
|
10464
10727
|
instruction: [
|
|
10465
|
-
"Files staged. Now
|
|
10728
|
+
"Files staged. Now commit.",
|
|
10466
10729
|
"",
|
|
10467
|
-
|
|
10468
|
-
|
|
10469
|
-
|
|
10470
|
-
|
|
10730
|
+
ctx.state.ticket ? `Commit with message: "feat: <description> (${ctx.state.ticket.id})"` : "Commit with a descriptive message.",
|
|
10731
|
+
"",
|
|
10732
|
+
'Call me with completedAction: "commit_done" and include the commitHash.'
|
|
10733
|
+
].join("\n")
|
|
10471
10734
|
};
|
|
10472
10735
|
}
|
|
10473
10736
|
async handlePrecommit(ctx) {
|
|
@@ -10484,8 +10747,9 @@ var init_finalize = __esm({
|
|
|
10484
10747
|
const baselineUntracked = ctx.state.git.baseline?.untrackedPaths ?? [];
|
|
10485
10748
|
if (baselineUntracked.length > 0) {
|
|
10486
10749
|
const sessionTicketPath = ctx.state.ticket?.id ? `.story/tickets/${ctx.state.ticket.id}.json` : null;
|
|
10750
|
+
const sessionIssuePath = ctx.state.currentIssue?.id ? `.story/issues/${ctx.state.currentIssue.id}.json` : null;
|
|
10487
10751
|
const overlap = stagedResult.data.filter(
|
|
10488
|
-
(f) => baselineUntracked.includes(f) && f !== sessionTicketPath
|
|
10752
|
+
(f) => baselineUntracked.includes(f) && f !== sessionTicketPath && f !== sessionIssuePath
|
|
10489
10753
|
);
|
|
10490
10754
|
if (overlap.length > 0) {
|
|
10491
10755
|
ctx.writeState({ finalizeCheckpoint: null });
|
|
@@ -10556,6 +10820,7 @@ var init_finalize = __esm({
|
|
|
10556
10820
|
finalizeCheckpoint: "committed",
|
|
10557
10821
|
resolvedIssues: [...ctx.state.resolvedIssues ?? [], currentIssue.id],
|
|
10558
10822
|
currentIssue: null,
|
|
10823
|
+
ticketStartedAt: null,
|
|
10559
10824
|
git: {
|
|
10560
10825
|
...ctx.state.git,
|
|
10561
10826
|
mergeBase: normalizedHash,
|
|
@@ -10565,11 +10830,20 @@ var init_finalize = __esm({
|
|
|
10565
10830
|
ctx.appendEvent("commit", { commitHash: normalizedHash, issueId: currentIssue.id });
|
|
10566
10831
|
return { action: "goto", target: "COMPLETE" };
|
|
10567
10832
|
}
|
|
10568
|
-
const completedTicket = ctx.state.ticket ? {
|
|
10833
|
+
const completedTicket = ctx.state.ticket ? {
|
|
10834
|
+
id: ctx.state.ticket.id,
|
|
10835
|
+
title: ctx.state.ticket.title,
|
|
10836
|
+
commitHash: normalizedHash,
|
|
10837
|
+
risk: ctx.state.ticket.risk,
|
|
10838
|
+
realizedRisk: ctx.state.ticket.realizedRisk,
|
|
10839
|
+
startedAt: ctx.state.ticketStartedAt ?? void 0,
|
|
10840
|
+
completedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
10841
|
+
} : void 0;
|
|
10569
10842
|
ctx.writeState({
|
|
10570
10843
|
finalizeCheckpoint: "committed",
|
|
10571
10844
|
completedTickets: completedTicket ? [...ctx.state.completedTickets, completedTicket] : ctx.state.completedTickets,
|
|
10572
10845
|
ticket: void 0,
|
|
10846
|
+
ticketStartedAt: null,
|
|
10573
10847
|
git: {
|
|
10574
10848
|
...ctx.state.git,
|
|
10575
10849
|
mergeBase: normalizedHash,
|
|
@@ -10612,7 +10886,7 @@ var init_complete = __esm({
|
|
|
10612
10886
|
target: "HANDOVER",
|
|
10613
10887
|
result: {
|
|
10614
10888
|
instruction: [
|
|
10615
|
-
`# Ticket Complete
|
|
10889
|
+
`# Ticket Complete -- ${mode} mode session ending`,
|
|
10616
10890
|
"",
|
|
10617
10891
|
`Ticket **${ctx.state.ticket?.id}** completed. Write a brief session handover.`,
|
|
10618
10892
|
"",
|
|
@@ -10623,36 +10897,23 @@ var init_complete = __esm({
|
|
|
10623
10897
|
}
|
|
10624
10898
|
};
|
|
10625
10899
|
}
|
|
10626
|
-
|
|
10627
|
-
|
|
10628
|
-
|
|
10629
|
-
|
|
10630
|
-
|
|
10631
|
-
|
|
10632
|
-
|
|
10633
|
-
|
|
10634
|
-
|
|
10635
|
-
|
|
10636
|
-
|
|
10637
|
-
|
|
10638
|
-
|
|
10639
|
-
|
|
10640
|
-
].join("\n");
|
|
10641
|
-
await handleHandoverCreate3(content, "checkpoint", "md", ctx.root);
|
|
10642
|
-
} catch {
|
|
10643
|
-
}
|
|
10644
|
-
try {
|
|
10645
|
-
const { loadProject: loadProject2 } = await Promise.resolve().then(() => (init_project_loader(), project_loader_exports));
|
|
10646
|
-
const { saveSnapshot: saveSnapshot2 } = await Promise.resolve().then(() => (init_snapshot(), snapshot_exports));
|
|
10647
|
-
const loadResult = await loadProject2(ctx.root);
|
|
10648
|
-
await saveSnapshot2(ctx.root, loadResult);
|
|
10649
|
-
} catch {
|
|
10650
|
-
}
|
|
10651
|
-
ctx.appendEvent("checkpoint", { ticketsDone, issuesDone, totalWorkDone, interval: handoverInterval });
|
|
10900
|
+
await this.tryCheckpoint(ctx, totalWorkDone, ticketsDone, issuesDone);
|
|
10901
|
+
let projectState;
|
|
10902
|
+
try {
|
|
10903
|
+
({ state: projectState } = await ctx.loadProject());
|
|
10904
|
+
} catch (err) {
|
|
10905
|
+
return {
|
|
10906
|
+
action: "goto",
|
|
10907
|
+
target: "HANDOVER",
|
|
10908
|
+
result: {
|
|
10909
|
+
instruction: `Failed to load project state: ${err instanceof Error ? err.message : String(err)}. Ending session -- write a handover noting the error.`,
|
|
10910
|
+
reminders: [],
|
|
10911
|
+
transitionedFrom: "COMPLETE"
|
|
10912
|
+
}
|
|
10913
|
+
};
|
|
10652
10914
|
}
|
|
10653
|
-
const { state: projectState } = await ctx.loadProject();
|
|
10654
|
-
let nextTarget;
|
|
10655
10915
|
const targetedRemaining = isTargetedMode(ctx.state) ? getRemainingTargets(ctx.state) : null;
|
|
10916
|
+
let nextTarget;
|
|
10656
10917
|
if (targetedRemaining !== null) {
|
|
10657
10918
|
nextTarget = targetedRemaining.length === 0 ? "HANDOVER" : "PICK_TICKET";
|
|
10658
10919
|
} else if (maxTickets > 0 && totalWorkDone >= maxTickets) {
|
|
@@ -10667,66 +10928,118 @@ var init_complete = __esm({
|
|
|
10667
10928
|
}
|
|
10668
10929
|
}
|
|
10669
10930
|
if (nextTarget === "HANDOVER") {
|
|
10670
|
-
|
|
10671
|
-
|
|
10672
|
-
|
|
10673
|
-
|
|
10674
|
-
|
|
10931
|
+
return this.buildHandoverResult(ctx, targetedRemaining, ticketsDone, issuesDone);
|
|
10932
|
+
}
|
|
10933
|
+
if (targetedRemaining !== null) {
|
|
10934
|
+
return this.buildTargetedPickResult(ctx, targetedRemaining, projectState);
|
|
10935
|
+
}
|
|
10936
|
+
return this.buildStandardPickResult(ctx, projectState, ticketsDone, maxTickets);
|
|
10937
|
+
}
|
|
10938
|
+
async report(ctx, _report) {
|
|
10939
|
+
return this.enter(ctx);
|
|
10940
|
+
}
|
|
10941
|
+
// ---------------------------------------------------------------------------
|
|
10942
|
+
// Checkpoint -- mid-session handover + snapshot (best-effort)
|
|
10943
|
+
// ---------------------------------------------------------------------------
|
|
10944
|
+
async tryCheckpoint(ctx, totalWorkDone, ticketsDone, issuesDone) {
|
|
10945
|
+
const handoverInterval = ctx.state.config.handoverInterval ?? 5;
|
|
10946
|
+
if (handoverInterval <= 0 || totalWorkDone <= 0 || totalWorkDone % handoverInterval !== 0) return;
|
|
10947
|
+
try {
|
|
10948
|
+
const { handleHandoverCreate: handleHandoverCreate3 } = await Promise.resolve().then(() => (init_handover(), handover_exports));
|
|
10949
|
+
const completedIds = ctx.state.completedTickets.map((t) => t.id).join(", ");
|
|
10950
|
+
const resolvedIds = (ctx.state.resolvedIssues ?? []).join(", ");
|
|
10951
|
+
const content = [
|
|
10952
|
+
`# Checkpoint -- ${totalWorkDone} items completed`,
|
|
10953
|
+
"",
|
|
10954
|
+
`**Session:** ${ctx.state.sessionId}`,
|
|
10955
|
+
...completedIds ? [`**Tickets:** ${completedIds}`] : [],
|
|
10956
|
+
...resolvedIds ? [`**Issues resolved:** ${resolvedIds}`] : [],
|
|
10957
|
+
"",
|
|
10958
|
+
"This is an automatic mid-session checkpoint. The session is still active."
|
|
10959
|
+
].join("\n");
|
|
10960
|
+
await handleHandoverCreate3(content, "checkpoint", "md", ctx.root);
|
|
10961
|
+
} catch {
|
|
10962
|
+
}
|
|
10963
|
+
try {
|
|
10964
|
+
const { loadProject: loadProject2 } = await Promise.resolve().then(() => (init_project_loader(), project_loader_exports));
|
|
10965
|
+
const { saveSnapshot: saveSnapshot2 } = await Promise.resolve().then(() => (init_snapshot(), snapshot_exports));
|
|
10966
|
+
const loadResult = await loadProject2(ctx.root);
|
|
10967
|
+
await saveSnapshot2(ctx.root, loadResult);
|
|
10968
|
+
} catch {
|
|
10969
|
+
}
|
|
10970
|
+
ctx.appendEvent("checkpoint", { ticketsDone, issuesDone, totalWorkDone, interval: handoverInterval });
|
|
10971
|
+
}
|
|
10972
|
+
// ---------------------------------------------------------------------------
|
|
10973
|
+
// HANDOVER instruction -- session ending
|
|
10974
|
+
// ---------------------------------------------------------------------------
|
|
10975
|
+
buildHandoverResult(ctx, targetedRemaining, ticketsDone, issuesDone) {
|
|
10976
|
+
const postComplete = ctx.state.resolvedPostComplete ?? ctx.recipe.postComplete;
|
|
10977
|
+
const postResult = findFirstPostComplete(postComplete, ctx);
|
|
10978
|
+
if (postResult.kind === "found") {
|
|
10979
|
+
ctx.writeState({ pipelinePhase: "postComplete" });
|
|
10980
|
+
return { action: "goto", target: postResult.stage.id };
|
|
10981
|
+
}
|
|
10982
|
+
const handoverHeader = targetedRemaining !== null ? `# Targeted Session Complete -- All ${ctx.state.targetWork.length} target(s) done` : `# Session Complete -- ${ticketsDone} ticket(s) and ${issuesDone} issue(s) done`;
|
|
10983
|
+
return {
|
|
10984
|
+
action: "goto",
|
|
10985
|
+
target: "HANDOVER",
|
|
10986
|
+
result: {
|
|
10987
|
+
instruction: [
|
|
10988
|
+
handoverHeader,
|
|
10989
|
+
"",
|
|
10990
|
+
"Write a session handover summarizing what was accomplished, decisions made, and what's next.",
|
|
10991
|
+
"",
|
|
10992
|
+
'Call me with completedAction: "handover_written" and include the content in handoverContent.'
|
|
10993
|
+
].join("\n"),
|
|
10994
|
+
reminders: [],
|
|
10995
|
+
transitionedFrom: "COMPLETE",
|
|
10996
|
+
contextAdvice: "ok"
|
|
10675
10997
|
}
|
|
10676
|
-
|
|
10998
|
+
};
|
|
10999
|
+
}
|
|
11000
|
+
// ---------------------------------------------------------------------------
|
|
11001
|
+
// Targeted PICK_TICKET instruction
|
|
11002
|
+
// ---------------------------------------------------------------------------
|
|
11003
|
+
buildTargetedPickResult(ctx, targetedRemaining, projectState) {
|
|
11004
|
+
const { text: candidatesText, firstReady } = buildTargetedCandidatesText(targetedRemaining, projectState);
|
|
11005
|
+
if (!firstReady) {
|
|
10677
11006
|
return {
|
|
10678
11007
|
action: "goto",
|
|
10679
11008
|
target: "HANDOVER",
|
|
10680
11009
|
result: {
|
|
10681
|
-
instruction:
|
|
10682
|
-
handoverHeader,
|
|
10683
|
-
"",
|
|
10684
|
-
"Write a session handover summarizing what was accomplished, decisions made, and what's next.",
|
|
10685
|
-
"",
|
|
10686
|
-
'Call me with completedAction: "handover_written" and include the content in handoverContent.'
|
|
10687
|
-
].join("\n"),
|
|
11010
|
+
instruction: buildTargetedStuckHandover(candidatesText, ctx.state.sessionId),
|
|
10688
11011
|
reminders: [],
|
|
10689
|
-
transitionedFrom: "COMPLETE"
|
|
10690
|
-
contextAdvice: "ok"
|
|
10691
|
-
}
|
|
10692
|
-
};
|
|
10693
|
-
}
|
|
10694
|
-
if (targetedRemaining !== null) {
|
|
10695
|
-
const { text: candidatesText2, firstReady } = buildTargetedCandidatesText(targetedRemaining, projectState);
|
|
10696
|
-
if (!firstReady) {
|
|
10697
|
-
return {
|
|
10698
|
-
action: "goto",
|
|
10699
|
-
target: "HANDOVER",
|
|
10700
|
-
result: {
|
|
10701
|
-
instruction: buildTargetedStuckHandover(candidatesText2, ctx.state.sessionId),
|
|
10702
|
-
reminders: [],
|
|
10703
|
-
transitionedFrom: "COMPLETE"
|
|
10704
|
-
}
|
|
10705
|
-
};
|
|
10706
|
-
}
|
|
10707
|
-
const precomputed = { text: candidatesText2, firstReady };
|
|
10708
|
-
const targetedInstruction = buildTargetedPickInstruction(targetedRemaining, projectState, ctx.state.sessionId, precomputed);
|
|
10709
|
-
return {
|
|
10710
|
-
action: "goto",
|
|
10711
|
-
target: "PICK_TICKET",
|
|
10712
|
-
result: {
|
|
10713
|
-
instruction: [
|
|
10714
|
-
`# Item Complete -- Continuing (${ctx.state.targetWork.length - targetedRemaining.length}/${ctx.state.targetWork.length} targets done)`,
|
|
10715
|
-
"",
|
|
10716
|
-
"Do NOT stop. Do NOT ask the user. Continue immediately with the next target.",
|
|
10717
|
-
"",
|
|
10718
|
-
targetedInstruction
|
|
10719
|
-
].join("\n"),
|
|
10720
|
-
reminders: [
|
|
10721
|
-
"Do NOT stop or summarize. Call autonomous_guide IMMEDIATELY to pick the next target.",
|
|
10722
|
-
"Do NOT ask the user for confirmation.",
|
|
10723
|
-
"You are in targeted auto mode -- pick ONLY from the listed items."
|
|
10724
|
-
],
|
|
10725
|
-
transitionedFrom: "COMPLETE",
|
|
10726
|
-
contextAdvice: "ok"
|
|
11012
|
+
transitionedFrom: "COMPLETE"
|
|
10727
11013
|
}
|
|
10728
11014
|
};
|
|
10729
11015
|
}
|
|
11016
|
+
const precomputed = { text: candidatesText, firstReady };
|
|
11017
|
+
const targetedInstruction = buildTargetedPickInstruction(targetedRemaining, projectState, ctx.state.sessionId, precomputed);
|
|
11018
|
+
return {
|
|
11019
|
+
action: "goto",
|
|
11020
|
+
target: "PICK_TICKET",
|
|
11021
|
+
result: {
|
|
11022
|
+
instruction: [
|
|
11023
|
+
`# Item Complete -- Continuing (${ctx.state.targetWork.length - targetedRemaining.length}/${ctx.state.targetWork.length} targets done)`,
|
|
11024
|
+
"",
|
|
11025
|
+
"Do NOT stop. Do NOT ask the user. Continue immediately with the next target.",
|
|
11026
|
+
"",
|
|
11027
|
+
targetedInstruction
|
|
11028
|
+
].join("\n"),
|
|
11029
|
+
reminders: [
|
|
11030
|
+
"Do NOT stop or summarize. Call autonomous_guide IMMEDIATELY to pick the next target.",
|
|
11031
|
+
"Do NOT ask the user for confirmation.",
|
|
11032
|
+
"You are in targeted auto mode -- pick ONLY from the listed items."
|
|
11033
|
+
],
|
|
11034
|
+
transitionedFrom: "COMPLETE",
|
|
11035
|
+
contextAdvice: "ok"
|
|
11036
|
+
}
|
|
11037
|
+
};
|
|
11038
|
+
}
|
|
11039
|
+
// ---------------------------------------------------------------------------
|
|
11040
|
+
// Standard auto PICK_TICKET instruction
|
|
11041
|
+
// ---------------------------------------------------------------------------
|
|
11042
|
+
buildStandardPickResult(ctx, projectState, ticketsDone, maxTickets) {
|
|
10730
11043
|
const candidates = nextTickets(projectState, 5);
|
|
10731
11044
|
let candidatesText = "";
|
|
10732
11045
|
if (candidates.kind === "found") {
|
|
@@ -10740,7 +11053,7 @@ var init_complete = __esm({
|
|
|
10740
11053
|
target: "PICK_TICKET",
|
|
10741
11054
|
result: {
|
|
10742
11055
|
instruction: [
|
|
10743
|
-
`# Ticket Complete
|
|
11056
|
+
`# Ticket Complete -- Continuing (${ticketsDone}/${maxTickets})`,
|
|
10744
11057
|
"",
|
|
10745
11058
|
"Do NOT stop. Do NOT ask the user. Continue immediately with the next ticket.",
|
|
10746
11059
|
"",
|
|
@@ -10754,16 +11067,13 @@ var init_complete = __esm({
|
|
|
10754
11067
|
reminders: [
|
|
10755
11068
|
"Do NOT stop or summarize. Call autonomous_guide IMMEDIATELY to pick the next ticket.",
|
|
10756
11069
|
"Do NOT ask the user for confirmation.",
|
|
10757
|
-
"You are in autonomous mode
|
|
11070
|
+
"You are in autonomous mode -- continue working until all tickets are done or the session limit is reached."
|
|
10758
11071
|
],
|
|
10759
11072
|
transitionedFrom: "COMPLETE",
|
|
10760
11073
|
contextAdvice: "ok"
|
|
10761
11074
|
}
|
|
10762
11075
|
};
|
|
10763
11076
|
}
|
|
10764
|
-
async report(ctx, _report) {
|
|
10765
|
-
return this.enter(ctx);
|
|
10766
|
-
}
|
|
10767
11077
|
};
|
|
10768
11078
|
}
|
|
10769
11079
|
});
|
|
@@ -10891,7 +11201,32 @@ var init_issue_fix = __esm({
|
|
|
10891
11201
|
if (!issue) {
|
|
10892
11202
|
return { action: "goto", target: "PICK_TICKET" };
|
|
10893
11203
|
}
|
|
10894
|
-
|
|
11204
|
+
let projectState;
|
|
11205
|
+
try {
|
|
11206
|
+
({ state: projectState } = await ctx.loadProject());
|
|
11207
|
+
} catch {
|
|
11208
|
+
return {
|
|
11209
|
+
instruction: [
|
|
11210
|
+
"# Fix Issue",
|
|
11211
|
+
"",
|
|
11212
|
+
`**${issue.id}**: ${issue.title} (severity: ${issue.severity})`,
|
|
11213
|
+
"",
|
|
11214
|
+
"(Warning: could not load full issue details from .story/ -- using session state.)",
|
|
11215
|
+
"",
|
|
11216
|
+
'Fix this issue, then update its status to "resolved" in `.story/issues/`.',
|
|
11217
|
+
"Add a resolution description explaining the fix.",
|
|
11218
|
+
"",
|
|
11219
|
+
"When done, call `claudestory_autonomous_guide` with:",
|
|
11220
|
+
"```json",
|
|
11221
|
+
`{ "sessionId": "${ctx.state.sessionId}", "action": "report", "report": { "completedAction": "issue_fixed" } }`,
|
|
11222
|
+
"```"
|
|
11223
|
+
].join("\n"),
|
|
11224
|
+
reminders: [
|
|
11225
|
+
'Update the issue JSON: set status to "resolved", add resolution text, set resolvedDate.',
|
|
11226
|
+
"Do NOT ask the user for confirmation."
|
|
11227
|
+
]
|
|
11228
|
+
};
|
|
11229
|
+
}
|
|
10895
11230
|
const fullIssue = projectState.issues.find((i) => i.id === issue.id);
|
|
10896
11231
|
const details = fullIssue ? [
|
|
10897
11232
|
`**${fullIssue.id}**: ${fullIssue.title}`,
|
|
@@ -10926,7 +11261,12 @@ var init_issue_fix = __esm({
|
|
|
10926
11261
|
if (!issue) {
|
|
10927
11262
|
return { action: "goto", target: "PICK_TICKET" };
|
|
10928
11263
|
}
|
|
10929
|
-
|
|
11264
|
+
let projectState;
|
|
11265
|
+
try {
|
|
11266
|
+
({ state: projectState } = await ctx.loadProject());
|
|
11267
|
+
} catch (err) {
|
|
11268
|
+
return { action: "retry", instruction: `Failed to load project state: ${err instanceof Error ? err.message : String(err)}. Check .story/ files for corruption, then report again.` };
|
|
11269
|
+
}
|
|
10930
11270
|
const current = projectState.issues.find((i) => i.id === issue.id);
|
|
10931
11271
|
if (!current || current.status !== "resolved") {
|
|
10932
11272
|
return {
|
|
@@ -10935,6 +11275,10 @@ var init_issue_fix = __esm({
|
|
|
10935
11275
|
reminders: ["Set status to 'resolved', add resolution text, set resolvedDate."]
|
|
10936
11276
|
};
|
|
10937
11277
|
}
|
|
11278
|
+
const enableCodeReview = !!ctx.recipe.stages.ISSUE_FIX?.enableCodeReview;
|
|
11279
|
+
if (enableCodeReview) {
|
|
11280
|
+
return { action: "goto", target: "CODE_REVIEW" };
|
|
11281
|
+
}
|
|
10938
11282
|
return {
|
|
10939
11283
|
action: "goto",
|
|
10940
11284
|
target: "FINALIZE",
|
|
@@ -10944,9 +11288,10 @@ var init_issue_fix = __esm({
|
|
|
10944
11288
|
"",
|
|
10945
11289
|
`Issue ${issue.id} resolved. Time to commit.`,
|
|
10946
11290
|
"",
|
|
10947
|
-
|
|
10948
|
-
|
|
10949
|
-
|
|
11291
|
+
"1. Run `git reset` to clear the staging area (ensures no stale files from prior operations)",
|
|
11292
|
+
`2. Ensure .story/issues/${issue.id}.json is updated with status: "resolved"`,
|
|
11293
|
+
"3. Stage only the files you modified for this fix (code + .story/ changes). Do NOT use `git add -A` or `git add .`",
|
|
11294
|
+
'4. Call me with completedAction: "files_staged"'
|
|
10950
11295
|
].join("\n"),
|
|
10951
11296
|
reminders: ["Stage both code changes and .story/ issue update in the same commit. Only stage files related to this fix."],
|
|
10952
11297
|
transitionedFrom: "ISSUE_FIX"
|
|
@@ -10970,7 +11315,12 @@ var init_issue_sweep = __esm({
|
|
|
10970
11315
|
return !issueConfig?.enabled;
|
|
10971
11316
|
}
|
|
10972
11317
|
async enter(ctx) {
|
|
10973
|
-
|
|
11318
|
+
let projectState;
|
|
11319
|
+
try {
|
|
11320
|
+
({ state: projectState } = await ctx.loadProject());
|
|
11321
|
+
} catch {
|
|
11322
|
+
return { action: "goto", target: "HANDOVER" };
|
|
11323
|
+
}
|
|
10974
11324
|
const allIssues = projectState.issues.filter((i) => i.status === "open");
|
|
10975
11325
|
if (allIssues.length === 0) {
|
|
10976
11326
|
return { action: "goto", target: "HANDOVER" };
|
|
@@ -11022,7 +11372,12 @@ var init_issue_sweep = __esm({
|
|
|
11022
11372
|
}
|
|
11023
11373
|
const current = sweep.current;
|
|
11024
11374
|
if (current) {
|
|
11025
|
-
|
|
11375
|
+
let verifyState;
|
|
11376
|
+
try {
|
|
11377
|
+
({ state: verifyState } = await ctx.loadProject());
|
|
11378
|
+
} catch (err) {
|
|
11379
|
+
return { action: "retry", instruction: `Failed to load project state: ${err instanceof Error ? err.message : String(err)}. Check .story/ files, then report again.` };
|
|
11380
|
+
}
|
|
11026
11381
|
const currentIssue = verifyState.issues.find((i) => i.id === current);
|
|
11027
11382
|
if (currentIssue && currentIssue.status === "open") {
|
|
11028
11383
|
return {
|
|
@@ -11041,7 +11396,16 @@ var init_issue_sweep = __esm({
|
|
|
11041
11396
|
ctx.appendEvent("issue_sweep_complete", { resolved: resolved.length });
|
|
11042
11397
|
return { action: "goto", target: "HANDOVER" };
|
|
11043
11398
|
}
|
|
11044
|
-
|
|
11399
|
+
let projectState;
|
|
11400
|
+
try {
|
|
11401
|
+
({ state: projectState } = await ctx.loadProject());
|
|
11402
|
+
} catch {
|
|
11403
|
+
return {
|
|
11404
|
+
action: "retry",
|
|
11405
|
+
instruction: `Issue ${next} is next. Fix it and report again. (Could not load full details from .story/.)`,
|
|
11406
|
+
reminders: ["Set status to 'resolved' and add a resolution description."]
|
|
11407
|
+
};
|
|
11408
|
+
}
|
|
11045
11409
|
const nextIssue = projectState.issues.find((i) => i.id === next);
|
|
11046
11410
|
return {
|
|
11047
11411
|
action: "retry",
|
|
@@ -11064,204 +11428,511 @@ Impact: ${nextIssue.impact}` : ""}` : `Fix issue ${next}.`,
|
|
|
11064
11428
|
}
|
|
11065
11429
|
});
|
|
11066
11430
|
|
|
11067
|
-
// src/autonomous/
|
|
11068
|
-
import { writeFileSync as writeFileSync4 } from "fs";
|
|
11431
|
+
// src/autonomous/resume-marker.ts
|
|
11432
|
+
import { mkdirSync as mkdirSync4, writeFileSync as writeFileSync4, unlinkSync as unlinkSync3, existsSync as existsSync11 } from "fs";
|
|
11069
11433
|
import { join as join17 } from "path";
|
|
11070
|
-
|
|
11071
|
-
|
|
11072
|
-
"src/autonomous/stages/handover.ts"() {
|
|
11073
|
-
"use strict";
|
|
11074
|
-
init_esm_shims();
|
|
11075
|
-
init_handover();
|
|
11076
|
-
init_git_inspector();
|
|
11077
|
-
HandoverStage = class {
|
|
11078
|
-
id = "HANDOVER";
|
|
11079
|
-
async enter(ctx) {
|
|
11080
|
-
const ticketsDone = ctx.state.completedTickets.length;
|
|
11081
|
-
const issuesDone = (ctx.state.resolvedIssues ?? []).length;
|
|
11082
|
-
return {
|
|
11083
|
-
instruction: [
|
|
11084
|
-
`# Session Complete \u2014 ${ticketsDone} ticket(s) and ${issuesDone} issue(s) done`,
|
|
11085
|
-
"",
|
|
11086
|
-
"Write a session handover summarizing what was accomplished, decisions made, and what's next.",
|
|
11087
|
-
"",
|
|
11088
|
-
'Call me with completedAction: "handover_written" and include the content in handoverContent.'
|
|
11089
|
-
].join("\n"),
|
|
11090
|
-
reminders: [
|
|
11091
|
-
"Before recording a new lesson, call claudestory_lesson_list to check existing lessons. Then choose: create (new insight), reinforce (existing lesson confirmed), update (refine wording), or skip."
|
|
11092
|
-
],
|
|
11093
|
-
transitionedFrom: ctx.state.previousState ?? void 0
|
|
11094
|
-
};
|
|
11095
|
-
}
|
|
11096
|
-
async report(ctx, report) {
|
|
11097
|
-
const content = report.handoverContent;
|
|
11098
|
-
if (!content) {
|
|
11099
|
-
return { action: "retry", instruction: "Missing handoverContent. Write the handover and include it in the report." };
|
|
11100
|
-
}
|
|
11101
|
-
let handoverFailed = false;
|
|
11102
|
-
try {
|
|
11103
|
-
await handleHandoverCreate(content, "auto-session", "md", ctx.root);
|
|
11104
|
-
} catch {
|
|
11105
|
-
handoverFailed = true;
|
|
11106
|
-
try {
|
|
11107
|
-
const fallbackPath = join17(ctx.dir, "handover-fallback.md");
|
|
11108
|
-
writeFileSync4(fallbackPath, content, "utf-8");
|
|
11109
|
-
} catch {
|
|
11110
|
-
}
|
|
11111
|
-
}
|
|
11112
|
-
let stashPopFailed = false;
|
|
11113
|
-
const autoStash = ctx.state.git.autoStash;
|
|
11114
|
-
if (autoStash) {
|
|
11115
|
-
const popResult = await gitStashPop(ctx.root, autoStash.ref);
|
|
11116
|
-
if (!popResult.ok) {
|
|
11117
|
-
stashPopFailed = true;
|
|
11118
|
-
}
|
|
11119
|
-
}
|
|
11120
|
-
await ctx.drainDeferrals();
|
|
11121
|
-
const hasUnfiled = (ctx.state.pendingDeferrals ?? []).length > 0;
|
|
11122
|
-
ctx.writeState({
|
|
11123
|
-
state: "SESSION_END",
|
|
11124
|
-
previousState: "HANDOVER",
|
|
11125
|
-
status: "completed",
|
|
11126
|
-
terminationReason: "normal",
|
|
11127
|
-
deferralsUnfiled: hasUnfiled
|
|
11128
|
-
});
|
|
11129
|
-
ctx.appendEvent("session_end", {
|
|
11130
|
-
ticketsCompleted: ctx.state.completedTickets.length,
|
|
11131
|
-
issuesResolved: (ctx.state.resolvedIssues ?? []).length,
|
|
11132
|
-
handoverFailed
|
|
11133
|
-
});
|
|
11134
|
-
const ticketsDone = ctx.state.completedTickets.length;
|
|
11135
|
-
const issuesDone = (ctx.state.resolvedIssues ?? []).length;
|
|
11136
|
-
const resolvedList = (ctx.state.resolvedIssues ?? []).map((id) => `- ${id} (resolved)`).join("\n");
|
|
11137
|
-
return {
|
|
11138
|
-
action: "advance",
|
|
11139
|
-
result: {
|
|
11140
|
-
instruction: [
|
|
11141
|
-
"# Session Complete",
|
|
11142
|
-
"",
|
|
11143
|
-
`${ticketsDone} ticket(s) and ${issuesDone} issue(s) completed.${handoverFailed ? " Handover creation failed \u2014 fallback saved to session directory." : " Handover written."}${stashPopFailed ? " Auto-stash pop failed \u2014 run `git stash pop` manually." : ""} Session ended.`,
|
|
11144
|
-
"",
|
|
11145
|
-
ctx.state.completedTickets.map((t) => `- ${t.id}${t.title ? `: ${t.title}` : ""} (${t.commitHash ?? "no commit"})`).join("\n"),
|
|
11146
|
-
...resolvedList ? [resolvedList] : []
|
|
11147
|
-
].join("\n"),
|
|
11148
|
-
reminders: [],
|
|
11149
|
-
transitionedFrom: "HANDOVER"
|
|
11150
|
-
}
|
|
11151
|
-
};
|
|
11152
|
-
}
|
|
11153
|
-
};
|
|
11154
|
-
}
|
|
11155
|
-
});
|
|
11156
|
-
|
|
11157
|
-
// src/autonomous/stages/index.ts
|
|
11158
|
-
var init_stages = __esm({
|
|
11159
|
-
"src/autonomous/stages/index.ts"() {
|
|
11160
|
-
"use strict";
|
|
11161
|
-
init_esm_shims();
|
|
11162
|
-
init_registry();
|
|
11163
|
-
init_pick_ticket();
|
|
11164
|
-
init_plan();
|
|
11165
|
-
init_plan_review();
|
|
11166
|
-
init_implement();
|
|
11167
|
-
init_write_tests();
|
|
11168
|
-
init_test();
|
|
11169
|
-
init_code_review();
|
|
11170
|
-
init_build();
|
|
11171
|
-
init_verify();
|
|
11172
|
-
init_finalize();
|
|
11173
|
-
init_complete();
|
|
11174
|
-
init_lesson_capture();
|
|
11175
|
-
init_issue_fix();
|
|
11176
|
-
init_issue_sweep();
|
|
11177
|
-
init_handover2();
|
|
11178
|
-
registerStage(new PickTicketStage());
|
|
11179
|
-
registerStage(new PlanStage());
|
|
11180
|
-
registerStage(new PlanReviewStage());
|
|
11181
|
-
registerStage(new ImplementStage());
|
|
11182
|
-
registerStage(new WriteTestsStage());
|
|
11183
|
-
registerStage(new TestStage());
|
|
11184
|
-
registerStage(new CodeReviewStage());
|
|
11185
|
-
registerStage(new BuildStage());
|
|
11186
|
-
registerStage(new VerifyStage());
|
|
11187
|
-
registerStage(new FinalizeStage());
|
|
11188
|
-
registerStage(new CompleteStage());
|
|
11189
|
-
registerStage(new LessonCaptureStage());
|
|
11190
|
-
registerStage(new IssueFixStage());
|
|
11191
|
-
registerStage(new IssueSweepStage());
|
|
11192
|
-
registerStage(new HandoverStage());
|
|
11193
|
-
}
|
|
11194
|
-
});
|
|
11195
|
-
|
|
11196
|
-
// src/autonomous/version-check.ts
|
|
11197
|
-
import { readFileSync as readFileSync9 } from "fs";
|
|
11198
|
-
import { join as join18, dirname as dirname4 } from "path";
|
|
11199
|
-
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
11200
|
-
function checkVersionMismatch(runningVersion, installedVersion) {
|
|
11201
|
-
if (!installedVersion) return null;
|
|
11202
|
-
if (runningVersion === "0.0.0-dev") return null;
|
|
11203
|
-
if (runningVersion === installedVersion) return null;
|
|
11204
|
-
return `claudestory MCP server is running v${runningVersion} but v${installedVersion} is installed. Restart Claude Code to load the updated version.`;
|
|
11434
|
+
function sanitize(input, maxLen = 120) {
|
|
11435
|
+
return input.replace(/[\r\n]+/g, " ").replace(/\s+/g, " ").trim().slice(0, maxLen);
|
|
11205
11436
|
}
|
|
11206
|
-
function
|
|
11437
|
+
function writeResumeMarker(root, sessionId, state) {
|
|
11207
11438
|
try {
|
|
11208
|
-
const
|
|
11209
|
-
|
|
11210
|
-
|
|
11211
|
-
|
|
11439
|
+
const rulesDir = join17(root, ".claude", "rules");
|
|
11440
|
+
mkdirSync4(rulesDir, { recursive: true });
|
|
11441
|
+
const ticketInfo = state.ticket ? `Working on: ${sanitize(state.ticket.id, 20)} (${sanitize(state.ticket.title)})` : "Between tickets";
|
|
11442
|
+
const progress = `Progress: ${state.completedTickets.length} tickets completed, ${(state.resolvedIssues ?? []).length} issues resolved`;
|
|
11443
|
+
const lines = [
|
|
11444
|
+
"CRITICAL: An autonomous coding session is active and waiting to resume.",
|
|
11445
|
+
"",
|
|
11446
|
+
`Session: ${sessionId}`,
|
|
11447
|
+
ticketInfo,
|
|
11448
|
+
progress
|
|
11212
11449
|
];
|
|
11213
|
-
|
|
11214
|
-
|
|
11215
|
-
const raw = readFileSync9(candidate, "utf-8");
|
|
11216
|
-
const pkg = JSON.parse(raw);
|
|
11217
|
-
if (pkg.version) return pkg.version;
|
|
11218
|
-
} catch {
|
|
11219
|
-
}
|
|
11450
|
+
if (state.preCompactState) {
|
|
11451
|
+
lines.push(`State before compaction: ${state.preCompactState}`);
|
|
11220
11452
|
}
|
|
11221
|
-
|
|
11453
|
+
lines.push(
|
|
11454
|
+
"",
|
|
11455
|
+
"You MUST call `claudestory_autonomous_guide` with:",
|
|
11456
|
+
`{"sessionId": "${sessionId}", "action": "resume"}`,
|
|
11457
|
+
"",
|
|
11458
|
+
"Do NOT do any other work until you have called this.",
|
|
11459
|
+
"Do NOT manually create tickets, issues, or handovers.",
|
|
11460
|
+
"The guide manages your workflow."
|
|
11461
|
+
);
|
|
11462
|
+
const content = lines.join("\n") + "\n";
|
|
11463
|
+
writeFileSync4(join17(rulesDir, MARKER_FILENAME), content, "utf-8");
|
|
11222
11464
|
} catch {
|
|
11223
|
-
return null;
|
|
11224
11465
|
}
|
|
11225
11466
|
}
|
|
11226
|
-
function
|
|
11227
|
-
|
|
11467
|
+
function removeResumeMarker(root) {
|
|
11468
|
+
try {
|
|
11469
|
+
const markerPath = join17(root, ".claude", "rules", MARKER_FILENAME);
|
|
11470
|
+
if (existsSync11(markerPath)) unlinkSync3(markerPath);
|
|
11471
|
+
} catch {
|
|
11472
|
+
}
|
|
11228
11473
|
}
|
|
11229
|
-
var
|
|
11230
|
-
|
|
11474
|
+
var MARKER_FILENAME;
|
|
11475
|
+
var init_resume_marker = __esm({
|
|
11476
|
+
"src/autonomous/resume-marker.ts"() {
|
|
11231
11477
|
"use strict";
|
|
11232
11478
|
init_esm_shims();
|
|
11479
|
+
MARKER_FILENAME = "autonomous-resume.md";
|
|
11233
11480
|
}
|
|
11234
11481
|
});
|
|
11235
11482
|
|
|
11236
|
-
// src/
|
|
11237
|
-
|
|
11238
|
-
|
|
11239
|
-
|
|
11240
|
-
|
|
11241
|
-
|
|
11242
|
-
|
|
11243
|
-
|
|
11244
|
-
|
|
11245
|
-
|
|
11246
|
-
|
|
11247
|
-
|
|
11248
|
-
|
|
11249
|
-
|
|
11250
|
-
|
|
11251
|
-
|
|
11252
|
-
|
|
11253
|
-
|
|
11254
|
-
|
|
11255
|
-
if (snap.issues) {
|
|
11256
|
-
opts.previousOpenIssueCount = snap.issues.filter((i) => i.status !== "resolved").length;
|
|
11483
|
+
// src/core/session-report-formatter.ts
|
|
11484
|
+
function formatSessionReport(data, format) {
|
|
11485
|
+
const { state, events, planContent, gitLog } = data;
|
|
11486
|
+
if (format === "json") {
|
|
11487
|
+
return JSON.stringify({
|
|
11488
|
+
ok: true,
|
|
11489
|
+
data: {
|
|
11490
|
+
summary: buildSummaryData(state),
|
|
11491
|
+
ticketProgression: state.completedTickets,
|
|
11492
|
+
reviewStats: state.reviews,
|
|
11493
|
+
events: events.events.slice(-50),
|
|
11494
|
+
malformedEventCount: events.malformedCount,
|
|
11495
|
+
contextPressure: state.contextPressure,
|
|
11496
|
+
git: {
|
|
11497
|
+
branch: state.git.branch,
|
|
11498
|
+
initHead: state.git.initHead,
|
|
11499
|
+
commits: gitLog
|
|
11500
|
+
},
|
|
11501
|
+
problems: buildProblems(state, events)
|
|
11257
11502
|
}
|
|
11258
|
-
}
|
|
11259
|
-
} catch {
|
|
11503
|
+
}, null, 2);
|
|
11260
11504
|
}
|
|
11261
|
-
|
|
11262
|
-
|
|
11263
|
-
|
|
11264
|
-
|
|
11505
|
+
const sections = [];
|
|
11506
|
+
sections.push(buildSummarySection(state));
|
|
11507
|
+
sections.push(buildTicketSection(state));
|
|
11508
|
+
sections.push(buildReviewSection(state));
|
|
11509
|
+
sections.push(buildEventSection(events));
|
|
11510
|
+
sections.push(buildPressureSection(state));
|
|
11511
|
+
sections.push(buildGitSection(state, gitLog));
|
|
11512
|
+
sections.push(buildProblemsSection(state, events));
|
|
11513
|
+
return sections.join("\n\n---\n\n");
|
|
11514
|
+
}
|
|
11515
|
+
function buildSummaryData(state) {
|
|
11516
|
+
return {
|
|
11517
|
+
sessionId: state.sessionId,
|
|
11518
|
+
mode: state.mode ?? "auto",
|
|
11519
|
+
recipe: state.recipe,
|
|
11520
|
+
status: state.status,
|
|
11521
|
+
terminationReason: state.terminationReason,
|
|
11522
|
+
startedAt: state.startedAt,
|
|
11523
|
+
lastGuideCall: state.lastGuideCall,
|
|
11524
|
+
guideCallCount: state.guideCallCount,
|
|
11525
|
+
ticketsCompleted: state.completedTickets.length
|
|
11526
|
+
};
|
|
11527
|
+
}
|
|
11528
|
+
function buildSummarySection(state) {
|
|
11529
|
+
const duration = state.startedAt && state.lastGuideCall ? formatDuration(state.startedAt, state.lastGuideCall) : "unknown";
|
|
11530
|
+
return [
|
|
11531
|
+
"## Session Summary",
|
|
11532
|
+
"",
|
|
11533
|
+
`- **ID:** ${state.sessionId}`,
|
|
11534
|
+
`- **Mode:** ${state.mode ?? "auto"}`,
|
|
11535
|
+
`- **Recipe:** ${state.recipe}`,
|
|
11536
|
+
`- **Status:** ${state.status}${state.terminationReason ? ` (${state.terminationReason})` : ""}`,
|
|
11537
|
+
`- **Duration:** ${duration}`,
|
|
11538
|
+
`- **Guide calls:** ${state.guideCallCount}`,
|
|
11539
|
+
`- **Tickets completed:** ${state.completedTickets.length}`
|
|
11540
|
+
].join("\n");
|
|
11541
|
+
}
|
|
11542
|
+
function buildTicketSection(state) {
|
|
11543
|
+
if (state.completedTickets.length === 0) {
|
|
11544
|
+
const current = state.ticket;
|
|
11545
|
+
if (current) {
|
|
11546
|
+
return [
|
|
11547
|
+
"## Ticket Progression",
|
|
11548
|
+
"",
|
|
11549
|
+
`In progress: **${current.id}** \u2014 ${current.title} (risk: ${current.risk ?? "unknown"})`
|
|
11550
|
+
].join("\n");
|
|
11551
|
+
}
|
|
11552
|
+
return "## Ticket Progression\n\nNo tickets completed.";
|
|
11553
|
+
}
|
|
11554
|
+
const lines = ["## Ticket Progression", ""];
|
|
11555
|
+
for (const t of state.completedTickets) {
|
|
11556
|
+
const risk = t.realizedRisk ? `${t.risk ?? "?"} \u2192 ${t.realizedRisk}` : t.risk ?? "unknown";
|
|
11557
|
+
const duration = t.startedAt && t.completedAt ? formatDuration(t.startedAt, t.completedAt) : null;
|
|
11558
|
+
const durationPart = duration ? ` | duration: ${duration}` : "";
|
|
11559
|
+
lines.push(`- **${t.id}:** ${t.title} | risk: ${risk}${durationPart} | commit: \`${t.commitHash ?? "?"}\``);
|
|
11560
|
+
}
|
|
11561
|
+
return lines.join("\n");
|
|
11562
|
+
}
|
|
11563
|
+
function buildReviewSection(state) {
|
|
11564
|
+
const plan = state.reviews.plan;
|
|
11565
|
+
const code = state.reviews.code;
|
|
11566
|
+
if (plan.length === 0 && code.length === 0) {
|
|
11567
|
+
return "## Review Stats\n\nNo reviews recorded.";
|
|
11568
|
+
}
|
|
11569
|
+
const lines = ["## Review Stats", ""];
|
|
11570
|
+
if (plan.length > 0) {
|
|
11571
|
+
lines.push(`**Plan reviews:** ${plan.length} round(s)`);
|
|
11572
|
+
for (const r of plan) {
|
|
11573
|
+
lines.push(` - Round ${r.round}: ${r.verdict} (${r.findingCount} findings, ${r.criticalCount} critical, ${r.majorCount} major) \u2014 ${r.reviewer}`);
|
|
11574
|
+
}
|
|
11575
|
+
}
|
|
11576
|
+
if (code.length > 0) {
|
|
11577
|
+
lines.push(`**Code reviews:** ${code.length} round(s)`);
|
|
11578
|
+
for (const r of code) {
|
|
11579
|
+
lines.push(` - Round ${r.round}: ${r.verdict} (${r.findingCount} findings, ${r.criticalCount} critical, ${r.majorCount} major) \u2014 ${r.reviewer}`);
|
|
11580
|
+
}
|
|
11581
|
+
}
|
|
11582
|
+
const totalFindings = [...plan, ...code].reduce((sum, r) => sum + r.findingCount, 0);
|
|
11583
|
+
lines.push("", `**Total findings:** ${totalFindings}`);
|
|
11584
|
+
return lines.join("\n");
|
|
11585
|
+
}
|
|
11586
|
+
function buildEventSection(events) {
|
|
11587
|
+
if (events.events.length === 0 && events.malformedCount === 0) {
|
|
11588
|
+
return "## Event Timeline\n\nNot available.";
|
|
11589
|
+
}
|
|
11590
|
+
const capped = events.events.slice(-50);
|
|
11591
|
+
const omitted = events.events.length - capped.length;
|
|
11592
|
+
const lines = ["## Event Timeline", ""];
|
|
11593
|
+
if (omitted > 0) {
|
|
11594
|
+
lines.push(`*${omitted} earlier events omitted*`, "");
|
|
11595
|
+
}
|
|
11596
|
+
for (const e of capped) {
|
|
11597
|
+
const ts = e.timestamp ? e.timestamp.slice(11, 19) : "??:??:??";
|
|
11598
|
+
const detail = e.data ? Object.entries(e.data).map(([k, v]) => `${k}=${JSON.stringify(v)}`).join(" ") : "";
|
|
11599
|
+
lines.push(`- \`${ts}\` [${e.type}] ${detail}`.trimEnd());
|
|
11600
|
+
}
|
|
11601
|
+
if (events.malformedCount > 0) {
|
|
11602
|
+
lines.push("", `*${events.malformedCount} malformed event line(s) skipped*`);
|
|
11603
|
+
}
|
|
11604
|
+
return lines.join("\n");
|
|
11605
|
+
}
|
|
11606
|
+
function buildPressureSection(state) {
|
|
11607
|
+
const p = state.contextPressure;
|
|
11608
|
+
return [
|
|
11609
|
+
"## Context Pressure",
|
|
11610
|
+
"",
|
|
11611
|
+
`- **Level:** ${p.level}`,
|
|
11612
|
+
`- **Guide calls:** ${p.guideCallCount}`,
|
|
11613
|
+
`- **Tickets completed:** ${p.ticketsCompleted}`,
|
|
11614
|
+
`- **Compactions:** ${p.compactionCount}`,
|
|
11615
|
+
`- **Events log:** ${p.eventsLogBytes} bytes`
|
|
11616
|
+
].join("\n");
|
|
11617
|
+
}
|
|
11618
|
+
function buildGitSection(state, gitLog) {
|
|
11619
|
+
const lines = [
|
|
11620
|
+
"## Git Summary",
|
|
11621
|
+
"",
|
|
11622
|
+
`- **Branch:** ${state.git.branch ?? "unknown"}`,
|
|
11623
|
+
`- **Init HEAD:** \`${state.git.initHead ?? "?"}\``,
|
|
11624
|
+
`- **Expected HEAD:** \`${state.git.expectedHead ?? "?"}\``
|
|
11625
|
+
];
|
|
11626
|
+
if (gitLog && gitLog.length > 0) {
|
|
11627
|
+
lines.push("", "**Commits:**");
|
|
11628
|
+
for (const c of gitLog) {
|
|
11629
|
+
lines.push(`- ${c}`);
|
|
11630
|
+
}
|
|
11631
|
+
} else {
|
|
11632
|
+
lines.push("", "Commits: Not available.");
|
|
11633
|
+
}
|
|
11634
|
+
return lines.join("\n");
|
|
11635
|
+
}
|
|
11636
|
+
function buildProblems(state, events) {
|
|
11637
|
+
const problems = [];
|
|
11638
|
+
if (state.terminationReason && state.terminationReason !== "normal") {
|
|
11639
|
+
problems.push(`Abnormal termination: ${state.terminationReason}`);
|
|
11640
|
+
}
|
|
11641
|
+
if (events.malformedCount > 0) {
|
|
11642
|
+
problems.push(`${events.malformedCount} malformed event line(s) in events.log`);
|
|
11643
|
+
}
|
|
11644
|
+
for (const e of events.events) {
|
|
11645
|
+
if (e.type.includes("error") || e.type.includes("exhaustion")) {
|
|
11646
|
+
problems.push(`[${e.type}] ${e.timestamp ?? ""} ${JSON.stringify(e.data)}`);
|
|
11647
|
+
} else if (e.data?.result === "exhaustion") {
|
|
11648
|
+
problems.push(`[${e.type}] exhaustion at ${e.timestamp ?? ""}`);
|
|
11649
|
+
}
|
|
11650
|
+
}
|
|
11651
|
+
if (state.deferralsUnfiled) {
|
|
11652
|
+
problems.push("Session has unfiled deferrals");
|
|
11653
|
+
}
|
|
11654
|
+
return problems;
|
|
11655
|
+
}
|
|
11656
|
+
function buildProblemsSection(state, events) {
|
|
11657
|
+
const problems = buildProblems(state, events);
|
|
11658
|
+
if (problems.length === 0) {
|
|
11659
|
+
return "## Problems\n\nNone detected.";
|
|
11660
|
+
}
|
|
11661
|
+
return ["## Problems", "", ...problems.map((p) => `- ${p}`)].join("\n");
|
|
11662
|
+
}
|
|
11663
|
+
function formatCompactReport(data) {
|
|
11664
|
+
const { state, remainingWork } = data;
|
|
11665
|
+
const endTime = data.endedAt ?? state.lastGuideCall ?? (/* @__PURE__ */ new Date()).toISOString();
|
|
11666
|
+
const duration = state.startedAt ? formatDuration(state.startedAt, endTime) : "unknown";
|
|
11667
|
+
const ticketCount = state.completedTickets.length;
|
|
11668
|
+
const issueCount = (state.resolvedIssues ?? []).length;
|
|
11669
|
+
const reviewRounds = state.reviews.plan.length + state.reviews.code.length;
|
|
11670
|
+
const totalFindings = [...state.reviews.plan, ...state.reviews.code].reduce((s, r) => s + r.findingCount, 0);
|
|
11671
|
+
const compactions = state.contextPressure?.compactionCount ?? 0;
|
|
11672
|
+
const lines = [
|
|
11673
|
+
"## Session Report",
|
|
11674
|
+
"",
|
|
11675
|
+
`**Duration:** ${duration} | **Tickets:** ${ticketCount} | **Issues:** ${issueCount} | **Reviews:** ${reviewRounds} rounds (${totalFindings} findings) | **Compactions:** ${compactions}`
|
|
11676
|
+
];
|
|
11677
|
+
if (ticketCount > 0) {
|
|
11678
|
+
lines.push("", "### Completed", "| Ticket | Title | Duration |", "|--------|-------|----------|");
|
|
11679
|
+
for (const t of state.completedTickets) {
|
|
11680
|
+
const ticketDuration = t.startedAt && t.completedAt ? formatDuration(t.startedAt, t.completedAt) : "--";
|
|
11681
|
+
const safeTitle = (t.title ?? "").replace(/\|/g, "\\|");
|
|
11682
|
+
lines.push(`| ${t.id} | ${safeTitle} | ${ticketDuration} |`);
|
|
11683
|
+
}
|
|
11684
|
+
const timings = state.completedTickets.filter((t) => t.startedAt && t.completedAt).map((t) => new Date(t.completedAt).getTime() - new Date(t.startedAt).getTime());
|
|
11685
|
+
if (timings.length > 0) {
|
|
11686
|
+
const avgMs = timings.reduce((a, b) => a + b, 0) / timings.length;
|
|
11687
|
+
const avgMins = Math.round(avgMs / 6e4);
|
|
11688
|
+
lines.push("", `**Avg time per ticket:** ${avgMins}m`);
|
|
11689
|
+
}
|
|
11690
|
+
}
|
|
11691
|
+
if (remainingWork && (remainingWork.tickets.length > 0 || remainingWork.issues.length > 0)) {
|
|
11692
|
+
lines.push("", "### What's Left");
|
|
11693
|
+
for (const t of remainingWork.tickets) {
|
|
11694
|
+
lines.push(`- ${t.id}: ${t.title} (unblocked)`);
|
|
11695
|
+
}
|
|
11696
|
+
for (const i of remainingWork.issues) {
|
|
11697
|
+
lines.push(`- ${i.id}: ${i.title} (${i.severity})`);
|
|
11698
|
+
}
|
|
11699
|
+
}
|
|
11700
|
+
return lines.join("\n");
|
|
11701
|
+
}
|
|
11702
|
+
function formatDuration(start, end) {
|
|
11703
|
+
try {
|
|
11704
|
+
const ms = new Date(end).getTime() - new Date(start).getTime();
|
|
11705
|
+
if (isNaN(ms) || ms < 0) return "unknown";
|
|
11706
|
+
const mins = Math.floor(ms / 6e4);
|
|
11707
|
+
if (mins < 60) return `${mins}m`;
|
|
11708
|
+
const hours = Math.floor(mins / 60);
|
|
11709
|
+
return `${hours}h ${mins % 60}m`;
|
|
11710
|
+
} catch {
|
|
11711
|
+
return "unknown";
|
|
11712
|
+
}
|
|
11713
|
+
}
|
|
11714
|
+
var init_session_report_formatter = __esm({
|
|
11715
|
+
"src/core/session-report-formatter.ts"() {
|
|
11716
|
+
"use strict";
|
|
11717
|
+
init_esm_shims();
|
|
11718
|
+
}
|
|
11719
|
+
});
|
|
11720
|
+
|
|
11721
|
+
// src/autonomous/stages/handover.ts
|
|
11722
|
+
import { writeFileSync as writeFileSync5 } from "fs";
|
|
11723
|
+
import { join as join18 } from "path";
|
|
11724
|
+
var HandoverStage;
|
|
11725
|
+
var init_handover2 = __esm({
|
|
11726
|
+
"src/autonomous/stages/handover.ts"() {
|
|
11727
|
+
"use strict";
|
|
11728
|
+
init_esm_shims();
|
|
11729
|
+
init_handover();
|
|
11730
|
+
init_git_inspector();
|
|
11731
|
+
init_resume_marker();
|
|
11732
|
+
init_session_report_formatter();
|
|
11733
|
+
init_project_loader();
|
|
11734
|
+
init_queries();
|
|
11735
|
+
HandoverStage = class {
|
|
11736
|
+
id = "HANDOVER";
|
|
11737
|
+
async enter(ctx) {
|
|
11738
|
+
const ticketsDone = ctx.state.completedTickets.length;
|
|
11739
|
+
const issuesDone = (ctx.state.resolvedIssues ?? []).length;
|
|
11740
|
+
return {
|
|
11741
|
+
instruction: [
|
|
11742
|
+
`# Session Complete \u2014 ${ticketsDone} ticket(s) and ${issuesDone} issue(s) done`,
|
|
11743
|
+
"",
|
|
11744
|
+
"Write a session handover summarizing what was accomplished, decisions made, and what's next.",
|
|
11745
|
+
"",
|
|
11746
|
+
'Call me with completedAction: "handover_written" and include the content in handoverContent.'
|
|
11747
|
+
].join("\n"),
|
|
11748
|
+
reminders: [
|
|
11749
|
+
"Before recording a new lesson, call claudestory_lesson_list to check existing lessons. Then choose: create (new insight), reinforce (existing lesson confirmed), update (refine wording), or skip."
|
|
11750
|
+
],
|
|
11751
|
+
transitionedFrom: ctx.state.previousState ?? void 0
|
|
11752
|
+
};
|
|
11753
|
+
}
|
|
11754
|
+
async report(ctx, report) {
|
|
11755
|
+
const content = report.handoverContent;
|
|
11756
|
+
if (!content) {
|
|
11757
|
+
return { action: "retry", instruction: "Missing handoverContent. Write the handover and include it in the report." };
|
|
11758
|
+
}
|
|
11759
|
+
let handoverFailed = false;
|
|
11760
|
+
try {
|
|
11761
|
+
await handleHandoverCreate(content, "auto-session", "md", ctx.root);
|
|
11762
|
+
} catch {
|
|
11763
|
+
handoverFailed = true;
|
|
11764
|
+
try {
|
|
11765
|
+
const fallbackPath = join18(ctx.dir, "handover-fallback.md");
|
|
11766
|
+
writeFileSync5(fallbackPath, content, "utf-8");
|
|
11767
|
+
} catch {
|
|
11768
|
+
}
|
|
11769
|
+
}
|
|
11770
|
+
let stashPopFailed = false;
|
|
11771
|
+
const autoStash = ctx.state.git.autoStash;
|
|
11772
|
+
if (autoStash) {
|
|
11773
|
+
const popResult = await gitStashPop(ctx.root, autoStash.ref);
|
|
11774
|
+
if (!popResult.ok) {
|
|
11775
|
+
stashPopFailed = true;
|
|
11776
|
+
}
|
|
11777
|
+
}
|
|
11778
|
+
await ctx.drainDeferrals();
|
|
11779
|
+
const hasUnfiled = (ctx.state.pendingDeferrals ?? []).length > 0;
|
|
11780
|
+
ctx.writeState({
|
|
11781
|
+
state: "SESSION_END",
|
|
11782
|
+
previousState: "HANDOVER",
|
|
11783
|
+
status: "completed",
|
|
11784
|
+
terminationReason: "normal",
|
|
11785
|
+
deferralsUnfiled: hasUnfiled
|
|
11786
|
+
});
|
|
11787
|
+
ctx.appendEvent("session_end", {
|
|
11788
|
+
ticketsCompleted: ctx.state.completedTickets.length,
|
|
11789
|
+
issuesResolved: (ctx.state.resolvedIssues ?? []).length,
|
|
11790
|
+
handoverFailed
|
|
11791
|
+
});
|
|
11792
|
+
removeResumeMarker(ctx.root);
|
|
11793
|
+
let reportSection = "";
|
|
11794
|
+
try {
|
|
11795
|
+
const { state: projectState } = await loadProject(ctx.root);
|
|
11796
|
+
const nextResult = nextTickets(projectState, 5);
|
|
11797
|
+
const openIssues = projectState.issues.filter((i) => i.status === "open" || i.status === "inprogress").slice(0, 5);
|
|
11798
|
+
const remainingWork = {
|
|
11799
|
+
tickets: nextResult.kind === "found" ? nextResult.candidates.map((c) => ({ id: c.ticket.id, title: c.ticket.title })) : [],
|
|
11800
|
+
issues: openIssues.map((i) => ({ id: i.id, title: i.title, severity: i.severity }))
|
|
11801
|
+
};
|
|
11802
|
+
reportSection = "\n\n" + formatCompactReport({ state: ctx.state, endedAt: (/* @__PURE__ */ new Date()).toISOString(), remainingWork });
|
|
11803
|
+
} catch {
|
|
11804
|
+
}
|
|
11805
|
+
const ticketsDone = ctx.state.completedTickets.length;
|
|
11806
|
+
const issuesDone = (ctx.state.resolvedIssues ?? []).length;
|
|
11807
|
+
const resolvedList = (ctx.state.resolvedIssues ?? []).map((id) => `- ${id} (resolved)`).join("\n");
|
|
11808
|
+
return {
|
|
11809
|
+
action: "advance",
|
|
11810
|
+
result: {
|
|
11811
|
+
instruction: [
|
|
11812
|
+
"# Session Complete",
|
|
11813
|
+
"",
|
|
11814
|
+
`${ticketsDone} ticket(s) and ${issuesDone} issue(s) completed.${handoverFailed ? " Handover creation failed \u2014 fallback saved to session directory." : " Handover written."}${stashPopFailed ? " Auto-stash pop failed \u2014 run `git stash pop` manually." : ""} Session ended.`,
|
|
11815
|
+
"",
|
|
11816
|
+
ctx.state.completedTickets.map((t) => `- ${t.id}${t.title ? `: ${t.title}` : ""} (${t.commitHash ?? "no commit"})`).join("\n"),
|
|
11817
|
+
...resolvedList ? [resolvedList] : []
|
|
11818
|
+
].join("\n") + reportSection,
|
|
11819
|
+
reminders: [],
|
|
11820
|
+
transitionedFrom: "HANDOVER"
|
|
11821
|
+
}
|
|
11822
|
+
};
|
|
11823
|
+
}
|
|
11824
|
+
};
|
|
11825
|
+
}
|
|
11826
|
+
});
|
|
11827
|
+
|
|
11828
|
+
// src/autonomous/stages/index.ts
|
|
11829
|
+
var init_stages = __esm({
|
|
11830
|
+
"src/autonomous/stages/index.ts"() {
|
|
11831
|
+
"use strict";
|
|
11832
|
+
init_esm_shims();
|
|
11833
|
+
init_registry();
|
|
11834
|
+
init_pick_ticket();
|
|
11835
|
+
init_plan();
|
|
11836
|
+
init_plan_review();
|
|
11837
|
+
init_implement();
|
|
11838
|
+
init_write_tests();
|
|
11839
|
+
init_test();
|
|
11840
|
+
init_code_review();
|
|
11841
|
+
init_build();
|
|
11842
|
+
init_verify();
|
|
11843
|
+
init_finalize();
|
|
11844
|
+
init_complete();
|
|
11845
|
+
init_lesson_capture();
|
|
11846
|
+
init_issue_fix();
|
|
11847
|
+
init_issue_sweep();
|
|
11848
|
+
init_handover2();
|
|
11849
|
+
registerStage(new PickTicketStage());
|
|
11850
|
+
registerStage(new PlanStage());
|
|
11851
|
+
registerStage(new PlanReviewStage());
|
|
11852
|
+
registerStage(new ImplementStage());
|
|
11853
|
+
registerStage(new WriteTestsStage());
|
|
11854
|
+
registerStage(new TestStage());
|
|
11855
|
+
registerStage(new CodeReviewStage());
|
|
11856
|
+
registerStage(new BuildStage());
|
|
11857
|
+
registerStage(new VerifyStage());
|
|
11858
|
+
registerStage(new FinalizeStage());
|
|
11859
|
+
registerStage(new CompleteStage());
|
|
11860
|
+
registerStage(new LessonCaptureStage());
|
|
11861
|
+
registerStage(new IssueFixStage());
|
|
11862
|
+
registerStage(new IssueSweepStage());
|
|
11863
|
+
registerStage(new HandoverStage());
|
|
11864
|
+
}
|
|
11865
|
+
});
|
|
11866
|
+
|
|
11867
|
+
// src/autonomous/version-check.ts
|
|
11868
|
+
import { readFileSync as readFileSync9 } from "fs";
|
|
11869
|
+
import { join as join19, dirname as dirname4 } from "path";
|
|
11870
|
+
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
11871
|
+
function checkVersionMismatch(runningVersion, installedVersion) {
|
|
11872
|
+
if (!installedVersion) return null;
|
|
11873
|
+
if (runningVersion === "0.0.0-dev") return null;
|
|
11874
|
+
if (runningVersion === installedVersion) return null;
|
|
11875
|
+
return `claudestory MCP server is running v${runningVersion} but v${installedVersion} is installed. Restart Claude Code to load the updated version.`;
|
|
11876
|
+
}
|
|
11877
|
+
function getInstalledVersion() {
|
|
11878
|
+
try {
|
|
11879
|
+
const thisFile = fileURLToPath3(import.meta.url);
|
|
11880
|
+
const candidates = [
|
|
11881
|
+
join19(dirname4(thisFile), "..", "..", "package.json"),
|
|
11882
|
+
join19(dirname4(thisFile), "..", "package.json")
|
|
11883
|
+
];
|
|
11884
|
+
for (const candidate of candidates) {
|
|
11885
|
+
try {
|
|
11886
|
+
const raw = readFileSync9(candidate, "utf-8");
|
|
11887
|
+
const pkg = JSON.parse(raw);
|
|
11888
|
+
if (pkg.version) return pkg.version;
|
|
11889
|
+
} catch {
|
|
11890
|
+
}
|
|
11891
|
+
}
|
|
11892
|
+
return null;
|
|
11893
|
+
} catch {
|
|
11894
|
+
return null;
|
|
11895
|
+
}
|
|
11896
|
+
}
|
|
11897
|
+
function getRunningVersion() {
|
|
11898
|
+
return "0.1.63";
|
|
11899
|
+
}
|
|
11900
|
+
var init_version_check = __esm({
|
|
11901
|
+
"src/autonomous/version-check.ts"() {
|
|
11902
|
+
"use strict";
|
|
11903
|
+
init_esm_shims();
|
|
11904
|
+
}
|
|
11905
|
+
});
|
|
11906
|
+
|
|
11907
|
+
// src/autonomous/guide.ts
|
|
11908
|
+
import { readFileSync as readFileSync10, writeFileSync as writeFileSync6, readdirSync as readdirSync4 } from "fs";
|
|
11909
|
+
import { join as join20 } from "path";
|
|
11910
|
+
function buildGuideRecommendOptions(root) {
|
|
11911
|
+
const opts = {};
|
|
11912
|
+
try {
|
|
11913
|
+
const handoversDir = join20(root, ".story", "handovers");
|
|
11914
|
+
const files = readdirSync4(handoversDir, "utf-8").filter((f) => f.endsWith(".md")).sort();
|
|
11915
|
+
if (files.length > 0) {
|
|
11916
|
+
opts.latestHandoverContent = readFileSync10(join20(handoversDir, files[files.length - 1]), "utf-8");
|
|
11917
|
+
}
|
|
11918
|
+
} catch {
|
|
11919
|
+
}
|
|
11920
|
+
try {
|
|
11921
|
+
const snapshotsDir = join20(root, ".story", "snapshots");
|
|
11922
|
+
const snapFiles = readdirSync4(snapshotsDir, "utf-8").filter((f) => f.endsWith(".json")).sort();
|
|
11923
|
+
if (snapFiles.length > 0) {
|
|
11924
|
+
const raw = readFileSync10(join20(snapshotsDir, snapFiles[snapFiles.length - 1]), "utf-8");
|
|
11925
|
+
const snap = JSON.parse(raw);
|
|
11926
|
+
if (snap.issues) {
|
|
11927
|
+
opts.previousOpenIssueCount = snap.issues.filter((i) => i.status !== "resolved").length;
|
|
11928
|
+
}
|
|
11929
|
+
}
|
|
11930
|
+
} catch {
|
|
11931
|
+
}
|
|
11932
|
+
return opts;
|
|
11933
|
+
}
|
|
11934
|
+
async function buildTargetedResumeResult(root, state, dir) {
|
|
11935
|
+
const remaining = getRemainingTargets(state);
|
|
11265
11936
|
if (remaining.length === 0) {
|
|
11266
11937
|
return { instruction: "", stuck: false, allDone: true, candidatesText: "" };
|
|
11267
11938
|
}
|
|
@@ -11328,6 +11999,33 @@ async function recoverPendingMutation(dir, state, root) {
|
|
|
11328
11999
|
const mutation = state.pendingProjectMutation;
|
|
11329
12000
|
if (!mutation || typeof mutation !== "object") return state;
|
|
11330
12001
|
const m = mutation;
|
|
12002
|
+
if (m.type === "issue_update") {
|
|
12003
|
+
const targetId2 = m.target;
|
|
12004
|
+
const targetValue2 = m.value;
|
|
12005
|
+
const expectedCurrent2 = m.expectedCurrent;
|
|
12006
|
+
try {
|
|
12007
|
+
const { loadProject: loadProject2 } = await Promise.resolve().then(() => (init_project_loader(), project_loader_exports));
|
|
12008
|
+
const { state: projectState } = await loadProject2(root);
|
|
12009
|
+
const issue = projectState.issues.find((i) => i.id === targetId2);
|
|
12010
|
+
if (issue) {
|
|
12011
|
+
if (issue.status === targetValue2) {
|
|
12012
|
+
} else if (expectedCurrent2 && issue.status === expectedCurrent2) {
|
|
12013
|
+
const { handleIssueUpdate: handleIssueUpdate2 } = await Promise.resolve().then(() => (init_issue2(), issue_exports));
|
|
12014
|
+
await handleIssueUpdate2(targetId2, { status: targetValue2 }, "json", root);
|
|
12015
|
+
} else {
|
|
12016
|
+
appendEvent(dir, {
|
|
12017
|
+
rev: state.revision,
|
|
12018
|
+
type: "mutation_conflict",
|
|
12019
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
12020
|
+
data: { targetId: targetId2, expected: expectedCurrent2, actual: issue.status, transitionId: m.transitionId }
|
|
12021
|
+
});
|
|
12022
|
+
}
|
|
12023
|
+
}
|
|
12024
|
+
} catch {
|
|
12025
|
+
}
|
|
12026
|
+
const cleared2 = { ...state, pendingProjectMutation: null };
|
|
12027
|
+
return writeSessionSync(dir, cleared2);
|
|
12028
|
+
}
|
|
11331
12029
|
if (m.type !== "ticket_update") return state;
|
|
11332
12030
|
const targetId = m.target;
|
|
11333
12031
|
const targetValue = m.value;
|
|
@@ -11597,6 +12295,7 @@ async function handleStart(root, args) {
|
|
|
11597
12295
|
reviewBackends: sessionConfig.reviewBackends,
|
|
11598
12296
|
stages: sessionConfig.stageOverrides
|
|
11599
12297
|
});
|
|
12298
|
+
removeResumeMarker(root);
|
|
11600
12299
|
const session = createSession(root, recipe, wsId, sessionConfig);
|
|
11601
12300
|
const dir = sessionDir(root, session.sessionId);
|
|
11602
12301
|
try {
|
|
@@ -11719,7 +12418,7 @@ Staged: ${stagedResult.data.join(", ")}`
|
|
|
11719
12418
|
const passMatch = combined.match(/(\d+)\s*pass/i);
|
|
11720
12419
|
const failMatch = combined.match(/(\d+)\s*fail/i);
|
|
11721
12420
|
const passCount = passMatch ? parseInt(passMatch[1], 10) : -1;
|
|
11722
|
-
const failCount = failMatch ? parseInt(failMatch[1], 10) : -1;
|
|
12421
|
+
const failCount = failMatch ? parseInt(failMatch[1], 10) : exitCode === 0 && passCount > 0 ? 0 : -1;
|
|
11723
12422
|
const output = combined.slice(-500);
|
|
11724
12423
|
updated = { ...updated, testBaseline: { exitCode, passCount, failCount, summary: output } };
|
|
11725
12424
|
if (writeTestsEnabled && failCount < 0) {
|
|
@@ -11759,7 +12458,7 @@ Staged: ${stagedResult.data.join(", ")}`
|
|
|
11759
12458
|
}
|
|
11760
12459
|
}
|
|
11761
12460
|
const { state: projectState, warnings } = await loadProject(root);
|
|
11762
|
-
const handoversDir =
|
|
12461
|
+
const handoversDir = join20(root, ".story", "handovers");
|
|
11763
12462
|
const ctx = { state: projectState, warnings, root, handoversDir, format: "md" };
|
|
11764
12463
|
let handoverText = "";
|
|
11765
12464
|
try {
|
|
@@ -11770,13 +12469,13 @@ Staged: ${stagedResult.data.join(", ")}`
|
|
|
11770
12469
|
let recapText = "";
|
|
11771
12470
|
try {
|
|
11772
12471
|
const snapshotInfo = await loadLatestSnapshot(root);
|
|
11773
|
-
const recap = buildRecap(projectState, snapshotInfo);
|
|
12472
|
+
const recap = await buildRecap(projectState, snapshotInfo, root);
|
|
11774
12473
|
if (recap.changes) {
|
|
11775
12474
|
recapText = "Changes since last snapshot available.";
|
|
11776
12475
|
}
|
|
11777
12476
|
} catch {
|
|
11778
12477
|
}
|
|
11779
|
-
const rulesText = readFileSafe2(
|
|
12478
|
+
const rulesText = readFileSafe2(join20(root, "RULES.md"));
|
|
11780
12479
|
const lessonDigest = buildLessonDigest(projectState.lessons);
|
|
11781
12480
|
const digestParts = [
|
|
11782
12481
|
handoverText ? `## Recent Handovers
|
|
@@ -11792,7 +12491,7 @@ ${rulesText}` : "",
|
|
|
11792
12491
|
].filter(Boolean);
|
|
11793
12492
|
const digest = digestParts.join("\n\n---\n\n");
|
|
11794
12493
|
try {
|
|
11795
|
-
|
|
12494
|
+
writeFileSync6(join20(dir, "context-digest.md"), digest, "utf-8");
|
|
11796
12495
|
} catch {
|
|
11797
12496
|
}
|
|
11798
12497
|
if (mode !== "auto" && args.ticketId) {
|
|
@@ -12189,8 +12888,18 @@ async function handleResume(root, args) {
|
|
|
12189
12888
|
`Cannot validate git state for session ${args.sessionId}. Check git status and try "resume" again, or run "claudestory session stop ${args.sessionId}" to end the session.`
|
|
12190
12889
|
));
|
|
12191
12890
|
}
|
|
12891
|
+
let ownCommitDrift = false;
|
|
12192
12892
|
if (expectedHead && headResult.data.hash !== expectedHead) {
|
|
12193
|
-
const
|
|
12893
|
+
const ancestorCheck = await gitIsAncestor(root, expectedHead, headResult.data.hash);
|
|
12894
|
+
if (ancestorCheck.ok && ancestorCheck.data) {
|
|
12895
|
+
ownCommitDrift = true;
|
|
12896
|
+
}
|
|
12897
|
+
}
|
|
12898
|
+
if (expectedHead && headResult.data.hash !== expectedHead && !ownCommitDrift) {
|
|
12899
|
+
let mapping = RECOVERY_MAPPING[resumeState] ?? { state: "PICK_TICKET", resetPlan: false, resetCode: false };
|
|
12900
|
+
if (info.state.currentIssue && resumeState === "CODE_REVIEW") {
|
|
12901
|
+
mapping = { state: "ISSUE_FIX", resetPlan: false, resetCode: true };
|
|
12902
|
+
}
|
|
12194
12903
|
const recoveryReviews = {
|
|
12195
12904
|
plan: mapping.resetPlan ? [] : info.state.reviews.plan,
|
|
12196
12905
|
code: mapping.resetCode ? [] : info.state.reviews.code
|
|
@@ -12218,6 +12927,19 @@ async function handleResume(root, args) {
|
|
|
12218
12927
|
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
12219
12928
|
data: { drift: true, previousState: resumeState, recoveryState: mapping.state, expectedHead, actualHead: headResult.data.hash, ticketId: info.state.ticket?.id }
|
|
12220
12929
|
});
|
|
12930
|
+
appendEvent(info.dir, {
|
|
12931
|
+
rev: driftWritten.revision,
|
|
12932
|
+
type: "resumed",
|
|
12933
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
12934
|
+
data: {
|
|
12935
|
+
preCompactState: resumeState,
|
|
12936
|
+
compactionCount: driftWritten.contextPressure?.compactionCount ?? 0,
|
|
12937
|
+
ticketId: info.state.ticket?.id ?? null,
|
|
12938
|
+
headMatch: false,
|
|
12939
|
+
recoveryState: mapping.state
|
|
12940
|
+
}
|
|
12941
|
+
});
|
|
12942
|
+
removeResumeMarker(root);
|
|
12221
12943
|
const driftPreamble = `**HEAD changed during compaction** (expected ${expectedHead.slice(0, 8)}, got ${headResult.data.hash.slice(0, 8)}). Review state invalidated.
|
|
12222
12944
|
|
|
12223
12945
|
`;
|
|
@@ -12290,6 +13012,29 @@ async function handleResume(root, args) {
|
|
|
12290
13012
|
reminders: ["Re-implement and verify before re-submitting for code review."]
|
|
12291
13013
|
});
|
|
12292
13014
|
}
|
|
13015
|
+
if (mapping.state === "ISSUE_FIX") {
|
|
13016
|
+
const issueFixStage = getStage("ISSUE_FIX");
|
|
13017
|
+
if (issueFixStage) {
|
|
13018
|
+
const recipe = resolveRecipeFromState(driftWritten);
|
|
13019
|
+
const ctx = new StageContext(root, info.dir, driftWritten, recipe);
|
|
13020
|
+
const enterResult = await issueFixStage.enter(ctx);
|
|
13021
|
+
if (isStageAdvance(enterResult)) {
|
|
13022
|
+
return processAdvance(ctx, issueFixStage, enterResult);
|
|
13023
|
+
}
|
|
13024
|
+
return guideResult(ctx.state, "ISSUE_FIX", {
|
|
13025
|
+
instruction: [
|
|
13026
|
+
"# Resumed After Compact \u2014 HEAD Mismatch",
|
|
13027
|
+
"",
|
|
13028
|
+
`${driftPreamble}Recovered to **ISSUE_FIX**. Re-fix the issue and mark resolved.`,
|
|
13029
|
+
"",
|
|
13030
|
+
"---",
|
|
13031
|
+
"",
|
|
13032
|
+
enterResult.instruction
|
|
13033
|
+
].join("\n"),
|
|
13034
|
+
reminders: enterResult.reminders ?? []
|
|
13035
|
+
});
|
|
13036
|
+
}
|
|
13037
|
+
}
|
|
12293
13038
|
return guideResult(driftWritten, mapping.state, {
|
|
12294
13039
|
instruction: `# Resumed After Compact \u2014 HEAD Mismatch
|
|
12295
13040
|
|
|
@@ -12311,8 +13056,23 @@ ${driftPreamble}Recovered to state: **${mapping.state}**. Continue from here.`,
|
|
|
12311
13056
|
compactPreparedAt: null,
|
|
12312
13057
|
resumeBlocked: false,
|
|
12313
13058
|
guideCallCount: 0,
|
|
12314
|
-
contextPressure: { ...resumePressure, level: evaluatePressure({ ...info.state, guideCallCount: 0, contextPressure: resumePressure }) }
|
|
13059
|
+
contextPressure: { ...resumePressure, level: evaluatePressure({ ...info.state, guideCallCount: 0, contextPressure: resumePressure }) },
|
|
13060
|
+
// T-184: Update expectedHead on own-commit drift (mergeBase stays at branch-off point)
|
|
13061
|
+
...ownCommitDrift ? { git: { ...info.state.git, expectedHead: headResult.data.hash } } : {}
|
|
12315
13062
|
});
|
|
13063
|
+
appendEvent(info.dir, {
|
|
13064
|
+
rev: written.revision,
|
|
13065
|
+
type: "resumed",
|
|
13066
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
13067
|
+
data: {
|
|
13068
|
+
preCompactState: resumeState,
|
|
13069
|
+
compactionCount: written.contextPressure?.compactionCount ?? 0,
|
|
13070
|
+
ticketId: info.state.ticket?.id ?? null,
|
|
13071
|
+
headMatch: !ownCommitDrift,
|
|
13072
|
+
ownCommit: ownCommitDrift || void 0
|
|
13073
|
+
}
|
|
13074
|
+
});
|
|
13075
|
+
removeResumeMarker(root);
|
|
12316
13076
|
if (resumeState === "PICK_TICKET") {
|
|
12317
13077
|
if (isTargetedMode(written)) {
|
|
12318
13078
|
const dispatched = await dispatchTargetedResume(root, written, info.dir, [
|
|
@@ -12426,6 +13186,12 @@ async function handlePreCompact(root, args) {
|
|
|
12426
13186
|
await saveSnapshot2(root, loadResult);
|
|
12427
13187
|
} catch {
|
|
12428
13188
|
}
|
|
13189
|
+
writeResumeMarker(root, result.sessionId, {
|
|
13190
|
+
ticket: info.state.ticket,
|
|
13191
|
+
completedTickets: info.state.completedTickets,
|
|
13192
|
+
resolvedIssues: info.state.resolvedIssues,
|
|
13193
|
+
preCompactState: result.preCompactState
|
|
13194
|
+
});
|
|
12429
13195
|
const reread = findSessionById(root, args.sessionId);
|
|
12430
13196
|
const written = reread?.state ?? info.state;
|
|
12431
13197
|
return guideResult(written, "COMPACT", {
|
|
@@ -12531,9 +13297,22 @@ async function handleCancel(root, args) {
|
|
|
12531
13297
|
stashPopFailed
|
|
12532
13298
|
}
|
|
12533
13299
|
});
|
|
13300
|
+
removeResumeMarker(root);
|
|
13301
|
+
let reportSection = "";
|
|
13302
|
+
try {
|
|
13303
|
+
const { state: projectState } = await loadProject(root);
|
|
13304
|
+
const nextResult = nextTickets(projectState, 5);
|
|
13305
|
+
const openIssues = projectState.issues.filter((i) => i.status === "open" || i.status === "inprogress").slice(0, 5);
|
|
13306
|
+
const remainingWork = {
|
|
13307
|
+
tickets: nextResult.kind === "found" ? nextResult.candidates.map((c) => ({ id: c.ticket.id, title: c.ticket.title })) : [],
|
|
13308
|
+
issues: openIssues.map((i) => ({ id: i.id, title: i.title, severity: i.severity }))
|
|
13309
|
+
};
|
|
13310
|
+
reportSection = "\n\n" + formatCompactReport({ state: written, endedAt: (/* @__PURE__ */ new Date()).toISOString(), remainingWork });
|
|
13311
|
+
} catch {
|
|
13312
|
+
}
|
|
12534
13313
|
const stashNote = stashPopFailed ? " Auto-stash pop failed \u2014 run `git stash pop` manually." : "";
|
|
12535
13314
|
return {
|
|
12536
|
-
content: [{ type: "text", text: `Session ${args.sessionId} cancelled. ${written.completedTickets.length} ticket(s) and ${(written.resolvedIssues ?? []).length} issue(s) were completed.${stashNote}` }]
|
|
13315
|
+
content: [{ type: "text", text: `Session ${args.sessionId} cancelled. ${written.completedTickets.length} ticket(s) and ${(written.resolvedIssues ?? []).length} issue(s) were completed.${stashNote}${reportSection}` }]
|
|
12537
13316
|
};
|
|
12538
13317
|
}
|
|
12539
13318
|
function guideResult(state, currentState, opts) {
|
|
@@ -12580,268 +13359,74 @@ ${output.reminders.map((r) => `- ${r}`).join("\n")}` : ""
|
|
|
12580
13359
|
function guideError(err) {
|
|
12581
13360
|
const message = err instanceof Error ? err.message : String(err);
|
|
12582
13361
|
return {
|
|
12583
|
-
content: [{ type: "text", text: `[autonomous_guide error] ${message}` }],
|
|
12584
|
-
isError: true
|
|
12585
|
-
};
|
|
12586
|
-
}
|
|
12587
|
-
function readFileSafe2(path2) {
|
|
12588
|
-
try {
|
|
12589
|
-
return readFileSync10(path2, "utf-8");
|
|
12590
|
-
} catch {
|
|
12591
|
-
return "";
|
|
12592
|
-
}
|
|
12593
|
-
}
|
|
12594
|
-
var RECOVERY_MAPPING, SEVERITY_MAP, workspaceLocks, MAX_AUTO_ADVANCE_DEPTH;
|
|
12595
|
-
var init_guide = __esm({
|
|
12596
|
-
"src/autonomous/guide.ts"() {
|
|
12597
|
-
"use strict";
|
|
12598
|
-
init_esm_shims();
|
|
12599
|
-
init_session_types();
|
|
12600
|
-
init_session();
|
|
12601
|
-
init_state_machine();
|
|
12602
|
-
init_context_pressure();
|
|
12603
|
-
init_review_depth();
|
|
12604
|
-
init_git_inspector();
|
|
12605
|
-
init_loader();
|
|
12606
|
-
init_registry();
|
|
12607
|
-
init_types3();
|
|
12608
|
-
init_stages();
|
|
12609
|
-
init_project_loader();
|
|
12610
|
-
init_lessons();
|
|
12611
|
-
init_snapshot();
|
|
12612
|
-
init_snapshot();
|
|
12613
|
-
init_queries();
|
|
12614
|
-
init_recommend();
|
|
12615
|
-
init_version_check();
|
|
12616
|
-
init_target_work();
|
|
12617
|
-
init_handover();
|
|
12618
|
-
RECOVERY_MAPPING = {
|
|
12619
|
-
PICK_TICKET: { state: "PICK_TICKET", resetPlan: false, resetCode: false },
|
|
12620
|
-
COMPLETE: { state: "PICK_TICKET", resetPlan: false, resetCode: false },
|
|
12621
|
-
HANDOVER: { state: "PICK_TICKET", resetPlan: false, resetCode: false },
|
|
12622
|
-
PLAN: { state: "PLAN", resetPlan: true, resetCode: false },
|
|
12623
|
-
IMPLEMENT: { state: "PLAN", resetPlan: true, resetCode: false },
|
|
12624
|
-
WRITE_TESTS: { state: "PLAN", resetPlan: true, resetCode: false },
|
|
12625
|
-
BUILD: { state: "IMPLEMENT", resetPlan: false, resetCode: true },
|
|
12626
|
-
VERIFY: { state: "IMPLEMENT", resetPlan: false, resetCode: true },
|
|
12627
|
-
PLAN_REVIEW: { state: "PLAN", resetPlan: true, resetCode: true },
|
|
12628
|
-
TEST: { state: "IMPLEMENT", resetPlan: false, resetCode: true },
|
|
12629
|
-
CODE_REVIEW: { state: "PLAN", resetPlan: true, resetCode: true },
|
|
12630
|
-
FINALIZE: { state: "IMPLEMENT", resetPlan: false, resetCode: true },
|
|
12631
|
-
LESSON_CAPTURE: { state: "PICK_TICKET", resetPlan: false, resetCode: false },
|
|
12632
|
-
ISSUE_FIX: { state: "PICK_TICKET", resetPlan: false, resetCode: false },
|
|
12633
|
-
ISSUE_SWEEP: { state: "PICK_TICKET", resetPlan: false, resetCode: false }
|
|
12634
|
-
};
|
|
12635
|
-
SEVERITY_MAP = {
|
|
12636
|
-
critical: "critical",
|
|
12637
|
-
major: "high",
|
|
12638
|
-
minor: "medium"
|
|
12639
|
-
};
|
|
12640
|
-
workspaceLocks = /* @__PURE__ */ new Map();
|
|
12641
|
-
MAX_AUTO_ADVANCE_DEPTH = 10;
|
|
12642
|
-
}
|
|
12643
|
-
});
|
|
12644
|
-
|
|
12645
|
-
// src/core/session-report-formatter.ts
|
|
12646
|
-
function formatSessionReport(data, format) {
|
|
12647
|
-
const { state, events, planContent, gitLog } = data;
|
|
12648
|
-
if (format === "json") {
|
|
12649
|
-
return JSON.stringify({
|
|
12650
|
-
ok: true,
|
|
12651
|
-
data: {
|
|
12652
|
-
summary: buildSummaryData(state),
|
|
12653
|
-
ticketProgression: state.completedTickets,
|
|
12654
|
-
reviewStats: state.reviews,
|
|
12655
|
-
events: events.events.slice(-50),
|
|
12656
|
-
malformedEventCount: events.malformedCount,
|
|
12657
|
-
contextPressure: state.contextPressure,
|
|
12658
|
-
git: {
|
|
12659
|
-
branch: state.git.branch,
|
|
12660
|
-
initHead: state.git.initHead,
|
|
12661
|
-
commits: gitLog
|
|
12662
|
-
},
|
|
12663
|
-
problems: buildProblems(state, events)
|
|
12664
|
-
}
|
|
12665
|
-
}, null, 2);
|
|
12666
|
-
}
|
|
12667
|
-
const sections = [];
|
|
12668
|
-
sections.push(buildSummarySection(state));
|
|
12669
|
-
sections.push(buildTicketSection(state));
|
|
12670
|
-
sections.push(buildReviewSection(state));
|
|
12671
|
-
sections.push(buildEventSection(events));
|
|
12672
|
-
sections.push(buildPressureSection(state));
|
|
12673
|
-
sections.push(buildGitSection(state, gitLog));
|
|
12674
|
-
sections.push(buildProblemsSection(state, events));
|
|
12675
|
-
return sections.join("\n\n---\n\n");
|
|
12676
|
-
}
|
|
12677
|
-
function buildSummaryData(state) {
|
|
12678
|
-
return {
|
|
12679
|
-
sessionId: state.sessionId,
|
|
12680
|
-
mode: state.mode ?? "auto",
|
|
12681
|
-
recipe: state.recipe,
|
|
12682
|
-
status: state.status,
|
|
12683
|
-
terminationReason: state.terminationReason,
|
|
12684
|
-
startedAt: state.startedAt,
|
|
12685
|
-
lastGuideCall: state.lastGuideCall,
|
|
12686
|
-
guideCallCount: state.guideCallCount,
|
|
12687
|
-
ticketsCompleted: state.completedTickets.length
|
|
12688
|
-
};
|
|
12689
|
-
}
|
|
12690
|
-
function buildSummarySection(state) {
|
|
12691
|
-
const duration = state.startedAt && state.lastGuideCall ? formatDuration(state.startedAt, state.lastGuideCall) : "unknown";
|
|
12692
|
-
return [
|
|
12693
|
-
"## Session Summary",
|
|
12694
|
-
"",
|
|
12695
|
-
`- **ID:** ${state.sessionId}`,
|
|
12696
|
-
`- **Mode:** ${state.mode ?? "auto"}`,
|
|
12697
|
-
`- **Recipe:** ${state.recipe}`,
|
|
12698
|
-
`- **Status:** ${state.status}${state.terminationReason ? ` (${state.terminationReason})` : ""}`,
|
|
12699
|
-
`- **Duration:** ${duration}`,
|
|
12700
|
-
`- **Guide calls:** ${state.guideCallCount}`,
|
|
12701
|
-
`- **Tickets completed:** ${state.completedTickets.length}`
|
|
12702
|
-
].join("\n");
|
|
12703
|
-
}
|
|
12704
|
-
function buildTicketSection(state) {
|
|
12705
|
-
if (state.completedTickets.length === 0) {
|
|
12706
|
-
const current = state.ticket;
|
|
12707
|
-
if (current) {
|
|
12708
|
-
return [
|
|
12709
|
-
"## Ticket Progression",
|
|
12710
|
-
"",
|
|
12711
|
-
`In progress: **${current.id}** \u2014 ${current.title} (risk: ${current.risk ?? "unknown"})`
|
|
12712
|
-
].join("\n");
|
|
12713
|
-
}
|
|
12714
|
-
return "## Ticket Progression\n\nNo tickets completed.";
|
|
12715
|
-
}
|
|
12716
|
-
const lines = ["## Ticket Progression", ""];
|
|
12717
|
-
for (const t of state.completedTickets) {
|
|
12718
|
-
const risk = t.realizedRisk ? `${t.risk ?? "?"} \u2192 ${t.realizedRisk}` : t.risk ?? "unknown";
|
|
12719
|
-
lines.push(`- **${t.id}:** ${t.title} | risk: ${risk} | commit: \`${t.commitHash ?? "?"}\``);
|
|
12720
|
-
}
|
|
12721
|
-
return lines.join("\n");
|
|
12722
|
-
}
|
|
12723
|
-
function buildReviewSection(state) {
|
|
12724
|
-
const plan = state.reviews.plan;
|
|
12725
|
-
const code = state.reviews.code;
|
|
12726
|
-
if (plan.length === 0 && code.length === 0) {
|
|
12727
|
-
return "## Review Stats\n\nNo reviews recorded.";
|
|
12728
|
-
}
|
|
12729
|
-
const lines = ["## Review Stats", ""];
|
|
12730
|
-
if (plan.length > 0) {
|
|
12731
|
-
lines.push(`**Plan reviews:** ${plan.length} round(s)`);
|
|
12732
|
-
for (const r of plan) {
|
|
12733
|
-
lines.push(` - Round ${r.round}: ${r.verdict} (${r.findingCount} findings, ${r.criticalCount} critical, ${r.majorCount} major) \u2014 ${r.reviewer}`);
|
|
12734
|
-
}
|
|
12735
|
-
}
|
|
12736
|
-
if (code.length > 0) {
|
|
12737
|
-
lines.push(`**Code reviews:** ${code.length} round(s)`);
|
|
12738
|
-
for (const r of code) {
|
|
12739
|
-
lines.push(` - Round ${r.round}: ${r.verdict} (${r.findingCount} findings, ${r.criticalCount} critical, ${r.majorCount} major) \u2014 ${r.reviewer}`);
|
|
12740
|
-
}
|
|
12741
|
-
}
|
|
12742
|
-
const totalFindings = [...plan, ...code].reduce((sum, r) => sum + r.findingCount, 0);
|
|
12743
|
-
lines.push("", `**Total findings:** ${totalFindings}`);
|
|
12744
|
-
return lines.join("\n");
|
|
12745
|
-
}
|
|
12746
|
-
function buildEventSection(events) {
|
|
12747
|
-
if (events.events.length === 0 && events.malformedCount === 0) {
|
|
12748
|
-
return "## Event Timeline\n\nNot available.";
|
|
12749
|
-
}
|
|
12750
|
-
const capped = events.events.slice(-50);
|
|
12751
|
-
const omitted = events.events.length - capped.length;
|
|
12752
|
-
const lines = ["## Event Timeline", ""];
|
|
12753
|
-
if (omitted > 0) {
|
|
12754
|
-
lines.push(`*${omitted} earlier events omitted*`, "");
|
|
12755
|
-
}
|
|
12756
|
-
for (const e of capped) {
|
|
12757
|
-
const ts = e.timestamp ? e.timestamp.slice(11, 19) : "??:??:??";
|
|
12758
|
-
const detail = e.data ? Object.entries(e.data).map(([k, v]) => `${k}=${JSON.stringify(v)}`).join(" ") : "";
|
|
12759
|
-
lines.push(`- \`${ts}\` [${e.type}] ${detail}`.trimEnd());
|
|
12760
|
-
}
|
|
12761
|
-
if (events.malformedCount > 0) {
|
|
12762
|
-
lines.push("", `*${events.malformedCount} malformed event line(s) skipped*`);
|
|
12763
|
-
}
|
|
12764
|
-
return lines.join("\n");
|
|
12765
|
-
}
|
|
12766
|
-
function buildPressureSection(state) {
|
|
12767
|
-
const p = state.contextPressure;
|
|
12768
|
-
return [
|
|
12769
|
-
"## Context Pressure",
|
|
12770
|
-
"",
|
|
12771
|
-
`- **Level:** ${p.level}`,
|
|
12772
|
-
`- **Guide calls:** ${p.guideCallCount}`,
|
|
12773
|
-
`- **Tickets completed:** ${p.ticketsCompleted}`,
|
|
12774
|
-
`- **Compactions:** ${p.compactionCount}`,
|
|
12775
|
-
`- **Events log:** ${p.eventsLogBytes} bytes`
|
|
12776
|
-
].join("\n");
|
|
12777
|
-
}
|
|
12778
|
-
function buildGitSection(state, gitLog) {
|
|
12779
|
-
const lines = [
|
|
12780
|
-
"## Git Summary",
|
|
12781
|
-
"",
|
|
12782
|
-
`- **Branch:** ${state.git.branch ?? "unknown"}`,
|
|
12783
|
-
`- **Init HEAD:** \`${state.git.initHead ?? "?"}\``,
|
|
12784
|
-
`- **Expected HEAD:** \`${state.git.expectedHead ?? "?"}\``
|
|
12785
|
-
];
|
|
12786
|
-
if (gitLog && gitLog.length > 0) {
|
|
12787
|
-
lines.push("", "**Commits:**");
|
|
12788
|
-
for (const c of gitLog) {
|
|
12789
|
-
lines.push(`- ${c}`);
|
|
12790
|
-
}
|
|
12791
|
-
} else {
|
|
12792
|
-
lines.push("", "Commits: Not available.");
|
|
12793
|
-
}
|
|
12794
|
-
return lines.join("\n");
|
|
12795
|
-
}
|
|
12796
|
-
function buildProblems(state, events) {
|
|
12797
|
-
const problems = [];
|
|
12798
|
-
if (state.terminationReason && state.terminationReason !== "normal") {
|
|
12799
|
-
problems.push(`Abnormal termination: ${state.terminationReason}`);
|
|
12800
|
-
}
|
|
12801
|
-
if (events.malformedCount > 0) {
|
|
12802
|
-
problems.push(`${events.malformedCount} malformed event line(s) in events.log`);
|
|
12803
|
-
}
|
|
12804
|
-
for (const e of events.events) {
|
|
12805
|
-
if (e.type.includes("error") || e.type.includes("exhaustion")) {
|
|
12806
|
-
problems.push(`[${e.type}] ${e.timestamp ?? ""} ${JSON.stringify(e.data)}`);
|
|
12807
|
-
} else if (e.data?.result === "exhaustion") {
|
|
12808
|
-
problems.push(`[${e.type}] exhaustion at ${e.timestamp ?? ""}`);
|
|
12809
|
-
}
|
|
12810
|
-
}
|
|
12811
|
-
if (state.deferralsUnfiled) {
|
|
12812
|
-
problems.push("Session has unfiled deferrals");
|
|
12813
|
-
}
|
|
12814
|
-
return problems;
|
|
12815
|
-
}
|
|
12816
|
-
function buildProblemsSection(state, events) {
|
|
12817
|
-
const problems = buildProblems(state, events);
|
|
12818
|
-
if (problems.length === 0) {
|
|
12819
|
-
return "## Problems\n\nNone detected.";
|
|
12820
|
-
}
|
|
12821
|
-
return ["## Problems", "", ...problems.map((p) => `- ${p}`)].join("\n");
|
|
13362
|
+
content: [{ type: "text", text: `[autonomous_guide error] ${message}` }],
|
|
13363
|
+
isError: true
|
|
13364
|
+
};
|
|
12822
13365
|
}
|
|
12823
|
-
function
|
|
13366
|
+
function readFileSafe2(path2) {
|
|
12824
13367
|
try {
|
|
12825
|
-
|
|
12826
|
-
if (isNaN(ms) || ms < 0) return "unknown";
|
|
12827
|
-
const mins = Math.floor(ms / 6e4);
|
|
12828
|
-
if (mins < 60) return `${mins}m`;
|
|
12829
|
-
const hours = Math.floor(mins / 60);
|
|
12830
|
-
return `${hours}h ${mins % 60}m`;
|
|
13368
|
+
return readFileSync10(path2, "utf-8");
|
|
12831
13369
|
} catch {
|
|
12832
|
-
return "
|
|
13370
|
+
return "";
|
|
12833
13371
|
}
|
|
12834
13372
|
}
|
|
12835
|
-
var
|
|
12836
|
-
|
|
13373
|
+
var RECOVERY_MAPPING, SEVERITY_MAP, workspaceLocks, MAX_AUTO_ADVANCE_DEPTH;
|
|
13374
|
+
var init_guide = __esm({
|
|
13375
|
+
"src/autonomous/guide.ts"() {
|
|
12837
13376
|
"use strict";
|
|
12838
13377
|
init_esm_shims();
|
|
13378
|
+
init_session_types();
|
|
13379
|
+
init_session();
|
|
13380
|
+
init_state_machine();
|
|
13381
|
+
init_context_pressure();
|
|
13382
|
+
init_review_depth();
|
|
13383
|
+
init_git_inspector();
|
|
13384
|
+
init_loader();
|
|
13385
|
+
init_registry();
|
|
13386
|
+
init_types3();
|
|
13387
|
+
init_stages();
|
|
13388
|
+
init_project_loader();
|
|
13389
|
+
init_lessons();
|
|
13390
|
+
init_snapshot();
|
|
13391
|
+
init_snapshot();
|
|
13392
|
+
init_queries();
|
|
13393
|
+
init_recommend();
|
|
13394
|
+
init_version_check();
|
|
13395
|
+
init_resume_marker();
|
|
13396
|
+
init_session_report_formatter();
|
|
13397
|
+
init_target_work();
|
|
13398
|
+
init_handover();
|
|
13399
|
+
RECOVERY_MAPPING = {
|
|
13400
|
+
PICK_TICKET: { state: "PICK_TICKET", resetPlan: false, resetCode: false },
|
|
13401
|
+
COMPLETE: { state: "PICK_TICKET", resetPlan: false, resetCode: false },
|
|
13402
|
+
HANDOVER: { state: "PICK_TICKET", resetPlan: false, resetCode: false },
|
|
13403
|
+
PLAN: { state: "PLAN", resetPlan: true, resetCode: false },
|
|
13404
|
+
IMPLEMENT: { state: "PLAN", resetPlan: true, resetCode: false },
|
|
13405
|
+
WRITE_TESTS: { state: "PLAN", resetPlan: true, resetCode: false },
|
|
13406
|
+
BUILD: { state: "IMPLEMENT", resetPlan: false, resetCode: true },
|
|
13407
|
+
VERIFY: { state: "IMPLEMENT", resetPlan: false, resetCode: true },
|
|
13408
|
+
PLAN_REVIEW: { state: "PLAN", resetPlan: true, resetCode: true },
|
|
13409
|
+
TEST: { state: "IMPLEMENT", resetPlan: false, resetCode: true },
|
|
13410
|
+
CODE_REVIEW: { state: "PLAN", resetPlan: true, resetCode: true },
|
|
13411
|
+
FINALIZE: { state: "IMPLEMENT", resetPlan: false, resetCode: true },
|
|
13412
|
+
LESSON_CAPTURE: { state: "PICK_TICKET", resetPlan: false, resetCode: false },
|
|
13413
|
+
ISSUE_FIX: { state: "ISSUE_FIX", resetPlan: false, resetCode: false },
|
|
13414
|
+
// T-208: self-recover to avoid dangling currentIssue
|
|
13415
|
+
ISSUE_SWEEP: { state: "PICK_TICKET", resetPlan: false, resetCode: false }
|
|
13416
|
+
};
|
|
13417
|
+
SEVERITY_MAP = {
|
|
13418
|
+
critical: "critical",
|
|
13419
|
+
major: "high",
|
|
13420
|
+
minor: "medium"
|
|
13421
|
+
};
|
|
13422
|
+
workspaceLocks = /* @__PURE__ */ new Map();
|
|
13423
|
+
MAX_AUTO_ADVANCE_DEPTH = 10;
|
|
12839
13424
|
}
|
|
12840
13425
|
});
|
|
12841
13426
|
|
|
12842
13427
|
// src/cli/commands/session-report.ts
|
|
12843
|
-
import { readFileSync as readFileSync11, existsSync as
|
|
12844
|
-
import { join as
|
|
13428
|
+
import { readFileSync as readFileSync11, existsSync as existsSync13 } from "fs";
|
|
13429
|
+
import { join as join21 } from "path";
|
|
12845
13430
|
async function handleSessionReport(sessionId, root, format = "md") {
|
|
12846
13431
|
if (!UUID_REGEX.test(sessionId)) {
|
|
12847
13432
|
return {
|
|
@@ -12852,7 +13437,7 @@ async function handleSessionReport(sessionId, root, format = "md") {
|
|
|
12852
13437
|
};
|
|
12853
13438
|
}
|
|
12854
13439
|
const dir = sessionDir(root, sessionId);
|
|
12855
|
-
if (!
|
|
13440
|
+
if (!existsSync13(dir)) {
|
|
12856
13441
|
return {
|
|
12857
13442
|
output: `Error: Session ${sessionId} not found.`,
|
|
12858
13443
|
exitCode: 1,
|
|
@@ -12860,8 +13445,8 @@ async function handleSessionReport(sessionId, root, format = "md") {
|
|
|
12860
13445
|
isError: true
|
|
12861
13446
|
};
|
|
12862
13447
|
}
|
|
12863
|
-
const statePath2 =
|
|
12864
|
-
if (!
|
|
13448
|
+
const statePath2 = join21(dir, "state.json");
|
|
13449
|
+
if (!existsSync13(statePath2)) {
|
|
12865
13450
|
return {
|
|
12866
13451
|
output: `Error: Session ${sessionId} corrupt \u2014 state.json missing.`,
|
|
12867
13452
|
exitCode: 1,
|
|
@@ -12899,7 +13484,7 @@ async function handleSessionReport(sessionId, root, format = "md") {
|
|
|
12899
13484
|
const events = readEvents(dir);
|
|
12900
13485
|
let planContent = null;
|
|
12901
13486
|
try {
|
|
12902
|
-
planContent = readFileSync11(
|
|
13487
|
+
planContent = readFileSync11(join21(dir, "plan.md"), "utf-8");
|
|
12903
13488
|
} catch {
|
|
12904
13489
|
}
|
|
12905
13490
|
let gitLog = null;
|
|
@@ -12928,7 +13513,7 @@ var init_session_report = __esm({
|
|
|
12928
13513
|
});
|
|
12929
13514
|
|
|
12930
13515
|
// src/cli/commands/phase.ts
|
|
12931
|
-
import { join as
|
|
13516
|
+
import { join as join22, resolve as resolve7 } from "path";
|
|
12932
13517
|
function validatePhaseId(id) {
|
|
12933
13518
|
if (id.length > PHASE_ID_MAX_LENGTH) {
|
|
12934
13519
|
throw new CliValidationError("invalid_input", `Phase ID "${id}" exceeds ${PHASE_ID_MAX_LENGTH} characters`);
|
|
@@ -13117,21 +13702,21 @@ async function handlePhaseDelete(id, reassign, format, root) {
|
|
|
13117
13702
|
const updated = { ...ticket, phase: reassign, order: maxOrder };
|
|
13118
13703
|
const parsed = TicketSchema.parse(updated);
|
|
13119
13704
|
const content = serializeJSON(parsed);
|
|
13120
|
-
const target =
|
|
13705
|
+
const target = join22(wrapDir, "tickets", `${parsed.id}.json`);
|
|
13121
13706
|
operations.push({ op: "write", target, content });
|
|
13122
13707
|
}
|
|
13123
13708
|
for (const issue of affectedIssues) {
|
|
13124
13709
|
const updated = { ...issue, phase: reassign };
|
|
13125
13710
|
const parsed = IssueSchema.parse(updated);
|
|
13126
13711
|
const content = serializeJSON(parsed);
|
|
13127
|
-
const target =
|
|
13712
|
+
const target = join22(wrapDir, "issues", `${parsed.id}.json`);
|
|
13128
13713
|
operations.push({ op: "write", target, content });
|
|
13129
13714
|
}
|
|
13130
13715
|
const newPhases = state.roadmap.phases.filter((p) => p.id !== id);
|
|
13131
13716
|
const newRoadmap = { ...state.roadmap, phases: newPhases };
|
|
13132
13717
|
const parsedRoadmap = RoadmapSchema.parse(newRoadmap);
|
|
13133
13718
|
const roadmapContent = serializeJSON(parsedRoadmap);
|
|
13134
|
-
const roadmapTarget =
|
|
13719
|
+
const roadmapTarget = join22(wrapDir, "roadmap.json");
|
|
13135
13720
|
operations.push({ op: "write", target: roadmapTarget, content: roadmapContent });
|
|
13136
13721
|
await runTransactionUnlocked(root, operations);
|
|
13137
13722
|
} else {
|
|
@@ -13164,14 +13749,15 @@ var init_phase = __esm({
|
|
|
13164
13749
|
|
|
13165
13750
|
// src/mcp/tools.ts
|
|
13166
13751
|
import { z as z10 } from "zod";
|
|
13167
|
-
import {
|
|
13752
|
+
import { readFileSync as readFileSync12, writeFileSync as writeFileSync7, mkdirSync as mkdirSync5 } from "fs";
|
|
13753
|
+
import { join as join23 } from "path";
|
|
13168
13754
|
function formatMcpError(code, message) {
|
|
13169
13755
|
return `[${code}] ${message}`;
|
|
13170
13756
|
}
|
|
13171
13757
|
async function runMcpReadTool(pinnedRoot, handler) {
|
|
13172
13758
|
try {
|
|
13173
13759
|
const { state, warnings } = await loadProject(pinnedRoot);
|
|
13174
|
-
const handoversDir =
|
|
13760
|
+
const handoversDir = join23(pinnedRoot, ".story", "handovers");
|
|
13175
13761
|
const ctx = { state, warnings, root: pinnedRoot, handoversDir, format: "md" };
|
|
13176
13762
|
const result = await handler(ctx);
|
|
13177
13763
|
if (result.errorCode && INFRASTRUCTURE_ERROR_CODES.includes(result.errorCode)) {
|
|
@@ -13719,6 +14305,7 @@ function registerAllTools(server, pinnedRoot) {
|
|
|
13719
14305
|
disposition: z10.string()
|
|
13720
14306
|
})).optional().describe("Review findings"),
|
|
13721
14307
|
reviewerSessionId: z10.string().optional().describe("Codex session ID"),
|
|
14308
|
+
reviewer: z10.string().optional().describe("Actual reviewer backend used (e.g. 'agent' when codex was unavailable)"),
|
|
13722
14309
|
notes: z10.string().optional().describe("Free-text notes")
|
|
13723
14310
|
}).optional().describe("Report data (required for report action)")
|
|
13724
14311
|
}
|
|
@@ -13743,7 +14330,7 @@ function registerAllTools(server, pinnedRoot) {
|
|
|
13743
14330
|
}
|
|
13744
14331
|
});
|
|
13745
14332
|
server.registerTool("claudestory_review_lenses_synthesize", {
|
|
13746
|
-
description: "Synthesize lens results after parallel review. Validates findings, applies blocking policy, generates merger prompt. Call after collecting all lens subagent results.",
|
|
14333
|
+
description: "Synthesize lens results after parallel review. Validates findings, applies blocking policy, classifies origin (introduced vs pre-existing), auto-files pre-existing issues, generates merger prompt. Call after collecting all lens subagent results.",
|
|
13747
14334
|
inputSchema: {
|
|
13748
14335
|
stage: z10.enum(["CODE_REVIEW", "PLAN_REVIEW"]).optional().describe("Review stage (defaults to CODE_REVIEW)"),
|
|
13749
14336
|
lensResults: z10.array(z10.object({
|
|
@@ -13754,9 +14341,13 @@ function registerAllTools(server, pinnedRoot) {
|
|
|
13754
14341
|
activeLenses: z10.array(z10.string()).describe("Active lens names from prepare step"),
|
|
13755
14342
|
skippedLenses: z10.array(z10.string()).describe("Skipped lens names from prepare step"),
|
|
13756
14343
|
reviewRound: z10.number().int().min(1).optional().describe("Current review round"),
|
|
13757
|
-
reviewId: z10.string().optional().describe("Review ID from prepare step")
|
|
14344
|
+
reviewId: z10.string().optional().describe("Review ID from prepare step"),
|
|
14345
|
+
// T-192: Origin classification inputs
|
|
14346
|
+
diff: z10.string().optional().describe("The diff being reviewed (for origin classification of findings into introduced vs pre-existing)"),
|
|
14347
|
+
changedFiles: z10.array(z10.string()).optional().describe("Changed file paths from prepare step (for origin classification)"),
|
|
14348
|
+
sessionId: z10.string().uuid().optional().describe("Active session ID (for dedup of auto-filed pre-existing issues across review rounds)")
|
|
13758
14349
|
}
|
|
13759
|
-
}, (args) => {
|
|
14350
|
+
}, async (args) => {
|
|
13760
14351
|
try {
|
|
13761
14352
|
const result = handleSynthesize({
|
|
13762
14353
|
stage: args.stage,
|
|
@@ -13767,9 +14358,55 @@ function registerAllTools(server, pinnedRoot) {
|
|
|
13767
14358
|
reviewRound: args.reviewRound ?? 1,
|
|
13768
14359
|
reviewId: args.reviewId ?? "unknown"
|
|
13769
14360
|
},
|
|
13770
|
-
projectRoot: pinnedRoot
|
|
14361
|
+
projectRoot: pinnedRoot,
|
|
14362
|
+
diff: args.diff,
|
|
14363
|
+
changedFiles: args.changedFiles
|
|
13771
14364
|
});
|
|
13772
|
-
|
|
14365
|
+
const filedIssues = [];
|
|
14366
|
+
if (result.preExistingFindings.length > 0) {
|
|
14367
|
+
const sessionDir2 = args.sessionId ? join23(pinnedRoot, ".story", "sessions", args.sessionId) : null;
|
|
14368
|
+
const alreadyFiled = sessionDir2 ? readFiledPreexisting(sessionDir2) : /* @__PURE__ */ new Set();
|
|
14369
|
+
const sizeBeforeLoop = alreadyFiled.size;
|
|
14370
|
+
for (const f of result.preExistingFindings) {
|
|
14371
|
+
const dedupKey = f.issueKey ?? `${f.file ?? ""}:${f.line ?? 0}:${f.category}`;
|
|
14372
|
+
if (alreadyFiled.has(dedupKey)) continue;
|
|
14373
|
+
try {
|
|
14374
|
+
const { handleIssueCreate: handleIssueCreate2 } = await Promise.resolve().then(() => (init_issue2(), issue_exports));
|
|
14375
|
+
const severityMap = { critical: "critical", major: "high", minor: "medium" };
|
|
14376
|
+
const severity = severityMap[f.severity] ?? "medium";
|
|
14377
|
+
const issueResult = await handleIssueCreate2(
|
|
14378
|
+
{
|
|
14379
|
+
title: `[pre-existing] [${f.category}] ${f.description.slice(0, 60)}`,
|
|
14380
|
+
severity,
|
|
14381
|
+
impact: f.description,
|
|
14382
|
+
components: ["review-lenses"],
|
|
14383
|
+
relatedTickets: [],
|
|
14384
|
+
location: f.file && f.line != null ? [`${f.file}:${f.line}`] : []
|
|
14385
|
+
},
|
|
14386
|
+
"json",
|
|
14387
|
+
pinnedRoot
|
|
14388
|
+
);
|
|
14389
|
+
let issueId;
|
|
14390
|
+
try {
|
|
14391
|
+
const parsed = JSON.parse(issueResult.output ?? "");
|
|
14392
|
+
issueId = parsed?.data?.id;
|
|
14393
|
+
} catch {
|
|
14394
|
+
const match = issueResult.output?.match(/ISS-\d+/);
|
|
14395
|
+
issueId = match?.[0];
|
|
14396
|
+
}
|
|
14397
|
+
if (issueId) {
|
|
14398
|
+
filedIssues.push({ issueKey: dedupKey, issueId });
|
|
14399
|
+
alreadyFiled.add(dedupKey);
|
|
14400
|
+
}
|
|
14401
|
+
} catch {
|
|
14402
|
+
}
|
|
14403
|
+
}
|
|
14404
|
+
if (sessionDir2 && alreadyFiled.size > sizeBeforeLoop) {
|
|
14405
|
+
writeFiledPreexisting(sessionDir2, alreadyFiled);
|
|
14406
|
+
}
|
|
14407
|
+
}
|
|
14408
|
+
const output = { ...result, filedIssues };
|
|
14409
|
+
return { content: [{ type: "text", text: JSON.stringify(output, null, 2) }] };
|
|
13773
14410
|
} catch (err) {
|
|
13774
14411
|
const msg = err instanceof Error ? err.message.replace(/\/[^\s]+/g, "<path>") : "unknown error";
|
|
13775
14412
|
return { content: [{ type: "text", text: `Error synthesizing lens results: ${msg}` }], isError: true };
|
|
@@ -13810,7 +14447,23 @@ function registerAllTools(server, pinnedRoot) {
|
|
|
13810
14447
|
}
|
|
13811
14448
|
});
|
|
13812
14449
|
}
|
|
13813
|
-
|
|
14450
|
+
function readFiledPreexisting(sessionDir2) {
|
|
14451
|
+
try {
|
|
14452
|
+
const raw = readFileSync12(join23(sessionDir2, FILED_PREEXISTING_FILE), "utf-8");
|
|
14453
|
+
const arr = JSON.parse(raw);
|
|
14454
|
+
return new Set(Array.isArray(arr) ? arr : []);
|
|
14455
|
+
} catch {
|
|
14456
|
+
return /* @__PURE__ */ new Set();
|
|
14457
|
+
}
|
|
14458
|
+
}
|
|
14459
|
+
function writeFiledPreexisting(sessionDir2, keys) {
|
|
14460
|
+
try {
|
|
14461
|
+
mkdirSync5(sessionDir2, { recursive: true });
|
|
14462
|
+
writeFileSync7(join23(sessionDir2, FILED_PREEXISTING_FILE), JSON.stringify([...keys], null, 2));
|
|
14463
|
+
} catch {
|
|
14464
|
+
}
|
|
14465
|
+
}
|
|
14466
|
+
var INFRASTRUCTURE_ERROR_CODES, FILED_PREEXISTING_FILE;
|
|
13814
14467
|
var init_tools = __esm({
|
|
13815
14468
|
"src/mcp/tools.ts"() {
|
|
13816
14469
|
"use strict";
|
|
@@ -13843,15 +14496,16 @@ var init_tools = __esm({
|
|
|
13843
14496
|
"project_corrupt",
|
|
13844
14497
|
"version_mismatch"
|
|
13845
14498
|
];
|
|
14499
|
+
FILED_PREEXISTING_FILE = "filed-preexisting.json";
|
|
13846
14500
|
}
|
|
13847
14501
|
});
|
|
13848
14502
|
|
|
13849
14503
|
// src/core/init.ts
|
|
13850
14504
|
import { mkdir as mkdir4, stat as stat2, readFile as readFile4, writeFile as writeFile2 } from "fs/promises";
|
|
13851
|
-
import { join as
|
|
14505
|
+
import { join as join24, resolve as resolve8 } from "path";
|
|
13852
14506
|
async function initProject(root, options) {
|
|
13853
14507
|
const absRoot = resolve8(root);
|
|
13854
|
-
const wrapDir =
|
|
14508
|
+
const wrapDir = join24(absRoot, ".story");
|
|
13855
14509
|
let exists = false;
|
|
13856
14510
|
try {
|
|
13857
14511
|
const s = await stat2(wrapDir);
|
|
@@ -13871,11 +14525,11 @@ async function initProject(root, options) {
|
|
|
13871
14525
|
".story/ already exists. Use --force to overwrite config and roadmap."
|
|
13872
14526
|
);
|
|
13873
14527
|
}
|
|
13874
|
-
await mkdir4(
|
|
13875
|
-
await mkdir4(
|
|
13876
|
-
await mkdir4(
|
|
13877
|
-
await mkdir4(
|
|
13878
|
-
await mkdir4(
|
|
14528
|
+
await mkdir4(join24(wrapDir, "tickets"), { recursive: true });
|
|
14529
|
+
await mkdir4(join24(wrapDir, "issues"), { recursive: true });
|
|
14530
|
+
await mkdir4(join24(wrapDir, "handovers"), { recursive: true });
|
|
14531
|
+
await mkdir4(join24(wrapDir, "notes"), { recursive: true });
|
|
14532
|
+
await mkdir4(join24(wrapDir, "lessons"), { recursive: true });
|
|
13879
14533
|
const created = [
|
|
13880
14534
|
".story/config.json",
|
|
13881
14535
|
".story/roadmap.json",
|
|
@@ -13915,7 +14569,7 @@ async function initProject(root, options) {
|
|
|
13915
14569
|
};
|
|
13916
14570
|
await writeConfig(config, absRoot);
|
|
13917
14571
|
await writeRoadmap(roadmap, absRoot);
|
|
13918
|
-
const gitignorePath =
|
|
14572
|
+
const gitignorePath = join24(wrapDir, ".gitignore");
|
|
13919
14573
|
await ensureGitignoreEntries(gitignorePath, STORY_GITIGNORE_ENTRIES);
|
|
13920
14574
|
const warnings = [];
|
|
13921
14575
|
if (options.force && exists) {
|
|
@@ -13960,11 +14614,405 @@ var init_init = __esm({
|
|
|
13960
14614
|
}
|
|
13961
14615
|
});
|
|
13962
14616
|
|
|
14617
|
+
// src/channel/events.ts
|
|
14618
|
+
import { z as z11 } from "zod";
|
|
14619
|
+
function isValidInboxFilename(filename) {
|
|
14620
|
+
if (filename.includes("/") || filename.includes("\\") || filename.includes("..")) {
|
|
14621
|
+
return false;
|
|
14622
|
+
}
|
|
14623
|
+
return VALID_FILENAME.test(filename);
|
|
14624
|
+
}
|
|
14625
|
+
function formatChannelContent(event) {
|
|
14626
|
+
switch (event.event) {
|
|
14627
|
+
case "ticket_requested":
|
|
14628
|
+
return `User requested ticket ${event.payload.ticketId} be started.`;
|
|
14629
|
+
case "pause_session":
|
|
14630
|
+
return "User requested the autonomous session be paused.";
|
|
14631
|
+
case "resume_session":
|
|
14632
|
+
return "User requested the autonomous session be resumed.";
|
|
14633
|
+
case "cancel_session": {
|
|
14634
|
+
const reason = event.payload.reason ? ` Reason: ${event.payload.reason}` : "";
|
|
14635
|
+
return `User requested the autonomous session be cancelled.${reason}`;
|
|
14636
|
+
}
|
|
14637
|
+
case "priority_changed":
|
|
14638
|
+
return `User changed priority of ticket ${event.payload.ticketId} to order ${event.payload.newOrder}.`;
|
|
14639
|
+
case "permission_response":
|
|
14640
|
+
return `Permission response for request ${event.payload.requestId}: ${event.payload.behavior}.`;
|
|
14641
|
+
}
|
|
14642
|
+
}
|
|
14643
|
+
function formatChannelMeta(event) {
|
|
14644
|
+
const meta = { event: event.event };
|
|
14645
|
+
switch (event.event) {
|
|
14646
|
+
case "ticket_requested":
|
|
14647
|
+
meta.ticketId = event.payload.ticketId;
|
|
14648
|
+
break;
|
|
14649
|
+
case "cancel_session":
|
|
14650
|
+
if (event.payload.reason) meta.reason = event.payload.reason;
|
|
14651
|
+
break;
|
|
14652
|
+
case "priority_changed":
|
|
14653
|
+
meta.ticketId = event.payload.ticketId;
|
|
14654
|
+
meta.newOrder = String(event.payload.newOrder);
|
|
14655
|
+
break;
|
|
14656
|
+
case "permission_response":
|
|
14657
|
+
meta.requestId = event.payload.requestId;
|
|
14658
|
+
meta.behavior = event.payload.behavior;
|
|
14659
|
+
break;
|
|
14660
|
+
}
|
|
14661
|
+
return meta;
|
|
14662
|
+
}
|
|
14663
|
+
var TicketRequestedPayload, PauseSessionPayload, ResumeSessionPayload, CancelSessionPayload, PriorityChangedPayload, PermissionResponsePayload, ChannelEventSchema, VALID_FILENAME;
|
|
14664
|
+
var init_events = __esm({
|
|
14665
|
+
"src/channel/events.ts"() {
|
|
14666
|
+
"use strict";
|
|
14667
|
+
init_esm_shims();
|
|
14668
|
+
TicketRequestedPayload = z11.object({
|
|
14669
|
+
ticketId: z11.string().regex(/^T-\d+[a-z]?$/)
|
|
14670
|
+
});
|
|
14671
|
+
PauseSessionPayload = z11.object({});
|
|
14672
|
+
ResumeSessionPayload = z11.object({});
|
|
14673
|
+
CancelSessionPayload = z11.object({
|
|
14674
|
+
reason: z11.string().max(1e3).optional()
|
|
14675
|
+
});
|
|
14676
|
+
PriorityChangedPayload = z11.object({
|
|
14677
|
+
ticketId: z11.string().regex(/^T-\d+[a-z]?$/),
|
|
14678
|
+
newOrder: z11.number().int()
|
|
14679
|
+
});
|
|
14680
|
+
PermissionResponsePayload = z11.object({
|
|
14681
|
+
requestId: z11.string().regex(/^[a-zA-Z0-9]{5}$/),
|
|
14682
|
+
behavior: z11.enum(["approve", "deny"])
|
|
14683
|
+
});
|
|
14684
|
+
ChannelEventSchema = z11.discriminatedUnion("event", [
|
|
14685
|
+
z11.object({
|
|
14686
|
+
event: z11.literal("ticket_requested"),
|
|
14687
|
+
timestamp: z11.string(),
|
|
14688
|
+
payload: TicketRequestedPayload
|
|
14689
|
+
}),
|
|
14690
|
+
z11.object({
|
|
14691
|
+
event: z11.literal("pause_session"),
|
|
14692
|
+
timestamp: z11.string(),
|
|
14693
|
+
payload: PauseSessionPayload
|
|
14694
|
+
}),
|
|
14695
|
+
z11.object({
|
|
14696
|
+
event: z11.literal("resume_session"),
|
|
14697
|
+
timestamp: z11.string(),
|
|
14698
|
+
payload: ResumeSessionPayload
|
|
14699
|
+
}),
|
|
14700
|
+
z11.object({
|
|
14701
|
+
event: z11.literal("cancel_session"),
|
|
14702
|
+
timestamp: z11.string(),
|
|
14703
|
+
payload: CancelSessionPayload
|
|
14704
|
+
}),
|
|
14705
|
+
z11.object({
|
|
14706
|
+
event: z11.literal("priority_changed"),
|
|
14707
|
+
timestamp: z11.string(),
|
|
14708
|
+
payload: PriorityChangedPayload
|
|
14709
|
+
}),
|
|
14710
|
+
z11.object({
|
|
14711
|
+
event: z11.literal("permission_response"),
|
|
14712
|
+
timestamp: z11.string(),
|
|
14713
|
+
payload: PermissionResponsePayload
|
|
14714
|
+
})
|
|
14715
|
+
]);
|
|
14716
|
+
VALID_FILENAME = /^[\d]{4}-[\d]{2}-[\d]{2}T[\w.:-]+-[\w]+\.json$/;
|
|
14717
|
+
}
|
|
14718
|
+
});
|
|
14719
|
+
|
|
14720
|
+
// src/channel/inbox-watcher.ts
|
|
14721
|
+
import { watch } from "fs";
|
|
14722
|
+
import { readdir as readdir4, readFile as readFile5, unlink as unlink3, rename as rename2, mkdir as mkdir5 } from "fs/promises";
|
|
14723
|
+
import { join as join25 } from "path";
|
|
14724
|
+
async function startInboxWatcher(root, server) {
|
|
14725
|
+
const inboxPath = join25(root, ".story", INBOX_DIR);
|
|
14726
|
+
if (watcher) {
|
|
14727
|
+
watcher.close();
|
|
14728
|
+
watcher = null;
|
|
14729
|
+
permissionRetryCount.clear();
|
|
14730
|
+
}
|
|
14731
|
+
await mkdir5(inboxPath, { recursive: true });
|
|
14732
|
+
await recoverStaleProcessingFiles(inboxPath);
|
|
14733
|
+
await processInbox(inboxPath, server);
|
|
14734
|
+
try {
|
|
14735
|
+
watcher = watch(inboxPath, (eventType) => {
|
|
14736
|
+
if (eventType === "rename") {
|
|
14737
|
+
scheduleDebouncedProcess(inboxPath, server);
|
|
14738
|
+
}
|
|
14739
|
+
});
|
|
14740
|
+
watcher.on("error", (err) => {
|
|
14741
|
+
process.stderr.write(`claudestory: channel inbox watcher error: ${err.message}
|
|
14742
|
+
`);
|
|
14743
|
+
startPollingFallback(inboxPath, server);
|
|
14744
|
+
});
|
|
14745
|
+
process.stderr.write(`claudestory: channel inbox watcher started at ${inboxPath}
|
|
14746
|
+
`);
|
|
14747
|
+
} catch (err) {
|
|
14748
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
14749
|
+
process.stderr.write(`claudestory: failed to start inbox watcher, using polling fallback: ${msg}
|
|
14750
|
+
`);
|
|
14751
|
+
startPollingFallback(inboxPath, server);
|
|
14752
|
+
}
|
|
14753
|
+
}
|
|
14754
|
+
function stopInboxWatcher() {
|
|
14755
|
+
if (debounceTimer) {
|
|
14756
|
+
clearTimeout(debounceTimer);
|
|
14757
|
+
debounceTimer = null;
|
|
14758
|
+
}
|
|
14759
|
+
if (watcher) {
|
|
14760
|
+
watcher.close();
|
|
14761
|
+
watcher = null;
|
|
14762
|
+
}
|
|
14763
|
+
if (pollInterval) {
|
|
14764
|
+
clearInterval(pollInterval);
|
|
14765
|
+
pollInterval = null;
|
|
14766
|
+
}
|
|
14767
|
+
permissionRetryCount.clear();
|
|
14768
|
+
}
|
|
14769
|
+
function scheduleDebouncedProcess(inboxPath, server) {
|
|
14770
|
+
if (debounceTimer) clearTimeout(debounceTimer);
|
|
14771
|
+
debounceTimer = setTimeout(() => {
|
|
14772
|
+
debounceTimer = null;
|
|
14773
|
+
processInbox(inboxPath, server).catch((err) => {
|
|
14774
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
14775
|
+
process.stderr.write(`claudestory: inbox processing error: ${msg}
|
|
14776
|
+
`);
|
|
14777
|
+
});
|
|
14778
|
+
}, DEBOUNCE_MS);
|
|
14779
|
+
}
|
|
14780
|
+
function startPollingFallback(inboxPath, server) {
|
|
14781
|
+
if (pollInterval) return;
|
|
14782
|
+
pollInterval = setInterval(() => {
|
|
14783
|
+
processInbox(inboxPath, server).catch((err) => {
|
|
14784
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
14785
|
+
process.stderr.write(`claudestory: poll processing error: ${msg}
|
|
14786
|
+
`);
|
|
14787
|
+
});
|
|
14788
|
+
}, 2e3);
|
|
14789
|
+
}
|
|
14790
|
+
async function recoverStaleProcessingFiles(inboxPath) {
|
|
14791
|
+
let entries;
|
|
14792
|
+
try {
|
|
14793
|
+
entries = await readdir4(inboxPath);
|
|
14794
|
+
} catch {
|
|
14795
|
+
return;
|
|
14796
|
+
}
|
|
14797
|
+
for (const f of entries) {
|
|
14798
|
+
if (f.endsWith(".processing")) {
|
|
14799
|
+
const originalName = f.slice(0, -".processing".length);
|
|
14800
|
+
try {
|
|
14801
|
+
await rename2(join25(inboxPath, f), join25(inboxPath, originalName));
|
|
14802
|
+
process.stderr.write(`claudestory: recovered stale processing file: ${f}
|
|
14803
|
+
`);
|
|
14804
|
+
} catch {
|
|
14805
|
+
}
|
|
14806
|
+
}
|
|
14807
|
+
}
|
|
14808
|
+
}
|
|
14809
|
+
async function processInbox(inboxPath, server) {
|
|
14810
|
+
while (true) {
|
|
14811
|
+
let entries;
|
|
14812
|
+
try {
|
|
14813
|
+
entries = await readdir4(inboxPath);
|
|
14814
|
+
} catch {
|
|
14815
|
+
return;
|
|
14816
|
+
}
|
|
14817
|
+
const eventFiles = entries.filter((f) => f.endsWith(".json") && !f.startsWith(".")).sort();
|
|
14818
|
+
if (eventFiles.length === 0) break;
|
|
14819
|
+
const batch = eventFiles.slice(0, MAX_INBOX_DEPTH);
|
|
14820
|
+
if (eventFiles.length > MAX_INBOX_DEPTH) {
|
|
14821
|
+
process.stderr.write(
|
|
14822
|
+
`claudestory: channel inbox has ${eventFiles.length} files, processing batch of ${MAX_INBOX_DEPTH}
|
|
14823
|
+
`
|
|
14824
|
+
);
|
|
14825
|
+
}
|
|
14826
|
+
for (const filename of batch) {
|
|
14827
|
+
await processEventFile(inboxPath, filename, server);
|
|
14828
|
+
}
|
|
14829
|
+
if (eventFiles.length <= MAX_INBOX_DEPTH) break;
|
|
14830
|
+
}
|
|
14831
|
+
await trimFailedDirectory(inboxPath);
|
|
14832
|
+
}
|
|
14833
|
+
async function processEventFile(inboxPath, filename, server) {
|
|
14834
|
+
if (!isValidInboxFilename(filename)) {
|
|
14835
|
+
process.stderr.write(`claudestory: rejecting invalid inbox filename: ${filename}
|
|
14836
|
+
`);
|
|
14837
|
+
await moveToFailed(inboxPath, filename);
|
|
14838
|
+
return;
|
|
14839
|
+
}
|
|
14840
|
+
const filePath = join25(inboxPath, filename);
|
|
14841
|
+
const processingPath = join25(inboxPath, `${filename}.processing`);
|
|
14842
|
+
try {
|
|
14843
|
+
await rename2(filePath, processingPath);
|
|
14844
|
+
} catch {
|
|
14845
|
+
return;
|
|
14846
|
+
}
|
|
14847
|
+
let raw;
|
|
14848
|
+
try {
|
|
14849
|
+
raw = await readFile5(processingPath, "utf-8");
|
|
14850
|
+
} catch {
|
|
14851
|
+
await moveToFailed(inboxPath, `${filename}.processing`, filename);
|
|
14852
|
+
return;
|
|
14853
|
+
}
|
|
14854
|
+
const processingFilename = `${filename}.processing`;
|
|
14855
|
+
let parsed;
|
|
14856
|
+
try {
|
|
14857
|
+
parsed = JSON.parse(raw);
|
|
14858
|
+
} catch {
|
|
14859
|
+
process.stderr.write(`claudestory: invalid JSON in channel event ${filename}
|
|
14860
|
+
`);
|
|
14861
|
+
await moveToFailed(inboxPath, processingFilename, filename);
|
|
14862
|
+
return;
|
|
14863
|
+
}
|
|
14864
|
+
const result = ChannelEventSchema.safeParse(parsed);
|
|
14865
|
+
if (!result.success) {
|
|
14866
|
+
process.stderr.write(`claudestory: invalid channel event schema in ${filename}: ${result.error.message}
|
|
14867
|
+
`);
|
|
14868
|
+
await moveToFailed(inboxPath, processingFilename, filename);
|
|
14869
|
+
return;
|
|
14870
|
+
}
|
|
14871
|
+
const event = result.data;
|
|
14872
|
+
try {
|
|
14873
|
+
if (event.event === "permission_response") {
|
|
14874
|
+
await server.server.sendNotification({
|
|
14875
|
+
method: "notifications/claude/channel/permission",
|
|
14876
|
+
params: {
|
|
14877
|
+
requestId: event.payload.requestId,
|
|
14878
|
+
behavior: event.payload.behavior
|
|
14879
|
+
}
|
|
14880
|
+
});
|
|
14881
|
+
} else {
|
|
14882
|
+
const content = formatChannelContent(event);
|
|
14883
|
+
const meta = formatChannelMeta(event);
|
|
14884
|
+
await server.server.sendNotification({
|
|
14885
|
+
method: "notifications/claude/channel",
|
|
14886
|
+
params: { content, meta }
|
|
14887
|
+
});
|
|
14888
|
+
}
|
|
14889
|
+
process.stderr.write(`claudestory: sent channel event ${event.event}
|
|
14890
|
+
`);
|
|
14891
|
+
permissionRetryCount.delete(filename);
|
|
14892
|
+
eventRetryCount.delete(filename);
|
|
14893
|
+
} catch (err) {
|
|
14894
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
14895
|
+
if (event.event === "permission_response") {
|
|
14896
|
+
const retries2 = (permissionRetryCount.get(filename) ?? 0) + 1;
|
|
14897
|
+
permissionRetryCount.set(filename, retries2);
|
|
14898
|
+
if (retries2 >= MAX_PERMISSION_RETRIES) {
|
|
14899
|
+
process.stderr.write(`claudestory: permission notification failed after ${retries2} retries, quarantining: ${msg}
|
|
14900
|
+
`);
|
|
14901
|
+
permissionRetryCount.delete(filename);
|
|
14902
|
+
await moveToFailed(inboxPath, processingFilename, filename);
|
|
14903
|
+
return;
|
|
14904
|
+
}
|
|
14905
|
+
try {
|
|
14906
|
+
await rename2(processingPath, filePath);
|
|
14907
|
+
} catch (renameErr) {
|
|
14908
|
+
const renameMsg = renameErr instanceof Error ? renameErr.message : String(renameErr);
|
|
14909
|
+
process.stderr.write(`claudestory: rename-back failed for ${filename}, quarantining: ${renameMsg}
|
|
14910
|
+
`);
|
|
14911
|
+
permissionRetryCount.delete(filename);
|
|
14912
|
+
await moveToFailed(inboxPath, processingFilename, filename);
|
|
14913
|
+
return;
|
|
14914
|
+
}
|
|
14915
|
+
process.stderr.write(`claudestory: permission notification failed (attempt ${retries2}/${MAX_PERMISSION_RETRIES}), keeping for retry: ${msg}
|
|
14916
|
+
`);
|
|
14917
|
+
return;
|
|
14918
|
+
}
|
|
14919
|
+
const eventAge = Date.now() - new Date(event.timestamp).getTime();
|
|
14920
|
+
if (eventAge > EVENT_EXPIRY_MS) {
|
|
14921
|
+
process.stderr.write(`claudestory: channel event ${event.event} expired after ${Math.round(eventAge / 1e3)}s, quarantining: ${msg}
|
|
14922
|
+
`);
|
|
14923
|
+
eventRetryCount.delete(filename);
|
|
14924
|
+
await moveToFailed(inboxPath, processingFilename, filename);
|
|
14925
|
+
return;
|
|
14926
|
+
}
|
|
14927
|
+
const retries = (eventRetryCount.get(filename) ?? 0) + 1;
|
|
14928
|
+
eventRetryCount.set(filename, retries);
|
|
14929
|
+
if (retries >= MAX_EVENT_RETRIES) {
|
|
14930
|
+
process.stderr.write(`claudestory: channel event ${event.event} failed after ${retries} retries, quarantining: ${msg}
|
|
14931
|
+
`);
|
|
14932
|
+
eventRetryCount.delete(filename);
|
|
14933
|
+
await moveToFailed(inboxPath, processingFilename, filename);
|
|
14934
|
+
return;
|
|
14935
|
+
}
|
|
14936
|
+
try {
|
|
14937
|
+
await rename2(processingPath, filePath);
|
|
14938
|
+
} catch (renameErr) {
|
|
14939
|
+
const renameMsg = renameErr instanceof Error ? renameErr.message : String(renameErr);
|
|
14940
|
+
process.stderr.write(`claudestory: rename-back failed for ${filename}, quarantining: ${renameMsg}
|
|
14941
|
+
`);
|
|
14942
|
+
eventRetryCount.delete(filename);
|
|
14943
|
+
await moveToFailed(inboxPath, processingFilename, filename);
|
|
14944
|
+
return;
|
|
14945
|
+
}
|
|
14946
|
+
process.stderr.write(`claudestory: channel event ${event.event} failed (attempt ${retries}/${MAX_EVENT_RETRIES}), keeping for retry: ${msg}
|
|
14947
|
+
`);
|
|
14948
|
+
return;
|
|
14949
|
+
}
|
|
14950
|
+
try {
|
|
14951
|
+
await unlink3(processingPath);
|
|
14952
|
+
} catch {
|
|
14953
|
+
}
|
|
14954
|
+
}
|
|
14955
|
+
async function moveToFailed(inboxPath, sourceFilename, destFilename) {
|
|
14956
|
+
const failedDir = join25(inboxPath, FAILED_DIR);
|
|
14957
|
+
const targetName = destFilename ?? sourceFilename;
|
|
14958
|
+
try {
|
|
14959
|
+
await mkdir5(failedDir, { recursive: true });
|
|
14960
|
+
await rename2(join25(inboxPath, sourceFilename), join25(failedDir, targetName));
|
|
14961
|
+
} catch (err) {
|
|
14962
|
+
try {
|
|
14963
|
+
await unlink3(join25(inboxPath, sourceFilename));
|
|
14964
|
+
} catch {
|
|
14965
|
+
}
|
|
14966
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
14967
|
+
process.stderr.write(`claudestory: failed to move ${sourceFilename} to .failed/: ${msg}
|
|
14968
|
+
`);
|
|
14969
|
+
}
|
|
14970
|
+
}
|
|
14971
|
+
async function trimFailedDirectory(inboxPath) {
|
|
14972
|
+
const failedDir = join25(inboxPath, FAILED_DIR);
|
|
14973
|
+
let files;
|
|
14974
|
+
try {
|
|
14975
|
+
files = await readdir4(failedDir);
|
|
14976
|
+
} catch {
|
|
14977
|
+
return;
|
|
14978
|
+
}
|
|
14979
|
+
const sorted = files.filter((f) => f.endsWith(".json")).sort();
|
|
14980
|
+
if (sorted.length <= MAX_FAILED_FILES) return;
|
|
14981
|
+
const toDelete = sorted.slice(0, sorted.length - MAX_FAILED_FILES);
|
|
14982
|
+
for (const f of toDelete) {
|
|
14983
|
+
try {
|
|
14984
|
+
await unlink3(join25(failedDir, f));
|
|
14985
|
+
} catch {
|
|
14986
|
+
}
|
|
14987
|
+
}
|
|
14988
|
+
}
|
|
14989
|
+
var INBOX_DIR, FAILED_DIR, MAX_INBOX_DEPTH, MAX_FAILED_FILES, DEBOUNCE_MS, MAX_PERMISSION_RETRIES, MAX_EVENT_RETRIES, EVENT_EXPIRY_MS, watcher, permissionRetryCount, eventRetryCount, debounceTimer, pollInterval;
|
|
14990
|
+
var init_inbox_watcher = __esm({
|
|
14991
|
+
"src/channel/inbox-watcher.ts"() {
|
|
14992
|
+
"use strict";
|
|
14993
|
+
init_esm_shims();
|
|
14994
|
+
init_events();
|
|
14995
|
+
INBOX_DIR = "channel-inbox";
|
|
14996
|
+
FAILED_DIR = ".failed";
|
|
14997
|
+
MAX_INBOX_DEPTH = 50;
|
|
14998
|
+
MAX_FAILED_FILES = 20;
|
|
14999
|
+
DEBOUNCE_MS = 100;
|
|
15000
|
+
MAX_PERMISSION_RETRIES = 15;
|
|
15001
|
+
MAX_EVENT_RETRIES = 30;
|
|
15002
|
+
EVENT_EXPIRY_MS = 6e4;
|
|
15003
|
+
watcher = null;
|
|
15004
|
+
permissionRetryCount = /* @__PURE__ */ new Map();
|
|
15005
|
+
eventRetryCount = /* @__PURE__ */ new Map();
|
|
15006
|
+
debounceTimer = null;
|
|
15007
|
+
pollInterval = null;
|
|
15008
|
+
}
|
|
15009
|
+
});
|
|
15010
|
+
|
|
13963
15011
|
// src/mcp/index.ts
|
|
13964
15012
|
var mcp_exports = {};
|
|
13965
|
-
import { realpathSync as realpathSync3, existsSync as
|
|
13966
|
-
import { resolve as resolve9, join as
|
|
13967
|
-
import { z as
|
|
15013
|
+
import { realpathSync as realpathSync3, existsSync as existsSync14 } from "fs";
|
|
15014
|
+
import { resolve as resolve9, join as join26, isAbsolute } from "path";
|
|
15015
|
+
import { z as z12 } from "zod";
|
|
13968
15016
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
13969
15017
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
13970
15018
|
function tryDiscoverRoot() {
|
|
@@ -13978,7 +15026,7 @@ function tryDiscoverRoot() {
|
|
|
13978
15026
|
const resolved = resolve9(envRoot);
|
|
13979
15027
|
try {
|
|
13980
15028
|
const canonical = realpathSync3(resolved);
|
|
13981
|
-
if (
|
|
15029
|
+
if (existsSync14(join26(canonical, CONFIG_PATH2))) {
|
|
13982
15030
|
return canonical;
|
|
13983
15031
|
}
|
|
13984
15032
|
process.stderr.write(`Warning: No .story/config.json at ${canonical}
|
|
@@ -14006,9 +15054,9 @@ function registerDegradedTools(server) {
|
|
|
14006
15054
|
const degradedInit = server.registerTool("claudestory_init", {
|
|
14007
15055
|
description: "Initialize a new .story/ project in the current directory",
|
|
14008
15056
|
inputSchema: {
|
|
14009
|
-
name:
|
|
14010
|
-
type:
|
|
14011
|
-
language:
|
|
15057
|
+
name: z12.string().describe("Project name"),
|
|
15058
|
+
type: z12.string().optional().describe("Project type (e.g. npm, macapp, cargo, generic)"),
|
|
15059
|
+
language: z12.string().optional().describe("Primary language (e.g. typescript, swift, rust)")
|
|
14012
15060
|
}
|
|
14013
15061
|
}, async (args) => {
|
|
14014
15062
|
let result;
|
|
@@ -14038,6 +15086,12 @@ function registerDegradedTools(server) {
|
|
|
14038
15086
|
return { content: [{ type: "text", text: `Initialized .story/ project "${args.name}" at ${result.root}
|
|
14039
15087
|
|
|
14040
15088
|
Warning: tool registration failed. Restart the MCP server for full tool access.` }] };
|
|
15089
|
+
}
|
|
15090
|
+
try {
|
|
15091
|
+
await startInboxWatcher(result.root, server);
|
|
15092
|
+
} catch (watchErr) {
|
|
15093
|
+
process.stderr.write(`claudestory: inbox watcher failed after init: ${watchErr instanceof Error ? watchErr.message : String(watchErr)}
|
|
15094
|
+
`);
|
|
14041
15095
|
}
|
|
14042
15096
|
process.stderr.write(`claudestory: initialized at ${result.root}
|
|
14043
15097
|
`);
|
|
@@ -14057,17 +15111,32 @@ async function main() {
|
|
|
14057
15111
|
const server = new McpServer(
|
|
14058
15112
|
{ name: "claudestory", version },
|
|
14059
15113
|
{
|
|
14060
|
-
instructions: root ? "Start with claudestory_status for a project overview, then claudestory_ticket_next for the highest-priority work, then claudestory_handover_latest for session context." : "No .story/ project found. Use claudestory_init to initialize a new project, or navigate to a directory with .story/."
|
|
15114
|
+
instructions: root ? "Start with claudestory_status for a project overview, then claudestory_ticket_next for the highest-priority work, then claudestory_handover_latest for session context." : "No .story/ project found. Use claudestory_init to initialize a new project, or navigate to a directory with .story/.",
|
|
15115
|
+
capabilities: {
|
|
15116
|
+
experimental: {
|
|
15117
|
+
"claude/channel": {},
|
|
15118
|
+
"claude/channel/permission": {}
|
|
15119
|
+
}
|
|
15120
|
+
}
|
|
14061
15121
|
}
|
|
14062
15122
|
);
|
|
14063
15123
|
if (root) {
|
|
14064
15124
|
registerAllTools(server, root);
|
|
15125
|
+
await startInboxWatcher(root, server);
|
|
14065
15126
|
process.stderr.write(`claudestory MCP server running (root: ${root})
|
|
14066
15127
|
`);
|
|
14067
15128
|
} else {
|
|
14068
15129
|
registerDegradedTools(server);
|
|
14069
15130
|
process.stderr.write("claudestory MCP server running (no project \u2014 claudestory_init available)\n");
|
|
14070
15131
|
}
|
|
15132
|
+
process.on("SIGINT", () => {
|
|
15133
|
+
stopInboxWatcher();
|
|
15134
|
+
process.exit(0);
|
|
15135
|
+
});
|
|
15136
|
+
process.on("SIGTERM", () => {
|
|
15137
|
+
stopInboxWatcher();
|
|
15138
|
+
process.exit(0);
|
|
15139
|
+
});
|
|
14071
15140
|
const transport = new StdioServerTransport();
|
|
14072
15141
|
await server.connect(transport);
|
|
14073
15142
|
}
|
|
@@ -14079,9 +15148,10 @@ var init_mcp = __esm({
|
|
|
14079
15148
|
init_project_root_discovery();
|
|
14080
15149
|
init_tools();
|
|
14081
15150
|
init_init();
|
|
15151
|
+
init_inbox_watcher();
|
|
14082
15152
|
ENV_VAR2 = "CLAUDESTORY_PROJECT_ROOT";
|
|
14083
15153
|
CONFIG_PATH2 = ".story/config.json";
|
|
14084
|
-
version = "0.1.
|
|
15154
|
+
version = "0.1.63";
|
|
14085
15155
|
main().catch((err) => {
|
|
14086
15156
|
process.stderr.write(`Fatal: ${err instanceof Error ? err.message : String(err)}
|
|
14087
15157
|
`);
|
|
@@ -14117,7 +15187,7 @@ __export(run_exports, {
|
|
|
14117
15187
|
runReadCommand: () => runReadCommand,
|
|
14118
15188
|
writeOutput: () => writeOutput
|
|
14119
15189
|
});
|
|
14120
|
-
import { join as
|
|
15190
|
+
import { join as join27 } from "path";
|
|
14121
15191
|
function writeOutput(text) {
|
|
14122
15192
|
try {
|
|
14123
15193
|
process.stdout.write(text + "\n");
|
|
@@ -14145,7 +15215,7 @@ async function runReadCommand(format, handler) {
|
|
|
14145
15215
|
return;
|
|
14146
15216
|
}
|
|
14147
15217
|
const { state, warnings } = await loadProject(root);
|
|
14148
|
-
const handoversDir =
|
|
15218
|
+
const handoversDir = join27(root, ".story", "handovers");
|
|
14149
15219
|
const result = await handler({ state, warnings, root, handoversDir, format });
|
|
14150
15220
|
writeOutput(result.output);
|
|
14151
15221
|
let exitCode = result.exitCode ?? ExitCode.OK;
|
|
@@ -14180,7 +15250,7 @@ async function runDeleteCommand(format, force, handler) {
|
|
|
14180
15250
|
return;
|
|
14181
15251
|
}
|
|
14182
15252
|
const { state, warnings } = await loadProject(root);
|
|
14183
|
-
const handoversDir =
|
|
15253
|
+
const handoversDir = join27(root, ".story", "handovers");
|
|
14184
15254
|
if (!force && hasIntegrityWarnings(warnings)) {
|
|
14185
15255
|
writeOutput(
|
|
14186
15256
|
formatError(
|
|
@@ -14696,9 +15766,9 @@ __export(setup_skill_exports, {
|
|
|
14696
15766
|
removeHook: () => removeHook,
|
|
14697
15767
|
resolveSkillSourceDir: () => resolveSkillSourceDir
|
|
14698
15768
|
});
|
|
14699
|
-
import { mkdir as
|
|
14700
|
-
import { existsSync as
|
|
14701
|
-
import { join as
|
|
15769
|
+
import { mkdir as mkdir6, writeFile as writeFile3, readFile as readFile6, readdir as readdir5, copyFile, rm, rename as rename3, unlink as unlink4 } from "fs/promises";
|
|
15770
|
+
import { existsSync as existsSync15 } from "fs";
|
|
15771
|
+
import { join as join28, dirname as dirname5 } from "path";
|
|
14702
15772
|
import { homedir } from "os";
|
|
14703
15773
|
import { execFileSync as execFileSync2 } from "child_process";
|
|
14704
15774
|
import { fileURLToPath as fileURLToPath4 } from "url";
|
|
@@ -14707,10 +15777,10 @@ function log(msg) {
|
|
|
14707
15777
|
}
|
|
14708
15778
|
function resolveSkillSourceDir() {
|
|
14709
15779
|
const thisDir = dirname5(fileURLToPath4(import.meta.url));
|
|
14710
|
-
const bundledPath =
|
|
14711
|
-
if (
|
|
14712
|
-
const sourcePath =
|
|
14713
|
-
if (
|
|
15780
|
+
const bundledPath = join28(thisDir, "..", "src", "skill");
|
|
15781
|
+
if (existsSync15(join28(bundledPath, "SKILL.md"))) return bundledPath;
|
|
15782
|
+
const sourcePath = join28(thisDir, "..", "..", "skill");
|
|
15783
|
+
if (existsSync15(join28(sourcePath, "SKILL.md"))) return sourcePath;
|
|
14714
15784
|
throw new Error(
|
|
14715
15785
|
`Cannot find bundled skill files. Checked:
|
|
14716
15786
|
${bundledPath}
|
|
@@ -14720,31 +15790,31 @@ function resolveSkillSourceDir() {
|
|
|
14720
15790
|
async function copyDirRecursive(srcDir, destDir) {
|
|
14721
15791
|
const tmpDir = destDir + ".tmp";
|
|
14722
15792
|
const bakDir = destDir + ".bak";
|
|
14723
|
-
if (!
|
|
14724
|
-
await
|
|
15793
|
+
if (!existsSync15(destDir) && existsSync15(bakDir)) {
|
|
15794
|
+
await rename3(bakDir, destDir);
|
|
14725
15795
|
}
|
|
14726
|
-
if (
|
|
14727
|
-
if (
|
|
14728
|
-
await
|
|
14729
|
-
const entries = await
|
|
15796
|
+
if (existsSync15(tmpDir)) await rm(tmpDir, { recursive: true, force: true });
|
|
15797
|
+
if (existsSync15(bakDir)) await rm(bakDir, { recursive: true, force: true });
|
|
15798
|
+
await mkdir6(tmpDir, { recursive: true });
|
|
15799
|
+
const entries = await readdir5(srcDir, { withFileTypes: true, recursive: true });
|
|
14730
15800
|
const written = [];
|
|
14731
15801
|
for (const entry of entries) {
|
|
14732
15802
|
if (!entry.isFile()) continue;
|
|
14733
15803
|
const parent = entry.parentPath ?? entry.path ?? srcDir;
|
|
14734
|
-
const relativePath =
|
|
14735
|
-
const srcPath =
|
|
14736
|
-
const destPath =
|
|
14737
|
-
await
|
|
15804
|
+
const relativePath = join28(parent, entry.name).slice(srcDir.length + 1);
|
|
15805
|
+
const srcPath = join28(srcDir, relativePath);
|
|
15806
|
+
const destPath = join28(tmpDir, relativePath);
|
|
15807
|
+
await mkdir6(dirname5(destPath), { recursive: true });
|
|
14738
15808
|
await copyFile(srcPath, destPath);
|
|
14739
15809
|
written.push(relativePath);
|
|
14740
15810
|
}
|
|
14741
|
-
if (
|
|
14742
|
-
await
|
|
15811
|
+
if (existsSync15(destDir)) {
|
|
15812
|
+
await rename3(destDir, bakDir);
|
|
14743
15813
|
}
|
|
14744
15814
|
try {
|
|
14745
|
-
await
|
|
15815
|
+
await rename3(tmpDir, destDir);
|
|
14746
15816
|
} catch (err) {
|
|
14747
|
-
if (
|
|
15817
|
+
if (existsSync15(bakDir)) await rename3(bakDir, destDir).catch(() => {
|
|
14748
15818
|
});
|
|
14749
15819
|
await rm(tmpDir, { recursive: true, force: true }).catch(() => {
|
|
14750
15820
|
});
|
|
@@ -14760,11 +15830,11 @@ function isHookWithCommand(entry, command) {
|
|
|
14760
15830
|
return e.type === "command" && typeof e.command === "string" && e.command.trim() === command;
|
|
14761
15831
|
}
|
|
14762
15832
|
async function registerHook(hookType, hookEntry, settingsPath, matcher) {
|
|
14763
|
-
const path2 = settingsPath ??
|
|
15833
|
+
const path2 = settingsPath ?? join28(homedir(), ".claude", "settings.json");
|
|
14764
15834
|
let raw = "{}";
|
|
14765
|
-
if (
|
|
15835
|
+
if (existsSync15(path2)) {
|
|
14766
15836
|
try {
|
|
14767
|
-
raw = await
|
|
15837
|
+
raw = await readFile6(path2, "utf-8");
|
|
14768
15838
|
} catch {
|
|
14769
15839
|
process.stderr.write(`Could not read ${path2} \u2014 skipping hook registration.
|
|
14770
15840
|
`);
|
|
@@ -14833,12 +15903,12 @@ async function registerHook(hookType, hookEntry, settingsPath, matcher) {
|
|
|
14833
15903
|
const tmpPath = `${path2}.${process.pid}.tmp`;
|
|
14834
15904
|
try {
|
|
14835
15905
|
const dir = dirname5(path2);
|
|
14836
|
-
await
|
|
15906
|
+
await mkdir6(dir, { recursive: true });
|
|
14837
15907
|
await writeFile3(tmpPath, JSON.stringify(settings, null, 2) + "\n", "utf-8");
|
|
14838
|
-
await
|
|
15908
|
+
await rename3(tmpPath, path2);
|
|
14839
15909
|
} catch (err) {
|
|
14840
15910
|
try {
|
|
14841
|
-
await
|
|
15911
|
+
await unlink4(tmpPath);
|
|
14842
15912
|
} catch {
|
|
14843
15913
|
}
|
|
14844
15914
|
const message = err instanceof Error ? err.message : String(err);
|
|
@@ -14858,11 +15928,11 @@ async function registerStopHook(settingsPath) {
|
|
|
14858
15928
|
return registerHook("Stop", { type: "command", command: STOP_HOOK_COMMAND, async: true }, settingsPath);
|
|
14859
15929
|
}
|
|
14860
15930
|
async function removeHook(hookType, command, settingsPath) {
|
|
14861
|
-
const path2 = settingsPath ??
|
|
15931
|
+
const path2 = settingsPath ?? join28(homedir(), ".claude", "settings.json");
|
|
14862
15932
|
let raw = "{}";
|
|
14863
|
-
if (
|
|
15933
|
+
if (existsSync15(path2)) {
|
|
14864
15934
|
try {
|
|
14865
|
-
raw = await
|
|
15935
|
+
raw = await readFile6(path2, "utf-8");
|
|
14866
15936
|
} catch {
|
|
14867
15937
|
return "skipped";
|
|
14868
15938
|
}
|
|
@@ -14891,12 +15961,12 @@ async function removeHook(hookType, command, settingsPath) {
|
|
|
14891
15961
|
const tmpPath = `${path2}.${process.pid}.tmp`;
|
|
14892
15962
|
try {
|
|
14893
15963
|
const dir = dirname5(path2);
|
|
14894
|
-
await
|
|
15964
|
+
await mkdir6(dir, { recursive: true });
|
|
14895
15965
|
await writeFile3(tmpPath, JSON.stringify(settings, null, 2) + "\n", "utf-8");
|
|
14896
|
-
await
|
|
15966
|
+
await rename3(tmpPath, path2);
|
|
14897
15967
|
} catch {
|
|
14898
15968
|
try {
|
|
14899
|
-
await
|
|
15969
|
+
await unlink4(tmpPath);
|
|
14900
15970
|
} catch {
|
|
14901
15971
|
}
|
|
14902
15972
|
return "skipped";
|
|
@@ -14905,8 +15975,8 @@ async function removeHook(hookType, command, settingsPath) {
|
|
|
14905
15975
|
}
|
|
14906
15976
|
async function handleSetupSkill(options = {}) {
|
|
14907
15977
|
const { skipHooks = false } = options;
|
|
14908
|
-
const skillDir =
|
|
14909
|
-
await
|
|
15978
|
+
const skillDir = join28(homedir(), ".claude", "skills", "story");
|
|
15979
|
+
await mkdir6(skillDir, { recursive: true });
|
|
14910
15980
|
let srcSkillDir;
|
|
14911
15981
|
try {
|
|
14912
15982
|
srcSkillDir = resolveSkillSourceDir();
|
|
@@ -14918,31 +15988,31 @@ async function handleSetupSkill(options = {}) {
|
|
|
14918
15988
|
process.exitCode = 1;
|
|
14919
15989
|
return;
|
|
14920
15990
|
}
|
|
14921
|
-
const oldPrimeDir =
|
|
14922
|
-
if (
|
|
15991
|
+
const oldPrimeDir = join28(homedir(), ".claude", "skills", "prime");
|
|
15992
|
+
if (existsSync15(oldPrimeDir)) {
|
|
14923
15993
|
await rm(oldPrimeDir, { recursive: true, force: true });
|
|
14924
15994
|
log("Removed old /prime skill (migrated to /story)");
|
|
14925
15995
|
}
|
|
14926
|
-
const existed =
|
|
14927
|
-
const skillContent = await
|
|
14928
|
-
await writeFile3(
|
|
15996
|
+
const existed = existsSync15(join28(skillDir, "SKILL.md"));
|
|
15997
|
+
const skillContent = await readFile6(join28(srcSkillDir, "SKILL.md"), "utf-8");
|
|
15998
|
+
await writeFile3(join28(skillDir, "SKILL.md"), skillContent, "utf-8");
|
|
14929
15999
|
const supportFiles = ["setup-flow.md", "autonomous-mode.md", "reference.md"];
|
|
14930
16000
|
const writtenFiles = ["SKILL.md"];
|
|
14931
16001
|
const missingFiles = [];
|
|
14932
16002
|
for (const filename of supportFiles) {
|
|
14933
|
-
const srcPath =
|
|
14934
|
-
if (
|
|
14935
|
-
const content = await
|
|
14936
|
-
await writeFile3(
|
|
16003
|
+
const srcPath = join28(srcSkillDir, filename);
|
|
16004
|
+
if (existsSync15(srcPath)) {
|
|
16005
|
+
const content = await readFile6(srcPath, "utf-8");
|
|
16006
|
+
await writeFile3(join28(skillDir, filename), content, "utf-8");
|
|
14937
16007
|
writtenFiles.push(filename);
|
|
14938
16008
|
} else {
|
|
14939
16009
|
missingFiles.push(filename);
|
|
14940
16010
|
}
|
|
14941
16011
|
}
|
|
14942
16012
|
for (const subdir of ["design", "review-lenses"]) {
|
|
14943
|
-
const srcDir =
|
|
14944
|
-
if (
|
|
14945
|
-
const destDir =
|
|
16013
|
+
const srcDir = join28(srcSkillDir, subdir);
|
|
16014
|
+
if (existsSync15(srcDir)) {
|
|
16015
|
+
const destDir = join28(skillDir, subdir);
|
|
14946
16016
|
try {
|
|
14947
16017
|
const files = await copyDirRecursive(srcDir, destDir);
|
|
14948
16018
|
for (const f of files) writtenFiles.push(`${subdir}/${f}`);
|
|
@@ -15066,8 +16136,8 @@ var hook_status_exports = {};
|
|
|
15066
16136
|
__export(hook_status_exports, {
|
|
15067
16137
|
handleHookStatus: () => handleHookStatus
|
|
15068
16138
|
});
|
|
15069
|
-
import { readFileSync as
|
|
15070
|
-
import { join as
|
|
16139
|
+
import { readFileSync as readFileSync13, writeFileSync as writeFileSync8, renameSync as renameSync3, unlinkSync as unlinkSync5 } from "fs";
|
|
16140
|
+
import { join as join29 } from "path";
|
|
15071
16141
|
async function readStdinSilent() {
|
|
15072
16142
|
try {
|
|
15073
16143
|
const chunks = [];
|
|
@@ -15084,12 +16154,12 @@ async function readStdinSilent() {
|
|
|
15084
16154
|
function atomicWriteSync(targetPath, content) {
|
|
15085
16155
|
const tmp = `${targetPath}.${process.pid}.tmp`;
|
|
15086
16156
|
try {
|
|
15087
|
-
|
|
16157
|
+
writeFileSync8(tmp, content, "utf-8");
|
|
15088
16158
|
renameSync3(tmp, targetPath);
|
|
15089
16159
|
return true;
|
|
15090
16160
|
} catch {
|
|
15091
16161
|
try {
|
|
15092
|
-
|
|
16162
|
+
unlinkSync5(tmp);
|
|
15093
16163
|
} catch {
|
|
15094
16164
|
}
|
|
15095
16165
|
return false;
|
|
@@ -15117,10 +16187,10 @@ function activePayload(session) {
|
|
|
15117
16187
|
};
|
|
15118
16188
|
}
|
|
15119
16189
|
function ensureGitignore(root) {
|
|
15120
|
-
const gitignorePath =
|
|
16190
|
+
const gitignorePath = join29(root, ".story", ".gitignore");
|
|
15121
16191
|
let existing = "";
|
|
15122
16192
|
try {
|
|
15123
|
-
existing =
|
|
16193
|
+
existing = readFileSync13(gitignorePath, "utf-8");
|
|
15124
16194
|
} catch {
|
|
15125
16195
|
}
|
|
15126
16196
|
const lines = existing.split("\n").map((l) => l.trim());
|
|
@@ -15130,13 +16200,13 @@ function ensureGitignore(root) {
|
|
|
15130
16200
|
if (content.length > 0 && !content.endsWith("\n")) content += "\n";
|
|
15131
16201
|
content += missing.join("\n") + "\n";
|
|
15132
16202
|
try {
|
|
15133
|
-
|
|
16203
|
+
writeFileSync8(gitignorePath, content, "utf-8");
|
|
15134
16204
|
} catch {
|
|
15135
16205
|
}
|
|
15136
16206
|
}
|
|
15137
16207
|
function writeStatus(root, payload) {
|
|
15138
16208
|
ensureGitignore(root);
|
|
15139
|
-
const statusPath =
|
|
16209
|
+
const statusPath = join29(root, ".story", "status.json");
|
|
15140
16210
|
const content = JSON.stringify(payload, null, 2) + "\n";
|
|
15141
16211
|
atomicWriteSync(statusPath, content);
|
|
15142
16212
|
}
|
|
@@ -15195,8 +16265,8 @@ var config_update_exports = {};
|
|
|
15195
16265
|
__export(config_update_exports, {
|
|
15196
16266
|
handleConfigSetOverrides: () => handleConfigSetOverrides
|
|
15197
16267
|
});
|
|
15198
|
-
import { readFileSync as
|
|
15199
|
-
import { join as
|
|
16268
|
+
import { readFileSync as readFileSync14 } from "fs";
|
|
16269
|
+
import { join as join30 } from "path";
|
|
15200
16270
|
async function handleConfigSetOverrides(root, format, options) {
|
|
15201
16271
|
const { json: jsonArg, clear } = options;
|
|
15202
16272
|
if (!clear && !jsonArg) {
|
|
@@ -15224,8 +16294,8 @@ async function handleConfigSetOverrides(root, format, options) {
|
|
|
15224
16294
|
}
|
|
15225
16295
|
let resultOverrides = null;
|
|
15226
16296
|
await withProjectLock(root, { strict: false }, async () => {
|
|
15227
|
-
const configPath =
|
|
15228
|
-
const rawContent =
|
|
16297
|
+
const configPath = join30(root, ".story", "config.json");
|
|
16298
|
+
const rawContent = readFileSync14(configPath, "utf-8");
|
|
15229
16299
|
const raw = JSON.parse(rawContent);
|
|
15230
16300
|
if (clear) {
|
|
15231
16301
|
delete raw.recipeOverrides;
|
|
@@ -15296,6 +16366,12 @@ async function handleSessionCompactPrepare() {
|
|
|
15296
16366
|
`);
|
|
15297
16367
|
return;
|
|
15298
16368
|
}
|
|
16369
|
+
writeResumeMarker(root, active.state.sessionId, {
|
|
16370
|
+
ticket: active.state.ticket,
|
|
16371
|
+
completedTickets: active.state.completedTickets,
|
|
16372
|
+
resolvedIssues: active.state.resolvedIssues,
|
|
16373
|
+
preCompactState: active.state.preCompactState ?? active.state.state
|
|
16374
|
+
});
|
|
15299
16375
|
try {
|
|
15300
16376
|
const loadResult = await loadProject(root);
|
|
15301
16377
|
const { saveSnapshot: saveSnapshot2 } = await Promise.resolve().then(() => (init_snapshot(), snapshot_exports));
|
|
@@ -15312,7 +16388,10 @@ async function handleSessionResumePrompt() {
|
|
|
15312
16388
|
const root = discoverProjectRoot();
|
|
15313
16389
|
if (!root) return;
|
|
15314
16390
|
const match = findResumableSession(root);
|
|
15315
|
-
if (!match)
|
|
16391
|
+
if (!match) {
|
|
16392
|
+
removeResumeMarker(root);
|
|
16393
|
+
return;
|
|
16394
|
+
}
|
|
15316
16395
|
const { info, stale } = match;
|
|
15317
16396
|
const sessionId = info.state.sessionId;
|
|
15318
16397
|
if (stale) {
|
|
@@ -15387,6 +16466,7 @@ claudestory_autonomous_guide {"sessionId": "${info.state.sessionId}", "action":
|
|
|
15387
16466
|
ticketId: info.state.ticket?.id ?? null
|
|
15388
16467
|
}
|
|
15389
16468
|
});
|
|
16469
|
+
removeResumeMarker(root);
|
|
15390
16470
|
return `Session ${info.state.sessionId} ended (unrecoverable \u2014 invalid preCompactState: ${preCompactState ?? "null"}). Run "start" for a new session.`;
|
|
15391
16471
|
});
|
|
15392
16472
|
}
|
|
@@ -15442,6 +16522,7 @@ async function handleSessionStop(root, sessionId) {
|
|
|
15442
16522
|
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
15443
16523
|
data: { previousState: info.state.state, ticketId: ticketId ?? null, ticketReleased }
|
|
15444
16524
|
});
|
|
16525
|
+
removeResumeMarker(root);
|
|
15445
16526
|
return `Session ${info.state.sessionId} stopped.${ticketReleased ? ` Ticket ${ticketId} released to open.` : ticketId ? ` Ticket ${ticketId} may need manual cleanup.` : ""}`;
|
|
15446
16527
|
});
|
|
15447
16528
|
}
|
|
@@ -15453,6 +16534,7 @@ var init_session_compact = __esm({
|
|
|
15453
16534
|
init_session();
|
|
15454
16535
|
init_session_types();
|
|
15455
16536
|
init_project_loader();
|
|
16537
|
+
init_resume_marker();
|
|
15456
16538
|
}
|
|
15457
16539
|
});
|
|
15458
16540
|
|
|
@@ -17568,7 +18650,7 @@ async function runCli() {
|
|
|
17568
18650
|
registerSessionCommand: registerSessionCommand2,
|
|
17569
18651
|
registerRepairCommand: registerRepairCommand2
|
|
17570
18652
|
} = await Promise.resolve().then(() => (init_register(), register_exports));
|
|
17571
|
-
const version2 = "0.1.
|
|
18653
|
+
const version2 = "0.1.63";
|
|
17572
18654
|
class HandledError extends Error {
|
|
17573
18655
|
constructor() {
|
|
17574
18656
|
super("HANDLED_ERROR");
|