opencode-athena 0.8.1-beta.4 → 0.8.1-beta.5

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
@@ -607,6 +607,152 @@ When implementation is complete:
607
607
  - Check the architecture document for patterns
608
608
  `.trim();
609
609
  }
610
+ var STORY_PATTERN = /^(story-)?(\d+)-(\d+)(?:-[a-zA-Z0-9-]+)?\.md$/i;
611
+ function parseStoryIdFromFilename(filename) {
612
+ const match = filename.match(STORY_PATTERN);
613
+ if (!match) {
614
+ return null;
615
+ }
616
+ const hasStoryPrefix = !!match[1];
617
+ const epic = match[2];
618
+ const number = match[3];
619
+ return {
620
+ id: `${epic}.${number}`,
621
+ epic,
622
+ number,
623
+ hasStoryPrefix
624
+ };
625
+ }
626
+ async function findStoryFile(storiesDir, storyId, logger) {
627
+ if (!existsSync(storiesDir)) {
628
+ return null;
629
+ }
630
+ const normalizedId = normalizeStoryId(storyId);
631
+ const [epicNum, storyNum] = normalizedId.split(".");
632
+ if (!epicNum || !storyNum) {
633
+ return null;
634
+ }
635
+ const files = await readdir(storiesDir);
636
+ const matches = [];
637
+ for (const file of files) {
638
+ const parsed = parseStoryIdFromFilename(file);
639
+ if (parsed && parsed.epic === epicNum && parsed.number === storyNum) {
640
+ matches.push({
641
+ ...parsed,
642
+ filename: file,
643
+ path: join(storiesDir, file)
644
+ });
645
+ }
646
+ }
647
+ if (matches.length === 0) {
648
+ return null;
649
+ }
650
+ matches.sort((a, b) => {
651
+ if (a.hasStoryPrefix !== b.hasStoryPrefix) {
652
+ return a.hasStoryPrefix ? -1 : 1;
653
+ }
654
+ return a.filename.localeCompare(b.filename);
655
+ });
656
+ const selected = matches[0];
657
+ const alternatives = matches.slice(1).map((m) => m.filename);
658
+ if (alternatives.length > 0 && logger) ;
659
+ return {
660
+ path: selected.path,
661
+ filename: selected.filename,
662
+ storyId: normalizedId,
663
+ hasMultipleMatches: alternatives.length > 0,
664
+ alternativeFiles: alternatives.length > 0 ? alternatives : void 0
665
+ };
666
+ }
667
+ async function findStoriesForEpic(storiesDir, epicNumber, logger) {
668
+ if (!existsSync(storiesDir)) {
669
+ return [];
670
+ }
671
+ const epicNum = epicNumber.replace(/^epic-/i, "");
672
+ const files = await readdir(storiesDir);
673
+ const allMatches = [];
674
+ for (const file of files) {
675
+ const parsed = parseStoryIdFromFilename(file);
676
+ if (parsed && parsed.epic === epicNum) {
677
+ allMatches.push({
678
+ ...parsed,
679
+ filename: file,
680
+ path: join(storiesDir, file)
681
+ });
682
+ }
683
+ }
684
+ const byId = /* @__PURE__ */ new Map();
685
+ for (const match of allMatches) {
686
+ const existing = byId.get(match.id) || [];
687
+ existing.push(match);
688
+ byId.set(match.id, existing);
689
+ }
690
+ const results = [];
691
+ for (const [id, matches] of byId) {
692
+ matches.sort((a, b) => {
693
+ if (a.hasStoryPrefix !== b.hasStoryPrefix) {
694
+ return a.hasStoryPrefix ? -1 : 1;
695
+ }
696
+ return a.filename.localeCompare(b.filename);
697
+ });
698
+ const selected = matches[0];
699
+ results.push(selected);
700
+ if (matches.length > 1 && logger) ;
701
+ }
702
+ return results.sort((a, b) => {
703
+ const numA = Number.parseInt(a.number, 10);
704
+ const numB = Number.parseInt(b.number, 10);
705
+ return numA - numB;
706
+ });
707
+ }
708
+ async function loadStoryContent(storiesDir, storyId, logger) {
709
+ const result = await findStoryFile(storiesDir, storyId, logger);
710
+ if (!result) {
711
+ return null;
712
+ }
713
+ try {
714
+ const content = await readFile(result.path, "utf-8");
715
+ return {
716
+ content,
717
+ path: result.path,
718
+ filename: result.filename
719
+ };
720
+ } catch {
721
+ return null;
722
+ }
723
+ }
724
+ function normalizeStoryId(identifier) {
725
+ if (identifier.includes("/")) {
726
+ const filename = identifier.split("/").pop() || "";
727
+ const parsed = parseStoryIdFromFilename(filename);
728
+ if (parsed) {
729
+ return parsed.id;
730
+ }
731
+ }
732
+ if (identifier.endsWith(".md")) {
733
+ const parsed = parseStoryIdFromFilename(identifier);
734
+ if (parsed) {
735
+ return parsed.id;
736
+ }
737
+ }
738
+ const cleaned = identifier.replace(/^story-/i, "");
739
+ if (cleaned.includes("-") && !cleaned.includes(".")) {
740
+ const parts = cleaned.split("-");
741
+ if (parts.length >= 2 && /^\d+$/.test(parts[0]) && /^\d+$/.test(parts[1])) {
742
+ return `${parts[0]}.${parts[1]}`;
743
+ }
744
+ }
745
+ return cleaned;
746
+ }
747
+ function getStoryFilenamePatterns(storyId) {
748
+ const [epic, number] = storyId.split(".");
749
+ return [
750
+ `story-${epic}-${number}.md`,
751
+ `story-${epic}-${number}-*.md`,
752
+ `${epic}-${number}.md`,
753
+ `${epic}-${number}-*.md`
754
+ ];
755
+ }
610
756
  var LOCK_EXT = ".lock";
611
757
  var LOCK_TIMEOUT = 1e4;
612
758
  var LOCK_RETRY_INTERVAL = 50;
@@ -873,24 +1019,8 @@ function findNextPendingStory(sprint) {
873
1019
  return null;
874
1020
  }
875
1021
  async function loadStoryFile(storiesDir, storyId) {
876
- const possibleNames = [
877
- `story-${storyId.replace(".", "-")}.md`,
878
- // story-2-3.md
879
- `story-${storyId}.md`,
880
- // story-2.3.md
881
- `${storyId}.md`
882
- // 2.3.md
883
- ];
884
- for (const fileName of possibleNames) {
885
- const filePath = join(storiesDir, fileName);
886
- if (existsSync(filePath)) {
887
- try {
888
- return await readFile(filePath, "utf-8");
889
- } catch {
890
- }
891
- }
892
- }
893
- return null;
1022
+ const result = await loadStoryContent(storiesDir, storyId);
1023
+ return result?.content ?? null;
894
1024
  }
895
1025
  function createParallelTool() {
896
1026
  return tool({
@@ -1754,12 +1884,13 @@ async function executeEpicReview(_ctx, config, paths, identifier, reviewsDir, fo
1754
1884
  const epicNumber = identifier.replace(/^epic-/, "");
1755
1885
  const stories = await findStoriesInEpic(paths.storiesDir, epicNumber);
1756
1886
  if (stories.length === 0) {
1887
+ const patterns = getStoryFilenamePatterns(`${epicNumber}.1`);
1757
1888
  return {
1758
1889
  success: false,
1759
1890
  scope: "epic",
1760
1891
  identifier,
1761
1892
  error: `No stories found for Epic ${epicNumber}`,
1762
- suggestion: `Check that story files exist in ${paths.storiesDir} with format story-${epicNumber}-*.md`
1893
+ suggestion: `Check that story files exist in ${paths.storiesDir} matching: ${patterns.join(", ")}`
1763
1894
  };
1764
1895
  }
1765
1896
  const storyContents = await Promise.all(
@@ -1783,15 +1914,16 @@ async function executeEpicReview(_ctx, config, paths, identifier, reviewsDir, fo
1783
1914
  };
1784
1915
  }
1785
1916
  async function executeStoryReview(_ctx, config, paths, identifier, reviewsDir, forceAdvancedModel) {
1786
- const storyId = normalizeStoryId(identifier);
1917
+ const storyId = normalizeStoryId2(identifier);
1787
1918
  const storyContent = await loadStoryFile2(paths.storiesDir, storyId);
1788
1919
  if (!storyContent) {
1920
+ const patterns = getStoryFilenamePatterns(storyId);
1789
1921
  return {
1790
1922
  success: false,
1791
1923
  scope: "story",
1792
1924
  identifier: storyId,
1793
1925
  error: `Story ${storyId} not found`,
1794
- suggestion: `Check that the story file exists at ${paths.storiesDir}/story-${storyId.replace(".", "-")}.md`
1926
+ suggestion: `Check that the story file exists in ${paths.storiesDir} matching: ${patterns.join(", ")}`
1795
1927
  };
1796
1928
  }
1797
1929
  const existingReviews = await findExistingReviews(reviewsDir, storyId);
@@ -1819,28 +1951,12 @@ async function executeStoryReview(_ctx, config, paths, identifier, reviewsDir, f
1819
1951
  };
1820
1952
  }
1821
1953
  async function findStoriesInEpic(storiesDir, epicNumber) {
1822
- if (!existsSync(storiesDir)) {
1823
- return [];
1824
- }
1825
- const files = await readdir(storiesDir);
1826
- const storyPattern = new RegExp(`^story-${epicNumber}-(\\d+)\\.md$`);
1827
- const stories = [];
1828
- for (const file of files) {
1829
- const match = file.match(storyPattern);
1830
- if (match) {
1831
- const storyNumber = match[1];
1832
- stories.push(`${epicNumber}.${storyNumber}`);
1833
- }
1834
- }
1835
- return stories.sort();
1954
+ const stories = await findStoriesForEpic(storiesDir, epicNumber);
1955
+ return stories.map((s) => s.id);
1836
1956
  }
1837
1957
  async function loadStoryFile2(storiesDir, storyId) {
1838
- const filename = `story-${storyId.replace(".", "-")}.md`;
1839
- const filePath = join(storiesDir, filename);
1840
- if (!existsSync(filePath)) {
1841
- return null;
1842
- }
1843
- return await readFile(filePath, "utf-8");
1958
+ const result = await loadStoryContent(storiesDir, storyId);
1959
+ return result?.content ?? null;
1844
1960
  }
1845
1961
  async function loadArchitecture(architectureFile) {
1846
1962
  if (!existsSync(architectureFile)) {
@@ -1848,15 +1964,8 @@ async function loadArchitecture(architectureFile) {
1848
1964
  }
1849
1965
  return await readFile(architectureFile, "utf-8");
1850
1966
  }
1851
- function normalizeStoryId(identifier) {
1852
- if (identifier.includes("/")) {
1853
- const filename = identifier.split("/").pop() || "";
1854
- const match = filename.match(/story-(\d+)-(\d+)\.md/);
1855
- if (match) {
1856
- return `${match[1]}.${match[2]}`;
1857
- }
1858
- }
1859
- return identifier.replace(/^story-/, "").replace("-", ".");
1967
+ function normalizeStoryId2(identifier) {
1968
+ return normalizeStoryId(identifier);
1860
1969
  }
1861
1970
  async function findExistingReviews(reviewsDir, storyId) {
1862
1971
  if (!existsSync(reviewsDir)) {
@@ -2325,28 +2434,19 @@ async function ensureDirectory(dir) {
2325
2434
  }
2326
2435
  }
2327
2436
  async function loadEpicStories(storiesDir, epicId) {
2328
- const epicNumber = epicId.replace(/^epic-/, "");
2329
- if (!existsSync(storiesDir)) return [];
2330
- const files = await readdir(storiesDir);
2331
- const storyPattern = new RegExp(`^story-${epicNumber}-(\\d+)\\.md$`);
2332
- const stories = [];
2333
- for (const file of files) {
2334
- const match = file.match(storyPattern);
2335
- if (match) {
2336
- const storyId = `${epicNumber}.${match[1]}`;
2337
- const content = await loadFile(join(storiesDir, file));
2338
- stories.push({ id: storyId, content });
2339
- }
2437
+ const stories = await findStoriesForEpic(storiesDir, epicId);
2438
+ const results = [];
2439
+ for (const story of stories) {
2440
+ const content = await loadFile(story.path);
2441
+ results.push({ id: story.id, content });
2340
2442
  }
2341
- return stories.sort((a, b) => a.id.localeCompare(b.id));
2443
+ return results;
2342
2444
  }
2343
2445
  async function loadSingleStory(storiesDir, storyId) {
2344
- const normalizedId = storyId.replace(/^story-/, "").replace("-", ".");
2345
- const filename = `story-${normalizedId.replace(".", "-")}.md`;
2346
- const filePath = join(storiesDir, filename);
2347
- if (!existsSync(filePath)) return [];
2348
- const content = await loadFile(filePath);
2349
- return [{ id: normalizedId, content }];
2446
+ const result = await loadStoryContent(storiesDir, storyId);
2447
+ if (!result) return [];
2448
+ const id = normalizeStoryId(storyId);
2449
+ return [{ id, content: result.content }];
2350
2450
  }
2351
2451
  async function loadFile(filePath) {
2352
2452
  if (!existsSync(filePath)) return null;