@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/cli.js
CHANGED
|
@@ -1761,7 +1761,7 @@ function fencedBlock(content, lang) {
|
|
|
1761
1761
|
${content}
|
|
1762
1762
|
${fence}`;
|
|
1763
1763
|
}
|
|
1764
|
-
function formatStatus(state, format) {
|
|
1764
|
+
function formatStatus(state, format, activeSessions = []) {
|
|
1765
1765
|
const phases = phasesWithStatus(state);
|
|
1766
1766
|
const data = {
|
|
1767
1767
|
project: state.config.project,
|
|
@@ -1781,7 +1781,8 @@ function formatStatus(state, format) {
|
|
|
1781
1781
|
name: p.phase.name,
|
|
1782
1782
|
status: p.status,
|
|
1783
1783
|
leafCount: p.leafCount
|
|
1784
|
-
}))
|
|
1784
|
+
})),
|
|
1785
|
+
...activeSessions.length > 0 ? { activeSessions } : {}
|
|
1785
1786
|
};
|
|
1786
1787
|
if (format === "json") {
|
|
1787
1788
|
return JSON.stringify(successEnvelope(data), null, 2);
|
|
@@ -1803,6 +1804,15 @@ function formatStatus(state, format) {
|
|
|
1803
1804
|
const summary = p.phase.summary ?? truncate(p.phase.description, 80);
|
|
1804
1805
|
lines.push(`${indicator} **${escapeMarkdownInline(p.phase.name)}** (${p.leafCount} tickets) \u2014 ${escapeMarkdownInline(summary)}`);
|
|
1805
1806
|
}
|
|
1807
|
+
if (activeSessions.length > 0) {
|
|
1808
|
+
lines.push("");
|
|
1809
|
+
lines.push("## Active Sessions");
|
|
1810
|
+
lines.push("");
|
|
1811
|
+
for (const s of activeSessions) {
|
|
1812
|
+
const ticket = s.ticketId ? `${s.ticketId}: ${escapeMarkdownInline(s.ticketTitle ?? "")}` : "no ticket";
|
|
1813
|
+
lines.push(`- ${s.sessionId.slice(0, 8)}: ${s.state} -- ${ticket} (${s.mode} mode)`);
|
|
1814
|
+
}
|
|
1815
|
+
}
|
|
1806
1816
|
if (state.isEmptyScaffold) {
|
|
1807
1817
|
lines.push("");
|
|
1808
1818
|
lines.push(EMPTY_SCAFFOLD_HEADING);
|
|
@@ -2691,15 +2701,71 @@ var init_output_formatter = __esm({
|
|
|
2691
2701
|
}
|
|
2692
2702
|
});
|
|
2693
2703
|
|
|
2704
|
+
// src/core/session-scan.ts
|
|
2705
|
+
import { readdirSync, readFileSync } from "fs";
|
|
2706
|
+
import { join as join4 } from "path";
|
|
2707
|
+
function scanActiveSessions(root) {
|
|
2708
|
+
const sessDir = join4(root, ".story", "sessions");
|
|
2709
|
+
let entries;
|
|
2710
|
+
try {
|
|
2711
|
+
entries = readdirSync(sessDir, { withFileTypes: true });
|
|
2712
|
+
} catch {
|
|
2713
|
+
return [];
|
|
2714
|
+
}
|
|
2715
|
+
const results = [];
|
|
2716
|
+
for (const entry of entries) {
|
|
2717
|
+
if (!entry.isDirectory()) continue;
|
|
2718
|
+
const statePath2 = join4(sessDir, entry.name, "state.json");
|
|
2719
|
+
let raw;
|
|
2720
|
+
try {
|
|
2721
|
+
raw = readFileSync(statePath2, "utf-8");
|
|
2722
|
+
} catch {
|
|
2723
|
+
continue;
|
|
2724
|
+
}
|
|
2725
|
+
let parsed;
|
|
2726
|
+
try {
|
|
2727
|
+
parsed = JSON.parse(raw);
|
|
2728
|
+
} catch {
|
|
2729
|
+
continue;
|
|
2730
|
+
}
|
|
2731
|
+
if (parsed.status !== "active") continue;
|
|
2732
|
+
if (parsed.state === "SESSION_END") continue;
|
|
2733
|
+
const lease = parsed.lease;
|
|
2734
|
+
if (lease?.expiresAt) {
|
|
2735
|
+
const expires = new Date(lease.expiresAt).getTime();
|
|
2736
|
+
if (!Number.isNaN(expires) && expires <= Date.now()) continue;
|
|
2737
|
+
} else {
|
|
2738
|
+
continue;
|
|
2739
|
+
}
|
|
2740
|
+
const ticket = parsed.ticket;
|
|
2741
|
+
results.push({
|
|
2742
|
+
sessionId: parsed.sessionId ?? entry.name,
|
|
2743
|
+
state: parsed.state ?? "unknown",
|
|
2744
|
+
mode: parsed.mode ?? "auto",
|
|
2745
|
+
ticketId: ticket?.id ?? null,
|
|
2746
|
+
ticketTitle: ticket?.title ?? null
|
|
2747
|
+
});
|
|
2748
|
+
}
|
|
2749
|
+
return results;
|
|
2750
|
+
}
|
|
2751
|
+
var init_session_scan = __esm({
|
|
2752
|
+
"src/core/session-scan.ts"() {
|
|
2753
|
+
"use strict";
|
|
2754
|
+
init_esm_shims();
|
|
2755
|
+
}
|
|
2756
|
+
});
|
|
2757
|
+
|
|
2694
2758
|
// src/cli/commands/status.ts
|
|
2695
2759
|
function handleStatus(ctx) {
|
|
2696
|
-
|
|
2760
|
+
const sessions = scanActiveSessions(ctx.root);
|
|
2761
|
+
return { output: formatStatus(ctx.state, ctx.format, sessions) };
|
|
2697
2762
|
}
|
|
2698
2763
|
var init_status = __esm({
|
|
2699
2764
|
"src/cli/commands/status.ts"() {
|
|
2700
2765
|
"use strict";
|
|
2701
2766
|
init_esm_shims();
|
|
2702
2767
|
init_output_formatter();
|
|
2768
|
+
init_session_scan();
|
|
2703
2769
|
}
|
|
2704
2770
|
});
|
|
2705
2771
|
|
|
@@ -2787,6 +2853,7 @@ function validateProject(state) {
|
|
|
2787
2853
|
}
|
|
2788
2854
|
}
|
|
2789
2855
|
}
|
|
2856
|
+
detectSupersedesCycles(state, findings);
|
|
2790
2857
|
const phaseIDCounts = /* @__PURE__ */ new Map();
|
|
2791
2858
|
for (const p of state.roadmap.phases) {
|
|
2792
2859
|
phaseIDCounts.set(p.id, (phaseIDCounts.get(p.id) ?? 0) + 1);
|
|
@@ -2991,6 +3058,33 @@ function dfsBlocked(id, state, visited, inStack, findings) {
|
|
|
2991
3058
|
inStack.delete(id);
|
|
2992
3059
|
visited.add(id);
|
|
2993
3060
|
}
|
|
3061
|
+
function detectSupersedesCycles(state, findings) {
|
|
3062
|
+
const visited = /* @__PURE__ */ new Set();
|
|
3063
|
+
const inStack = /* @__PURE__ */ new Set();
|
|
3064
|
+
for (const l of state.lessons) {
|
|
3065
|
+
if (l.supersedes == null || visited.has(l.id)) continue;
|
|
3066
|
+
dfsSupersedesChain(l.id, state, visited, inStack, findings);
|
|
3067
|
+
}
|
|
3068
|
+
}
|
|
3069
|
+
function dfsSupersedesChain(id, state, visited, inStack, findings) {
|
|
3070
|
+
if (inStack.has(id)) {
|
|
3071
|
+
findings.push({
|
|
3072
|
+
level: "error",
|
|
3073
|
+
code: "supersedes_cycle",
|
|
3074
|
+
message: `Cycle detected in supersedes chain involving ${id}.`,
|
|
3075
|
+
entity: id
|
|
3076
|
+
});
|
|
3077
|
+
return;
|
|
3078
|
+
}
|
|
3079
|
+
if (visited.has(id)) return;
|
|
3080
|
+
inStack.add(id);
|
|
3081
|
+
const lesson = state.lessonByID(id);
|
|
3082
|
+
if (lesson?.supersedes && lesson.supersedes !== id) {
|
|
3083
|
+
dfsSupersedesChain(lesson.supersedes, state, visited, inStack, findings);
|
|
3084
|
+
}
|
|
3085
|
+
inStack.delete(id);
|
|
3086
|
+
visited.add(id);
|
|
3087
|
+
}
|
|
2994
3088
|
var init_validation = __esm({
|
|
2995
3089
|
"src/core/validation.ts"() {
|
|
2996
3090
|
"use strict";
|
|
@@ -3027,7 +3121,7 @@ __export(handover_exports, {
|
|
|
3027
3121
|
});
|
|
3028
3122
|
import { existsSync as existsSync4 } from "fs";
|
|
3029
3123
|
import { mkdir as mkdir2 } from "fs/promises";
|
|
3030
|
-
import { join as
|
|
3124
|
+
import { join as join5, resolve as resolve4 } from "path";
|
|
3031
3125
|
function handleHandoverList(ctx) {
|
|
3032
3126
|
return { output: formatHandoverList(ctx.state.handoverFilenames, ctx.format) };
|
|
3033
3127
|
}
|
|
@@ -3112,15 +3206,15 @@ async function handleHandoverCreate(content, slugRaw, format, root) {
|
|
|
3112
3206
|
let filename;
|
|
3113
3207
|
await withProjectLock(root, { strict: false }, async () => {
|
|
3114
3208
|
const absRoot = resolve4(root);
|
|
3115
|
-
const handoversDir =
|
|
3209
|
+
const handoversDir = join5(absRoot, ".story", "handovers");
|
|
3116
3210
|
await mkdir2(handoversDir, { recursive: true });
|
|
3117
|
-
const wrapDir =
|
|
3211
|
+
const wrapDir = join5(absRoot, ".story");
|
|
3118
3212
|
const datePrefix = `${date}-`;
|
|
3119
3213
|
const seqRegex = new RegExp(`^${date}-(\\d{2})-`);
|
|
3120
3214
|
let maxSeq = 0;
|
|
3121
|
-
const { readdirSync:
|
|
3215
|
+
const { readdirSync: readdirSync5 } = await import("fs");
|
|
3122
3216
|
try {
|
|
3123
|
-
for (const f of
|
|
3217
|
+
for (const f of readdirSync5(handoversDir)) {
|
|
3124
3218
|
const m = f.match(seqRegex);
|
|
3125
3219
|
if (m) {
|
|
3126
3220
|
const n = parseInt(m[1], 10);
|
|
@@ -3137,7 +3231,7 @@ async function handleHandoverCreate(content, slugRaw, format, root) {
|
|
|
3137
3231
|
);
|
|
3138
3232
|
}
|
|
3139
3233
|
let candidate = `${date}-${String(nextSeq).padStart(2, "0")}-${slug}.md`;
|
|
3140
|
-
let candidatePath =
|
|
3234
|
+
let candidatePath = join5(handoversDir, candidate);
|
|
3141
3235
|
while (existsSync4(candidatePath)) {
|
|
3142
3236
|
nextSeq++;
|
|
3143
3237
|
if (nextSeq > 99) {
|
|
@@ -3147,7 +3241,7 @@ async function handleHandoverCreate(content, slugRaw, format, root) {
|
|
|
3147
3241
|
);
|
|
3148
3242
|
}
|
|
3149
3243
|
candidate = `${date}-${String(nextSeq).padStart(2, "0")}-${slug}.md`;
|
|
3150
|
-
candidatePath =
|
|
3244
|
+
candidatePath = join5(handoversDir, candidate);
|
|
3151
3245
|
}
|
|
3152
3246
|
await parseHandoverFilename(candidate, handoversDir);
|
|
3153
3247
|
await guardPath(candidatePath, wrapDir);
|
|
@@ -3780,11 +3874,11 @@ __export(snapshot_exports, {
|
|
|
3780
3874
|
});
|
|
3781
3875
|
import { readdir as readdir3, readFile as readFile3, mkdir as mkdir3, unlink as unlink2 } from "fs/promises";
|
|
3782
3876
|
import { existsSync as existsSync5 } from "fs";
|
|
3783
|
-
import { join as
|
|
3877
|
+
import { join as join6, resolve as resolve5 } from "path";
|
|
3784
3878
|
import { z as z8 } from "zod";
|
|
3785
3879
|
async function saveSnapshot(root, loadResult) {
|
|
3786
3880
|
const absRoot = resolve5(root);
|
|
3787
|
-
const snapshotsDir =
|
|
3881
|
+
const snapshotsDir = join6(absRoot, ".story", "snapshots");
|
|
3788
3882
|
await mkdir3(snapshotsDir, { recursive: true });
|
|
3789
3883
|
const { state, warnings } = loadResult;
|
|
3790
3884
|
const now = /* @__PURE__ */ new Date();
|
|
@@ -3809,8 +3903,8 @@ async function saveSnapshot(root, loadResult) {
|
|
|
3809
3903
|
} : {}
|
|
3810
3904
|
};
|
|
3811
3905
|
const json = JSON.stringify(snapshot, null, 2) + "\n";
|
|
3812
|
-
const targetPath =
|
|
3813
|
-
const wrapDir =
|
|
3906
|
+
const targetPath = join6(snapshotsDir, filename);
|
|
3907
|
+
const wrapDir = join6(absRoot, ".story");
|
|
3814
3908
|
await guardPath(targetPath, wrapDir);
|
|
3815
3909
|
await atomicWrite(targetPath, json);
|
|
3816
3910
|
const pruned = await pruneSnapshots(snapshotsDir);
|
|
@@ -3818,13 +3912,13 @@ async function saveSnapshot(root, loadResult) {
|
|
|
3818
3912
|
return { filename, retained: entries.length, pruned };
|
|
3819
3913
|
}
|
|
3820
3914
|
async function loadLatestSnapshot(root) {
|
|
3821
|
-
const snapshotsDir =
|
|
3915
|
+
const snapshotsDir = join6(resolve5(root), ".story", "snapshots");
|
|
3822
3916
|
if (!existsSync5(snapshotsDir)) return null;
|
|
3823
3917
|
const files = await listSnapshotFiles(snapshotsDir);
|
|
3824
3918
|
if (files.length === 0) return null;
|
|
3825
3919
|
for (const filename of files) {
|
|
3826
3920
|
try {
|
|
3827
|
-
const content = await readFile3(
|
|
3921
|
+
const content = await readFile3(join6(snapshotsDir, filename), "utf-8");
|
|
3828
3922
|
const parsed = JSON.parse(content);
|
|
3829
3923
|
const snapshot = SnapshotV1Schema.parse(parsed);
|
|
3830
3924
|
return { snapshot, filename };
|
|
@@ -4068,7 +4162,7 @@ async function pruneSnapshots(dir) {
|
|
|
4068
4162
|
const toRemove = files.slice(MAX_SNAPSHOTS);
|
|
4069
4163
|
for (const f of toRemove) {
|
|
4070
4164
|
try {
|
|
4071
|
-
await unlink2(
|
|
4165
|
+
await unlink2(join6(dir, f));
|
|
4072
4166
|
} catch {
|
|
4073
4167
|
}
|
|
4074
4168
|
}
|
|
@@ -4487,7 +4581,7 @@ var init_lesson2 = __esm({
|
|
|
4487
4581
|
});
|
|
4488
4582
|
|
|
4489
4583
|
// src/core/recommend.ts
|
|
4490
|
-
function recommend(state, count) {
|
|
4584
|
+
function recommend(state, count, options) {
|
|
4491
4585
|
const effectiveCount = Math.max(1, Math.min(10, count));
|
|
4492
4586
|
const dedup = /* @__PURE__ */ new Map();
|
|
4493
4587
|
const phaseIndex = buildPhaseIndex(state);
|
|
@@ -4499,7 +4593,8 @@ function recommend(state, count) {
|
|
|
4499
4593
|
() => generateNearCompleteUmbrellas(state, phaseIndex),
|
|
4500
4594
|
() => generatePhaseMomentum(state),
|
|
4501
4595
|
() => generateQuickWins(state, phaseIndex),
|
|
4502
|
-
() => generateOpenIssues(state)
|
|
4596
|
+
() => generateOpenIssues(state),
|
|
4597
|
+
() => generateDebtTrend(state, options)
|
|
4503
4598
|
];
|
|
4504
4599
|
for (const gen of generators) {
|
|
4505
4600
|
for (const rec of gen()) {
|
|
@@ -4509,6 +4604,7 @@ function recommend(state, count) {
|
|
|
4509
4604
|
}
|
|
4510
4605
|
}
|
|
4511
4606
|
}
|
|
4607
|
+
applyHandoverBoost(state, dedup, options);
|
|
4512
4608
|
const curPhase = currentPhase(state);
|
|
4513
4609
|
const curPhaseIdx = curPhase ? phaseIndex.get(curPhase.id) ?? 0 : 0;
|
|
4514
4610
|
for (const [id, rec] of dedup) {
|
|
@@ -4698,7 +4794,76 @@ function sortByPhaseAndOrder(tickets, phaseIndex) {
|
|
|
4698
4794
|
return a.order - b.order;
|
|
4699
4795
|
});
|
|
4700
4796
|
}
|
|
4701
|
-
|
|
4797
|
+
function applyHandoverBoost(state, dedup, options) {
|
|
4798
|
+
if (!options?.latestHandoverContent) return;
|
|
4799
|
+
const content = options.latestHandoverContent;
|
|
4800
|
+
let actionableIds = extractTicketIdsFromActionableSections(content);
|
|
4801
|
+
if (actionableIds.size === 0) {
|
|
4802
|
+
const allIds = new Set(content.match(TICKET_ID_RE) ?? []);
|
|
4803
|
+
for (const id of allIds) {
|
|
4804
|
+
const ticket = state.ticketByID(id);
|
|
4805
|
+
if (ticket && ticket.status !== "complete" && ticket.status !== "inprogress") {
|
|
4806
|
+
actionableIds.add(id);
|
|
4807
|
+
}
|
|
4808
|
+
}
|
|
4809
|
+
}
|
|
4810
|
+
for (const id of actionableIds) {
|
|
4811
|
+
const ticket = state.ticketByID(id);
|
|
4812
|
+
if (!ticket || ticket.status === "complete") continue;
|
|
4813
|
+
const existing = dedup.get(id);
|
|
4814
|
+
if (existing) {
|
|
4815
|
+
dedup.set(id, {
|
|
4816
|
+
...existing,
|
|
4817
|
+
score: existing.score + HANDOVER_BOOST,
|
|
4818
|
+
reason: existing.reason + " (handover context)"
|
|
4819
|
+
});
|
|
4820
|
+
} else {
|
|
4821
|
+
dedup.set(id, {
|
|
4822
|
+
id,
|
|
4823
|
+
kind: "ticket",
|
|
4824
|
+
title: ticket.title,
|
|
4825
|
+
category: "handover_context",
|
|
4826
|
+
reason: "Referenced in latest handover",
|
|
4827
|
+
score: HANDOVER_BASE_SCORE
|
|
4828
|
+
});
|
|
4829
|
+
}
|
|
4830
|
+
}
|
|
4831
|
+
}
|
|
4832
|
+
function extractTicketIdsFromActionableSections(content) {
|
|
4833
|
+
const ids = /* @__PURE__ */ new Set();
|
|
4834
|
+
const lines = content.split("\n");
|
|
4835
|
+
let inActionable = false;
|
|
4836
|
+
for (const line of lines) {
|
|
4837
|
+
if (/^#+\s/.test(line)) {
|
|
4838
|
+
inActionable = ACTIONABLE_HEADING_RE.test(line);
|
|
4839
|
+
}
|
|
4840
|
+
if (inActionable) {
|
|
4841
|
+
const matches = line.match(TICKET_ID_RE);
|
|
4842
|
+
if (matches) for (const m of matches) ids.add(m);
|
|
4843
|
+
}
|
|
4844
|
+
}
|
|
4845
|
+
return ids;
|
|
4846
|
+
}
|
|
4847
|
+
function generateDebtTrend(state, options) {
|
|
4848
|
+
if (options?.previousOpenIssueCount == null) return [];
|
|
4849
|
+
const currentOpen = state.issues.filter((i) => i.status !== "resolved").length;
|
|
4850
|
+
const previous = options.previousOpenIssueCount;
|
|
4851
|
+
if (previous <= 0) return [];
|
|
4852
|
+
const growth = (currentOpen - previous) / previous;
|
|
4853
|
+
const absolute = currentOpen - previous;
|
|
4854
|
+
if (growth > DEBT_GROWTH_THRESHOLD && absolute >= DEBT_ABSOLUTE_MINIMUM) {
|
|
4855
|
+
return [{
|
|
4856
|
+
id: "DEBT_TREND",
|
|
4857
|
+
kind: "action",
|
|
4858
|
+
title: "Issue debt growing",
|
|
4859
|
+
category: "debt_trend",
|
|
4860
|
+
reason: `Open issues grew from ${previous} to ${currentOpen} (+${Math.round(growth * 100)}%). Consider triaging or resolving issues before adding features.`,
|
|
4861
|
+
score: DEBT_TREND_SCORE
|
|
4862
|
+
}];
|
|
4863
|
+
}
|
|
4864
|
+
return [];
|
|
4865
|
+
}
|
|
4866
|
+
var SEVERITY_RANK, PHASE_DISTANCE_PENALTY, MAX_PHASE_PENALTY, CATEGORY_PRIORITY, TICKET_ID_RE, ACTIONABLE_HEADING_RE, HANDOVER_BOOST, HANDOVER_BASE_SCORE, DEBT_TREND_SCORE, DEBT_GROWTH_THRESHOLD, DEBT_ABSOLUTE_MINIMUM;
|
|
4702
4867
|
var init_recommend = __esm({
|
|
4703
4868
|
"src/core/recommend.ts"() {
|
|
4704
4869
|
"use strict";
|
|
@@ -4720,17 +4885,52 @@ var init_recommend = __esm({
|
|
|
4720
4885
|
high_impact_unblock: 4,
|
|
4721
4886
|
near_complete_umbrella: 5,
|
|
4722
4887
|
phase_momentum: 6,
|
|
4723
|
-
|
|
4724
|
-
|
|
4888
|
+
debt_trend: 7,
|
|
4889
|
+
quick_win: 8,
|
|
4890
|
+
handover_context: 9,
|
|
4891
|
+
open_issue: 10
|
|
4725
4892
|
};
|
|
4893
|
+
TICKET_ID_RE = /\bT-\d{3}[a-z]?\b/g;
|
|
4894
|
+
ACTIONABLE_HEADING_RE = /^#+\s.*(next|open|remaining|todo|blocked)/im;
|
|
4895
|
+
HANDOVER_BOOST = 50;
|
|
4896
|
+
HANDOVER_BASE_SCORE = 350;
|
|
4897
|
+
DEBT_TREND_SCORE = 450;
|
|
4898
|
+
DEBT_GROWTH_THRESHOLD = 0.25;
|
|
4899
|
+
DEBT_ABSOLUTE_MINIMUM = 2;
|
|
4726
4900
|
}
|
|
4727
4901
|
});
|
|
4728
4902
|
|
|
4729
4903
|
// src/cli/commands/recommend.ts
|
|
4904
|
+
import { readFileSync as readFileSync2, readdirSync as readdirSync2 } from "fs";
|
|
4905
|
+
import { join as join7 } from "path";
|
|
4730
4906
|
function handleRecommend(ctx, count) {
|
|
4731
|
-
const
|
|
4907
|
+
const options = buildRecommendOptions(ctx);
|
|
4908
|
+
const result = recommend(ctx.state, count, options);
|
|
4732
4909
|
return { output: formatRecommendations(result, ctx.state, ctx.format) };
|
|
4733
4910
|
}
|
|
4911
|
+
function buildRecommendOptions(ctx) {
|
|
4912
|
+
const opts = {};
|
|
4913
|
+
try {
|
|
4914
|
+
const files = readdirSync2(ctx.handoversDir).filter((f) => f.endsWith(".md")).sort();
|
|
4915
|
+
if (files.length > 0) {
|
|
4916
|
+
opts.latestHandoverContent = readFileSync2(join7(ctx.handoversDir, files[files.length - 1]), "utf-8");
|
|
4917
|
+
}
|
|
4918
|
+
} catch {
|
|
4919
|
+
}
|
|
4920
|
+
try {
|
|
4921
|
+
const snapshotsDir = join7(ctx.root, ".story", "snapshots");
|
|
4922
|
+
const snapFiles = readdirSync2(snapshotsDir).filter((f) => f.endsWith(".json")).sort();
|
|
4923
|
+
if (snapFiles.length > 0) {
|
|
4924
|
+
const raw = readFileSync2(join7(snapshotsDir, snapFiles[snapFiles.length - 1]), "utf-8");
|
|
4925
|
+
const snap = JSON.parse(raw);
|
|
4926
|
+
if (snap.issues) {
|
|
4927
|
+
opts.previousOpenIssueCount = snap.issues.filter((i) => i.status !== "resolved").length;
|
|
4928
|
+
}
|
|
4929
|
+
}
|
|
4930
|
+
} catch {
|
|
4931
|
+
}
|
|
4932
|
+
return opts;
|
|
4933
|
+
}
|
|
4734
4934
|
var init_recommend2 = __esm({
|
|
4735
4935
|
"src/cli/commands/recommend.ts"() {
|
|
4736
4936
|
"use strict";
|
|
@@ -5280,27 +5480,27 @@ __export(session_exports, {
|
|
|
5280
5480
|
import { randomUUID } from "crypto";
|
|
5281
5481
|
import {
|
|
5282
5482
|
mkdirSync,
|
|
5283
|
-
readdirSync,
|
|
5284
|
-
readFileSync,
|
|
5483
|
+
readdirSync as readdirSync3,
|
|
5484
|
+
readFileSync as readFileSync3,
|
|
5285
5485
|
writeFileSync,
|
|
5286
5486
|
renameSync,
|
|
5287
5487
|
unlinkSync,
|
|
5288
5488
|
existsSync as existsSync6,
|
|
5289
5489
|
rmSync
|
|
5290
5490
|
} from "fs";
|
|
5291
|
-
import { join as
|
|
5491
|
+
import { join as join8 } from "path";
|
|
5292
5492
|
import lockfile2 from "proper-lockfile";
|
|
5293
5493
|
function sessionsRoot(root) {
|
|
5294
|
-
return
|
|
5494
|
+
return join8(root, ".story", SESSIONS_DIR);
|
|
5295
5495
|
}
|
|
5296
5496
|
function sessionDir(root, sessionId) {
|
|
5297
|
-
return
|
|
5497
|
+
return join8(sessionsRoot(root), sessionId);
|
|
5298
5498
|
}
|
|
5299
5499
|
function statePath(dir) {
|
|
5300
|
-
return
|
|
5500
|
+
return join8(dir, "state.json");
|
|
5301
5501
|
}
|
|
5302
5502
|
function eventsPath(dir) {
|
|
5303
|
-
return
|
|
5503
|
+
return join8(dir, "events.log");
|
|
5304
5504
|
}
|
|
5305
5505
|
function createSession(root, recipe, workspaceId, configOverrides) {
|
|
5306
5506
|
const id = randomUUID();
|
|
@@ -5356,7 +5556,7 @@ function readSession(dir) {
|
|
|
5356
5556
|
const path2 = statePath(dir);
|
|
5357
5557
|
let raw;
|
|
5358
5558
|
try {
|
|
5359
|
-
raw =
|
|
5559
|
+
raw = readFileSync3(path2, "utf-8");
|
|
5360
5560
|
} catch {
|
|
5361
5561
|
return null;
|
|
5362
5562
|
}
|
|
@@ -5399,7 +5599,7 @@ function readEvents(dir) {
|
|
|
5399
5599
|
const path2 = eventsPath(dir);
|
|
5400
5600
|
let raw;
|
|
5401
5601
|
try {
|
|
5402
|
-
raw =
|
|
5602
|
+
raw = readFileSync3(path2, "utf-8");
|
|
5403
5603
|
} catch {
|
|
5404
5604
|
return { events: [], malformedCount: 0 };
|
|
5405
5605
|
}
|
|
@@ -5456,7 +5656,7 @@ function findActiveSessionFull(root) {
|
|
|
5456
5656
|
const sessDir = sessionsRoot(root);
|
|
5457
5657
|
let entries;
|
|
5458
5658
|
try {
|
|
5459
|
-
entries =
|
|
5659
|
+
entries = readdirSync3(sessDir, { withFileTypes: true });
|
|
5460
5660
|
} catch {
|
|
5461
5661
|
return null;
|
|
5462
5662
|
}
|
|
@@ -5470,7 +5670,7 @@ function findActiveSessionFull(root) {
|
|
|
5470
5670
|
let bestGuideCall = 0;
|
|
5471
5671
|
for (const entry of entries) {
|
|
5472
5672
|
if (!entry.isDirectory()) continue;
|
|
5473
|
-
const dir =
|
|
5673
|
+
const dir = join8(sessDir, entry.name);
|
|
5474
5674
|
const session = readSession(dir);
|
|
5475
5675
|
if (!session) continue;
|
|
5476
5676
|
if (session.status !== "active") continue;
|
|
@@ -5493,7 +5693,7 @@ function findStaleSessions(root) {
|
|
|
5493
5693
|
const sessDir = sessionsRoot(root);
|
|
5494
5694
|
let entries;
|
|
5495
5695
|
try {
|
|
5496
|
-
entries =
|
|
5696
|
+
entries = readdirSync3(sessDir, { withFileTypes: true });
|
|
5497
5697
|
} catch {
|
|
5498
5698
|
return [];
|
|
5499
5699
|
}
|
|
@@ -5506,7 +5706,7 @@ function findStaleSessions(root) {
|
|
|
5506
5706
|
const results = [];
|
|
5507
5707
|
for (const entry of entries) {
|
|
5508
5708
|
if (!entry.isDirectory()) continue;
|
|
5509
|
-
const dir =
|
|
5709
|
+
const dir = join8(sessDir, entry.name);
|
|
5510
5710
|
const session = readSession(dir);
|
|
5511
5711
|
if (!session) continue;
|
|
5512
5712
|
if (session.status !== "active") continue;
|
|
@@ -5562,7 +5762,7 @@ function findResumableSession(root) {
|
|
|
5562
5762
|
const sessDir = sessionsRoot(root);
|
|
5563
5763
|
let entries;
|
|
5564
5764
|
try {
|
|
5565
|
-
entries =
|
|
5765
|
+
entries = readdirSync3(sessDir, { withFileTypes: true });
|
|
5566
5766
|
} catch {
|
|
5567
5767
|
return null;
|
|
5568
5768
|
}
|
|
@@ -5577,7 +5777,7 @@ function findResumableSession(root) {
|
|
|
5577
5777
|
let bestPreparedAt = 0;
|
|
5578
5778
|
for (const entry of entries) {
|
|
5579
5779
|
if (!entry.isDirectory()) continue;
|
|
5580
|
-
const dir =
|
|
5780
|
+
const dir = join8(sessDir, entry.name);
|
|
5581
5781
|
const session = readSession(dir);
|
|
5582
5782
|
if (!session) continue;
|
|
5583
5783
|
if (session.status !== "active") continue;
|
|
@@ -5601,7 +5801,7 @@ async function withSessionLock(root, fn) {
|
|
|
5601
5801
|
release = await lockfile2.lock(sessDir, {
|
|
5602
5802
|
retries: { retries: 3, minTimeout: 100, maxTimeout: 1e3 },
|
|
5603
5803
|
stale: 3e4,
|
|
5604
|
-
lockfilePath:
|
|
5804
|
+
lockfilePath: join8(sessDir, ".lock")
|
|
5605
5805
|
});
|
|
5606
5806
|
return await fn();
|
|
5607
5807
|
} finally {
|
|
@@ -5917,16 +6117,16 @@ var init_git_inspector = __esm({
|
|
|
5917
6117
|
});
|
|
5918
6118
|
|
|
5919
6119
|
// src/autonomous/recipes/loader.ts
|
|
5920
|
-
import { readFileSync as
|
|
5921
|
-
import { join as
|
|
6120
|
+
import { readFileSync as readFileSync4 } from "fs";
|
|
6121
|
+
import { join as join9, dirname as dirname3 } from "path";
|
|
5922
6122
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
5923
6123
|
function loadRecipe(recipeName) {
|
|
5924
6124
|
if (!/^[A-Za-z0-9_-]+$/.test(recipeName)) {
|
|
5925
6125
|
throw new Error(`Invalid recipe name: ${recipeName}`);
|
|
5926
6126
|
}
|
|
5927
|
-
const recipesDir =
|
|
5928
|
-
const path2 =
|
|
5929
|
-
const raw =
|
|
6127
|
+
const recipesDir = join9(dirname3(fileURLToPath2(import.meta.url)), "..", "recipes");
|
|
6128
|
+
const path2 = join9(recipesDir, `${recipeName}.json`);
|
|
6129
|
+
const raw = readFileSync4(path2, "utf-8");
|
|
5930
6130
|
return JSON.parse(raw);
|
|
5931
6131
|
}
|
|
5932
6132
|
function resolveRecipe(recipeName, projectOverrides) {
|
|
@@ -6202,7 +6402,7 @@ var init_types2 = __esm({
|
|
|
6202
6402
|
|
|
6203
6403
|
// src/autonomous/stages/pick-ticket.ts
|
|
6204
6404
|
import { existsSync as existsSync7, unlinkSync as unlinkSync2 } from "fs";
|
|
6205
|
-
import { join as
|
|
6405
|
+
import { join as join10 } from "path";
|
|
6206
6406
|
var PickTicketStage;
|
|
6207
6407
|
var init_pick_ticket = __esm({
|
|
6208
6408
|
"src/autonomous/stages/pick-ticket.ts"() {
|
|
@@ -6257,7 +6457,7 @@ var init_pick_ticket = __esm({
|
|
|
6257
6457
|
return { action: "retry", instruction: `Ticket ${ticketId} is ${ticket.status} \u2014 pick an open ticket.` };
|
|
6258
6458
|
}
|
|
6259
6459
|
}
|
|
6260
|
-
const planPath =
|
|
6460
|
+
const planPath = join10(ctx.dir, "plan.md");
|
|
6261
6461
|
try {
|
|
6262
6462
|
if (existsSync7(planPath)) unlinkSync2(planPath);
|
|
6263
6463
|
} catch {
|
|
@@ -6297,11 +6497,11 @@ ${ticket.description}` : "",
|
|
|
6297
6497
|
});
|
|
6298
6498
|
|
|
6299
6499
|
// src/autonomous/stages/plan.ts
|
|
6300
|
-
import { existsSync as existsSync8, readFileSync as
|
|
6301
|
-
import { join as
|
|
6500
|
+
import { existsSync as existsSync8, readFileSync as readFileSync5 } from "fs";
|
|
6501
|
+
import { join as join11 } from "path";
|
|
6302
6502
|
function readFileSafe(path2) {
|
|
6303
6503
|
try {
|
|
6304
|
-
return
|
|
6504
|
+
return readFileSync5(path2, "utf-8");
|
|
6305
6505
|
} catch {
|
|
6306
6506
|
return "";
|
|
6307
6507
|
}
|
|
@@ -6342,7 +6542,7 @@ var init_plan = __esm({
|
|
|
6342
6542
|
};
|
|
6343
6543
|
}
|
|
6344
6544
|
async report(ctx, _report) {
|
|
6345
|
-
const planPath =
|
|
6545
|
+
const planPath = join11(ctx.dir, "plan.md");
|
|
6346
6546
|
if (!existsSync8(planPath)) {
|
|
6347
6547
|
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"] };
|
|
6348
6548
|
}
|
|
@@ -6588,7 +6788,8 @@ var init_implement = __esm({
|
|
|
6588
6788
|
reminders: [
|
|
6589
6789
|
"Follow the plan exactly. Do NOT deviate without re-planning.",
|
|
6590
6790
|
"Do NOT ask the user for confirmation.",
|
|
6591
|
-
"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."
|
|
6791
|
+
"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.",
|
|
6792
|
+
"Track which files you create or modify. Only these files should be staged at commit time."
|
|
6592
6793
|
],
|
|
6593
6794
|
transitionedFrom: ctx.state.previousState ?? void 0
|
|
6594
6795
|
};
|
|
@@ -7285,10 +7486,10 @@ var init_finalize = __esm({
|
|
|
7285
7486
|
"Code review passed. Time to commit.",
|
|
7286
7487
|
"",
|
|
7287
7488
|
ctx.state.ticket ? `1. Update ticket ${ctx.state.ticket.id} status to "complete" in .story/` : "",
|
|
7288
|
-
"2. Stage
|
|
7489
|
+
"2. Stage only the files you created or modified for this ticket (code + .story/ changes). Do NOT use `git add -A` or `git add .`",
|
|
7289
7490
|
'3. Call me with completedAction: "files_staged"'
|
|
7290
7491
|
].filter(Boolean).join("\n"),
|
|
7291
|
-
reminders: ["Stage both code changes and .story/ ticket update in the same commit."],
|
|
7492
|
+
reminders: ["Stage both code changes and .story/ ticket update in the same commit. Only stage files related to this ticket."],
|
|
7292
7493
|
transitionedFrom: ctx.state.previousState ?? void 0
|
|
7293
7494
|
};
|
|
7294
7495
|
}
|
|
@@ -7792,7 +7993,7 @@ Impact: ${nextIssue.impact}` : ""}` : `Fix issue ${next}.`,
|
|
|
7792
7993
|
|
|
7793
7994
|
// src/autonomous/stages/handover.ts
|
|
7794
7995
|
import { writeFileSync as writeFileSync2 } from "fs";
|
|
7795
|
-
import { join as
|
|
7996
|
+
import { join as join12 } from "path";
|
|
7796
7997
|
var HandoverStage;
|
|
7797
7998
|
var init_handover2 = __esm({
|
|
7798
7999
|
"src/autonomous/stages/handover.ts"() {
|
|
@@ -7829,7 +8030,7 @@ var init_handover2 = __esm({
|
|
|
7829
8030
|
} catch {
|
|
7830
8031
|
handoverFailed = true;
|
|
7831
8032
|
try {
|
|
7832
|
-
const fallbackPath =
|
|
8033
|
+
const fallbackPath = join12(ctx.dir, "handover-fallback.md");
|
|
7833
8034
|
writeFileSync2(fallbackPath, content, "utf-8");
|
|
7834
8035
|
} catch {
|
|
7835
8036
|
}
|
|
@@ -7913,8 +8114,32 @@ var init_stages = __esm({
|
|
|
7913
8114
|
});
|
|
7914
8115
|
|
|
7915
8116
|
// src/autonomous/guide.ts
|
|
7916
|
-
import { readFileSync as
|
|
7917
|
-
import { join as
|
|
8117
|
+
import { readFileSync as readFileSync6, writeFileSync as writeFileSync3, readdirSync as readdirSync4 } from "fs";
|
|
8118
|
+
import { join as join13 } from "path";
|
|
8119
|
+
function buildGuideRecommendOptions(root) {
|
|
8120
|
+
const opts = {};
|
|
8121
|
+
try {
|
|
8122
|
+
const handoversDir = join13(root, ".story", "handovers");
|
|
8123
|
+
const files = readdirSync4(handoversDir, "utf-8").filter((f) => f.endsWith(".md")).sort();
|
|
8124
|
+
if (files.length > 0) {
|
|
8125
|
+
opts.latestHandoverContent = readFileSync6(join13(handoversDir, files[files.length - 1]), "utf-8");
|
|
8126
|
+
}
|
|
8127
|
+
} catch {
|
|
8128
|
+
}
|
|
8129
|
+
try {
|
|
8130
|
+
const snapshotsDir = join13(root, ".story", "snapshots");
|
|
8131
|
+
const snapFiles = readdirSync4(snapshotsDir, "utf-8").filter((f) => f.endsWith(".json")).sort();
|
|
8132
|
+
if (snapFiles.length > 0) {
|
|
8133
|
+
const raw = readFileSync6(join13(snapshotsDir, snapFiles[snapFiles.length - 1]), "utf-8");
|
|
8134
|
+
const snap = JSON.parse(raw);
|
|
8135
|
+
if (snap.issues) {
|
|
8136
|
+
opts.previousOpenIssueCount = snap.issues.filter((i) => i.status !== "resolved").length;
|
|
8137
|
+
}
|
|
8138
|
+
}
|
|
8139
|
+
} catch {
|
|
8140
|
+
}
|
|
8141
|
+
return opts;
|
|
8142
|
+
}
|
|
7918
8143
|
async function recoverPendingMutation(dir, state, root) {
|
|
7919
8144
|
const mutation = state.pendingProjectMutation;
|
|
7920
8145
|
if (!mutation || typeof mutation !== "object") return state;
|
|
@@ -8286,7 +8511,7 @@ Staged: ${stagedResult.data.join(", ")}`
|
|
|
8286
8511
|
}
|
|
8287
8512
|
}
|
|
8288
8513
|
const { state: projectState, warnings } = await loadProject(root);
|
|
8289
|
-
const handoversDir =
|
|
8514
|
+
const handoversDir = join13(root, ".story", "handovers");
|
|
8290
8515
|
const ctx = { state: projectState, warnings, root, handoversDir, format: "md" };
|
|
8291
8516
|
let handoverText = "";
|
|
8292
8517
|
try {
|
|
@@ -8303,7 +8528,7 @@ Staged: ${stagedResult.data.join(", ")}`
|
|
|
8303
8528
|
}
|
|
8304
8529
|
} catch {
|
|
8305
8530
|
}
|
|
8306
|
-
const rulesText = readFileSafe2(
|
|
8531
|
+
const rulesText = readFileSafe2(join13(root, "RULES.md"));
|
|
8307
8532
|
const lessonDigest = buildLessonDigest(projectState.lessons);
|
|
8308
8533
|
const digestParts = [
|
|
8309
8534
|
handoverText ? `## Recent Handovers
|
|
@@ -8319,7 +8544,7 @@ ${rulesText}` : "",
|
|
|
8319
8544
|
].filter(Boolean);
|
|
8320
8545
|
const digest = digestParts.join("\n\n---\n\n");
|
|
8321
8546
|
try {
|
|
8322
|
-
writeFileSync3(
|
|
8547
|
+
writeFileSync3(join13(dir, "context-digest.md"), digest, "utf-8");
|
|
8323
8548
|
} catch {
|
|
8324
8549
|
}
|
|
8325
8550
|
if (mode !== "auto" && args.ticketId) {
|
|
@@ -8338,6 +8563,18 @@ ${rulesText}` : "",
|
|
|
8338
8563
|
return guideError(new Error(`Ticket ${args.ticketId} is blocked by: ${ticket.blockedBy.join(", ")}.`));
|
|
8339
8564
|
}
|
|
8340
8565
|
}
|
|
8566
|
+
if (mode !== "review") {
|
|
8567
|
+
const claimId = ticket.claimedBySession;
|
|
8568
|
+
if (claimId && typeof claimId === "string" && claimId !== session.sessionId) {
|
|
8569
|
+
const claimingSession = findSessionById(root, claimId);
|
|
8570
|
+
if (claimingSession && claimingSession.state.status === "active" && !isLeaseExpired(claimingSession.state)) {
|
|
8571
|
+
deleteSession(root, session.sessionId);
|
|
8572
|
+
return guideError(new Error(
|
|
8573
|
+
`Ticket ${args.ticketId} is claimed by active session ${claimId}. Wait for it to finish or stop it with "claudestory session stop ${claimId}".`
|
|
8574
|
+
));
|
|
8575
|
+
}
|
|
8576
|
+
}
|
|
8577
|
+
}
|
|
8341
8578
|
let entryState;
|
|
8342
8579
|
if (mode === "review") {
|
|
8343
8580
|
entryState = "CODE_REVIEW";
|
|
@@ -8433,7 +8670,8 @@ ${ticket.description}` : "",
|
|
|
8433
8670
|
} else {
|
|
8434
8671
|
candidatesText = "No tickets found.";
|
|
8435
8672
|
}
|
|
8436
|
-
const
|
|
8673
|
+
const guideRecOptions = buildGuideRecommendOptions(root);
|
|
8674
|
+
const recResult = recommend(projectState, 5, guideRecOptions);
|
|
8437
8675
|
let recsText = "";
|
|
8438
8676
|
if (recResult.recommendations.length > 0) {
|
|
8439
8677
|
const ticketRecs = recResult.recommendations.filter((r) => r.kind === "ticket");
|
|
@@ -8658,26 +8896,7 @@ async function handleResume(root, args) {
|
|
|
8658
8896
|
));
|
|
8659
8897
|
}
|
|
8660
8898
|
if (expectedHead && headResult.data.hash !== expectedHead) {
|
|
8661
|
-
const
|
|
8662
|
-
PICK_TICKET: { state: "PICK_TICKET", resetPlan: false, resetCode: false },
|
|
8663
|
-
COMPLETE: { state: "PICK_TICKET", resetPlan: false, resetCode: false },
|
|
8664
|
-
HANDOVER: { state: "PICK_TICKET", resetPlan: false, resetCode: false },
|
|
8665
|
-
PLAN: { state: "PLAN", resetPlan: true, resetCode: false },
|
|
8666
|
-
IMPLEMENT: { state: "PLAN", resetPlan: true, resetCode: false },
|
|
8667
|
-
WRITE_TESTS: { state: "PLAN", resetPlan: true, resetCode: false },
|
|
8668
|
-
// T-139: baseline stale after HEAD change
|
|
8669
|
-
VERIFY: { state: "IMPLEMENT", resetPlan: false, resetCode: true },
|
|
8670
|
-
// T-131: reviewed code stale after HEAD drift
|
|
8671
|
-
PLAN_REVIEW: { state: "PLAN", resetPlan: true, resetCode: true },
|
|
8672
|
-
TEST: { state: "IMPLEMENT", resetPlan: false, resetCode: true },
|
|
8673
|
-
// T-128: tests invalidated by HEAD change
|
|
8674
|
-
CODE_REVIEW: { state: "PLAN", resetPlan: true, resetCode: true },
|
|
8675
|
-
FINALIZE: { state: "IMPLEMENT", resetPlan: false, resetCode: true },
|
|
8676
|
-
LESSON_CAPTURE: { state: "PICK_TICKET", resetPlan: false, resetCode: false },
|
|
8677
|
-
ISSUE_SWEEP: { state: "PICK_TICKET", resetPlan: false, resetCode: false }
|
|
8678
|
-
// T-128: post-complete, restart sweep
|
|
8679
|
-
};
|
|
8680
|
-
const mapping = recoveryMapping[resumeState] ?? { state: "PICK_TICKET", resetPlan: false, resetCode: false };
|
|
8899
|
+
const mapping = RECOVERY_MAPPING[resumeState] ?? { state: "PICK_TICKET", resetPlan: false, resetCode: false };
|
|
8681
8900
|
const recoveryReviews = {
|
|
8682
8901
|
plan: mapping.resetPlan ? [] : info.state.reviews.plan,
|
|
8683
8902
|
code: mapping.resetCode ? [] : info.state.reviews.code
|
|
@@ -9025,12 +9244,12 @@ function guideError(err) {
|
|
|
9025
9244
|
}
|
|
9026
9245
|
function readFileSafe2(path2) {
|
|
9027
9246
|
try {
|
|
9028
|
-
return
|
|
9247
|
+
return readFileSync6(path2, "utf-8");
|
|
9029
9248
|
} catch {
|
|
9030
9249
|
return "";
|
|
9031
9250
|
}
|
|
9032
9251
|
}
|
|
9033
|
-
var SEVERITY_MAP, workspaceLocks, MAX_AUTO_ADVANCE_DEPTH;
|
|
9252
|
+
var RECOVERY_MAPPING, SEVERITY_MAP, workspaceLocks, MAX_AUTO_ADVANCE_DEPTH;
|
|
9034
9253
|
var init_guide = __esm({
|
|
9035
9254
|
"src/autonomous/guide.ts"() {
|
|
9036
9255
|
"use strict";
|
|
@@ -9052,6 +9271,22 @@ var init_guide = __esm({
|
|
|
9052
9271
|
init_queries();
|
|
9053
9272
|
init_recommend();
|
|
9054
9273
|
init_handover();
|
|
9274
|
+
RECOVERY_MAPPING = {
|
|
9275
|
+
PICK_TICKET: { state: "PICK_TICKET", resetPlan: false, resetCode: false },
|
|
9276
|
+
COMPLETE: { state: "PICK_TICKET", resetPlan: false, resetCode: false },
|
|
9277
|
+
HANDOVER: { state: "PICK_TICKET", resetPlan: false, resetCode: false },
|
|
9278
|
+
PLAN: { state: "PLAN", resetPlan: true, resetCode: false },
|
|
9279
|
+
IMPLEMENT: { state: "PLAN", resetPlan: true, resetCode: false },
|
|
9280
|
+
WRITE_TESTS: { state: "PLAN", resetPlan: true, resetCode: false },
|
|
9281
|
+
BUILD: { state: "IMPLEMENT", resetPlan: false, resetCode: true },
|
|
9282
|
+
VERIFY: { state: "IMPLEMENT", resetPlan: false, resetCode: true },
|
|
9283
|
+
PLAN_REVIEW: { state: "PLAN", resetPlan: true, resetCode: true },
|
|
9284
|
+
TEST: { state: "IMPLEMENT", resetPlan: false, resetCode: true },
|
|
9285
|
+
CODE_REVIEW: { state: "PLAN", resetPlan: true, resetCode: true },
|
|
9286
|
+
FINALIZE: { state: "IMPLEMENT", resetPlan: false, resetCode: true },
|
|
9287
|
+
LESSON_CAPTURE: { state: "PICK_TICKET", resetPlan: false, resetCode: false },
|
|
9288
|
+
ISSUE_SWEEP: { state: "PICK_TICKET", resetPlan: false, resetCode: false }
|
|
9289
|
+
};
|
|
9055
9290
|
SEVERITY_MAP = {
|
|
9056
9291
|
critical: "critical",
|
|
9057
9292
|
major: "high",
|
|
@@ -9260,8 +9495,8 @@ var init_session_report_formatter = __esm({
|
|
|
9260
9495
|
});
|
|
9261
9496
|
|
|
9262
9497
|
// src/cli/commands/session-report.ts
|
|
9263
|
-
import { readFileSync as
|
|
9264
|
-
import { join as
|
|
9498
|
+
import { readFileSync as readFileSync7, existsSync as existsSync10 } from "fs";
|
|
9499
|
+
import { join as join14 } from "path";
|
|
9265
9500
|
async function handleSessionReport(sessionId, root, format = "md") {
|
|
9266
9501
|
if (!UUID_REGEX.test(sessionId)) {
|
|
9267
9502
|
return {
|
|
@@ -9280,7 +9515,7 @@ async function handleSessionReport(sessionId, root, format = "md") {
|
|
|
9280
9515
|
isError: true
|
|
9281
9516
|
};
|
|
9282
9517
|
}
|
|
9283
|
-
const statePath2 =
|
|
9518
|
+
const statePath2 = join14(dir, "state.json");
|
|
9284
9519
|
if (!existsSync10(statePath2)) {
|
|
9285
9520
|
return {
|
|
9286
9521
|
output: `Error: Session ${sessionId} corrupt \u2014 state.json missing.`,
|
|
@@ -9290,7 +9525,7 @@ async function handleSessionReport(sessionId, root, format = "md") {
|
|
|
9290
9525
|
};
|
|
9291
9526
|
}
|
|
9292
9527
|
try {
|
|
9293
|
-
const rawJson = JSON.parse(
|
|
9528
|
+
const rawJson = JSON.parse(readFileSync7(statePath2, "utf-8"));
|
|
9294
9529
|
if (rawJson && typeof rawJson === "object" && "schemaVersion" in rawJson && rawJson.schemaVersion !== CURRENT_SESSION_SCHEMA_VERSION) {
|
|
9295
9530
|
return {
|
|
9296
9531
|
output: `Error: Session ${sessionId} \u2014 unsupported session schema version ${rawJson.schemaVersion}.`,
|
|
@@ -9319,7 +9554,7 @@ async function handleSessionReport(sessionId, root, format = "md") {
|
|
|
9319
9554
|
const events = readEvents(dir);
|
|
9320
9555
|
let planContent = null;
|
|
9321
9556
|
try {
|
|
9322
|
-
planContent =
|
|
9557
|
+
planContent = readFileSync7(join14(dir, "plan.md"), "utf-8");
|
|
9323
9558
|
} catch {
|
|
9324
9559
|
}
|
|
9325
9560
|
let gitLog = null;
|
|
@@ -9348,7 +9583,7 @@ var init_session_report = __esm({
|
|
|
9348
9583
|
});
|
|
9349
9584
|
|
|
9350
9585
|
// src/cli/commands/phase.ts
|
|
9351
|
-
import { join as
|
|
9586
|
+
import { join as join15, resolve as resolve6 } from "path";
|
|
9352
9587
|
function validatePhaseId(id) {
|
|
9353
9588
|
if (id.length > PHASE_ID_MAX_LENGTH) {
|
|
9354
9589
|
throw new CliValidationError("invalid_input", `Phase ID "${id}" exceeds ${PHASE_ID_MAX_LENGTH} characters`);
|
|
@@ -9537,21 +9772,21 @@ async function handlePhaseDelete(id, reassign, format, root) {
|
|
|
9537
9772
|
const updated = { ...ticket, phase: reassign, order: maxOrder };
|
|
9538
9773
|
const parsed = TicketSchema.parse(updated);
|
|
9539
9774
|
const content = serializeJSON(parsed);
|
|
9540
|
-
const target =
|
|
9775
|
+
const target = join15(wrapDir, "tickets", `${parsed.id}.json`);
|
|
9541
9776
|
operations.push({ op: "write", target, content });
|
|
9542
9777
|
}
|
|
9543
9778
|
for (const issue of affectedIssues) {
|
|
9544
9779
|
const updated = { ...issue, phase: reassign };
|
|
9545
9780
|
const parsed = IssueSchema.parse(updated);
|
|
9546
9781
|
const content = serializeJSON(parsed);
|
|
9547
|
-
const target =
|
|
9782
|
+
const target = join15(wrapDir, "issues", `${parsed.id}.json`);
|
|
9548
9783
|
operations.push({ op: "write", target, content });
|
|
9549
9784
|
}
|
|
9550
9785
|
const newPhases = state.roadmap.phases.filter((p) => p.id !== id);
|
|
9551
9786
|
const newRoadmap = { ...state.roadmap, phases: newPhases };
|
|
9552
9787
|
const parsedRoadmap = RoadmapSchema.parse(newRoadmap);
|
|
9553
9788
|
const roadmapContent = serializeJSON(parsedRoadmap);
|
|
9554
|
-
const roadmapTarget =
|
|
9789
|
+
const roadmapTarget = join15(wrapDir, "roadmap.json");
|
|
9555
9790
|
operations.push({ op: "write", target: roadmapTarget, content: roadmapContent });
|
|
9556
9791
|
await runTransactionUnlocked(root, operations);
|
|
9557
9792
|
} else {
|
|
@@ -9584,14 +9819,14 @@ var init_phase = __esm({
|
|
|
9584
9819
|
|
|
9585
9820
|
// src/mcp/tools.ts
|
|
9586
9821
|
import { z as z10 } from "zod";
|
|
9587
|
-
import { join as
|
|
9822
|
+
import { join as join16 } from "path";
|
|
9588
9823
|
function formatMcpError(code, message) {
|
|
9589
9824
|
return `[${code}] ${message}`;
|
|
9590
9825
|
}
|
|
9591
9826
|
async function runMcpReadTool(pinnedRoot, handler) {
|
|
9592
9827
|
try {
|
|
9593
9828
|
const { state, warnings } = await loadProject(pinnedRoot);
|
|
9594
|
-
const handoversDir =
|
|
9829
|
+
const handoversDir = join16(pinnedRoot, ".story", "handovers");
|
|
9595
9830
|
const ctx = { state, warnings, root: pinnedRoot, handoversDir, format: "md" };
|
|
9596
9831
|
const result = await handler(ctx);
|
|
9597
9832
|
if (result.errorCode && INFRASTRUCTURE_ERROR_CODES.includes(result.errorCode)) {
|
|
@@ -10178,10 +10413,10 @@ var init_tools = __esm({
|
|
|
10178
10413
|
|
|
10179
10414
|
// src/core/init.ts
|
|
10180
10415
|
import { mkdir as mkdir4, stat as stat2, readFile as readFile4, writeFile as writeFile2 } from "fs/promises";
|
|
10181
|
-
import { join as
|
|
10416
|
+
import { join as join17, resolve as resolve7 } from "path";
|
|
10182
10417
|
async function initProject(root, options) {
|
|
10183
10418
|
const absRoot = resolve7(root);
|
|
10184
|
-
const wrapDir =
|
|
10419
|
+
const wrapDir = join17(absRoot, ".story");
|
|
10185
10420
|
let exists = false;
|
|
10186
10421
|
try {
|
|
10187
10422
|
const s = await stat2(wrapDir);
|
|
@@ -10201,11 +10436,11 @@ async function initProject(root, options) {
|
|
|
10201
10436
|
".story/ already exists. Use --force to overwrite config and roadmap."
|
|
10202
10437
|
);
|
|
10203
10438
|
}
|
|
10204
|
-
await mkdir4(
|
|
10205
|
-
await mkdir4(
|
|
10206
|
-
await mkdir4(
|
|
10207
|
-
await mkdir4(
|
|
10208
|
-
await mkdir4(
|
|
10439
|
+
await mkdir4(join17(wrapDir, "tickets"), { recursive: true });
|
|
10440
|
+
await mkdir4(join17(wrapDir, "issues"), { recursive: true });
|
|
10441
|
+
await mkdir4(join17(wrapDir, "handovers"), { recursive: true });
|
|
10442
|
+
await mkdir4(join17(wrapDir, "notes"), { recursive: true });
|
|
10443
|
+
await mkdir4(join17(wrapDir, "lessons"), { recursive: true });
|
|
10209
10444
|
const created = [
|
|
10210
10445
|
".story/config.json",
|
|
10211
10446
|
".story/roadmap.json",
|
|
@@ -10245,7 +10480,7 @@ async function initProject(root, options) {
|
|
|
10245
10480
|
};
|
|
10246
10481
|
await writeConfig(config, absRoot);
|
|
10247
10482
|
await writeRoadmap(roadmap, absRoot);
|
|
10248
|
-
const gitignorePath =
|
|
10483
|
+
const gitignorePath = join17(wrapDir, ".gitignore");
|
|
10249
10484
|
await ensureGitignoreEntries(gitignorePath, STORY_GITIGNORE_ENTRIES);
|
|
10250
10485
|
const warnings = [];
|
|
10251
10486
|
if (options.force && exists) {
|
|
@@ -10293,7 +10528,7 @@ var init_init = __esm({
|
|
|
10293
10528
|
// src/mcp/index.ts
|
|
10294
10529
|
var mcp_exports = {};
|
|
10295
10530
|
import { realpathSync as realpathSync2, existsSync as existsSync11 } from "fs";
|
|
10296
|
-
import { resolve as resolve8, join as
|
|
10531
|
+
import { resolve as resolve8, join as join18, isAbsolute } from "path";
|
|
10297
10532
|
import { z as z11 } from "zod";
|
|
10298
10533
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
10299
10534
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
@@ -10308,7 +10543,7 @@ function tryDiscoverRoot() {
|
|
|
10308
10543
|
const resolved = resolve8(envRoot);
|
|
10309
10544
|
try {
|
|
10310
10545
|
const canonical = realpathSync2(resolved);
|
|
10311
|
-
if (existsSync11(
|
|
10546
|
+
if (existsSync11(join18(canonical, CONFIG_PATH2))) {
|
|
10312
10547
|
return canonical;
|
|
10313
10548
|
}
|
|
10314
10549
|
process.stderr.write(`Warning: No .story/config.json at ${canonical}
|
|
@@ -10411,7 +10646,7 @@ var init_mcp = __esm({
|
|
|
10411
10646
|
init_init();
|
|
10412
10647
|
ENV_VAR2 = "CLAUDESTORY_PROJECT_ROOT";
|
|
10413
10648
|
CONFIG_PATH2 = ".story/config.json";
|
|
10414
|
-
version = "0.1.
|
|
10649
|
+
version = "0.1.36";
|
|
10415
10650
|
main().catch((err) => {
|
|
10416
10651
|
process.stderr.write(`Fatal: ${err instanceof Error ? err.message : String(err)}
|
|
10417
10652
|
`);
|
|
@@ -10447,7 +10682,7 @@ __export(run_exports, {
|
|
|
10447
10682
|
runReadCommand: () => runReadCommand,
|
|
10448
10683
|
writeOutput: () => writeOutput
|
|
10449
10684
|
});
|
|
10450
|
-
import { join as
|
|
10685
|
+
import { join as join19 } from "path";
|
|
10451
10686
|
function writeOutput(text) {
|
|
10452
10687
|
try {
|
|
10453
10688
|
process.stdout.write(text + "\n");
|
|
@@ -10475,7 +10710,7 @@ async function runReadCommand(format, handler) {
|
|
|
10475
10710
|
return;
|
|
10476
10711
|
}
|
|
10477
10712
|
const { state, warnings } = await loadProject(root);
|
|
10478
|
-
const handoversDir =
|
|
10713
|
+
const handoversDir = join19(root, ".story", "handovers");
|
|
10479
10714
|
const result = await handler({ state, warnings, root, handoversDir, format });
|
|
10480
10715
|
writeOutput(result.output);
|
|
10481
10716
|
let exitCode = result.exitCode ?? ExitCode.OK;
|
|
@@ -10510,7 +10745,7 @@ async function runDeleteCommand(format, force, handler) {
|
|
|
10510
10745
|
return;
|
|
10511
10746
|
}
|
|
10512
10747
|
const { state, warnings } = await loadProject(root);
|
|
10513
|
-
const handoversDir =
|
|
10748
|
+
const handoversDir = join19(root, ".story", "handovers");
|
|
10514
10749
|
if (!force && hasIntegrityWarnings(warnings)) {
|
|
10515
10750
|
writeOutput(
|
|
10516
10751
|
formatError(
|
|
@@ -11024,7 +11259,7 @@ __export(setup_skill_exports, {
|
|
|
11024
11259
|
});
|
|
11025
11260
|
import { mkdir as mkdir5, writeFile as writeFile3, readFile as readFile5, rm, rename as rename2, unlink as unlink3 } from "fs/promises";
|
|
11026
11261
|
import { existsSync as existsSync12 } from "fs";
|
|
11027
|
-
import { join as
|
|
11262
|
+
import { join as join20, dirname as dirname4 } from "path";
|
|
11028
11263
|
import { homedir } from "os";
|
|
11029
11264
|
import { execFileSync } from "child_process";
|
|
11030
11265
|
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
@@ -11033,10 +11268,10 @@ function log(msg) {
|
|
|
11033
11268
|
}
|
|
11034
11269
|
function resolveSkillSourceDir() {
|
|
11035
11270
|
const thisDir = dirname4(fileURLToPath3(import.meta.url));
|
|
11036
|
-
const bundledPath =
|
|
11037
|
-
if (existsSync12(
|
|
11038
|
-
const sourcePath =
|
|
11039
|
-
if (existsSync12(
|
|
11271
|
+
const bundledPath = join20(thisDir, "..", "src", "skill");
|
|
11272
|
+
if (existsSync12(join20(bundledPath, "SKILL.md"))) return bundledPath;
|
|
11273
|
+
const sourcePath = join20(thisDir, "..", "..", "skill");
|
|
11274
|
+
if (existsSync12(join20(sourcePath, "SKILL.md"))) return sourcePath;
|
|
11040
11275
|
throw new Error(
|
|
11041
11276
|
`Cannot find bundled skill files. Checked:
|
|
11042
11277
|
${bundledPath}
|
|
@@ -11049,7 +11284,7 @@ function isHookWithCommand(entry, command) {
|
|
|
11049
11284
|
return e.type === "command" && typeof e.command === "string" && e.command.trim() === command;
|
|
11050
11285
|
}
|
|
11051
11286
|
async function registerHook(hookType, hookEntry, settingsPath, matcher) {
|
|
11052
|
-
const path2 = settingsPath ??
|
|
11287
|
+
const path2 = settingsPath ?? join20(homedir(), ".claude", "settings.json");
|
|
11053
11288
|
let raw = "{}";
|
|
11054
11289
|
if (existsSync12(path2)) {
|
|
11055
11290
|
try {
|
|
@@ -11147,7 +11382,7 @@ async function registerStopHook(settingsPath) {
|
|
|
11147
11382
|
return registerHook("Stop", { type: "command", command: STOP_HOOK_COMMAND, async: true }, settingsPath);
|
|
11148
11383
|
}
|
|
11149
11384
|
async function removeHook(hookType, command, settingsPath) {
|
|
11150
|
-
const path2 = settingsPath ??
|
|
11385
|
+
const path2 = settingsPath ?? join20(homedir(), ".claude", "settings.json");
|
|
11151
11386
|
let raw = "{}";
|
|
11152
11387
|
if (existsSync12(path2)) {
|
|
11153
11388
|
try {
|
|
@@ -11194,7 +11429,7 @@ async function removeHook(hookType, command, settingsPath) {
|
|
|
11194
11429
|
}
|
|
11195
11430
|
async function handleSetupSkill(options = {}) {
|
|
11196
11431
|
const { skipHooks = false } = options;
|
|
11197
|
-
const skillDir =
|
|
11432
|
+
const skillDir = join20(homedir(), ".claude", "skills", "story");
|
|
11198
11433
|
await mkdir5(skillDir, { recursive: true });
|
|
11199
11434
|
let srcSkillDir;
|
|
11200
11435
|
try {
|
|
@@ -11207,19 +11442,19 @@ async function handleSetupSkill(options = {}) {
|
|
|
11207
11442
|
process.exitCode = 1;
|
|
11208
11443
|
return;
|
|
11209
11444
|
}
|
|
11210
|
-
const oldPrimeDir =
|
|
11445
|
+
const oldPrimeDir = join20(homedir(), ".claude", "skills", "prime");
|
|
11211
11446
|
if (existsSync12(oldPrimeDir)) {
|
|
11212
11447
|
await rm(oldPrimeDir, { recursive: true, force: true });
|
|
11213
11448
|
log("Removed old /prime skill (migrated to /story)");
|
|
11214
11449
|
}
|
|
11215
|
-
const existed = existsSync12(
|
|
11216
|
-
const skillContent = await readFile5(
|
|
11217
|
-
await writeFile3(
|
|
11450
|
+
const existed = existsSync12(join20(skillDir, "SKILL.md"));
|
|
11451
|
+
const skillContent = await readFile5(join20(srcSkillDir, "SKILL.md"), "utf-8");
|
|
11452
|
+
await writeFile3(join20(skillDir, "SKILL.md"), skillContent, "utf-8");
|
|
11218
11453
|
let referenceWritten = false;
|
|
11219
|
-
const refSrcPath =
|
|
11454
|
+
const refSrcPath = join20(srcSkillDir, "reference.md");
|
|
11220
11455
|
if (existsSync12(refSrcPath)) {
|
|
11221
11456
|
const refContent = await readFile5(refSrcPath, "utf-8");
|
|
11222
|
-
await writeFile3(
|
|
11457
|
+
await writeFile3(join20(skillDir, "reference.md"), refContent, "utf-8");
|
|
11223
11458
|
referenceWritten = true;
|
|
11224
11459
|
}
|
|
11225
11460
|
log(`${existed ? "Updated" : "Installed"} /story skill at ${skillDir}/`);
|
|
@@ -11333,8 +11568,8 @@ var hook_status_exports = {};
|
|
|
11333
11568
|
__export(hook_status_exports, {
|
|
11334
11569
|
handleHookStatus: () => handleHookStatus
|
|
11335
11570
|
});
|
|
11336
|
-
import { readFileSync as
|
|
11337
|
-
import { join as
|
|
11571
|
+
import { readFileSync as readFileSync8, writeFileSync as writeFileSync4, renameSync as renameSync2, unlinkSync as unlinkSync4 } from "fs";
|
|
11572
|
+
import { join as join21 } from "path";
|
|
11338
11573
|
async function readStdinSilent() {
|
|
11339
11574
|
try {
|
|
11340
11575
|
const chunks = [];
|
|
@@ -11384,10 +11619,10 @@ function activePayload(session) {
|
|
|
11384
11619
|
};
|
|
11385
11620
|
}
|
|
11386
11621
|
function ensureGitignore(root) {
|
|
11387
|
-
const gitignorePath =
|
|
11622
|
+
const gitignorePath = join21(root, ".story", ".gitignore");
|
|
11388
11623
|
let existing = "";
|
|
11389
11624
|
try {
|
|
11390
|
-
existing =
|
|
11625
|
+
existing = readFileSync8(gitignorePath, "utf-8");
|
|
11391
11626
|
} catch {
|
|
11392
11627
|
}
|
|
11393
11628
|
const lines = existing.split("\n").map((l) => l.trim());
|
|
@@ -11403,7 +11638,7 @@ function ensureGitignore(root) {
|
|
|
11403
11638
|
}
|
|
11404
11639
|
function writeStatus(root, payload) {
|
|
11405
11640
|
ensureGitignore(root);
|
|
11406
|
-
const statusPath =
|
|
11641
|
+
const statusPath = join21(root, ".story", "status.json");
|
|
11407
11642
|
const content = JSON.stringify(payload, null, 2) + "\n";
|
|
11408
11643
|
atomicWriteSync(statusPath, content);
|
|
11409
11644
|
}
|
|
@@ -11462,8 +11697,8 @@ var config_update_exports = {};
|
|
|
11462
11697
|
__export(config_update_exports, {
|
|
11463
11698
|
handleConfigSetOverrides: () => handleConfigSetOverrides
|
|
11464
11699
|
});
|
|
11465
|
-
import { readFileSync as
|
|
11466
|
-
import { join as
|
|
11700
|
+
import { readFileSync as readFileSync9 } from "fs";
|
|
11701
|
+
import { join as join22 } from "path";
|
|
11467
11702
|
async function handleConfigSetOverrides(root, format, options) {
|
|
11468
11703
|
const { json: jsonArg, clear } = options;
|
|
11469
11704
|
if (!clear && !jsonArg) {
|
|
@@ -11491,8 +11726,8 @@ async function handleConfigSetOverrides(root, format, options) {
|
|
|
11491
11726
|
}
|
|
11492
11727
|
let resultOverrides = null;
|
|
11493
11728
|
await withProjectLock(root, { strict: false }, async () => {
|
|
11494
|
-
const configPath =
|
|
11495
|
-
const rawContent =
|
|
11729
|
+
const configPath = join22(root, ".story", "config.json");
|
|
11730
|
+
const rawContent = readFileSync9(configPath, "utf-8");
|
|
11496
11731
|
const raw = JSON.parse(rawContent);
|
|
11497
11732
|
if (clear) {
|
|
11498
11733
|
delete raw.recipeOverrides;
|
|
@@ -13835,7 +14070,7 @@ async function runCli() {
|
|
|
13835
14070
|
registerSessionCommand: registerSessionCommand2,
|
|
13836
14071
|
registerRepairCommand: registerRepairCommand2
|
|
13837
14072
|
} = await Promise.resolve().then(() => (init_register(), register_exports));
|
|
13838
|
-
const version2 = "0.1.
|
|
14073
|
+
const version2 = "0.1.36";
|
|
13839
14074
|
class HandledError extends Error {
|
|
13840
14075
|
constructor() {
|
|
13841
14076
|
super("HANDLED_ERROR");
|