@anthropologies/claudestory 0.1.35 → 0.1.36
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 +367 -132
- package/dist/index.js +124 -6
- package/dist/mcp.js +331 -100
- package/package.json +1 -1
- package/dist/cli.d.ts +0 -1
- package/dist/index.d.ts +0 -1842
- package/dist/mcp.d.ts +0 -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
|
}
|
|
@@ -3702,27 +3740,27 @@ __export(session_exports, {
|
|
|
3702
3740
|
import { randomUUID } from "crypto";
|
|
3703
3741
|
import {
|
|
3704
3742
|
mkdirSync,
|
|
3705
|
-
readdirSync,
|
|
3706
|
-
readFileSync,
|
|
3743
|
+
readdirSync as readdirSync3,
|
|
3744
|
+
readFileSync as readFileSync3,
|
|
3707
3745
|
writeFileSync,
|
|
3708
3746
|
renameSync,
|
|
3709
3747
|
unlinkSync,
|
|
3710
3748
|
existsSync as existsSync6,
|
|
3711
3749
|
rmSync
|
|
3712
3750
|
} from "fs";
|
|
3713
|
-
import { join as
|
|
3751
|
+
import { join as join8 } from "path";
|
|
3714
3752
|
import lockfile2 from "proper-lockfile";
|
|
3715
3753
|
function sessionsRoot(root) {
|
|
3716
|
-
return
|
|
3754
|
+
return join8(root, ".story", SESSIONS_DIR);
|
|
3717
3755
|
}
|
|
3718
3756
|
function sessionDir(root, sessionId) {
|
|
3719
|
-
return
|
|
3757
|
+
return join8(sessionsRoot(root), sessionId);
|
|
3720
3758
|
}
|
|
3721
3759
|
function statePath(dir) {
|
|
3722
|
-
return
|
|
3760
|
+
return join8(dir, "state.json");
|
|
3723
3761
|
}
|
|
3724
3762
|
function eventsPath(dir) {
|
|
3725
|
-
return
|
|
3763
|
+
return join8(dir, "events.log");
|
|
3726
3764
|
}
|
|
3727
3765
|
function createSession(root, recipe, workspaceId, configOverrides) {
|
|
3728
3766
|
const id = randomUUID();
|
|
@@ -3778,7 +3816,7 @@ function readSession(dir) {
|
|
|
3778
3816
|
const path2 = statePath(dir);
|
|
3779
3817
|
let raw;
|
|
3780
3818
|
try {
|
|
3781
|
-
raw =
|
|
3819
|
+
raw = readFileSync3(path2, "utf-8");
|
|
3782
3820
|
} catch {
|
|
3783
3821
|
return null;
|
|
3784
3822
|
}
|
|
@@ -3821,7 +3859,7 @@ function readEvents(dir) {
|
|
|
3821
3859
|
const path2 = eventsPath(dir);
|
|
3822
3860
|
let raw;
|
|
3823
3861
|
try {
|
|
3824
|
-
raw =
|
|
3862
|
+
raw = readFileSync3(path2, "utf-8");
|
|
3825
3863
|
} catch {
|
|
3826
3864
|
return { events: [], malformedCount: 0 };
|
|
3827
3865
|
}
|
|
@@ -3878,7 +3916,7 @@ function findActiveSessionFull(root) {
|
|
|
3878
3916
|
const sessDir = sessionsRoot(root);
|
|
3879
3917
|
let entries;
|
|
3880
3918
|
try {
|
|
3881
|
-
entries =
|
|
3919
|
+
entries = readdirSync3(sessDir, { withFileTypes: true });
|
|
3882
3920
|
} catch {
|
|
3883
3921
|
return null;
|
|
3884
3922
|
}
|
|
@@ -3892,7 +3930,7 @@ function findActiveSessionFull(root) {
|
|
|
3892
3930
|
let bestGuideCall = 0;
|
|
3893
3931
|
for (const entry of entries) {
|
|
3894
3932
|
if (!entry.isDirectory()) continue;
|
|
3895
|
-
const dir =
|
|
3933
|
+
const dir = join8(sessDir, entry.name);
|
|
3896
3934
|
const session = readSession(dir);
|
|
3897
3935
|
if (!session) continue;
|
|
3898
3936
|
if (session.status !== "active") continue;
|
|
@@ -3915,7 +3953,7 @@ function findStaleSessions(root) {
|
|
|
3915
3953
|
const sessDir = sessionsRoot(root);
|
|
3916
3954
|
let entries;
|
|
3917
3955
|
try {
|
|
3918
|
-
entries =
|
|
3956
|
+
entries = readdirSync3(sessDir, { withFileTypes: true });
|
|
3919
3957
|
} catch {
|
|
3920
3958
|
return [];
|
|
3921
3959
|
}
|
|
@@ -3928,7 +3966,7 @@ function findStaleSessions(root) {
|
|
|
3928
3966
|
const results = [];
|
|
3929
3967
|
for (const entry of entries) {
|
|
3930
3968
|
if (!entry.isDirectory()) continue;
|
|
3931
|
-
const dir =
|
|
3969
|
+
const dir = join8(sessDir, entry.name);
|
|
3932
3970
|
const session = readSession(dir);
|
|
3933
3971
|
if (!session) continue;
|
|
3934
3972
|
if (session.status !== "active") continue;
|
|
@@ -3984,7 +4022,7 @@ function findResumableSession(root) {
|
|
|
3984
4022
|
const sessDir = sessionsRoot(root);
|
|
3985
4023
|
let entries;
|
|
3986
4024
|
try {
|
|
3987
|
-
entries =
|
|
4025
|
+
entries = readdirSync3(sessDir, { withFileTypes: true });
|
|
3988
4026
|
} catch {
|
|
3989
4027
|
return null;
|
|
3990
4028
|
}
|
|
@@ -3999,7 +4037,7 @@ function findResumableSession(root) {
|
|
|
3999
4037
|
let bestPreparedAt = 0;
|
|
4000
4038
|
for (const entry of entries) {
|
|
4001
4039
|
if (!entry.isDirectory()) continue;
|
|
4002
|
-
const dir =
|
|
4040
|
+
const dir = join8(sessDir, entry.name);
|
|
4003
4041
|
const session = readSession(dir);
|
|
4004
4042
|
if (!session) continue;
|
|
4005
4043
|
if (session.status !== "active") continue;
|
|
@@ -4023,7 +4061,7 @@ async function withSessionLock(root, fn) {
|
|
|
4023
4061
|
release = await lockfile2.lock(sessDir, {
|
|
4024
4062
|
retries: { retries: 3, minTimeout: 100, maxTimeout: 1e3 },
|
|
4025
4063
|
stale: 3e4,
|
|
4026
|
-
lockfilePath:
|
|
4064
|
+
lockfilePath: join8(sessDir, ".lock")
|
|
4027
4065
|
});
|
|
4028
4066
|
return await fn();
|
|
4029
4067
|
} finally {
|
|
@@ -4049,7 +4087,7 @@ var init_session = __esm({
|
|
|
4049
4087
|
// src/mcp/index.ts
|
|
4050
4088
|
init_esm_shims();
|
|
4051
4089
|
import { realpathSync as realpathSync2, existsSync as existsSync11 } from "fs";
|
|
4052
|
-
import { resolve as resolve8, join as
|
|
4090
|
+
import { resolve as resolve8, join as join18, isAbsolute } from "path";
|
|
4053
4091
|
import { z as z11 } from "zod";
|
|
4054
4092
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
4055
4093
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
@@ -4102,13 +4140,65 @@ init_errors();
|
|
|
4102
4140
|
init_helpers();
|
|
4103
4141
|
init_types();
|
|
4104
4142
|
import { z as z10 } from "zod";
|
|
4105
|
-
import { join as
|
|
4143
|
+
import { join as join16 } from "path";
|
|
4106
4144
|
|
|
4107
4145
|
// src/cli/commands/status.ts
|
|
4108
4146
|
init_esm_shims();
|
|
4109
4147
|
init_output_formatter();
|
|
4148
|
+
|
|
4149
|
+
// src/core/session-scan.ts
|
|
4150
|
+
init_esm_shims();
|
|
4151
|
+
import { readdirSync, readFileSync } from "fs";
|
|
4152
|
+
import { join as join4 } from "path";
|
|
4153
|
+
function scanActiveSessions(root) {
|
|
4154
|
+
const sessDir = join4(root, ".story", "sessions");
|
|
4155
|
+
let entries;
|
|
4156
|
+
try {
|
|
4157
|
+
entries = readdirSync(sessDir, { withFileTypes: true });
|
|
4158
|
+
} catch {
|
|
4159
|
+
return [];
|
|
4160
|
+
}
|
|
4161
|
+
const results = [];
|
|
4162
|
+
for (const entry of entries) {
|
|
4163
|
+
if (!entry.isDirectory()) continue;
|
|
4164
|
+
const statePath2 = join4(sessDir, entry.name, "state.json");
|
|
4165
|
+
let raw;
|
|
4166
|
+
try {
|
|
4167
|
+
raw = readFileSync(statePath2, "utf-8");
|
|
4168
|
+
} catch {
|
|
4169
|
+
continue;
|
|
4170
|
+
}
|
|
4171
|
+
let parsed;
|
|
4172
|
+
try {
|
|
4173
|
+
parsed = JSON.parse(raw);
|
|
4174
|
+
} catch {
|
|
4175
|
+
continue;
|
|
4176
|
+
}
|
|
4177
|
+
if (parsed.status !== "active") continue;
|
|
4178
|
+
if (parsed.state === "SESSION_END") continue;
|
|
4179
|
+
const lease = parsed.lease;
|
|
4180
|
+
if (lease?.expiresAt) {
|
|
4181
|
+
const expires = new Date(lease.expiresAt).getTime();
|
|
4182
|
+
if (!Number.isNaN(expires) && expires <= Date.now()) continue;
|
|
4183
|
+
} else {
|
|
4184
|
+
continue;
|
|
4185
|
+
}
|
|
4186
|
+
const ticket = parsed.ticket;
|
|
4187
|
+
results.push({
|
|
4188
|
+
sessionId: parsed.sessionId ?? entry.name,
|
|
4189
|
+
state: parsed.state ?? "unknown",
|
|
4190
|
+
mode: parsed.mode ?? "auto",
|
|
4191
|
+
ticketId: ticket?.id ?? null,
|
|
4192
|
+
ticketTitle: ticket?.title ?? null
|
|
4193
|
+
});
|
|
4194
|
+
}
|
|
4195
|
+
return results;
|
|
4196
|
+
}
|
|
4197
|
+
|
|
4198
|
+
// src/cli/commands/status.ts
|
|
4110
4199
|
function handleStatus(ctx) {
|
|
4111
|
-
|
|
4200
|
+
const sessions = scanActiveSessions(ctx.root);
|
|
4201
|
+
return { output: formatStatus(ctx.state, ctx.format, sessions) };
|
|
4112
4202
|
}
|
|
4113
4203
|
|
|
4114
4204
|
// src/cli/commands/validate.ts
|
|
@@ -4708,6 +4798,8 @@ async function handleLessonReinforce(id, format, root) {
|
|
|
4708
4798
|
|
|
4709
4799
|
// src/cli/commands/recommend.ts
|
|
4710
4800
|
init_esm_shims();
|
|
4801
|
+
import { readFileSync as readFileSync2, readdirSync as readdirSync2 } from "fs";
|
|
4802
|
+
import { join as join7 } from "path";
|
|
4711
4803
|
|
|
4712
4804
|
// src/core/recommend.ts
|
|
4713
4805
|
init_esm_shims();
|
|
@@ -4728,10 +4820,12 @@ var CATEGORY_PRIORITY = {
|
|
|
4728
4820
|
high_impact_unblock: 4,
|
|
4729
4821
|
near_complete_umbrella: 5,
|
|
4730
4822
|
phase_momentum: 6,
|
|
4731
|
-
|
|
4732
|
-
|
|
4823
|
+
debt_trend: 7,
|
|
4824
|
+
quick_win: 8,
|
|
4825
|
+
handover_context: 9,
|
|
4826
|
+
open_issue: 10
|
|
4733
4827
|
};
|
|
4734
|
-
function recommend(state, count) {
|
|
4828
|
+
function recommend(state, count, options) {
|
|
4735
4829
|
const effectiveCount = Math.max(1, Math.min(10, count));
|
|
4736
4830
|
const dedup = /* @__PURE__ */ new Map();
|
|
4737
4831
|
const phaseIndex = buildPhaseIndex(state);
|
|
@@ -4743,7 +4837,8 @@ function recommend(state, count) {
|
|
|
4743
4837
|
() => generateNearCompleteUmbrellas(state, phaseIndex),
|
|
4744
4838
|
() => generatePhaseMomentum(state),
|
|
4745
4839
|
() => generateQuickWins(state, phaseIndex),
|
|
4746
|
-
() => generateOpenIssues(state)
|
|
4840
|
+
() => generateOpenIssues(state),
|
|
4841
|
+
() => generateDebtTrend(state, options)
|
|
4747
4842
|
];
|
|
4748
4843
|
for (const gen of generators) {
|
|
4749
4844
|
for (const rec of gen()) {
|
|
@@ -4753,6 +4848,7 @@ function recommend(state, count) {
|
|
|
4753
4848
|
}
|
|
4754
4849
|
}
|
|
4755
4850
|
}
|
|
4851
|
+
applyHandoverBoost(state, dedup, options);
|
|
4756
4852
|
const curPhase = currentPhase(state);
|
|
4757
4853
|
const curPhaseIdx = curPhase ? phaseIndex.get(curPhase.id) ?? 0 : 0;
|
|
4758
4854
|
for (const [id, rec] of dedup) {
|
|
@@ -4942,13 +5038,113 @@ function sortByPhaseAndOrder(tickets, phaseIndex) {
|
|
|
4942
5038
|
return a.order - b.order;
|
|
4943
5039
|
});
|
|
4944
5040
|
}
|
|
5041
|
+
var TICKET_ID_RE = /\bT-\d{3}[a-z]?\b/g;
|
|
5042
|
+
var ACTIONABLE_HEADING_RE = /^#+\s.*(next|open|remaining|todo|blocked)/im;
|
|
5043
|
+
var HANDOVER_BOOST = 50;
|
|
5044
|
+
var HANDOVER_BASE_SCORE = 350;
|
|
5045
|
+
function applyHandoverBoost(state, dedup, options) {
|
|
5046
|
+
if (!options?.latestHandoverContent) return;
|
|
5047
|
+
const content = options.latestHandoverContent;
|
|
5048
|
+
let actionableIds = extractTicketIdsFromActionableSections(content);
|
|
5049
|
+
if (actionableIds.size === 0) {
|
|
5050
|
+
const allIds = new Set(content.match(TICKET_ID_RE) ?? []);
|
|
5051
|
+
for (const id of allIds) {
|
|
5052
|
+
const ticket = state.ticketByID(id);
|
|
5053
|
+
if (ticket && ticket.status !== "complete" && ticket.status !== "inprogress") {
|
|
5054
|
+
actionableIds.add(id);
|
|
5055
|
+
}
|
|
5056
|
+
}
|
|
5057
|
+
}
|
|
5058
|
+
for (const id of actionableIds) {
|
|
5059
|
+
const ticket = state.ticketByID(id);
|
|
5060
|
+
if (!ticket || ticket.status === "complete") continue;
|
|
5061
|
+
const existing = dedup.get(id);
|
|
5062
|
+
if (existing) {
|
|
5063
|
+
dedup.set(id, {
|
|
5064
|
+
...existing,
|
|
5065
|
+
score: existing.score + HANDOVER_BOOST,
|
|
5066
|
+
reason: existing.reason + " (handover context)"
|
|
5067
|
+
});
|
|
5068
|
+
} else {
|
|
5069
|
+
dedup.set(id, {
|
|
5070
|
+
id,
|
|
5071
|
+
kind: "ticket",
|
|
5072
|
+
title: ticket.title,
|
|
5073
|
+
category: "handover_context",
|
|
5074
|
+
reason: "Referenced in latest handover",
|
|
5075
|
+
score: HANDOVER_BASE_SCORE
|
|
5076
|
+
});
|
|
5077
|
+
}
|
|
5078
|
+
}
|
|
5079
|
+
}
|
|
5080
|
+
function extractTicketIdsFromActionableSections(content) {
|
|
5081
|
+
const ids = /* @__PURE__ */ new Set();
|
|
5082
|
+
const lines = content.split("\n");
|
|
5083
|
+
let inActionable = false;
|
|
5084
|
+
for (const line of lines) {
|
|
5085
|
+
if (/^#+\s/.test(line)) {
|
|
5086
|
+
inActionable = ACTIONABLE_HEADING_RE.test(line);
|
|
5087
|
+
}
|
|
5088
|
+
if (inActionable) {
|
|
5089
|
+
const matches = line.match(TICKET_ID_RE);
|
|
5090
|
+
if (matches) for (const m of matches) ids.add(m);
|
|
5091
|
+
}
|
|
5092
|
+
}
|
|
5093
|
+
return ids;
|
|
5094
|
+
}
|
|
5095
|
+
var DEBT_TREND_SCORE = 450;
|
|
5096
|
+
var DEBT_GROWTH_THRESHOLD = 0.25;
|
|
5097
|
+
var DEBT_ABSOLUTE_MINIMUM = 2;
|
|
5098
|
+
function generateDebtTrend(state, options) {
|
|
5099
|
+
if (options?.previousOpenIssueCount == null) return [];
|
|
5100
|
+
const currentOpen = state.issues.filter((i) => i.status !== "resolved").length;
|
|
5101
|
+
const previous = options.previousOpenIssueCount;
|
|
5102
|
+
if (previous <= 0) return [];
|
|
5103
|
+
const growth = (currentOpen - previous) / previous;
|
|
5104
|
+
const absolute = currentOpen - previous;
|
|
5105
|
+
if (growth > DEBT_GROWTH_THRESHOLD && absolute >= DEBT_ABSOLUTE_MINIMUM) {
|
|
5106
|
+
return [{
|
|
5107
|
+
id: "DEBT_TREND",
|
|
5108
|
+
kind: "action",
|
|
5109
|
+
title: "Issue debt growing",
|
|
5110
|
+
category: "debt_trend",
|
|
5111
|
+
reason: `Open issues grew from ${previous} to ${currentOpen} (+${Math.round(growth * 100)}%). Consider triaging or resolving issues before adding features.`,
|
|
5112
|
+
score: DEBT_TREND_SCORE
|
|
5113
|
+
}];
|
|
5114
|
+
}
|
|
5115
|
+
return [];
|
|
5116
|
+
}
|
|
4945
5117
|
|
|
4946
5118
|
// src/cli/commands/recommend.ts
|
|
4947
5119
|
init_output_formatter();
|
|
4948
5120
|
function handleRecommend(ctx, count) {
|
|
4949
|
-
const
|
|
5121
|
+
const options = buildRecommendOptions(ctx);
|
|
5122
|
+
const result = recommend(ctx.state, count, options);
|
|
4950
5123
|
return { output: formatRecommendations(result, ctx.state, ctx.format) };
|
|
4951
5124
|
}
|
|
5125
|
+
function buildRecommendOptions(ctx) {
|
|
5126
|
+
const opts = {};
|
|
5127
|
+
try {
|
|
5128
|
+
const files = readdirSync2(ctx.handoversDir).filter((f) => f.endsWith(".md")).sort();
|
|
5129
|
+
if (files.length > 0) {
|
|
5130
|
+
opts.latestHandoverContent = readFileSync2(join7(ctx.handoversDir, files[files.length - 1]), "utf-8");
|
|
5131
|
+
}
|
|
5132
|
+
} catch {
|
|
5133
|
+
}
|
|
5134
|
+
try {
|
|
5135
|
+
const snapshotsDir = join7(ctx.root, ".story", "snapshots");
|
|
5136
|
+
const snapFiles = readdirSync2(snapshotsDir).filter((f) => f.endsWith(".json")).sort();
|
|
5137
|
+
if (snapFiles.length > 0) {
|
|
5138
|
+
const raw = readFileSync2(join7(snapshotsDir, snapFiles[snapFiles.length - 1]), "utf-8");
|
|
5139
|
+
const snap = JSON.parse(raw);
|
|
5140
|
+
if (snap.issues) {
|
|
5141
|
+
opts.previousOpenIssueCount = snap.issues.filter((i) => i.status !== "resolved").length;
|
|
5142
|
+
}
|
|
5143
|
+
}
|
|
5144
|
+
} catch {
|
|
5145
|
+
}
|
|
5146
|
+
return opts;
|
|
5147
|
+
}
|
|
4952
5148
|
|
|
4953
5149
|
// src/cli/commands/snapshot.ts
|
|
4954
5150
|
init_esm_shims();
|
|
@@ -5238,8 +5434,8 @@ init_handover();
|
|
|
5238
5434
|
init_esm_shims();
|
|
5239
5435
|
init_session_types();
|
|
5240
5436
|
init_session();
|
|
5241
|
-
import { readFileSync as
|
|
5242
|
-
import { join as
|
|
5437
|
+
import { readFileSync as readFileSync6, writeFileSync as writeFileSync3, readdirSync as readdirSync4 } from "fs";
|
|
5438
|
+
import { join as join13 } from "path";
|
|
5243
5439
|
|
|
5244
5440
|
// src/autonomous/state-machine.ts
|
|
5245
5441
|
init_esm_shims();
|
|
@@ -5511,8 +5707,8 @@ function parseDiffNumstat(out) {
|
|
|
5511
5707
|
|
|
5512
5708
|
// src/autonomous/recipes/loader.ts
|
|
5513
5709
|
init_esm_shims();
|
|
5514
|
-
import { readFileSync as
|
|
5515
|
-
import { join as
|
|
5710
|
+
import { readFileSync as readFileSync4 } from "fs";
|
|
5711
|
+
import { join as join9, dirname as dirname3 } from "path";
|
|
5516
5712
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
5517
5713
|
var DEFAULT_PIPELINE = [
|
|
5518
5714
|
"PICK_TICKET",
|
|
@@ -5532,9 +5728,9 @@ function loadRecipe(recipeName) {
|
|
|
5532
5728
|
if (!/^[A-Za-z0-9_-]+$/.test(recipeName)) {
|
|
5533
5729
|
throw new Error(`Invalid recipe name: ${recipeName}`);
|
|
5534
5730
|
}
|
|
5535
|
-
const recipesDir =
|
|
5536
|
-
const path2 =
|
|
5537
|
-
const raw =
|
|
5731
|
+
const recipesDir = join9(dirname3(fileURLToPath2(import.meta.url)), "..", "recipes");
|
|
5732
|
+
const path2 = join9(recipesDir, `${recipeName}.json`);
|
|
5733
|
+
const raw = readFileSync4(path2, "utf-8");
|
|
5538
5734
|
return JSON.parse(raw);
|
|
5539
5735
|
}
|
|
5540
5736
|
function resolveRecipe(recipeName, projectOverrides) {
|
|
@@ -5781,7 +5977,7 @@ init_esm_shims();
|
|
|
5781
5977
|
// src/autonomous/stages/pick-ticket.ts
|
|
5782
5978
|
init_esm_shims();
|
|
5783
5979
|
import { existsSync as existsSync7, unlinkSync as unlinkSync2 } from "fs";
|
|
5784
|
-
import { join as
|
|
5980
|
+
import { join as join10 } from "path";
|
|
5785
5981
|
var PickTicketStage = class {
|
|
5786
5982
|
id = "PICK_TICKET";
|
|
5787
5983
|
async enter(ctx) {
|
|
@@ -5831,7 +6027,7 @@ var PickTicketStage = class {
|
|
|
5831
6027
|
return { action: "retry", instruction: `Ticket ${ticketId} is ${ticket.status} \u2014 pick an open ticket.` };
|
|
5832
6028
|
}
|
|
5833
6029
|
}
|
|
5834
|
-
const planPath =
|
|
6030
|
+
const planPath = join10(ctx.dir, "plan.md");
|
|
5835
6031
|
try {
|
|
5836
6032
|
if (existsSync7(planPath)) unlinkSync2(planPath);
|
|
5837
6033
|
} catch {
|
|
@@ -5870,11 +6066,11 @@ ${ticket.description}` : "",
|
|
|
5870
6066
|
|
|
5871
6067
|
// src/autonomous/stages/plan.ts
|
|
5872
6068
|
init_esm_shims();
|
|
5873
|
-
import { existsSync as existsSync8, readFileSync as
|
|
5874
|
-
import { join as
|
|
6069
|
+
import { existsSync as existsSync8, readFileSync as readFileSync5 } from "fs";
|
|
6070
|
+
import { join as join11 } from "path";
|
|
5875
6071
|
function readFileSafe(path2) {
|
|
5876
6072
|
try {
|
|
5877
|
-
return
|
|
6073
|
+
return readFileSync5(path2, "utf-8");
|
|
5878
6074
|
} catch {
|
|
5879
6075
|
return "";
|
|
5880
6076
|
}
|
|
@@ -5909,7 +6105,7 @@ var PlanStage = class {
|
|
|
5909
6105
|
};
|
|
5910
6106
|
}
|
|
5911
6107
|
async report(ctx, _report) {
|
|
5912
|
-
const planPath =
|
|
6108
|
+
const planPath = join11(ctx.dir, "plan.md");
|
|
5913
6109
|
if (!existsSync8(planPath)) {
|
|
5914
6110
|
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
6111
|
}
|
|
@@ -6140,7 +6336,8 @@ var ImplementStage = class {
|
|
|
6140
6336
|
reminders: [
|
|
6141
6337
|
"Follow the plan exactly. Do NOT deviate without re-planning.",
|
|
6142
6338
|
"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."
|
|
6339
|
+
"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.",
|
|
6340
|
+
"Track which files you create or modify. Only these files should be staged at commit time."
|
|
6144
6341
|
],
|
|
6145
6342
|
transitionedFrom: ctx.state.previousState ?? void 0
|
|
6146
6343
|
};
|
|
@@ -6798,10 +6995,10 @@ var FinalizeStage = class {
|
|
|
6798
6995
|
"Code review passed. Time to commit.",
|
|
6799
6996
|
"",
|
|
6800
6997
|
ctx.state.ticket ? `1. Update ticket ${ctx.state.ticket.id} status to "complete" in .story/` : "",
|
|
6801
|
-
"2. Stage
|
|
6998
|
+
"2. Stage only the files you created or modified for this ticket (code + .story/ changes). Do NOT use `git add -A` or `git add .`",
|
|
6802
6999
|
'3. Call me with completedAction: "files_staged"'
|
|
6803
7000
|
].filter(Boolean).join("\n"),
|
|
6804
|
-
reminders: ["Stage both code changes and .story/ ticket update in the same commit."],
|
|
7001
|
+
reminders: ["Stage both code changes and .story/ ticket update in the same commit. Only stage files related to this ticket."],
|
|
6805
7002
|
transitionedFrom: ctx.state.previousState ?? void 0
|
|
6806
7003
|
};
|
|
6807
7004
|
}
|
|
@@ -7285,7 +7482,7 @@ Impact: ${nextIssue.impact}` : ""}` : `Fix issue ${next}.`,
|
|
|
7285
7482
|
init_esm_shims();
|
|
7286
7483
|
init_handover();
|
|
7287
7484
|
import { writeFileSync as writeFileSync2 } from "fs";
|
|
7288
|
-
import { join as
|
|
7485
|
+
import { join as join12 } from "path";
|
|
7289
7486
|
var HandoverStage = class {
|
|
7290
7487
|
id = "HANDOVER";
|
|
7291
7488
|
async enter(ctx) {
|
|
@@ -7315,7 +7512,7 @@ var HandoverStage = class {
|
|
|
7315
7512
|
} catch {
|
|
7316
7513
|
handoverFailed = true;
|
|
7317
7514
|
try {
|
|
7318
|
-
const fallbackPath =
|
|
7515
|
+
const fallbackPath = join12(ctx.dir, "handover-fallback.md");
|
|
7319
7516
|
writeFileSync2(fallbackPath, content, "utf-8");
|
|
7320
7517
|
} catch {
|
|
7321
7518
|
}
|
|
@@ -7381,6 +7578,46 @@ init_snapshot();
|
|
|
7381
7578
|
init_snapshot();
|
|
7382
7579
|
init_queries();
|
|
7383
7580
|
init_handover();
|
|
7581
|
+
var RECOVERY_MAPPING = {
|
|
7582
|
+
PICK_TICKET: { state: "PICK_TICKET", resetPlan: false, resetCode: false },
|
|
7583
|
+
COMPLETE: { state: "PICK_TICKET", resetPlan: false, resetCode: false },
|
|
7584
|
+
HANDOVER: { state: "PICK_TICKET", resetPlan: false, resetCode: false },
|
|
7585
|
+
PLAN: { state: "PLAN", resetPlan: true, resetCode: false },
|
|
7586
|
+
IMPLEMENT: { state: "PLAN", resetPlan: true, resetCode: false },
|
|
7587
|
+
WRITE_TESTS: { state: "PLAN", resetPlan: true, resetCode: false },
|
|
7588
|
+
BUILD: { state: "IMPLEMENT", resetPlan: false, resetCode: true },
|
|
7589
|
+
VERIFY: { state: "IMPLEMENT", resetPlan: false, resetCode: true },
|
|
7590
|
+
PLAN_REVIEW: { state: "PLAN", resetPlan: true, resetCode: true },
|
|
7591
|
+
TEST: { state: "IMPLEMENT", resetPlan: false, resetCode: true },
|
|
7592
|
+
CODE_REVIEW: { state: "PLAN", resetPlan: true, resetCode: true },
|
|
7593
|
+
FINALIZE: { state: "IMPLEMENT", resetPlan: false, resetCode: true },
|
|
7594
|
+
LESSON_CAPTURE: { state: "PICK_TICKET", resetPlan: false, resetCode: false },
|
|
7595
|
+
ISSUE_SWEEP: { state: "PICK_TICKET", resetPlan: false, resetCode: false }
|
|
7596
|
+
};
|
|
7597
|
+
function buildGuideRecommendOptions(root) {
|
|
7598
|
+
const opts = {};
|
|
7599
|
+
try {
|
|
7600
|
+
const handoversDir = join13(root, ".story", "handovers");
|
|
7601
|
+
const files = readdirSync4(handoversDir, "utf-8").filter((f) => f.endsWith(".md")).sort();
|
|
7602
|
+
if (files.length > 0) {
|
|
7603
|
+
opts.latestHandoverContent = readFileSync6(join13(handoversDir, files[files.length - 1]), "utf-8");
|
|
7604
|
+
}
|
|
7605
|
+
} catch {
|
|
7606
|
+
}
|
|
7607
|
+
try {
|
|
7608
|
+
const snapshotsDir = join13(root, ".story", "snapshots");
|
|
7609
|
+
const snapFiles = readdirSync4(snapshotsDir, "utf-8").filter((f) => f.endsWith(".json")).sort();
|
|
7610
|
+
if (snapFiles.length > 0) {
|
|
7611
|
+
const raw = readFileSync6(join13(snapshotsDir, snapFiles[snapFiles.length - 1]), "utf-8");
|
|
7612
|
+
const snap = JSON.parse(raw);
|
|
7613
|
+
if (snap.issues) {
|
|
7614
|
+
opts.previousOpenIssueCount = snap.issues.filter((i) => i.status !== "resolved").length;
|
|
7615
|
+
}
|
|
7616
|
+
}
|
|
7617
|
+
} catch {
|
|
7618
|
+
}
|
|
7619
|
+
return opts;
|
|
7620
|
+
}
|
|
7384
7621
|
async function recoverPendingMutation(dir, state, root) {
|
|
7385
7622
|
const mutation = state.pendingProjectMutation;
|
|
7386
7623
|
if (!mutation || typeof mutation !== "object") return state;
|
|
@@ -7758,7 +7995,7 @@ Staged: ${stagedResult.data.join(", ")}`
|
|
|
7758
7995
|
}
|
|
7759
7996
|
}
|
|
7760
7997
|
const { state: projectState, warnings } = await loadProject(root);
|
|
7761
|
-
const handoversDir =
|
|
7998
|
+
const handoversDir = join13(root, ".story", "handovers");
|
|
7762
7999
|
const ctx = { state: projectState, warnings, root, handoversDir, format: "md" };
|
|
7763
8000
|
let handoverText = "";
|
|
7764
8001
|
try {
|
|
@@ -7775,7 +8012,7 @@ Staged: ${stagedResult.data.join(", ")}`
|
|
|
7775
8012
|
}
|
|
7776
8013
|
} catch {
|
|
7777
8014
|
}
|
|
7778
|
-
const rulesText = readFileSafe2(
|
|
8015
|
+
const rulesText = readFileSafe2(join13(root, "RULES.md"));
|
|
7779
8016
|
const lessonDigest = buildLessonDigest(projectState.lessons);
|
|
7780
8017
|
const digestParts = [
|
|
7781
8018
|
handoverText ? `## Recent Handovers
|
|
@@ -7791,7 +8028,7 @@ ${rulesText}` : "",
|
|
|
7791
8028
|
].filter(Boolean);
|
|
7792
8029
|
const digest = digestParts.join("\n\n---\n\n");
|
|
7793
8030
|
try {
|
|
7794
|
-
writeFileSync3(
|
|
8031
|
+
writeFileSync3(join13(dir, "context-digest.md"), digest, "utf-8");
|
|
7795
8032
|
} catch {
|
|
7796
8033
|
}
|
|
7797
8034
|
if (mode !== "auto" && args.ticketId) {
|
|
@@ -7810,6 +8047,18 @@ ${rulesText}` : "",
|
|
|
7810
8047
|
return guideError(new Error(`Ticket ${args.ticketId} is blocked by: ${ticket.blockedBy.join(", ")}.`));
|
|
7811
8048
|
}
|
|
7812
8049
|
}
|
|
8050
|
+
if (mode !== "review") {
|
|
8051
|
+
const claimId = ticket.claimedBySession;
|
|
8052
|
+
if (claimId && typeof claimId === "string" && claimId !== session.sessionId) {
|
|
8053
|
+
const claimingSession = findSessionById(root, claimId);
|
|
8054
|
+
if (claimingSession && claimingSession.state.status === "active" && !isLeaseExpired(claimingSession.state)) {
|
|
8055
|
+
deleteSession(root, session.sessionId);
|
|
8056
|
+
return guideError(new Error(
|
|
8057
|
+
`Ticket ${args.ticketId} is claimed by active session ${claimId}. Wait for it to finish or stop it with "claudestory session stop ${claimId}".`
|
|
8058
|
+
));
|
|
8059
|
+
}
|
|
8060
|
+
}
|
|
8061
|
+
}
|
|
7813
8062
|
let entryState;
|
|
7814
8063
|
if (mode === "review") {
|
|
7815
8064
|
entryState = "CODE_REVIEW";
|
|
@@ -7905,7 +8154,8 @@ ${ticket.description}` : "",
|
|
|
7905
8154
|
} else {
|
|
7906
8155
|
candidatesText = "No tickets found.";
|
|
7907
8156
|
}
|
|
7908
|
-
const
|
|
8157
|
+
const guideRecOptions = buildGuideRecommendOptions(root);
|
|
8158
|
+
const recResult = recommend(projectState, 5, guideRecOptions);
|
|
7909
8159
|
let recsText = "";
|
|
7910
8160
|
if (recResult.recommendations.length > 0) {
|
|
7911
8161
|
const ticketRecs = recResult.recommendations.filter((r) => r.kind === "ticket");
|
|
@@ -8131,26 +8381,7 @@ async function handleResume(root, args) {
|
|
|
8131
8381
|
));
|
|
8132
8382
|
}
|
|
8133
8383
|
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 };
|
|
8384
|
+
const mapping = RECOVERY_MAPPING[resumeState] ?? { state: "PICK_TICKET", resetPlan: false, resetCode: false };
|
|
8154
8385
|
const recoveryReviews = {
|
|
8155
8386
|
plan: mapping.resetPlan ? [] : info.state.reviews.plan,
|
|
8156
8387
|
code: mapping.resetCode ? [] : info.state.reviews.code
|
|
@@ -8498,7 +8729,7 @@ function guideError(err) {
|
|
|
8498
8729
|
}
|
|
8499
8730
|
function readFileSafe2(path2) {
|
|
8500
8731
|
try {
|
|
8501
|
-
return
|
|
8732
|
+
return readFileSync6(path2, "utf-8");
|
|
8502
8733
|
} catch {
|
|
8503
8734
|
return "";
|
|
8504
8735
|
}
|
|
@@ -8508,8 +8739,8 @@ function readFileSafe2(path2) {
|
|
|
8508
8739
|
init_esm_shims();
|
|
8509
8740
|
init_session();
|
|
8510
8741
|
init_session_types();
|
|
8511
|
-
import { readFileSync as
|
|
8512
|
-
import { join as
|
|
8742
|
+
import { readFileSync as readFileSync7, existsSync as existsSync10 } from "fs";
|
|
8743
|
+
import { join as join14 } from "path";
|
|
8513
8744
|
|
|
8514
8745
|
// src/core/session-report-formatter.ts
|
|
8515
8746
|
init_esm_shims();
|
|
@@ -8723,7 +8954,7 @@ async function handleSessionReport(sessionId, root, format = "md") {
|
|
|
8723
8954
|
isError: true
|
|
8724
8955
|
};
|
|
8725
8956
|
}
|
|
8726
|
-
const statePath2 =
|
|
8957
|
+
const statePath2 = join14(dir, "state.json");
|
|
8727
8958
|
if (!existsSync10(statePath2)) {
|
|
8728
8959
|
return {
|
|
8729
8960
|
output: `Error: Session ${sessionId} corrupt \u2014 state.json missing.`,
|
|
@@ -8733,7 +8964,7 @@ async function handleSessionReport(sessionId, root, format = "md") {
|
|
|
8733
8964
|
};
|
|
8734
8965
|
}
|
|
8735
8966
|
try {
|
|
8736
|
-
const rawJson = JSON.parse(
|
|
8967
|
+
const rawJson = JSON.parse(readFileSync7(statePath2, "utf-8"));
|
|
8737
8968
|
if (rawJson && typeof rawJson === "object" && "schemaVersion" in rawJson && rawJson.schemaVersion !== CURRENT_SESSION_SCHEMA_VERSION) {
|
|
8738
8969
|
return {
|
|
8739
8970
|
output: `Error: Session ${sessionId} \u2014 unsupported session schema version ${rawJson.schemaVersion}.`,
|
|
@@ -8762,7 +8993,7 @@ async function handleSessionReport(sessionId, root, format = "md") {
|
|
|
8762
8993
|
const events = readEvents(dir);
|
|
8763
8994
|
let planContent = null;
|
|
8764
8995
|
try {
|
|
8765
|
-
planContent =
|
|
8996
|
+
planContent = readFileSync7(join14(dir, "plan.md"), "utf-8");
|
|
8766
8997
|
} catch {
|
|
8767
8998
|
}
|
|
8768
8999
|
let gitLog = null;
|
|
@@ -8787,7 +9018,7 @@ init_issue();
|
|
|
8787
9018
|
init_roadmap();
|
|
8788
9019
|
init_output_formatter();
|
|
8789
9020
|
init_helpers();
|
|
8790
|
-
import { join as
|
|
9021
|
+
import { join as join15, resolve as resolve6 } from "path";
|
|
8791
9022
|
var PHASE_ID_REGEX = /^[a-z0-9]+(-[a-z0-9]+)*$/;
|
|
8792
9023
|
var PHASE_ID_MAX_LENGTH = 40;
|
|
8793
9024
|
function validatePhaseId(id) {
|
|
@@ -8896,7 +9127,7 @@ function formatMcpError(code, message) {
|
|
|
8896
9127
|
async function runMcpReadTool(pinnedRoot, handler) {
|
|
8897
9128
|
try {
|
|
8898
9129
|
const { state, warnings } = await loadProject(pinnedRoot);
|
|
8899
|
-
const handoversDir =
|
|
9130
|
+
const handoversDir = join16(pinnedRoot, ".story", "handovers");
|
|
8900
9131
|
const ctx = { state, warnings, root: pinnedRoot, handoversDir, format: "md" };
|
|
8901
9132
|
const result = await handler(ctx);
|
|
8902
9133
|
if (result.errorCode && INFRASTRUCTURE_ERROR_CODES.includes(result.errorCode)) {
|
|
@@ -9453,10 +9684,10 @@ init_esm_shims();
|
|
|
9453
9684
|
init_project_loader();
|
|
9454
9685
|
init_errors();
|
|
9455
9686
|
import { mkdir as mkdir4, stat as stat2, readFile as readFile4, writeFile as writeFile2 } from "fs/promises";
|
|
9456
|
-
import { join as
|
|
9687
|
+
import { join as join17, resolve as resolve7 } from "path";
|
|
9457
9688
|
async function initProject(root, options) {
|
|
9458
9689
|
const absRoot = resolve7(root);
|
|
9459
|
-
const wrapDir =
|
|
9690
|
+
const wrapDir = join17(absRoot, ".story");
|
|
9460
9691
|
let exists = false;
|
|
9461
9692
|
try {
|
|
9462
9693
|
const s = await stat2(wrapDir);
|
|
@@ -9476,11 +9707,11 @@ async function initProject(root, options) {
|
|
|
9476
9707
|
".story/ already exists. Use --force to overwrite config and roadmap."
|
|
9477
9708
|
);
|
|
9478
9709
|
}
|
|
9479
|
-
await mkdir4(
|
|
9480
|
-
await mkdir4(
|
|
9481
|
-
await mkdir4(
|
|
9482
|
-
await mkdir4(
|
|
9483
|
-
await mkdir4(
|
|
9710
|
+
await mkdir4(join17(wrapDir, "tickets"), { recursive: true });
|
|
9711
|
+
await mkdir4(join17(wrapDir, "issues"), { recursive: true });
|
|
9712
|
+
await mkdir4(join17(wrapDir, "handovers"), { recursive: true });
|
|
9713
|
+
await mkdir4(join17(wrapDir, "notes"), { recursive: true });
|
|
9714
|
+
await mkdir4(join17(wrapDir, "lessons"), { recursive: true });
|
|
9484
9715
|
const created = [
|
|
9485
9716
|
".story/config.json",
|
|
9486
9717
|
".story/roadmap.json",
|
|
@@ -9520,7 +9751,7 @@ async function initProject(root, options) {
|
|
|
9520
9751
|
};
|
|
9521
9752
|
await writeConfig(config, absRoot);
|
|
9522
9753
|
await writeRoadmap(roadmap, absRoot);
|
|
9523
|
-
const gitignorePath =
|
|
9754
|
+
const gitignorePath = join17(wrapDir, ".gitignore");
|
|
9524
9755
|
await ensureGitignoreEntries(gitignorePath, STORY_GITIGNORE_ENTRIES);
|
|
9525
9756
|
const warnings = [];
|
|
9526
9757
|
if (options.force && exists) {
|
|
@@ -9559,7 +9790,7 @@ async function ensureGitignoreEntries(gitignorePath, entries) {
|
|
|
9559
9790
|
// src/mcp/index.ts
|
|
9560
9791
|
var ENV_VAR2 = "CLAUDESTORY_PROJECT_ROOT";
|
|
9561
9792
|
var CONFIG_PATH2 = ".story/config.json";
|
|
9562
|
-
var version = "0.1.
|
|
9793
|
+
var version = "0.1.36";
|
|
9563
9794
|
function tryDiscoverRoot() {
|
|
9564
9795
|
const envRoot = process.env[ENV_VAR2];
|
|
9565
9796
|
if (envRoot) {
|
|
@@ -9571,7 +9802,7 @@ function tryDiscoverRoot() {
|
|
|
9571
9802
|
const resolved = resolve8(envRoot);
|
|
9572
9803
|
try {
|
|
9573
9804
|
const canonical = realpathSync2(resolved);
|
|
9574
|
-
if (existsSync11(
|
|
9805
|
+
if (existsSync11(join18(canonical, CONFIG_PATH2))) {
|
|
9575
9806
|
return canonical;
|
|
9576
9807
|
}
|
|
9577
9808
|
process.stderr.write(`Warning: No .story/config.json at ${canonical}
|