@anthropologies/claudestory 0.1.35 → 0.1.37
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 +584 -146
- package/dist/index.d.ts +18 -3
- package/dist/index.js +124 -6
- package/dist/mcp.js +540 -114
- package/package.json +1 -1
package/dist/mcp.js
CHANGED
|
@@ -1560,7 +1560,7 @@ function fencedBlock(content, lang) {
|
|
|
1560
1560
|
${content}
|
|
1561
1561
|
${fence}`;
|
|
1562
1562
|
}
|
|
1563
|
-
function formatStatus(state, format) {
|
|
1563
|
+
function formatStatus(state, format, activeSessions = []) {
|
|
1564
1564
|
const phases = phasesWithStatus(state);
|
|
1565
1565
|
const data = {
|
|
1566
1566
|
project: state.config.project,
|
|
@@ -1580,7 +1580,8 @@ function formatStatus(state, format) {
|
|
|
1580
1580
|
name: p.phase.name,
|
|
1581
1581
|
status: p.status,
|
|
1582
1582
|
leafCount: p.leafCount
|
|
1583
|
-
}))
|
|
1583
|
+
})),
|
|
1584
|
+
...activeSessions.length > 0 ? { activeSessions } : {}
|
|
1584
1585
|
};
|
|
1585
1586
|
if (format === "json") {
|
|
1586
1587
|
return JSON.stringify(successEnvelope(data), null, 2);
|
|
@@ -1602,6 +1603,15 @@ function formatStatus(state, format) {
|
|
|
1602
1603
|
const summary = p.phase.summary ?? truncate(p.phase.description, 80);
|
|
1603
1604
|
lines.push(`${indicator} **${escapeMarkdownInline(p.phase.name)}** (${p.leafCount} tickets) \u2014 ${escapeMarkdownInline(summary)}`);
|
|
1604
1605
|
}
|
|
1606
|
+
if (activeSessions.length > 0) {
|
|
1607
|
+
lines.push("");
|
|
1608
|
+
lines.push("## Active Sessions");
|
|
1609
|
+
lines.push("");
|
|
1610
|
+
for (const s of activeSessions) {
|
|
1611
|
+
const ticket = s.ticketId ? `${s.ticketId}: ${escapeMarkdownInline(s.ticketTitle ?? "")}` : "no ticket";
|
|
1612
|
+
lines.push(`- ${s.sessionId.slice(0, 8)}: ${s.state} -- ${ticket} (${s.mode} mode)`);
|
|
1613
|
+
}
|
|
1614
|
+
}
|
|
1605
1615
|
if (state.isEmptyScaffold) {
|
|
1606
1616
|
lines.push("");
|
|
1607
1617
|
lines.push(EMPTY_SCAFFOLD_HEADING);
|
|
@@ -2501,6 +2511,7 @@ function validateProject(state) {
|
|
|
2501
2511
|
}
|
|
2502
2512
|
}
|
|
2503
2513
|
}
|
|
2514
|
+
detectSupersedesCycles(state, findings);
|
|
2504
2515
|
const phaseIDCounts = /* @__PURE__ */ new Map();
|
|
2505
2516
|
for (const p of state.roadmap.phases) {
|
|
2506
2517
|
phaseIDCounts.set(p.id, (phaseIDCounts.get(p.id) ?? 0) + 1);
|
|
@@ -2705,6 +2716,33 @@ function dfsBlocked(id, state, visited, inStack, findings) {
|
|
|
2705
2716
|
inStack.delete(id);
|
|
2706
2717
|
visited.add(id);
|
|
2707
2718
|
}
|
|
2719
|
+
function detectSupersedesCycles(state, findings) {
|
|
2720
|
+
const visited = /* @__PURE__ */ new Set();
|
|
2721
|
+
const inStack = /* @__PURE__ */ new Set();
|
|
2722
|
+
for (const l of state.lessons) {
|
|
2723
|
+
if (l.supersedes == null || visited.has(l.id)) continue;
|
|
2724
|
+
dfsSupersedesChain(l.id, state, visited, inStack, findings);
|
|
2725
|
+
}
|
|
2726
|
+
}
|
|
2727
|
+
function dfsSupersedesChain(id, state, visited, inStack, findings) {
|
|
2728
|
+
if (inStack.has(id)) {
|
|
2729
|
+
findings.push({
|
|
2730
|
+
level: "error",
|
|
2731
|
+
code: "supersedes_cycle",
|
|
2732
|
+
message: `Cycle detected in supersedes chain involving ${id}.`,
|
|
2733
|
+
entity: id
|
|
2734
|
+
});
|
|
2735
|
+
return;
|
|
2736
|
+
}
|
|
2737
|
+
if (visited.has(id)) return;
|
|
2738
|
+
inStack.add(id);
|
|
2739
|
+
const lesson = state.lessonByID(id);
|
|
2740
|
+
if (lesson?.supersedes && lesson.supersedes !== id) {
|
|
2741
|
+
dfsSupersedesChain(lesson.supersedes, state, visited, inStack, findings);
|
|
2742
|
+
}
|
|
2743
|
+
inStack.delete(id);
|
|
2744
|
+
visited.add(id);
|
|
2745
|
+
}
|
|
2708
2746
|
var init_validation = __esm({
|
|
2709
2747
|
"src/core/validation.ts"() {
|
|
2710
2748
|
"use strict";
|
|
@@ -2723,7 +2761,7 @@ __export(handover_exports, {
|
|
|
2723
2761
|
});
|
|
2724
2762
|
import { existsSync as existsSync4 } from "fs";
|
|
2725
2763
|
import { mkdir as mkdir2 } from "fs/promises";
|
|
2726
|
-
import { join as
|
|
2764
|
+
import { join as join5, resolve as resolve4 } from "path";
|
|
2727
2765
|
function handleHandoverList(ctx) {
|
|
2728
2766
|
return { output: formatHandoverList(ctx.state.handoverFilenames, ctx.format) };
|
|
2729
2767
|
}
|
|
@@ -2808,15 +2846,15 @@ async function handleHandoverCreate(content, slugRaw, format, root) {
|
|
|
2808
2846
|
let filename;
|
|
2809
2847
|
await withProjectLock(root, { strict: false }, async () => {
|
|
2810
2848
|
const absRoot = resolve4(root);
|
|
2811
|
-
const handoversDir =
|
|
2849
|
+
const handoversDir = join5(absRoot, ".story", "handovers");
|
|
2812
2850
|
await mkdir2(handoversDir, { recursive: true });
|
|
2813
|
-
const wrapDir =
|
|
2851
|
+
const wrapDir = join5(absRoot, ".story");
|
|
2814
2852
|
const datePrefix = `${date}-`;
|
|
2815
2853
|
const seqRegex = new RegExp(`^${date}-(\\d{2})-`);
|
|
2816
2854
|
let maxSeq = 0;
|
|
2817
|
-
const { readdirSync:
|
|
2855
|
+
const { readdirSync: readdirSync5 } = await import("fs");
|
|
2818
2856
|
try {
|
|
2819
|
-
for (const f of
|
|
2857
|
+
for (const f of readdirSync5(handoversDir)) {
|
|
2820
2858
|
const m = f.match(seqRegex);
|
|
2821
2859
|
if (m) {
|
|
2822
2860
|
const n = parseInt(m[1], 10);
|
|
@@ -2833,7 +2871,7 @@ async function handleHandoverCreate(content, slugRaw, format, root) {
|
|
|
2833
2871
|
);
|
|
2834
2872
|
}
|
|
2835
2873
|
let candidate = `${date}-${String(nextSeq).padStart(2, "0")}-${slug}.md`;
|
|
2836
|
-
let candidatePath =
|
|
2874
|
+
let candidatePath = join5(handoversDir, candidate);
|
|
2837
2875
|
while (existsSync4(candidatePath)) {
|
|
2838
2876
|
nextSeq++;
|
|
2839
2877
|
if (nextSeq > 99) {
|
|
@@ -2843,7 +2881,7 @@ async function handleHandoverCreate(content, slugRaw, format, root) {
|
|
|
2843
2881
|
);
|
|
2844
2882
|
}
|
|
2845
2883
|
candidate = `${date}-${String(nextSeq).padStart(2, "0")}-${slug}.md`;
|
|
2846
|
-
candidatePath =
|
|
2884
|
+
candidatePath = join5(handoversDir, candidate);
|
|
2847
2885
|
}
|
|
2848
2886
|
await parseHandoverFilename(candidate, handoversDir);
|
|
2849
2887
|
await guardPath(candidatePath, wrapDir);
|
|
@@ -3158,11 +3196,11 @@ __export(snapshot_exports, {
|
|
|
3158
3196
|
});
|
|
3159
3197
|
import { readdir as readdir3, readFile as readFile3, mkdir as mkdir3, unlink as unlink2 } from "fs/promises";
|
|
3160
3198
|
import { existsSync as existsSync5 } from "fs";
|
|
3161
|
-
import { join as
|
|
3199
|
+
import { join as join6, resolve as resolve5 } from "path";
|
|
3162
3200
|
import { z as z8 } from "zod";
|
|
3163
3201
|
async function saveSnapshot(root, loadResult) {
|
|
3164
3202
|
const absRoot = resolve5(root);
|
|
3165
|
-
const snapshotsDir =
|
|
3203
|
+
const snapshotsDir = join6(absRoot, ".story", "snapshots");
|
|
3166
3204
|
await mkdir3(snapshotsDir, { recursive: true });
|
|
3167
3205
|
const { state, warnings } = loadResult;
|
|
3168
3206
|
const now = /* @__PURE__ */ new Date();
|
|
@@ -3187,8 +3225,8 @@ async function saveSnapshot(root, loadResult) {
|
|
|
3187
3225
|
} : {}
|
|
3188
3226
|
};
|
|
3189
3227
|
const json = JSON.stringify(snapshot, null, 2) + "\n";
|
|
3190
|
-
const targetPath =
|
|
3191
|
-
const wrapDir =
|
|
3228
|
+
const targetPath = join6(snapshotsDir, filename);
|
|
3229
|
+
const wrapDir = join6(absRoot, ".story");
|
|
3192
3230
|
await guardPath(targetPath, wrapDir);
|
|
3193
3231
|
await atomicWrite(targetPath, json);
|
|
3194
3232
|
const pruned = await pruneSnapshots(snapshotsDir);
|
|
@@ -3196,13 +3234,13 @@ async function saveSnapshot(root, loadResult) {
|
|
|
3196
3234
|
return { filename, retained: entries.length, pruned };
|
|
3197
3235
|
}
|
|
3198
3236
|
async function loadLatestSnapshot(root) {
|
|
3199
|
-
const snapshotsDir =
|
|
3237
|
+
const snapshotsDir = join6(resolve5(root), ".story", "snapshots");
|
|
3200
3238
|
if (!existsSync5(snapshotsDir)) return null;
|
|
3201
3239
|
const files = await listSnapshotFiles(snapshotsDir);
|
|
3202
3240
|
if (files.length === 0) return null;
|
|
3203
3241
|
for (const filename of files) {
|
|
3204
3242
|
try {
|
|
3205
|
-
const content = await readFile3(
|
|
3243
|
+
const content = await readFile3(join6(snapshotsDir, filename), "utf-8");
|
|
3206
3244
|
const parsed = JSON.parse(content);
|
|
3207
3245
|
const snapshot = SnapshotV1Schema.parse(parsed);
|
|
3208
3246
|
return { snapshot, filename };
|
|
@@ -3446,7 +3484,7 @@ async function pruneSnapshots(dir) {
|
|
|
3446
3484
|
const toRemove = files.slice(MAX_SNAPSHOTS);
|
|
3447
3485
|
for (const f of toRemove) {
|
|
3448
3486
|
try {
|
|
3449
|
-
await unlink2(
|
|
3487
|
+
await unlink2(join6(dir, f));
|
|
3450
3488
|
} catch {
|
|
3451
3489
|
}
|
|
3452
3490
|
}
|
|
@@ -3516,6 +3554,7 @@ var init_session_types = __esm({
|
|
|
3516
3554
|
"HANDOVER",
|
|
3517
3555
|
"COMPLETE",
|
|
3518
3556
|
"LESSON_CAPTURE",
|
|
3557
|
+
"ISSUE_FIX",
|
|
3519
3558
|
"ISSUE_SWEEP",
|
|
3520
3559
|
"SESSION_END"
|
|
3521
3560
|
];
|
|
@@ -3564,6 +3603,14 @@ var init_session_types = __esm({
|
|
|
3564
3603
|
timestamp: z9.string()
|
|
3565
3604
|
})).default([])
|
|
3566
3605
|
}).default({ plan: [], code: [] }),
|
|
3606
|
+
// T-153: Current issue being fixed (null when working on a ticket)
|
|
3607
|
+
currentIssue: z9.object({
|
|
3608
|
+
id: z9.string(),
|
|
3609
|
+
title: z9.string(),
|
|
3610
|
+
severity: z9.string()
|
|
3611
|
+
}).nullable().default(null),
|
|
3612
|
+
// T-153: Issues resolved this session
|
|
3613
|
+
resolvedIssues: z9.array(z9.string()).default([]),
|
|
3567
3614
|
// Completed tickets this session
|
|
3568
3615
|
completedTickets: z9.array(z9.object({
|
|
3569
3616
|
id: z9.string(),
|
|
@@ -3702,27 +3749,27 @@ __export(session_exports, {
|
|
|
3702
3749
|
import { randomUUID } from "crypto";
|
|
3703
3750
|
import {
|
|
3704
3751
|
mkdirSync,
|
|
3705
|
-
readdirSync,
|
|
3706
|
-
readFileSync,
|
|
3752
|
+
readdirSync as readdirSync3,
|
|
3753
|
+
readFileSync as readFileSync3,
|
|
3707
3754
|
writeFileSync,
|
|
3708
3755
|
renameSync,
|
|
3709
3756
|
unlinkSync,
|
|
3710
3757
|
existsSync as existsSync6,
|
|
3711
3758
|
rmSync
|
|
3712
3759
|
} from "fs";
|
|
3713
|
-
import { join as
|
|
3760
|
+
import { join as join8 } from "path";
|
|
3714
3761
|
import lockfile2 from "proper-lockfile";
|
|
3715
3762
|
function sessionsRoot(root) {
|
|
3716
|
-
return
|
|
3763
|
+
return join8(root, ".story", SESSIONS_DIR);
|
|
3717
3764
|
}
|
|
3718
3765
|
function sessionDir(root, sessionId) {
|
|
3719
|
-
return
|
|
3766
|
+
return join8(sessionsRoot(root), sessionId);
|
|
3720
3767
|
}
|
|
3721
3768
|
function statePath(dir) {
|
|
3722
|
-
return
|
|
3769
|
+
return join8(dir, "state.json");
|
|
3723
3770
|
}
|
|
3724
3771
|
function eventsPath(dir) {
|
|
3725
|
-
return
|
|
3772
|
+
return join8(dir, "events.log");
|
|
3726
3773
|
}
|
|
3727
3774
|
function createSession(root, recipe, workspaceId, configOverrides) {
|
|
3728
3775
|
const id = randomUUID();
|
|
@@ -3778,7 +3825,7 @@ function readSession(dir) {
|
|
|
3778
3825
|
const path2 = statePath(dir);
|
|
3779
3826
|
let raw;
|
|
3780
3827
|
try {
|
|
3781
|
-
raw =
|
|
3828
|
+
raw = readFileSync3(path2, "utf-8");
|
|
3782
3829
|
} catch {
|
|
3783
3830
|
return null;
|
|
3784
3831
|
}
|
|
@@ -3821,7 +3868,7 @@ function readEvents(dir) {
|
|
|
3821
3868
|
const path2 = eventsPath(dir);
|
|
3822
3869
|
let raw;
|
|
3823
3870
|
try {
|
|
3824
|
-
raw =
|
|
3871
|
+
raw = readFileSync3(path2, "utf-8");
|
|
3825
3872
|
} catch {
|
|
3826
3873
|
return { events: [], malformedCount: 0 };
|
|
3827
3874
|
}
|
|
@@ -3878,7 +3925,7 @@ function findActiveSessionFull(root) {
|
|
|
3878
3925
|
const sessDir = sessionsRoot(root);
|
|
3879
3926
|
let entries;
|
|
3880
3927
|
try {
|
|
3881
|
-
entries =
|
|
3928
|
+
entries = readdirSync3(sessDir, { withFileTypes: true });
|
|
3882
3929
|
} catch {
|
|
3883
3930
|
return null;
|
|
3884
3931
|
}
|
|
@@ -3892,7 +3939,7 @@ function findActiveSessionFull(root) {
|
|
|
3892
3939
|
let bestGuideCall = 0;
|
|
3893
3940
|
for (const entry of entries) {
|
|
3894
3941
|
if (!entry.isDirectory()) continue;
|
|
3895
|
-
const dir =
|
|
3942
|
+
const dir = join8(sessDir, entry.name);
|
|
3896
3943
|
const session = readSession(dir);
|
|
3897
3944
|
if (!session) continue;
|
|
3898
3945
|
if (session.status !== "active") continue;
|
|
@@ -3915,7 +3962,7 @@ function findStaleSessions(root) {
|
|
|
3915
3962
|
const sessDir = sessionsRoot(root);
|
|
3916
3963
|
let entries;
|
|
3917
3964
|
try {
|
|
3918
|
-
entries =
|
|
3965
|
+
entries = readdirSync3(sessDir, { withFileTypes: true });
|
|
3919
3966
|
} catch {
|
|
3920
3967
|
return [];
|
|
3921
3968
|
}
|
|
@@ -3928,7 +3975,7 @@ function findStaleSessions(root) {
|
|
|
3928
3975
|
const results = [];
|
|
3929
3976
|
for (const entry of entries) {
|
|
3930
3977
|
if (!entry.isDirectory()) continue;
|
|
3931
|
-
const dir =
|
|
3978
|
+
const dir = join8(sessDir, entry.name);
|
|
3932
3979
|
const session = readSession(dir);
|
|
3933
3980
|
if (!session) continue;
|
|
3934
3981
|
if (session.status !== "active") continue;
|
|
@@ -3984,7 +4031,7 @@ function findResumableSession(root) {
|
|
|
3984
4031
|
const sessDir = sessionsRoot(root);
|
|
3985
4032
|
let entries;
|
|
3986
4033
|
try {
|
|
3987
|
-
entries =
|
|
4034
|
+
entries = readdirSync3(sessDir, { withFileTypes: true });
|
|
3988
4035
|
} catch {
|
|
3989
4036
|
return null;
|
|
3990
4037
|
}
|
|
@@ -3999,7 +4046,7 @@ function findResumableSession(root) {
|
|
|
3999
4046
|
let bestPreparedAt = 0;
|
|
4000
4047
|
for (const entry of entries) {
|
|
4001
4048
|
if (!entry.isDirectory()) continue;
|
|
4002
|
-
const dir =
|
|
4049
|
+
const dir = join8(sessDir, entry.name);
|
|
4003
4050
|
const session = readSession(dir);
|
|
4004
4051
|
if (!session) continue;
|
|
4005
4052
|
if (session.status !== "active") continue;
|
|
@@ -4023,7 +4070,7 @@ async function withSessionLock(root, fn) {
|
|
|
4023
4070
|
release = await lockfile2.lock(sessDir, {
|
|
4024
4071
|
retries: { retries: 3, minTimeout: 100, maxTimeout: 1e3 },
|
|
4025
4072
|
stale: 3e4,
|
|
4026
|
-
lockfilePath:
|
|
4073
|
+
lockfilePath: join8(sessDir, ".lock")
|
|
4027
4074
|
});
|
|
4028
4075
|
return await fn();
|
|
4029
4076
|
} finally {
|
|
@@ -4049,7 +4096,7 @@ var init_session = __esm({
|
|
|
4049
4096
|
// src/mcp/index.ts
|
|
4050
4097
|
init_esm_shims();
|
|
4051
4098
|
import { realpathSync as realpathSync2, existsSync as existsSync11 } from "fs";
|
|
4052
|
-
import { resolve as resolve8, join as
|
|
4099
|
+
import { resolve as resolve8, join as join18, isAbsolute } from "path";
|
|
4053
4100
|
import { z as z11 } from "zod";
|
|
4054
4101
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
4055
4102
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
@@ -4102,13 +4149,65 @@ init_errors();
|
|
|
4102
4149
|
init_helpers();
|
|
4103
4150
|
init_types();
|
|
4104
4151
|
import { z as z10 } from "zod";
|
|
4105
|
-
import { join as
|
|
4152
|
+
import { join as join16 } from "path";
|
|
4106
4153
|
|
|
4107
4154
|
// src/cli/commands/status.ts
|
|
4108
4155
|
init_esm_shims();
|
|
4109
4156
|
init_output_formatter();
|
|
4157
|
+
|
|
4158
|
+
// src/core/session-scan.ts
|
|
4159
|
+
init_esm_shims();
|
|
4160
|
+
import { readdirSync, readFileSync } from "fs";
|
|
4161
|
+
import { join as join4 } from "path";
|
|
4162
|
+
function scanActiveSessions(root) {
|
|
4163
|
+
const sessDir = join4(root, ".story", "sessions");
|
|
4164
|
+
let entries;
|
|
4165
|
+
try {
|
|
4166
|
+
entries = readdirSync(sessDir, { withFileTypes: true });
|
|
4167
|
+
} catch {
|
|
4168
|
+
return [];
|
|
4169
|
+
}
|
|
4170
|
+
const results = [];
|
|
4171
|
+
for (const entry of entries) {
|
|
4172
|
+
if (!entry.isDirectory()) continue;
|
|
4173
|
+
const statePath2 = join4(sessDir, entry.name, "state.json");
|
|
4174
|
+
let raw;
|
|
4175
|
+
try {
|
|
4176
|
+
raw = readFileSync(statePath2, "utf-8");
|
|
4177
|
+
} catch {
|
|
4178
|
+
continue;
|
|
4179
|
+
}
|
|
4180
|
+
let parsed;
|
|
4181
|
+
try {
|
|
4182
|
+
parsed = JSON.parse(raw);
|
|
4183
|
+
} catch {
|
|
4184
|
+
continue;
|
|
4185
|
+
}
|
|
4186
|
+
if (parsed.status !== "active") continue;
|
|
4187
|
+
if (parsed.state === "SESSION_END") continue;
|
|
4188
|
+
const lease = parsed.lease;
|
|
4189
|
+
if (lease?.expiresAt) {
|
|
4190
|
+
const expires = new Date(lease.expiresAt).getTime();
|
|
4191
|
+
if (!Number.isNaN(expires) && expires <= Date.now()) continue;
|
|
4192
|
+
} else {
|
|
4193
|
+
continue;
|
|
4194
|
+
}
|
|
4195
|
+
const ticket = parsed.ticket;
|
|
4196
|
+
results.push({
|
|
4197
|
+
sessionId: parsed.sessionId ?? entry.name,
|
|
4198
|
+
state: parsed.state ?? "unknown",
|
|
4199
|
+
mode: parsed.mode ?? "auto",
|
|
4200
|
+
ticketId: ticket?.id ?? null,
|
|
4201
|
+
ticketTitle: ticket?.title ?? null
|
|
4202
|
+
});
|
|
4203
|
+
}
|
|
4204
|
+
return results;
|
|
4205
|
+
}
|
|
4206
|
+
|
|
4207
|
+
// src/cli/commands/status.ts
|
|
4110
4208
|
function handleStatus(ctx) {
|
|
4111
|
-
|
|
4209
|
+
const sessions = scanActiveSessions(ctx.root);
|
|
4210
|
+
return { output: formatStatus(ctx.state, ctx.format, sessions) };
|
|
4112
4211
|
}
|
|
4113
4212
|
|
|
4114
4213
|
// src/cli/commands/validate.ts
|
|
@@ -4708,6 +4807,8 @@ async function handleLessonReinforce(id, format, root) {
|
|
|
4708
4807
|
|
|
4709
4808
|
// src/cli/commands/recommend.ts
|
|
4710
4809
|
init_esm_shims();
|
|
4810
|
+
import { readFileSync as readFileSync2, readdirSync as readdirSync2 } from "fs";
|
|
4811
|
+
import { join as join7 } from "path";
|
|
4711
4812
|
|
|
4712
4813
|
// src/core/recommend.ts
|
|
4713
4814
|
init_esm_shims();
|
|
@@ -4728,10 +4829,12 @@ var CATEGORY_PRIORITY = {
|
|
|
4728
4829
|
high_impact_unblock: 4,
|
|
4729
4830
|
near_complete_umbrella: 5,
|
|
4730
4831
|
phase_momentum: 6,
|
|
4731
|
-
|
|
4732
|
-
|
|
4832
|
+
debt_trend: 7,
|
|
4833
|
+
quick_win: 8,
|
|
4834
|
+
handover_context: 9,
|
|
4835
|
+
open_issue: 10
|
|
4733
4836
|
};
|
|
4734
|
-
function recommend(state, count) {
|
|
4837
|
+
function recommend(state, count, options) {
|
|
4735
4838
|
const effectiveCount = Math.max(1, Math.min(10, count));
|
|
4736
4839
|
const dedup = /* @__PURE__ */ new Map();
|
|
4737
4840
|
const phaseIndex = buildPhaseIndex(state);
|
|
@@ -4743,7 +4846,8 @@ function recommend(state, count) {
|
|
|
4743
4846
|
() => generateNearCompleteUmbrellas(state, phaseIndex),
|
|
4744
4847
|
() => generatePhaseMomentum(state),
|
|
4745
4848
|
() => generateQuickWins(state, phaseIndex),
|
|
4746
|
-
() => generateOpenIssues(state)
|
|
4849
|
+
() => generateOpenIssues(state),
|
|
4850
|
+
() => generateDebtTrend(state, options)
|
|
4747
4851
|
];
|
|
4748
4852
|
for (const gen of generators) {
|
|
4749
4853
|
for (const rec of gen()) {
|
|
@@ -4753,6 +4857,7 @@ function recommend(state, count) {
|
|
|
4753
4857
|
}
|
|
4754
4858
|
}
|
|
4755
4859
|
}
|
|
4860
|
+
applyHandoverBoost(state, dedup, options);
|
|
4756
4861
|
const curPhase = currentPhase(state);
|
|
4757
4862
|
const curPhaseIdx = curPhase ? phaseIndex.get(curPhase.id) ?? 0 : 0;
|
|
4758
4863
|
for (const [id, rec] of dedup) {
|
|
@@ -4942,13 +5047,113 @@ function sortByPhaseAndOrder(tickets, phaseIndex) {
|
|
|
4942
5047
|
return a.order - b.order;
|
|
4943
5048
|
});
|
|
4944
5049
|
}
|
|
5050
|
+
var TICKET_ID_RE = /\bT-\d{3}[a-z]?\b/g;
|
|
5051
|
+
var ACTIONABLE_HEADING_RE = /^#+\s.*(next|open|remaining|todo|blocked)/im;
|
|
5052
|
+
var HANDOVER_BOOST = 50;
|
|
5053
|
+
var HANDOVER_BASE_SCORE = 350;
|
|
5054
|
+
function applyHandoverBoost(state, dedup, options) {
|
|
5055
|
+
if (!options?.latestHandoverContent) return;
|
|
5056
|
+
const content = options.latestHandoverContent;
|
|
5057
|
+
let actionableIds = extractTicketIdsFromActionableSections(content);
|
|
5058
|
+
if (actionableIds.size === 0) {
|
|
5059
|
+
const allIds = new Set(content.match(TICKET_ID_RE) ?? []);
|
|
5060
|
+
for (const id of allIds) {
|
|
5061
|
+
const ticket = state.ticketByID(id);
|
|
5062
|
+
if (ticket && ticket.status !== "complete" && ticket.status !== "inprogress") {
|
|
5063
|
+
actionableIds.add(id);
|
|
5064
|
+
}
|
|
5065
|
+
}
|
|
5066
|
+
}
|
|
5067
|
+
for (const id of actionableIds) {
|
|
5068
|
+
const ticket = state.ticketByID(id);
|
|
5069
|
+
if (!ticket || ticket.status === "complete") continue;
|
|
5070
|
+
const existing = dedup.get(id);
|
|
5071
|
+
if (existing) {
|
|
5072
|
+
dedup.set(id, {
|
|
5073
|
+
...existing,
|
|
5074
|
+
score: existing.score + HANDOVER_BOOST,
|
|
5075
|
+
reason: existing.reason + " (handover context)"
|
|
5076
|
+
});
|
|
5077
|
+
} else {
|
|
5078
|
+
dedup.set(id, {
|
|
5079
|
+
id,
|
|
5080
|
+
kind: "ticket",
|
|
5081
|
+
title: ticket.title,
|
|
5082
|
+
category: "handover_context",
|
|
5083
|
+
reason: "Referenced in latest handover",
|
|
5084
|
+
score: HANDOVER_BASE_SCORE
|
|
5085
|
+
});
|
|
5086
|
+
}
|
|
5087
|
+
}
|
|
5088
|
+
}
|
|
5089
|
+
function extractTicketIdsFromActionableSections(content) {
|
|
5090
|
+
const ids = /* @__PURE__ */ new Set();
|
|
5091
|
+
const lines = content.split("\n");
|
|
5092
|
+
let inActionable = false;
|
|
5093
|
+
for (const line of lines) {
|
|
5094
|
+
if (/^#+\s/.test(line)) {
|
|
5095
|
+
inActionable = ACTIONABLE_HEADING_RE.test(line);
|
|
5096
|
+
}
|
|
5097
|
+
if (inActionable) {
|
|
5098
|
+
const matches = line.match(TICKET_ID_RE);
|
|
5099
|
+
if (matches) for (const m of matches) ids.add(m);
|
|
5100
|
+
}
|
|
5101
|
+
}
|
|
5102
|
+
return ids;
|
|
5103
|
+
}
|
|
5104
|
+
var DEBT_TREND_SCORE = 450;
|
|
5105
|
+
var DEBT_GROWTH_THRESHOLD = 0.25;
|
|
5106
|
+
var DEBT_ABSOLUTE_MINIMUM = 2;
|
|
5107
|
+
function generateDebtTrend(state, options) {
|
|
5108
|
+
if (options?.previousOpenIssueCount == null) return [];
|
|
5109
|
+
const currentOpen = state.issues.filter((i) => i.status !== "resolved").length;
|
|
5110
|
+
const previous = options.previousOpenIssueCount;
|
|
5111
|
+
if (previous <= 0) return [];
|
|
5112
|
+
const growth = (currentOpen - previous) / previous;
|
|
5113
|
+
const absolute = currentOpen - previous;
|
|
5114
|
+
if (growth > DEBT_GROWTH_THRESHOLD && absolute >= DEBT_ABSOLUTE_MINIMUM) {
|
|
5115
|
+
return [{
|
|
5116
|
+
id: "DEBT_TREND",
|
|
5117
|
+
kind: "action",
|
|
5118
|
+
title: "Issue debt growing",
|
|
5119
|
+
category: "debt_trend",
|
|
5120
|
+
reason: `Open issues grew from ${previous} to ${currentOpen} (+${Math.round(growth * 100)}%). Consider triaging or resolving issues before adding features.`,
|
|
5121
|
+
score: DEBT_TREND_SCORE
|
|
5122
|
+
}];
|
|
5123
|
+
}
|
|
5124
|
+
return [];
|
|
5125
|
+
}
|
|
4945
5126
|
|
|
4946
5127
|
// src/cli/commands/recommend.ts
|
|
4947
5128
|
init_output_formatter();
|
|
4948
5129
|
function handleRecommend(ctx, count) {
|
|
4949
|
-
const
|
|
5130
|
+
const options = buildRecommendOptions(ctx);
|
|
5131
|
+
const result = recommend(ctx.state, count, options);
|
|
4950
5132
|
return { output: formatRecommendations(result, ctx.state, ctx.format) };
|
|
4951
5133
|
}
|
|
5134
|
+
function buildRecommendOptions(ctx) {
|
|
5135
|
+
const opts = {};
|
|
5136
|
+
try {
|
|
5137
|
+
const files = readdirSync2(ctx.handoversDir).filter((f) => f.endsWith(".md")).sort();
|
|
5138
|
+
if (files.length > 0) {
|
|
5139
|
+
opts.latestHandoverContent = readFileSync2(join7(ctx.handoversDir, files[files.length - 1]), "utf-8");
|
|
5140
|
+
}
|
|
5141
|
+
} catch {
|
|
5142
|
+
}
|
|
5143
|
+
try {
|
|
5144
|
+
const snapshotsDir = join7(ctx.root, ".story", "snapshots");
|
|
5145
|
+
const snapFiles = readdirSync2(snapshotsDir).filter((f) => f.endsWith(".json")).sort();
|
|
5146
|
+
if (snapFiles.length > 0) {
|
|
5147
|
+
const raw = readFileSync2(join7(snapshotsDir, snapFiles[snapFiles.length - 1]), "utf-8");
|
|
5148
|
+
const snap = JSON.parse(raw);
|
|
5149
|
+
if (snap.issues) {
|
|
5150
|
+
opts.previousOpenIssueCount = snap.issues.filter((i) => i.status !== "resolved").length;
|
|
5151
|
+
}
|
|
5152
|
+
}
|
|
5153
|
+
} catch {
|
|
5154
|
+
}
|
|
5155
|
+
return opts;
|
|
5156
|
+
}
|
|
4952
5157
|
|
|
4953
5158
|
// src/cli/commands/snapshot.ts
|
|
4954
5159
|
init_esm_shims();
|
|
@@ -5238,8 +5443,8 @@ init_handover();
|
|
|
5238
5443
|
init_esm_shims();
|
|
5239
5444
|
init_session_types();
|
|
5240
5445
|
init_session();
|
|
5241
|
-
import { readFileSync as
|
|
5242
|
-
import { join as
|
|
5446
|
+
import { readFileSync as readFileSync6, writeFileSync as writeFileSync3, readdirSync as readdirSync4 } from "fs";
|
|
5447
|
+
import { join as join13 } from "path";
|
|
5243
5448
|
|
|
5244
5449
|
// src/autonomous/state-machine.ts
|
|
5245
5450
|
init_esm_shims();
|
|
@@ -5248,7 +5453,7 @@ var TRANSITIONS = {
|
|
|
5248
5453
|
// start does INIT + LOAD_CONTEXT internally
|
|
5249
5454
|
LOAD_CONTEXT: ["PICK_TICKET"],
|
|
5250
5455
|
// internal (never seen by Claude)
|
|
5251
|
-
PICK_TICKET: ["PLAN", "SESSION_END"],
|
|
5456
|
+
PICK_TICKET: ["PLAN", "ISSUE_FIX", "SESSION_END"],
|
|
5252
5457
|
PLAN: ["PLAN_REVIEW"],
|
|
5253
5458
|
PLAN_REVIEW: ["IMPLEMENT", "WRITE_TESTS", "PLAN", "PLAN_REVIEW", "SESSION_END"],
|
|
5254
5459
|
// approve → IMPLEMENT/WRITE_TESTS, reject → PLAN, stay for next round; SESSION_END for tiered exit
|
|
@@ -5262,8 +5467,11 @@ var TRANSITIONS = {
|
|
|
5262
5467
|
// approve → VERIFY/FINALIZE, reject → IMPLEMENT/PLAN, stay for next round; SESSION_END for tiered exit
|
|
5263
5468
|
VERIFY: ["FINALIZE", "IMPLEMENT", "VERIFY"],
|
|
5264
5469
|
// pass → FINALIZE, fail → IMPLEMENT, retry
|
|
5265
|
-
FINALIZE: ["COMPLETE"],
|
|
5470
|
+
FINALIZE: ["COMPLETE", "PICK_TICKET"],
|
|
5471
|
+
// PICK_TICKET for issue-fix flow (bypass COMPLETE)
|
|
5266
5472
|
COMPLETE: ["PICK_TICKET", "HANDOVER", "ISSUE_SWEEP", "SESSION_END"],
|
|
5473
|
+
ISSUE_FIX: ["FINALIZE", "PICK_TICKET", "ISSUE_FIX"],
|
|
5474
|
+
// T-153: fix done → FINALIZE, cancel → PICK_TICKET, retry self
|
|
5267
5475
|
LESSON_CAPTURE: ["ISSUE_SWEEP", "HANDOVER", "LESSON_CAPTURE"],
|
|
5268
5476
|
// advance → ISSUE_SWEEP, retry self, done → HANDOVER
|
|
5269
5477
|
ISSUE_SWEEP: ["ISSUE_SWEEP", "HANDOVER", "PICK_TICKET"],
|
|
@@ -5511,8 +5719,8 @@ function parseDiffNumstat(out) {
|
|
|
5511
5719
|
|
|
5512
5720
|
// src/autonomous/recipes/loader.ts
|
|
5513
5721
|
init_esm_shims();
|
|
5514
|
-
import { readFileSync as
|
|
5515
|
-
import { join as
|
|
5722
|
+
import { readFileSync as readFileSync4 } from "fs";
|
|
5723
|
+
import { join as join9, dirname as dirname3 } from "path";
|
|
5516
5724
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
5517
5725
|
var DEFAULT_PIPELINE = [
|
|
5518
5726
|
"PICK_TICKET",
|
|
@@ -5532,9 +5740,9 @@ function loadRecipe(recipeName) {
|
|
|
5532
5740
|
if (!/^[A-Za-z0-9_-]+$/.test(recipeName)) {
|
|
5533
5741
|
throw new Error(`Invalid recipe name: ${recipeName}`);
|
|
5534
5742
|
}
|
|
5535
|
-
const recipesDir =
|
|
5536
|
-
const path2 =
|
|
5537
|
-
const raw =
|
|
5743
|
+
const recipesDir = join9(dirname3(fileURLToPath2(import.meta.url)), "..", "recipes");
|
|
5744
|
+
const path2 = join9(recipesDir, `${recipeName}.json`);
|
|
5745
|
+
const raw = readFileSync4(path2, "utf-8");
|
|
5538
5746
|
return JSON.parse(raw);
|
|
5539
5747
|
}
|
|
5540
5748
|
function resolveRecipe(recipeName, projectOverrides) {
|
|
@@ -5781,7 +5989,7 @@ init_esm_shims();
|
|
|
5781
5989
|
// src/autonomous/stages/pick-ticket.ts
|
|
5782
5990
|
init_esm_shims();
|
|
5783
5991
|
import { existsSync as existsSync7, unlinkSync as unlinkSync2 } from "fs";
|
|
5784
|
-
import { join as
|
|
5992
|
+
import { join as join10 } from "path";
|
|
5785
5993
|
var PickTicketStage = class {
|
|
5786
5994
|
id = "PICK_TICKET";
|
|
5787
5995
|
async enter(ctx) {
|
|
@@ -5794,28 +6002,52 @@ var PickTicketStage = class {
|
|
|
5794
6002
|
(c, i) => `${i + 1}. **${c.ticket.id}: ${c.ticket.title}** (${c.ticket.type})`
|
|
5795
6003
|
).join("\n");
|
|
5796
6004
|
}
|
|
6005
|
+
const highIssues = projectState.issues.filter(
|
|
6006
|
+
(i) => i.status === "open" && (i.severity === "critical" || i.severity === "high")
|
|
6007
|
+
);
|
|
6008
|
+
let issuesText = "";
|
|
6009
|
+
if (highIssues.length > 0) {
|
|
6010
|
+
issuesText = "\n\n## Open Issues (high+ severity)\n\n" + highIssues.map(
|
|
6011
|
+
(i, idx) => `${idx + 1}. **${i.id}: ${i.title}** (${i.severity})`
|
|
6012
|
+
).join("\n");
|
|
6013
|
+
}
|
|
5797
6014
|
const topCandidate = candidates.kind === "found" ? candidates.candidates[0] : null;
|
|
6015
|
+
const hasIssues = highIssues.length > 0;
|
|
5798
6016
|
return {
|
|
5799
6017
|
instruction: [
|
|
5800
|
-
"# Pick a Ticket",
|
|
6018
|
+
"# Pick a Ticket or Issue",
|
|
6019
|
+
"",
|
|
6020
|
+
"## Ticket Candidates",
|
|
5801
6021
|
"",
|
|
5802
6022
|
candidatesText || "No ticket candidates found.",
|
|
6023
|
+
issuesText,
|
|
5803
6024
|
"",
|
|
5804
|
-
topCandidate ? `Pick **${topCandidate.ticket.id}** (highest priority) by calling \`claudestory_autonomous_guide\` now:` : "Pick a ticket by calling `claudestory_autonomous_guide` now:",
|
|
6025
|
+
topCandidate ? `Pick **${topCandidate.ticket.id}** (highest priority) or an open issue by calling \`claudestory_autonomous_guide\` now:` : hasIssues ? `Pick an issue to fix by calling \`claudestory_autonomous_guide\` now:` : "Pick a ticket by calling `claudestory_autonomous_guide` now:",
|
|
5805
6026
|
"```json",
|
|
5806
6027
|
topCandidate ? `{ "sessionId": "${ctx.state.sessionId}", "action": "report", "report": { "completedAction": "ticket_picked", "ticketId": "${topCandidate.ticket.id}" } }` : `{ "sessionId": "${ctx.state.sessionId}", "action": "report", "report": { "completedAction": "ticket_picked", "ticketId": "T-XXX" } }`,
|
|
5807
|
-
"```"
|
|
6028
|
+
"```",
|
|
6029
|
+
...hasIssues ? [
|
|
6030
|
+
"",
|
|
6031
|
+
"Or to fix an issue:",
|
|
6032
|
+
"```json",
|
|
6033
|
+
`{ "sessionId": "${ctx.state.sessionId}", "action": "report", "report": { "completedAction": "issue_picked", "issueId": "${highIssues[0].id}" } }`,
|
|
6034
|
+
"```"
|
|
6035
|
+
] : []
|
|
5808
6036
|
].join("\n"),
|
|
5809
6037
|
reminders: [
|
|
5810
|
-
"Do NOT stop or summarize. Call autonomous_guide IMMEDIATELY to pick a ticket.",
|
|
6038
|
+
"Do NOT stop or summarize. Call autonomous_guide IMMEDIATELY to pick a ticket or issue.",
|
|
5811
6039
|
"Do NOT ask the user for confirmation."
|
|
5812
6040
|
]
|
|
5813
6041
|
};
|
|
5814
6042
|
}
|
|
5815
6043
|
async report(ctx, report) {
|
|
6044
|
+
const issueId = report.issueId;
|
|
6045
|
+
if (issueId) {
|
|
6046
|
+
return this.handleIssuePick(ctx, issueId);
|
|
6047
|
+
}
|
|
5816
6048
|
const ticketId = report.ticketId;
|
|
5817
6049
|
if (!ticketId) {
|
|
5818
|
-
return { action: "retry", instruction: "report.ticketId is required
|
|
6050
|
+
return { action: "retry", instruction: "report.ticketId or report.issueId is required." };
|
|
5819
6051
|
}
|
|
5820
6052
|
const { state: projectState } = await ctx.loadProject();
|
|
5821
6053
|
const ticket = projectState.ticketByID(ticketId);
|
|
@@ -5831,7 +6063,7 @@ var PickTicketStage = class {
|
|
|
5831
6063
|
return { action: "retry", instruction: `Ticket ${ticketId} is ${ticket.status} \u2014 pick an open ticket.` };
|
|
5832
6064
|
}
|
|
5833
6065
|
}
|
|
5834
|
-
const planPath =
|
|
6066
|
+
const planPath = join10(ctx.dir, "plan.md");
|
|
5835
6067
|
try {
|
|
5836
6068
|
if (existsSync7(planPath)) unlinkSync2(planPath);
|
|
5837
6069
|
} catch {
|
|
@@ -5866,15 +6098,33 @@ ${ticket.description}` : "",
|
|
|
5866
6098
|
}
|
|
5867
6099
|
};
|
|
5868
6100
|
}
|
|
6101
|
+
// T-153: Handle issue pick -- validate and route to ISSUE_FIX
|
|
6102
|
+
async handleIssuePick(ctx, issueId) {
|
|
6103
|
+
const { state: projectState } = await ctx.loadProject();
|
|
6104
|
+
const issue = projectState.issues.find((i) => i.id === issueId);
|
|
6105
|
+
if (!issue) {
|
|
6106
|
+
return { action: "retry", instruction: `Issue ${issueId} not found. Pick a valid issue or ticket.` };
|
|
6107
|
+
}
|
|
6108
|
+
if (issue.status !== "open") {
|
|
6109
|
+
return { action: "retry", instruction: `Issue ${issueId} is ${issue.status}. Pick an open issue.` };
|
|
6110
|
+
}
|
|
6111
|
+
ctx.updateDraft({
|
|
6112
|
+
currentIssue: { id: issue.id, title: issue.title, severity: issue.severity },
|
|
6113
|
+
ticket: void 0,
|
|
6114
|
+
reviews: { plan: [], code: [] },
|
|
6115
|
+
finalizeCheckpoint: null
|
|
6116
|
+
});
|
|
6117
|
+
return { action: "goto", target: "ISSUE_FIX" };
|
|
6118
|
+
}
|
|
5869
6119
|
};
|
|
5870
6120
|
|
|
5871
6121
|
// src/autonomous/stages/plan.ts
|
|
5872
6122
|
init_esm_shims();
|
|
5873
|
-
import { existsSync as existsSync8, readFileSync as
|
|
5874
|
-
import { join as
|
|
6123
|
+
import { existsSync as existsSync8, readFileSync as readFileSync5 } from "fs";
|
|
6124
|
+
import { join as join11 } from "path";
|
|
5875
6125
|
function readFileSafe(path2) {
|
|
5876
6126
|
try {
|
|
5877
|
-
return
|
|
6127
|
+
return readFileSync5(path2, "utf-8");
|
|
5878
6128
|
} catch {
|
|
5879
6129
|
return "";
|
|
5880
6130
|
}
|
|
@@ -5909,7 +6159,7 @@ var PlanStage = class {
|
|
|
5909
6159
|
};
|
|
5910
6160
|
}
|
|
5911
6161
|
async report(ctx, _report) {
|
|
5912
|
-
const planPath =
|
|
6162
|
+
const planPath = join11(ctx.dir, "plan.md");
|
|
5913
6163
|
if (!existsSync8(planPath)) {
|
|
5914
6164
|
return { action: "retry", instruction: `Plan file not found at ${planPath}. Write your plan there and call me again.`, reminders: ["Save plan to .story/sessions/<id>/plan.md"] };
|
|
5915
6165
|
}
|
|
@@ -6140,7 +6390,8 @@ var ImplementStage = class {
|
|
|
6140
6390
|
reminders: [
|
|
6141
6391
|
"Follow the plan exactly. Do NOT deviate without re-planning.",
|
|
6142
6392
|
"Do NOT ask the user for confirmation.",
|
|
6143
|
-
"If you discover pre-existing bugs, failing tests not caused by your changes, or other out-of-scope problems, file them as issues using claudestory_issue_create. Do not fix them inline."
|
|
6393
|
+
"If you discover pre-existing bugs, failing tests not caused by your changes, or other out-of-scope problems, file them as issues using claudestory_issue_create. Do not fix them inline.",
|
|
6394
|
+
"Track which files you create or modify. Only these files should be staged at commit time."
|
|
6144
6395
|
],
|
|
6145
6396
|
transitionedFrom: ctx.state.previousState ?? void 0
|
|
6146
6397
|
};
|
|
@@ -6798,10 +7049,13 @@ var FinalizeStage = class {
|
|
|
6798
7049
|
"Code review passed. Time to commit.",
|
|
6799
7050
|
"",
|
|
6800
7051
|
ctx.state.ticket ? `1. Update ticket ${ctx.state.ticket.id} status to "complete" in .story/` : "",
|
|
6801
|
-
|
|
7052
|
+
ctx.state.currentIssue ? `1. Ensure issue ${ctx.state.currentIssue.id} status is "resolved" in .story/issues/` : "",
|
|
7053
|
+
"2. Stage only the files you created or modified for this work (code + .story/ changes). Do NOT use `git add -A` or `git add .`",
|
|
6802
7054
|
'3. Call me with completedAction: "files_staged"'
|
|
6803
7055
|
].filter(Boolean).join("\n"),
|
|
6804
|
-
reminders: [
|
|
7056
|
+
reminders: [
|
|
7057
|
+
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."
|
|
7058
|
+
],
|
|
6805
7059
|
transitionedFrom: ctx.state.previousState ?? void 0
|
|
6806
7060
|
};
|
|
6807
7061
|
}
|
|
@@ -6844,9 +7098,9 @@ var FinalizeStage = class {
|
|
|
6844
7098
|
const headResult = await gitHead(ctx.root);
|
|
6845
7099
|
const previousHead = ctx.state.git.expectedHead ?? ctx.state.git.initHead;
|
|
6846
7100
|
if (headResult.ok && previousHead && headResult.data.hash !== previousHead) {
|
|
7101
|
+
const treeResult = await gitDiffTreeNames(ctx.root, headResult.data.hash);
|
|
6847
7102
|
const ticketId2 = ctx.state.ticket?.id;
|
|
6848
7103
|
if (ticketId2) {
|
|
6849
|
-
const treeResult = await gitDiffTreeNames(ctx.root, headResult.data.hash);
|
|
6850
7104
|
const ticketPath = `.story/tickets/${ticketId2}.json`;
|
|
6851
7105
|
if (treeResult.ok && !treeResult.data.includes(ticketPath)) {
|
|
6852
7106
|
return {
|
|
@@ -6855,6 +7109,16 @@ var FinalizeStage = class {
|
|
|
6855
7109
|
};
|
|
6856
7110
|
}
|
|
6857
7111
|
}
|
|
7112
|
+
const earlyIssueId = ctx.state.currentIssue?.id;
|
|
7113
|
+
if (earlyIssueId) {
|
|
7114
|
+
const issuePath = `.story/issues/${earlyIssueId}.json`;
|
|
7115
|
+
if (treeResult.ok && !treeResult.data.includes(issuePath)) {
|
|
7116
|
+
return {
|
|
7117
|
+
action: "retry",
|
|
7118
|
+
instruction: `Commit detected (${headResult.data.hash.slice(0, 7)}) but issue file ${issuePath} is not in the commit. Amend the commit to include it: \`git add ${issuePath} && git commit --amend --no-edit\`, then report completedAction: "commit_done" with the new hash.`
|
|
7119
|
+
};
|
|
7120
|
+
}
|
|
7121
|
+
}
|
|
6858
7122
|
ctx.writeState({ finalizeCheckpoint: "precommit_passed" });
|
|
6859
7123
|
return this.handleCommit(ctx, { ...report, commitHash: headResult.data.hash });
|
|
6860
7124
|
}
|
|
@@ -6888,6 +7152,16 @@ var FinalizeStage = class {
|
|
|
6888
7152
|
};
|
|
6889
7153
|
}
|
|
6890
7154
|
}
|
|
7155
|
+
const issueId = ctx.state.currentIssue?.id;
|
|
7156
|
+
if (issueId) {
|
|
7157
|
+
const issuePath = `.story/issues/${issueId}.json`;
|
|
7158
|
+
if (!stagedResult.data.includes(issuePath)) {
|
|
7159
|
+
return {
|
|
7160
|
+
action: "retry",
|
|
7161
|
+
instruction: `Issue file ${issuePath} is not staged. Run \`git add ${issuePath}\` and call me again with completedAction: "files_staged".`
|
|
7162
|
+
};
|
|
7163
|
+
}
|
|
7164
|
+
}
|
|
6891
7165
|
ctx.writeState({
|
|
6892
7166
|
finalizeCheckpoint: overlapOverridden ? "staged_override" : "staged"
|
|
6893
7167
|
});
|
|
@@ -6935,6 +7209,16 @@ var FinalizeStage = class {
|
|
|
6935
7209
|
};
|
|
6936
7210
|
}
|
|
6937
7211
|
}
|
|
7212
|
+
const precommitIssueId = ctx.state.currentIssue?.id;
|
|
7213
|
+
if (precommitIssueId) {
|
|
7214
|
+
const issuePath = `.story/issues/${precommitIssueId}.json`;
|
|
7215
|
+
if (!stagedResult.data.includes(issuePath)) {
|
|
7216
|
+
return {
|
|
7217
|
+
action: "retry",
|
|
7218
|
+
instruction: `Pre-commit hooks may have modified the staged set. Issue file ${issuePath} is no longer staged. Run \`git add ${issuePath}\` and call me again with completedAction: "files_staged".`
|
|
7219
|
+
};
|
|
7220
|
+
}
|
|
7221
|
+
}
|
|
6938
7222
|
ctx.writeState({ finalizeCheckpoint: "precommit_passed" });
|
|
6939
7223
|
return {
|
|
6940
7224
|
action: "retry",
|
|
@@ -6972,6 +7256,21 @@ var FinalizeStage = class {
|
|
|
6972
7256
|
if (previousHead && normalizedHash === previousHead) {
|
|
6973
7257
|
return { action: "retry", instruction: `No new commit detected: HEAD (${normalizedHash}) has not changed. Create a commit first, then report the new hash.` };
|
|
6974
7258
|
}
|
|
7259
|
+
const currentIssue = ctx.state.currentIssue;
|
|
7260
|
+
if (currentIssue) {
|
|
7261
|
+
ctx.writeState({
|
|
7262
|
+
finalizeCheckpoint: "committed",
|
|
7263
|
+
resolvedIssues: [...ctx.state.resolvedIssues ?? [], currentIssue.id],
|
|
7264
|
+
currentIssue: null,
|
|
7265
|
+
git: {
|
|
7266
|
+
...ctx.state.git,
|
|
7267
|
+
mergeBase: normalizedHash,
|
|
7268
|
+
expectedHead: normalizedHash
|
|
7269
|
+
}
|
|
7270
|
+
});
|
|
7271
|
+
ctx.appendEvent("commit", { commitHash: normalizedHash, issueId: currentIssue.id });
|
|
7272
|
+
return { action: "goto", target: "PICK_TICKET" };
|
|
7273
|
+
}
|
|
6975
7274
|
const completedTicket = ctx.state.ticket ? { id: ctx.state.ticket.id, title: ctx.state.ticket.title, commitHash: normalizedHash, risk: ctx.state.ticket.risk, realizedRisk: ctx.state.ticket.realizedRisk } : void 0;
|
|
6976
7275
|
ctx.writeState({
|
|
6977
7276
|
finalizeCheckpoint: "committed",
|
|
@@ -7180,6 +7479,79 @@ var LessonCaptureStage = class {
|
|
|
7180
7479
|
}
|
|
7181
7480
|
};
|
|
7182
7481
|
|
|
7482
|
+
// src/autonomous/stages/issue-fix.ts
|
|
7483
|
+
init_esm_shims();
|
|
7484
|
+
var IssueFixStage = class {
|
|
7485
|
+
id = "ISSUE_FIX";
|
|
7486
|
+
async enter(ctx) {
|
|
7487
|
+
const issue = ctx.state.currentIssue;
|
|
7488
|
+
if (!issue) {
|
|
7489
|
+
return { action: "goto", target: "PICK_TICKET" };
|
|
7490
|
+
}
|
|
7491
|
+
const { state: projectState } = await ctx.loadProject();
|
|
7492
|
+
const fullIssue = projectState.issues.find((i) => i.id === issue.id);
|
|
7493
|
+
const details = fullIssue ? [
|
|
7494
|
+
`**${fullIssue.id}**: ${fullIssue.title}`,
|
|
7495
|
+
"",
|
|
7496
|
+
`Severity: ${fullIssue.severity}`,
|
|
7497
|
+
fullIssue.impact ? `Impact: ${fullIssue.impact}` : "",
|
|
7498
|
+
fullIssue.components.length > 0 ? `Components: ${fullIssue.components.join(", ")}` : "",
|
|
7499
|
+
fullIssue.location.length > 0 ? `Location: ${fullIssue.location.join(", ")}` : ""
|
|
7500
|
+
].filter(Boolean).join("\n") : `**${issue.id}**: ${issue.title} (severity: ${issue.severity})`;
|
|
7501
|
+
return {
|
|
7502
|
+
instruction: [
|
|
7503
|
+
"# Fix Issue",
|
|
7504
|
+
"",
|
|
7505
|
+
details,
|
|
7506
|
+
"",
|
|
7507
|
+
'Fix this issue, then update its status to "resolved" in `.story/issues/`.',
|
|
7508
|
+
"Add a resolution description explaining the fix.",
|
|
7509
|
+
"",
|
|
7510
|
+
"When done, call `claudestory_autonomous_guide` with:",
|
|
7511
|
+
"```json",
|
|
7512
|
+
`{ "sessionId": "${ctx.state.sessionId}", "action": "report", "report": { "completedAction": "issue_fixed" } }`,
|
|
7513
|
+
"```"
|
|
7514
|
+
].join("\n"),
|
|
7515
|
+
reminders: [
|
|
7516
|
+
'Update the issue JSON: set status to "resolved", add resolution text, set resolvedDate.',
|
|
7517
|
+
"Do NOT ask the user for confirmation."
|
|
7518
|
+
]
|
|
7519
|
+
};
|
|
7520
|
+
}
|
|
7521
|
+
async report(ctx, _report) {
|
|
7522
|
+
const issue = ctx.state.currentIssue;
|
|
7523
|
+
if (!issue) {
|
|
7524
|
+
return { action: "goto", target: "PICK_TICKET" };
|
|
7525
|
+
}
|
|
7526
|
+
const { state: projectState } = await ctx.loadProject();
|
|
7527
|
+
const current = projectState.issues.find((i) => i.id === issue.id);
|
|
7528
|
+
if (!current || current.status !== "resolved") {
|
|
7529
|
+
return {
|
|
7530
|
+
action: "retry",
|
|
7531
|
+
instruction: `Issue ${issue.id} is still ${current?.status ?? "missing"}. Update its status to "resolved" in .story/issues/${issue.id}.json with a resolution description and resolvedDate, then report again.`,
|
|
7532
|
+
reminders: ["Set status to 'resolved', add resolution text, set resolvedDate."]
|
|
7533
|
+
};
|
|
7534
|
+
}
|
|
7535
|
+
return {
|
|
7536
|
+
action: "goto",
|
|
7537
|
+
target: "FINALIZE",
|
|
7538
|
+
result: {
|
|
7539
|
+
instruction: [
|
|
7540
|
+
"# Finalize Issue Fix",
|
|
7541
|
+
"",
|
|
7542
|
+
`Issue ${issue.id} resolved. Time to commit.`,
|
|
7543
|
+
"",
|
|
7544
|
+
`1. Ensure .story/issues/${issue.id}.json is updated with status: "resolved"`,
|
|
7545
|
+
"2. Stage only the files you modified for this fix (code + .story/ changes). Do NOT use `git add -A` or `git add .`",
|
|
7546
|
+
'3. Call me with completedAction: "files_staged"'
|
|
7547
|
+
].join("\n"),
|
|
7548
|
+
reminders: ["Stage both code changes and .story/ issue update in the same commit. Only stage files related to this fix."],
|
|
7549
|
+
transitionedFrom: "ISSUE_FIX"
|
|
7550
|
+
}
|
|
7551
|
+
};
|
|
7552
|
+
}
|
|
7553
|
+
};
|
|
7554
|
+
|
|
7183
7555
|
// src/autonomous/stages/issue-sweep.ts
|
|
7184
7556
|
init_esm_shims();
|
|
7185
7557
|
var IssueSweepStage = class {
|
|
@@ -7285,7 +7657,7 @@ Impact: ${nextIssue.impact}` : ""}` : `Fix issue ${next}.`,
|
|
|
7285
7657
|
init_esm_shims();
|
|
7286
7658
|
init_handover();
|
|
7287
7659
|
import { writeFileSync as writeFileSync2 } from "fs";
|
|
7288
|
-
import { join as
|
|
7660
|
+
import { join as join12 } from "path";
|
|
7289
7661
|
var HandoverStage = class {
|
|
7290
7662
|
id = "HANDOVER";
|
|
7291
7663
|
async enter(ctx) {
|
|
@@ -7315,7 +7687,7 @@ var HandoverStage = class {
|
|
|
7315
7687
|
} catch {
|
|
7316
7688
|
handoverFailed = true;
|
|
7317
7689
|
try {
|
|
7318
|
-
const fallbackPath =
|
|
7690
|
+
const fallbackPath = join12(ctx.dir, "handover-fallback.md");
|
|
7319
7691
|
writeFileSync2(fallbackPath, content, "utf-8");
|
|
7320
7692
|
} catch {
|
|
7321
7693
|
}
|
|
@@ -7372,6 +7744,7 @@ registerStage(new VerifyStage());
|
|
|
7372
7744
|
registerStage(new FinalizeStage());
|
|
7373
7745
|
registerStage(new CompleteStage());
|
|
7374
7746
|
registerStage(new LessonCaptureStage());
|
|
7747
|
+
registerStage(new IssueFixStage());
|
|
7375
7748
|
registerStage(new IssueSweepStage());
|
|
7376
7749
|
registerStage(new HandoverStage());
|
|
7377
7750
|
|
|
@@ -7381,6 +7754,47 @@ init_snapshot();
|
|
|
7381
7754
|
init_snapshot();
|
|
7382
7755
|
init_queries();
|
|
7383
7756
|
init_handover();
|
|
7757
|
+
var RECOVERY_MAPPING = {
|
|
7758
|
+
PICK_TICKET: { state: "PICK_TICKET", resetPlan: false, resetCode: false },
|
|
7759
|
+
COMPLETE: { state: "PICK_TICKET", resetPlan: false, resetCode: false },
|
|
7760
|
+
HANDOVER: { state: "PICK_TICKET", resetPlan: false, resetCode: false },
|
|
7761
|
+
PLAN: { state: "PLAN", resetPlan: true, resetCode: false },
|
|
7762
|
+
IMPLEMENT: { state: "PLAN", resetPlan: true, resetCode: false },
|
|
7763
|
+
WRITE_TESTS: { state: "PLAN", resetPlan: true, resetCode: false },
|
|
7764
|
+
BUILD: { state: "IMPLEMENT", resetPlan: false, resetCode: true },
|
|
7765
|
+
VERIFY: { state: "IMPLEMENT", resetPlan: false, resetCode: true },
|
|
7766
|
+
PLAN_REVIEW: { state: "PLAN", resetPlan: true, resetCode: true },
|
|
7767
|
+
TEST: { state: "IMPLEMENT", resetPlan: false, resetCode: true },
|
|
7768
|
+
CODE_REVIEW: { state: "PLAN", resetPlan: true, resetCode: true },
|
|
7769
|
+
FINALIZE: { state: "IMPLEMENT", resetPlan: false, resetCode: true },
|
|
7770
|
+
LESSON_CAPTURE: { state: "PICK_TICKET", resetPlan: false, resetCode: false },
|
|
7771
|
+
ISSUE_FIX: { state: "PICK_TICKET", resetPlan: false, resetCode: false },
|
|
7772
|
+
ISSUE_SWEEP: { state: "PICK_TICKET", resetPlan: false, resetCode: false }
|
|
7773
|
+
};
|
|
7774
|
+
function buildGuideRecommendOptions(root) {
|
|
7775
|
+
const opts = {};
|
|
7776
|
+
try {
|
|
7777
|
+
const handoversDir = join13(root, ".story", "handovers");
|
|
7778
|
+
const files = readdirSync4(handoversDir, "utf-8").filter((f) => f.endsWith(".md")).sort();
|
|
7779
|
+
if (files.length > 0) {
|
|
7780
|
+
opts.latestHandoverContent = readFileSync6(join13(handoversDir, files[files.length - 1]), "utf-8");
|
|
7781
|
+
}
|
|
7782
|
+
} catch {
|
|
7783
|
+
}
|
|
7784
|
+
try {
|
|
7785
|
+
const snapshotsDir = join13(root, ".story", "snapshots");
|
|
7786
|
+
const snapFiles = readdirSync4(snapshotsDir, "utf-8").filter((f) => f.endsWith(".json")).sort();
|
|
7787
|
+
if (snapFiles.length > 0) {
|
|
7788
|
+
const raw = readFileSync6(join13(snapshotsDir, snapFiles[snapFiles.length - 1]), "utf-8");
|
|
7789
|
+
const snap = JSON.parse(raw);
|
|
7790
|
+
if (snap.issues) {
|
|
7791
|
+
opts.previousOpenIssueCount = snap.issues.filter((i) => i.status !== "resolved").length;
|
|
7792
|
+
}
|
|
7793
|
+
}
|
|
7794
|
+
} catch {
|
|
7795
|
+
}
|
|
7796
|
+
return opts;
|
|
7797
|
+
}
|
|
7384
7798
|
async function recoverPendingMutation(dir, state, root) {
|
|
7385
7799
|
const mutation = state.pendingProjectMutation;
|
|
7386
7800
|
if (!mutation || typeof mutation !== "object") return state;
|
|
@@ -7758,7 +8172,7 @@ Staged: ${stagedResult.data.join(", ")}`
|
|
|
7758
8172
|
}
|
|
7759
8173
|
}
|
|
7760
8174
|
const { state: projectState, warnings } = await loadProject(root);
|
|
7761
|
-
const handoversDir =
|
|
8175
|
+
const handoversDir = join13(root, ".story", "handovers");
|
|
7762
8176
|
const ctx = { state: projectState, warnings, root, handoversDir, format: "md" };
|
|
7763
8177
|
let handoverText = "";
|
|
7764
8178
|
try {
|
|
@@ -7775,7 +8189,7 @@ Staged: ${stagedResult.data.join(", ")}`
|
|
|
7775
8189
|
}
|
|
7776
8190
|
} catch {
|
|
7777
8191
|
}
|
|
7778
|
-
const rulesText = readFileSafe2(
|
|
8192
|
+
const rulesText = readFileSafe2(join13(root, "RULES.md"));
|
|
7779
8193
|
const lessonDigest = buildLessonDigest(projectState.lessons);
|
|
7780
8194
|
const digestParts = [
|
|
7781
8195
|
handoverText ? `## Recent Handovers
|
|
@@ -7791,7 +8205,7 @@ ${rulesText}` : "",
|
|
|
7791
8205
|
].filter(Boolean);
|
|
7792
8206
|
const digest = digestParts.join("\n\n---\n\n");
|
|
7793
8207
|
try {
|
|
7794
|
-
writeFileSync3(
|
|
8208
|
+
writeFileSync3(join13(dir, "context-digest.md"), digest, "utf-8");
|
|
7795
8209
|
} catch {
|
|
7796
8210
|
}
|
|
7797
8211
|
if (mode !== "auto" && args.ticketId) {
|
|
@@ -7810,6 +8224,18 @@ ${rulesText}` : "",
|
|
|
7810
8224
|
return guideError(new Error(`Ticket ${args.ticketId} is blocked by: ${ticket.blockedBy.join(", ")}.`));
|
|
7811
8225
|
}
|
|
7812
8226
|
}
|
|
8227
|
+
if (mode !== "review") {
|
|
8228
|
+
const claimId = ticket.claimedBySession;
|
|
8229
|
+
if (claimId && typeof claimId === "string" && claimId !== session.sessionId) {
|
|
8230
|
+
const claimingSession = findSessionById(root, claimId);
|
|
8231
|
+
if (claimingSession && claimingSession.state.status === "active" && !isLeaseExpired(claimingSession.state)) {
|
|
8232
|
+
deleteSession(root, session.sessionId);
|
|
8233
|
+
return guideError(new Error(
|
|
8234
|
+
`Ticket ${args.ticketId} is claimed by active session ${claimId}. Wait for it to finish or stop it with "claudestory session stop ${claimId}".`
|
|
8235
|
+
));
|
|
8236
|
+
}
|
|
8237
|
+
}
|
|
8238
|
+
}
|
|
7813
8239
|
let entryState;
|
|
7814
8240
|
if (mode === "review") {
|
|
7815
8241
|
entryState = "CODE_REVIEW";
|
|
@@ -7905,12 +8331,22 @@ ${ticket.description}` : "",
|
|
|
7905
8331
|
} else {
|
|
7906
8332
|
candidatesText = "No tickets found.";
|
|
7907
8333
|
}
|
|
7908
|
-
const
|
|
8334
|
+
const highIssues = projectState.issues.filter(
|
|
8335
|
+
(i) => i.status === "open" && (i.severity === "critical" || i.severity === "high")
|
|
8336
|
+
);
|
|
8337
|
+
let issuesText = "";
|
|
8338
|
+
if (highIssues.length > 0) {
|
|
8339
|
+
issuesText = "\n\n## Open Issues (high+ severity)\n\n" + highIssues.map(
|
|
8340
|
+
(i, idx) => `${idx + 1}. **${i.id}: ${i.title}** (${i.severity})`
|
|
8341
|
+
).join("\n");
|
|
8342
|
+
}
|
|
8343
|
+
const guideRecOptions = buildGuideRecommendOptions(root);
|
|
8344
|
+
const recResult = recommend(projectState, 5, guideRecOptions);
|
|
7909
8345
|
let recsText = "";
|
|
7910
8346
|
if (recResult.recommendations.length > 0) {
|
|
7911
|
-
const
|
|
7912
|
-
if (
|
|
7913
|
-
recsText = "\n\n**Recommended:**\n" +
|
|
8347
|
+
const actionableRecs = recResult.recommendations.filter((r) => r.kind === "ticket" || r.kind === "issue");
|
|
8348
|
+
if (actionableRecs.length > 0) {
|
|
8349
|
+
recsText = "\n\n**Recommended:**\n" + actionableRecs.map(
|
|
7914
8350
|
(r) => `- ${r.id}: ${r.title} (${r.reason})`
|
|
7915
8351
|
).join("\n");
|
|
7916
8352
|
}
|
|
@@ -7930,21 +8366,30 @@ ${ticket.description}` : "",
|
|
|
7930
8366
|
const interval = updated.config.handoverInterval ?? 3;
|
|
7931
8367
|
const sessionDesc = maxTickets > 0 ? `Work continuously until all tickets are done or you reach ${maxTickets} tickets.` : "Work continuously until all tickets are done.";
|
|
7932
8368
|
const checkpointDesc = interval > 0 ? ` A checkpoint handover will be saved every ${interval} tickets.` : "";
|
|
8369
|
+
const hasHighIssues = highIssues.length > 0;
|
|
7933
8370
|
const instruction = [
|
|
7934
8371
|
"# Autonomous Session Started",
|
|
7935
8372
|
"",
|
|
7936
8373
|
`You are now in autonomous mode. ${sessionDesc}${checkpointDesc}`,
|
|
7937
|
-
"Do NOT stop to summarize. Do NOT ask the user. Pick a ticket and start working immediately.",
|
|
8374
|
+
"Do NOT stop to summarize. Do NOT ask the user. Pick a ticket or issue and start working immediately.",
|
|
7938
8375
|
"",
|
|
7939
8376
|
"## Ticket Candidates",
|
|
7940
8377
|
"",
|
|
7941
8378
|
candidatesText,
|
|
8379
|
+
issuesText,
|
|
7942
8380
|
recsText,
|
|
7943
8381
|
"",
|
|
7944
|
-
topCandidate ? `Pick **${topCandidate.ticket.id}** (highest priority) by calling \`claudestory_autonomous_guide\` now:` : "Pick a ticket by calling `claudestory_autonomous_guide` now:",
|
|
8382
|
+
topCandidate ? `Pick **${topCandidate.ticket.id}** (highest priority) or an open issue by calling \`claudestory_autonomous_guide\` now:` : hasHighIssues ? "Pick an issue to fix by calling `claudestory_autonomous_guide` now:" : "Pick a ticket by calling `claudestory_autonomous_guide` now:",
|
|
7945
8383
|
"```json",
|
|
7946
8384
|
topCandidate ? `{ "sessionId": "${updated.sessionId}", "action": "report", "report": { "completedAction": "ticket_picked", "ticketId": "${topCandidate.ticket.id}" } }` : `{ "sessionId": "${updated.sessionId}", "action": "report", "report": { "completedAction": "ticket_picked", "ticketId": "T-XXX" } }`,
|
|
7947
|
-
"```"
|
|
8385
|
+
"```",
|
|
8386
|
+
...hasHighIssues ? [
|
|
8387
|
+
"",
|
|
8388
|
+
"Or to fix an issue:",
|
|
8389
|
+
"```json",
|
|
8390
|
+
`{ "sessionId": "${updated.sessionId}", "action": "report", "report": { "completedAction": "issue_picked", "issueId": "${highIssues[0].id}" } }`,
|
|
8391
|
+
"```"
|
|
8392
|
+
] : []
|
|
7948
8393
|
].join("\n");
|
|
7949
8394
|
return guideResult(updated, "PICK_TICKET", {
|
|
7950
8395
|
instruction,
|
|
@@ -8131,26 +8576,7 @@ async function handleResume(root, args) {
|
|
|
8131
8576
|
));
|
|
8132
8577
|
}
|
|
8133
8578
|
if (expectedHead && headResult.data.hash !== expectedHead) {
|
|
8134
|
-
const
|
|
8135
|
-
PICK_TICKET: { state: "PICK_TICKET", resetPlan: false, resetCode: false },
|
|
8136
|
-
COMPLETE: { state: "PICK_TICKET", resetPlan: false, resetCode: false },
|
|
8137
|
-
HANDOVER: { state: "PICK_TICKET", resetPlan: false, resetCode: false },
|
|
8138
|
-
PLAN: { state: "PLAN", resetPlan: true, resetCode: false },
|
|
8139
|
-
IMPLEMENT: { state: "PLAN", resetPlan: true, resetCode: false },
|
|
8140
|
-
WRITE_TESTS: { state: "PLAN", resetPlan: true, resetCode: false },
|
|
8141
|
-
// T-139: baseline stale after HEAD change
|
|
8142
|
-
VERIFY: { state: "IMPLEMENT", resetPlan: false, resetCode: true },
|
|
8143
|
-
// T-131: reviewed code stale after HEAD drift
|
|
8144
|
-
PLAN_REVIEW: { state: "PLAN", resetPlan: true, resetCode: true },
|
|
8145
|
-
TEST: { state: "IMPLEMENT", resetPlan: false, resetCode: true },
|
|
8146
|
-
// T-128: tests invalidated by HEAD change
|
|
8147
|
-
CODE_REVIEW: { state: "PLAN", resetPlan: true, resetCode: true },
|
|
8148
|
-
FINALIZE: { state: "IMPLEMENT", resetPlan: false, resetCode: true },
|
|
8149
|
-
LESSON_CAPTURE: { state: "PICK_TICKET", resetPlan: false, resetCode: false },
|
|
8150
|
-
ISSUE_SWEEP: { state: "PICK_TICKET", resetPlan: false, resetCode: false }
|
|
8151
|
-
// T-128: post-complete, restart sweep
|
|
8152
|
-
};
|
|
8153
|
-
const mapping = recoveryMapping[resumeState] ?? { state: "PICK_TICKET", resetPlan: false, resetCode: false };
|
|
8579
|
+
const mapping = RECOVERY_MAPPING[resumeState] ?? { state: "PICK_TICKET", resetPlan: false, resetCode: false };
|
|
8154
8580
|
const recoveryReviews = {
|
|
8155
8581
|
plan: mapping.resetPlan ? [] : info.state.reviews.plan,
|
|
8156
8582
|
code: mapping.resetCode ? [] : info.state.reviews.code
|
|
@@ -8498,7 +8924,7 @@ function guideError(err) {
|
|
|
8498
8924
|
}
|
|
8499
8925
|
function readFileSafe2(path2) {
|
|
8500
8926
|
try {
|
|
8501
|
-
return
|
|
8927
|
+
return readFileSync6(path2, "utf-8");
|
|
8502
8928
|
} catch {
|
|
8503
8929
|
return "";
|
|
8504
8930
|
}
|
|
@@ -8508,8 +8934,8 @@ function readFileSafe2(path2) {
|
|
|
8508
8934
|
init_esm_shims();
|
|
8509
8935
|
init_session();
|
|
8510
8936
|
init_session_types();
|
|
8511
|
-
import { readFileSync as
|
|
8512
|
-
import { join as
|
|
8937
|
+
import { readFileSync as readFileSync7, existsSync as existsSync10 } from "fs";
|
|
8938
|
+
import { join as join14 } from "path";
|
|
8513
8939
|
|
|
8514
8940
|
// src/core/session-report-formatter.ts
|
|
8515
8941
|
init_esm_shims();
|
|
@@ -8723,7 +9149,7 @@ async function handleSessionReport(sessionId, root, format = "md") {
|
|
|
8723
9149
|
isError: true
|
|
8724
9150
|
};
|
|
8725
9151
|
}
|
|
8726
|
-
const statePath2 =
|
|
9152
|
+
const statePath2 = join14(dir, "state.json");
|
|
8727
9153
|
if (!existsSync10(statePath2)) {
|
|
8728
9154
|
return {
|
|
8729
9155
|
output: `Error: Session ${sessionId} corrupt \u2014 state.json missing.`,
|
|
@@ -8733,7 +9159,7 @@ async function handleSessionReport(sessionId, root, format = "md") {
|
|
|
8733
9159
|
};
|
|
8734
9160
|
}
|
|
8735
9161
|
try {
|
|
8736
|
-
const rawJson = JSON.parse(
|
|
9162
|
+
const rawJson = JSON.parse(readFileSync7(statePath2, "utf-8"));
|
|
8737
9163
|
if (rawJson && typeof rawJson === "object" && "schemaVersion" in rawJson && rawJson.schemaVersion !== CURRENT_SESSION_SCHEMA_VERSION) {
|
|
8738
9164
|
return {
|
|
8739
9165
|
output: `Error: Session ${sessionId} \u2014 unsupported session schema version ${rawJson.schemaVersion}.`,
|
|
@@ -8762,7 +9188,7 @@ async function handleSessionReport(sessionId, root, format = "md") {
|
|
|
8762
9188
|
const events = readEvents(dir);
|
|
8763
9189
|
let planContent = null;
|
|
8764
9190
|
try {
|
|
8765
|
-
planContent =
|
|
9191
|
+
planContent = readFileSync7(join14(dir, "plan.md"), "utf-8");
|
|
8766
9192
|
} catch {
|
|
8767
9193
|
}
|
|
8768
9194
|
let gitLog = null;
|
|
@@ -8787,7 +9213,7 @@ init_issue();
|
|
|
8787
9213
|
init_roadmap();
|
|
8788
9214
|
init_output_formatter();
|
|
8789
9215
|
init_helpers();
|
|
8790
|
-
import { join as
|
|
9216
|
+
import { join as join15, resolve as resolve6 } from "path";
|
|
8791
9217
|
var PHASE_ID_REGEX = /^[a-z0-9]+(-[a-z0-9]+)*$/;
|
|
8792
9218
|
var PHASE_ID_MAX_LENGTH = 40;
|
|
8793
9219
|
function validatePhaseId(id) {
|
|
@@ -8896,7 +9322,7 @@ function formatMcpError(code, message) {
|
|
|
8896
9322
|
async function runMcpReadTool(pinnedRoot, handler) {
|
|
8897
9323
|
try {
|
|
8898
9324
|
const { state, warnings } = await loadProject(pinnedRoot);
|
|
8899
|
-
const handoversDir =
|
|
9325
|
+
const handoversDir = join16(pinnedRoot, ".story", "handovers");
|
|
8900
9326
|
const ctx = { state, warnings, root: pinnedRoot, handoversDir, format: "md" };
|
|
8901
9327
|
const result = await handler(ctx);
|
|
8902
9328
|
if (result.errorCode && INFRASTRUCTURE_ERROR_CODES.includes(result.errorCode)) {
|
|
@@ -9453,10 +9879,10 @@ init_esm_shims();
|
|
|
9453
9879
|
init_project_loader();
|
|
9454
9880
|
init_errors();
|
|
9455
9881
|
import { mkdir as mkdir4, stat as stat2, readFile as readFile4, writeFile as writeFile2 } from "fs/promises";
|
|
9456
|
-
import { join as
|
|
9882
|
+
import { join as join17, resolve as resolve7 } from "path";
|
|
9457
9883
|
async function initProject(root, options) {
|
|
9458
9884
|
const absRoot = resolve7(root);
|
|
9459
|
-
const wrapDir =
|
|
9885
|
+
const wrapDir = join17(absRoot, ".story");
|
|
9460
9886
|
let exists = false;
|
|
9461
9887
|
try {
|
|
9462
9888
|
const s = await stat2(wrapDir);
|
|
@@ -9476,11 +9902,11 @@ async function initProject(root, options) {
|
|
|
9476
9902
|
".story/ already exists. Use --force to overwrite config and roadmap."
|
|
9477
9903
|
);
|
|
9478
9904
|
}
|
|
9479
|
-
await mkdir4(
|
|
9480
|
-
await mkdir4(
|
|
9481
|
-
await mkdir4(
|
|
9482
|
-
await mkdir4(
|
|
9483
|
-
await mkdir4(
|
|
9905
|
+
await mkdir4(join17(wrapDir, "tickets"), { recursive: true });
|
|
9906
|
+
await mkdir4(join17(wrapDir, "issues"), { recursive: true });
|
|
9907
|
+
await mkdir4(join17(wrapDir, "handovers"), { recursive: true });
|
|
9908
|
+
await mkdir4(join17(wrapDir, "notes"), { recursive: true });
|
|
9909
|
+
await mkdir4(join17(wrapDir, "lessons"), { recursive: true });
|
|
9484
9910
|
const created = [
|
|
9485
9911
|
".story/config.json",
|
|
9486
9912
|
".story/roadmap.json",
|
|
@@ -9520,7 +9946,7 @@ async function initProject(root, options) {
|
|
|
9520
9946
|
};
|
|
9521
9947
|
await writeConfig(config, absRoot);
|
|
9522
9948
|
await writeRoadmap(roadmap, absRoot);
|
|
9523
|
-
const gitignorePath =
|
|
9949
|
+
const gitignorePath = join17(wrapDir, ".gitignore");
|
|
9524
9950
|
await ensureGitignoreEntries(gitignorePath, STORY_GITIGNORE_ENTRIES);
|
|
9525
9951
|
const warnings = [];
|
|
9526
9952
|
if (options.force && exists) {
|
|
@@ -9559,7 +9985,7 @@ async function ensureGitignoreEntries(gitignorePath, entries) {
|
|
|
9559
9985
|
// src/mcp/index.ts
|
|
9560
9986
|
var ENV_VAR2 = "CLAUDESTORY_PROJECT_ROOT";
|
|
9561
9987
|
var CONFIG_PATH2 = ".story/config.json";
|
|
9562
|
-
var version = "0.1.
|
|
9988
|
+
var version = "0.1.37";
|
|
9563
9989
|
function tryDiscoverRoot() {
|
|
9564
9990
|
const envRoot = process.env[ENV_VAR2];
|
|
9565
9991
|
if (envRoot) {
|
|
@@ -9571,7 +9997,7 @@ function tryDiscoverRoot() {
|
|
|
9571
9997
|
const resolved = resolve8(envRoot);
|
|
9572
9998
|
try {
|
|
9573
9999
|
const canonical = realpathSync2(resolved);
|
|
9574
|
-
if (existsSync11(
|
|
10000
|
+
if (existsSync11(join18(canonical, CONFIG_PATH2))) {
|
|
9575
10001
|
return canonical;
|
|
9576
10002
|
}
|
|
9577
10003
|
process.stderr.write(`Warning: No .story/config.json at ${canonical}
|