@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/index.js
CHANGED
|
@@ -1395,6 +1395,7 @@ function validateProject(state) {
|
|
|
1395
1395
|
}
|
|
1396
1396
|
}
|
|
1397
1397
|
}
|
|
1398
|
+
detectSupersedesCycles(state, findings);
|
|
1398
1399
|
const phaseIDCounts = /* @__PURE__ */ new Map();
|
|
1399
1400
|
for (const p of state.roadmap.phases) {
|
|
1400
1401
|
phaseIDCounts.set(p.id, (phaseIDCounts.get(p.id) ?? 0) + 1);
|
|
@@ -1599,6 +1600,33 @@ function dfsBlocked(id, state, visited, inStack, findings) {
|
|
|
1599
1600
|
inStack.delete(id);
|
|
1600
1601
|
visited.add(id);
|
|
1601
1602
|
}
|
|
1603
|
+
function detectSupersedesCycles(state, findings) {
|
|
1604
|
+
const visited = /* @__PURE__ */ new Set();
|
|
1605
|
+
const inStack = /* @__PURE__ */ new Set();
|
|
1606
|
+
for (const l of state.lessons) {
|
|
1607
|
+
if (l.supersedes == null || visited.has(l.id)) continue;
|
|
1608
|
+
dfsSupersedesChain(l.id, state, visited, inStack, findings);
|
|
1609
|
+
}
|
|
1610
|
+
}
|
|
1611
|
+
function dfsSupersedesChain(id, state, visited, inStack, findings) {
|
|
1612
|
+
if (inStack.has(id)) {
|
|
1613
|
+
findings.push({
|
|
1614
|
+
level: "error",
|
|
1615
|
+
code: "supersedes_cycle",
|
|
1616
|
+
message: `Cycle detected in supersedes chain involving ${id}.`,
|
|
1617
|
+
entity: id
|
|
1618
|
+
});
|
|
1619
|
+
return;
|
|
1620
|
+
}
|
|
1621
|
+
if (visited.has(id)) return;
|
|
1622
|
+
inStack.add(id);
|
|
1623
|
+
const lesson = state.lessonByID(id);
|
|
1624
|
+
if (lesson?.supersedes && lesson.supersedes !== id) {
|
|
1625
|
+
dfsSupersedesChain(lesson.supersedes, state, visited, inStack, findings);
|
|
1626
|
+
}
|
|
1627
|
+
inStack.delete(id);
|
|
1628
|
+
visited.add(id);
|
|
1629
|
+
}
|
|
1602
1630
|
|
|
1603
1631
|
// src/core/recommend.ts
|
|
1604
1632
|
var SEVERITY_RANK = {
|
|
@@ -1616,10 +1644,12 @@ var CATEGORY_PRIORITY = {
|
|
|
1616
1644
|
high_impact_unblock: 4,
|
|
1617
1645
|
near_complete_umbrella: 5,
|
|
1618
1646
|
phase_momentum: 6,
|
|
1619
|
-
|
|
1620
|
-
|
|
1647
|
+
debt_trend: 7,
|
|
1648
|
+
quick_win: 8,
|
|
1649
|
+
handover_context: 9,
|
|
1650
|
+
open_issue: 10
|
|
1621
1651
|
};
|
|
1622
|
-
function recommend(state, count) {
|
|
1652
|
+
function recommend(state, count, options) {
|
|
1623
1653
|
const effectiveCount = Math.max(1, Math.min(10, count));
|
|
1624
1654
|
const dedup = /* @__PURE__ */ new Map();
|
|
1625
1655
|
const phaseIndex = buildPhaseIndex(state);
|
|
@@ -1631,7 +1661,8 @@ function recommend(state, count) {
|
|
|
1631
1661
|
() => generateNearCompleteUmbrellas(state, phaseIndex),
|
|
1632
1662
|
() => generatePhaseMomentum(state),
|
|
1633
1663
|
() => generateQuickWins(state, phaseIndex),
|
|
1634
|
-
() => generateOpenIssues(state)
|
|
1664
|
+
() => generateOpenIssues(state),
|
|
1665
|
+
() => generateDebtTrend(state, options)
|
|
1635
1666
|
];
|
|
1636
1667
|
for (const gen of generators) {
|
|
1637
1668
|
for (const rec of gen()) {
|
|
@@ -1641,6 +1672,7 @@ function recommend(state, count) {
|
|
|
1641
1672
|
}
|
|
1642
1673
|
}
|
|
1643
1674
|
}
|
|
1675
|
+
applyHandoverBoost(state, dedup, options);
|
|
1644
1676
|
const curPhase = currentPhase(state);
|
|
1645
1677
|
const curPhaseIdx = curPhase ? phaseIndex.get(curPhase.id) ?? 0 : 0;
|
|
1646
1678
|
for (const [id, rec] of dedup) {
|
|
@@ -1830,6 +1862,82 @@ function sortByPhaseAndOrder(tickets, phaseIndex) {
|
|
|
1830
1862
|
return a.order - b.order;
|
|
1831
1863
|
});
|
|
1832
1864
|
}
|
|
1865
|
+
var TICKET_ID_RE = /\bT-\d{3}[a-z]?\b/g;
|
|
1866
|
+
var ACTIONABLE_HEADING_RE = /^#+\s.*(next|open|remaining|todo|blocked)/im;
|
|
1867
|
+
var HANDOVER_BOOST = 50;
|
|
1868
|
+
var HANDOVER_BASE_SCORE = 350;
|
|
1869
|
+
function applyHandoverBoost(state, dedup, options) {
|
|
1870
|
+
if (!options?.latestHandoverContent) return;
|
|
1871
|
+
const content = options.latestHandoverContent;
|
|
1872
|
+
let actionableIds = extractTicketIdsFromActionableSections(content);
|
|
1873
|
+
if (actionableIds.size === 0) {
|
|
1874
|
+
const allIds = new Set(content.match(TICKET_ID_RE) ?? []);
|
|
1875
|
+
for (const id of allIds) {
|
|
1876
|
+
const ticket = state.ticketByID(id);
|
|
1877
|
+
if (ticket && ticket.status !== "complete" && ticket.status !== "inprogress") {
|
|
1878
|
+
actionableIds.add(id);
|
|
1879
|
+
}
|
|
1880
|
+
}
|
|
1881
|
+
}
|
|
1882
|
+
for (const id of actionableIds) {
|
|
1883
|
+
const ticket = state.ticketByID(id);
|
|
1884
|
+
if (!ticket || ticket.status === "complete") continue;
|
|
1885
|
+
const existing = dedup.get(id);
|
|
1886
|
+
if (existing) {
|
|
1887
|
+
dedup.set(id, {
|
|
1888
|
+
...existing,
|
|
1889
|
+
score: existing.score + HANDOVER_BOOST,
|
|
1890
|
+
reason: existing.reason + " (handover context)"
|
|
1891
|
+
});
|
|
1892
|
+
} else {
|
|
1893
|
+
dedup.set(id, {
|
|
1894
|
+
id,
|
|
1895
|
+
kind: "ticket",
|
|
1896
|
+
title: ticket.title,
|
|
1897
|
+
category: "handover_context",
|
|
1898
|
+
reason: "Referenced in latest handover",
|
|
1899
|
+
score: HANDOVER_BASE_SCORE
|
|
1900
|
+
});
|
|
1901
|
+
}
|
|
1902
|
+
}
|
|
1903
|
+
}
|
|
1904
|
+
function extractTicketIdsFromActionableSections(content) {
|
|
1905
|
+
const ids = /* @__PURE__ */ new Set();
|
|
1906
|
+
const lines = content.split("\n");
|
|
1907
|
+
let inActionable = false;
|
|
1908
|
+
for (const line of lines) {
|
|
1909
|
+
if (/^#+\s/.test(line)) {
|
|
1910
|
+
inActionable = ACTIONABLE_HEADING_RE.test(line);
|
|
1911
|
+
}
|
|
1912
|
+
if (inActionable) {
|
|
1913
|
+
const matches = line.match(TICKET_ID_RE);
|
|
1914
|
+
if (matches) for (const m of matches) ids.add(m);
|
|
1915
|
+
}
|
|
1916
|
+
}
|
|
1917
|
+
return ids;
|
|
1918
|
+
}
|
|
1919
|
+
var DEBT_TREND_SCORE = 450;
|
|
1920
|
+
var DEBT_GROWTH_THRESHOLD = 0.25;
|
|
1921
|
+
var DEBT_ABSOLUTE_MINIMUM = 2;
|
|
1922
|
+
function generateDebtTrend(state, options) {
|
|
1923
|
+
if (options?.previousOpenIssueCount == null) return [];
|
|
1924
|
+
const currentOpen = state.issues.filter((i) => i.status !== "resolved").length;
|
|
1925
|
+
const previous = options.previousOpenIssueCount;
|
|
1926
|
+
if (previous <= 0) return [];
|
|
1927
|
+
const growth = (currentOpen - previous) / previous;
|
|
1928
|
+
const absolute = currentOpen - previous;
|
|
1929
|
+
if (growth > DEBT_GROWTH_THRESHOLD && absolute >= DEBT_ABSOLUTE_MINIMUM) {
|
|
1930
|
+
return [{
|
|
1931
|
+
id: "DEBT_TREND",
|
|
1932
|
+
kind: "action",
|
|
1933
|
+
title: "Issue debt growing",
|
|
1934
|
+
category: "debt_trend",
|
|
1935
|
+
reason: `Open issues grew from ${previous} to ${currentOpen} (+${Math.round(growth * 100)}%). Consider triaging or resolving issues before adding features.`,
|
|
1936
|
+
score: DEBT_TREND_SCORE
|
|
1937
|
+
}];
|
|
1938
|
+
}
|
|
1939
|
+
return [];
|
|
1940
|
+
}
|
|
1833
1941
|
|
|
1834
1942
|
// src/core/id-allocation.ts
|
|
1835
1943
|
var TICKET_NUMERIC_REGEX = /^T-(\d+)[a-z]?$/;
|
|
@@ -2341,7 +2449,7 @@ function fencedBlock(content, lang) {
|
|
|
2341
2449
|
${content}
|
|
2342
2450
|
${fence}`;
|
|
2343
2451
|
}
|
|
2344
|
-
function formatStatus(state, format) {
|
|
2452
|
+
function formatStatus(state, format, activeSessions = []) {
|
|
2345
2453
|
const phases = phasesWithStatus(state);
|
|
2346
2454
|
const data = {
|
|
2347
2455
|
project: state.config.project,
|
|
@@ -2361,7 +2469,8 @@ function formatStatus(state, format) {
|
|
|
2361
2469
|
name: p.phase.name,
|
|
2362
2470
|
status: p.status,
|
|
2363
2471
|
leafCount: p.leafCount
|
|
2364
|
-
}))
|
|
2472
|
+
})),
|
|
2473
|
+
...activeSessions.length > 0 ? { activeSessions } : {}
|
|
2365
2474
|
};
|
|
2366
2475
|
if (format === "json") {
|
|
2367
2476
|
return JSON.stringify(successEnvelope(data), null, 2);
|
|
@@ -2383,6 +2492,15 @@ function formatStatus(state, format) {
|
|
|
2383
2492
|
const summary = p.phase.summary ?? truncate(p.phase.description, 80);
|
|
2384
2493
|
lines.push(`${indicator} **${escapeMarkdownInline(p.phase.name)}** (${p.leafCount} tickets) \u2014 ${escapeMarkdownInline(summary)}`);
|
|
2385
2494
|
}
|
|
2495
|
+
if (activeSessions.length > 0) {
|
|
2496
|
+
lines.push("");
|
|
2497
|
+
lines.push("## Active Sessions");
|
|
2498
|
+
lines.push("");
|
|
2499
|
+
for (const s of activeSessions) {
|
|
2500
|
+
const ticket = s.ticketId ? `${s.ticketId}: ${escapeMarkdownInline(s.ticketTitle ?? "")}` : "no ticket";
|
|
2501
|
+
lines.push(`- ${s.sessionId.slice(0, 8)}: ${s.state} -- ${ticket} (${s.mode} mode)`);
|
|
2502
|
+
}
|
|
2503
|
+
}
|
|
2386
2504
|
if (state.isEmptyScaffold) {
|
|
2387
2505
|
lines.push("");
|
|
2388
2506
|
lines.push(EMPTY_SCAFFOLD_HEADING);
|