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 +169 -69
- package/dist/index.js.map +1 -1
- package/dist/plugin/index.js +169 -69
- package/dist/plugin/index.js.map +1 -1
- package/package.json +1 -1
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
|
|
877
|
-
|
|
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}
|
|
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 =
|
|
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
|
|
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
|
-
|
|
1823
|
-
|
|
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
|
|
1839
|
-
|
|
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
|
|
1852
|
-
|
|
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
|
|
2329
|
-
|
|
2330
|
-
const
|
|
2331
|
-
|
|
2332
|
-
|
|
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
|
|
2443
|
+
return results;
|
|
2342
2444
|
}
|
|
2343
2445
|
async function loadSingleStory(storiesDir, storyId) {
|
|
2344
|
-
const
|
|
2345
|
-
|
|
2346
|
-
const
|
|
2347
|
-
|
|
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;
|