@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/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
- quick_win: 7,
1620
- open_issue: 8
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);