opencode-athena 0.0.1

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 ADDED
@@ -0,0 +1,1360 @@
1
+ import { homedir, platform } from 'os';
2
+ import { tool } from '@opencode-ai/plugin';
3
+ import { join, dirname } from 'path';
4
+ import { existsSync } from 'fs';
5
+ import { readFile, mkdir, writeFile, rm } from 'fs/promises';
6
+ import { parse, stringify } from 'yaml';
7
+ import { z } from 'zod';
8
+
9
+ // src/plugin/utils/notifications.ts
10
+ async function sendNotification(message, title, $) {
11
+ const os = platform();
12
+ try {
13
+ if (os === "darwin") {
14
+ await $`osascript -e ${`display notification "${escapeAppleScript(message)}" with title "${escapeAppleScript(title)}"`}`;
15
+ } else if (os === "linux") {
16
+ await $`notify-send ${title} ${message}`;
17
+ } else {
18
+ logNotification(title, message);
19
+ }
20
+ } catch {
21
+ logNotification(title, message);
22
+ }
23
+ }
24
+ function escapeAppleScript(str) {
25
+ return str.replace(/\\/g, "\\\\").replace(/"/g, '\\"');
26
+ }
27
+ function logNotification(title, message) {
28
+ console.log(`[${title}] ${message}`);
29
+ }
30
+
31
+ // src/plugin/hooks/session-hooks.ts
32
+ function createSessionHooks(ctx, tracker, config) {
33
+ return async ({ event }) => {
34
+ const eventType = event.type;
35
+ switch (eventType) {
36
+ case "session.idle":
37
+ await handleSessionIdle(ctx, tracker, config);
38
+ break;
39
+ case "session.created":
40
+ handleSessionCreated(tracker);
41
+ break;
42
+ case "session.error":
43
+ handleSessionError(event, tracker);
44
+ break;
45
+ }
46
+ };
47
+ }
48
+ async function handleSessionIdle(ctx, tracker, config) {
49
+ const currentStory = tracker.getCurrentStory();
50
+ if (currentStory && currentStory.status === "in_progress") {
51
+ if (config.features?.notifications) {
52
+ await sendNotification(
53
+ `Story ${currentStory.id} in progress. Remember to update status when complete!`,
54
+ "OpenCode Athena",
55
+ ctx.$
56
+ );
57
+ }
58
+ }
59
+ }
60
+ function handleSessionCreated(tracker) {
61
+ const currentStory = tracker.getCurrentStory();
62
+ if (currentStory) {
63
+ console.log(`[Athena] Resuming with Story ${currentStory.id} (${currentStory.status})`);
64
+ }
65
+ }
66
+ function handleSessionError(event, tracker) {
67
+ const currentStory = tracker.getCurrentStory();
68
+ if (currentStory && event.error) {
69
+ console.error(`[Athena] Error during Story ${currentStory.id}:`, event.error);
70
+ }
71
+ }
72
+
73
+ // src/plugin/hooks/tool-hooks.ts
74
+ function createToolHooks(_tracker, _config) {
75
+ return {
76
+ /**
77
+ * Called before a tool executes
78
+ * Can modify args or throw to block execution
79
+ */
80
+ before: async (_input, _output) => {
81
+ },
82
+ /**
83
+ * Called after a tool executes
84
+ * Can modify output or append messages
85
+ */
86
+ after: async (_input, _output) => {
87
+ }
88
+ };
89
+ }
90
+
91
+ // src/plugin/hooks/compaction-hook.ts
92
+ function createCompactionHook(tracker) {
93
+ return async (_input, output) => {
94
+ const storyContext = await tracker.getCurrentStoryContext();
95
+ if (storyContext) {
96
+ output.context.push(`## OpenCode Athena - Current BMAD Story Context
97
+
98
+ ${storyContext}
99
+
100
+ IMPORTANT: You are implementing a BMAD story. Use athena_get_story to reload full context if needed. Use athena_update_status to update the story status when complete.
101
+ `);
102
+ }
103
+ };
104
+ }
105
+ var VERSION = "0.0.1";
106
+ var PACKAGE_NAME = "opencode-athena";
107
+ var DISPLAY_NAME = "OpenCode Athena";
108
+ var CONFIG_PATHS = {
109
+ /** Global OpenCode config directory */
110
+ globalConfigDir: join(homedir(), ".config", "opencode"),
111
+ /** Global Athena config file */
112
+ globalAthenaConfig: join(homedir(), ".config", "opencode", "athena.json"),
113
+ /** Global OpenCode config file */
114
+ globalOpencodeConfig: join(homedir(), ".config", "opencode", "opencode.json"),
115
+ /** Global oh-my-opencode config file */
116
+ globalOmoConfig: join(homedir(), ".config", "opencode", "oh-my-opencode.json"),
117
+ /** Commands directory */
118
+ commandsDir: join(homedir(), ".config", "opencode", "command"),
119
+ /** Plugin directory */
120
+ pluginDir: join(homedir(), ".config", "opencode", "plugin"),
121
+ /** Athena state file (for story tracking) */
122
+ stateFile: join(homedir(), ".config", "opencode", "athena-state.json")
123
+ };
124
+ var PROJECT_PATHS = {
125
+ /** Local Athena config */
126
+ localConfig: ".opencode/athena.json",
127
+ /** BMAD directory */
128
+ bmadDir: "docs",
129
+ /** BMAD docs directory (deprecated - same as bmadDir in v6) */
130
+ bmadDocsDir: "docs",
131
+ /** Sprint status file */
132
+ sprintStatus: "docs/implementation-artifacts/sprint-status.yaml",
133
+ /** Stories directory */
134
+ storiesDir: "docs/implementation-artifacts/stories",
135
+ /** Architecture document */
136
+ architecture: "docs/project-planning-artifacts/architecture.md",
137
+ /** PRD document */
138
+ prd: "docs/project-planning-artifacts/PRD.md"
139
+ };
140
+ var DEFAULTS = {
141
+ /** Default BMAD track for new projects */
142
+ defaultTrack: "bmad-method",
143
+ /** Whether to auto-update sprint status */
144
+ autoStatusUpdate: true,
145
+ /** Maximum parallel stories */
146
+ parallelStoryLimit: 3,
147
+ /** Default features enabled */
148
+ features: {
149
+ bmadBridge: true,
150
+ autoStatus: true,
151
+ parallelExecution: true,
152
+ notifications: true,
153
+ contextMonitor: true,
154
+ commentChecker: true,
155
+ lspTools: true
156
+ },
157
+ /** Default MCPs enabled */
158
+ mcps: {
159
+ context7: true,
160
+ exa: true,
161
+ grepApp: true
162
+ }
163
+ };
164
+
165
+ // src/plugin/tools/config.ts
166
+ function createConfigTool(config) {
167
+ return tool({
168
+ description: `View the current OpenCode Athena configuration.
169
+
170
+ Shows:
171
+ - Version information
172
+ - Subscription settings (Claude, OpenAI, Google)
173
+ - Model assignments for each agent role
174
+ - BMAD integration settings
175
+ - Enabled features and MCP servers`,
176
+ args: {
177
+ section: tool.schema.enum(["all", "subscriptions", "models", "bmad", "features", "mcps"]).optional().describe("Specific section to view (default: all)")
178
+ },
179
+ async execute(args) {
180
+ const section = args.section || "all";
181
+ if (section === "all") {
182
+ return JSON.stringify(
183
+ {
184
+ athenaVersion: VERSION,
185
+ configVersion: config.version,
186
+ subscriptions: config.subscriptions,
187
+ models: config.models,
188
+ bmad: config.bmad,
189
+ features: config.features,
190
+ mcps: config.mcps
191
+ },
192
+ null,
193
+ 2
194
+ );
195
+ }
196
+ const sectionData = {
197
+ subscriptions: config.subscriptions,
198
+ models: config.models,
199
+ bmad: config.bmad,
200
+ features: config.features,
201
+ mcps: config.mcps
202
+ };
203
+ return JSON.stringify(
204
+ {
205
+ section,
206
+ data: sectionData[section] || null
207
+ },
208
+ null,
209
+ 2
210
+ );
211
+ }
212
+ });
213
+ }
214
+ function createGetContextTool(tracker, _config) {
215
+ return tool({
216
+ description: `Get the current story context without reloading from files.
217
+
218
+ Returns the cached context from the story tracker including:
219
+ - Current story ID and status
220
+ - When the story was started
221
+ - Recent activity history
222
+
223
+ Use this for quick status checks. Use athena_get_story to reload full context from files.`,
224
+ args: {},
225
+ async execute() {
226
+ const currentStory = tracker.getCurrentStory();
227
+ if (!currentStory) {
228
+ return JSON.stringify({
229
+ status: "no_active_story",
230
+ message: "No story is currently being tracked. Use athena_get_story to load a story."
231
+ });
232
+ }
233
+ const context = await tracker.getCurrentStoryContext();
234
+ const history = tracker.getHistory().slice(-10);
235
+ return JSON.stringify(
236
+ {
237
+ currentStory: {
238
+ id: currentStory.id,
239
+ status: currentStory.status,
240
+ startedAt: currentStory.startedAt,
241
+ completedAt: currentStory.completedAt
242
+ },
243
+ contextSummary: context,
244
+ recentHistory: history,
245
+ sessionId: tracker.getSessionId()
246
+ },
247
+ null,
248
+ 2
249
+ );
250
+ }
251
+ });
252
+ }
253
+ var BMAD_DIR_NAMES = ["docs", ".bmad", "bmad"];
254
+ var BMAD_V6_DEFAULTS = {
255
+ planningArtifacts: "docs/project-planning-artifacts",
256
+ implementationArtifacts: "docs/implementation-artifacts"};
257
+ var LEGACY_PATHS = {
258
+ docsDir: "docs",
259
+ sprintArtifacts: "docs/sprint-artifacts"
260
+ };
261
+ async function findBmadDir(startDir) {
262
+ let currentDir = startDir;
263
+ const visited = /* @__PURE__ */ new Set();
264
+ while (currentDir && !visited.has(currentDir)) {
265
+ visited.add(currentDir);
266
+ for (const dirName of BMAD_DIR_NAMES) {
267
+ const bmadPath = join(currentDir, dirName);
268
+ if (existsSync(bmadPath)) {
269
+ return bmadPath;
270
+ }
271
+ }
272
+ const parentDir = dirname(currentDir);
273
+ if (parentDir === currentDir) {
274
+ break;
275
+ }
276
+ currentDir = parentDir;
277
+ }
278
+ return null;
279
+ }
280
+ async function readBmadConfig(bmadDir) {
281
+ const configPath = join(bmadDir, "bmm", "config.yaml");
282
+ if (!existsSync(configPath)) {
283
+ return null;
284
+ }
285
+ try {
286
+ const content = await readFile(configPath, "utf-8");
287
+ return parse(content);
288
+ } catch {
289
+ return null;
290
+ }
291
+ }
292
+ function searchForFile(projectRoot, filename, searchPaths) {
293
+ for (const searchPath of searchPaths) {
294
+ const fullPath = join(projectRoot, searchPath, filename);
295
+ if (existsSync(fullPath)) {
296
+ return fullPath;
297
+ }
298
+ }
299
+ return join(projectRoot, searchPaths[0], filename);
300
+ }
301
+ async function getBmadPaths(startDir) {
302
+ const bmadDir = await findBmadDir(startDir);
303
+ const projectRoot = bmadDir ? dirname(bmadDir) : startDir;
304
+ let config = null;
305
+ if (bmadDir) {
306
+ config = await readBmadConfig(bmadDir);
307
+ }
308
+ const planningDir = config?.planning_artifacts || join(projectRoot, BMAD_V6_DEFAULTS.planningArtifacts);
309
+ const implementationDir = config?.implementation_artifacts || config?.sprint_artifacts || join(projectRoot, BMAD_V6_DEFAULTS.implementationArtifacts);
310
+ const storiesDir = join(implementationDir, "stories");
311
+ const sprintStatusSearchPaths = [
312
+ config?.implementation_artifacts || BMAD_V6_DEFAULTS.implementationArtifacts,
313
+ config?.sprint_artifacts || "docs/sprint-artifacts",
314
+ LEGACY_PATHS.sprintArtifacts,
315
+ LEGACY_PATHS.docsDir
316
+ ];
317
+ const sprintStatus = searchForFile(projectRoot, "sprint-status.yaml", sprintStatusSearchPaths);
318
+ const architectureSearchPaths = [
319
+ config?.planning_artifacts || BMAD_V6_DEFAULTS.planningArtifacts,
320
+ LEGACY_PATHS.docsDir
321
+ ];
322
+ const architecture = searchForFile(projectRoot, "architecture.md", architectureSearchPaths);
323
+ const prdSearchPaths = [
324
+ config?.planning_artifacts || BMAD_V6_DEFAULTS.planningArtifacts,
325
+ LEGACY_PATHS.docsDir
326
+ ];
327
+ const prd = searchForFile(projectRoot, "PRD.md", prdSearchPaths);
328
+ const epicsSearchPaths = [
329
+ config?.planning_artifacts || BMAD_V6_DEFAULTS.planningArtifacts,
330
+ LEGACY_PATHS.docsDir
331
+ ];
332
+ const epics = searchForFile(projectRoot, "epics.md", epicsSearchPaths);
333
+ return {
334
+ projectRoot,
335
+ bmadDir,
336
+ planningDir,
337
+ implementationDir,
338
+ storiesDir,
339
+ sprintStatus,
340
+ architecture,
341
+ prd,
342
+ epics
343
+ };
344
+ }
345
+ async function extractRelevantArchitecture(archPath, storyContent) {
346
+ if (!existsSync(archPath)) {
347
+ return "";
348
+ }
349
+ try {
350
+ const archContent = await readFile(archPath, "utf-8");
351
+ const sections = [];
352
+ const techStackMatch = archContent.match(/## Tech(?:nology)? Stack[\s\S]*?(?=\n## |$)/i);
353
+ if (techStackMatch) {
354
+ sections.push(techStackMatch[0].trim());
355
+ }
356
+ const patternsMatch = archContent.match(
357
+ /## (?:Patterns|Conventions|Standards|Coding Standards)[\s\S]*?(?=\n## |$)/i
358
+ );
359
+ if (patternsMatch) {
360
+ sections.push(patternsMatch[0].trim());
361
+ }
362
+ if (/data|database|model|schema|entity/i.test(storyContent)) {
363
+ const dataMatch = archContent.match(
364
+ /## (?:Data Model|Database|Schema|Data Layer)[\s\S]*?(?=\n## |$)/i
365
+ );
366
+ if (dataMatch) {
367
+ sections.push(dataMatch[0].trim());
368
+ }
369
+ }
370
+ if (/api|endpoint|route|rest|graphql/i.test(storyContent)) {
371
+ const apiMatch = archContent.match(
372
+ /## (?:API|Endpoints|Routes|REST|GraphQL)[\s\S]*?(?=\n## |$)/i
373
+ );
374
+ if (apiMatch) {
375
+ sections.push(apiMatch[0].trim());
376
+ }
377
+ }
378
+ if (/ui|component|view|page|screen|frontend/i.test(storyContent)) {
379
+ const uiMatch = archContent.match(
380
+ /## (?:UI|Components|Views|Frontend|User Interface)[\s\S]*?(?=\n## |$)/i
381
+ );
382
+ if (uiMatch) {
383
+ sections.push(uiMatch[0].trim());
384
+ }
385
+ }
386
+ return sections.length > 0 ? sections.join("\n\n---\n\n") : "See full architecture document for details.";
387
+ } catch (error) {
388
+ console.warn("[Athena] Failed to read architecture file:", error);
389
+ return "";
390
+ }
391
+ }
392
+ async function extractRelevantPRD(prdPath, storyContent) {
393
+ if (!existsSync(prdPath)) {
394
+ return "";
395
+ }
396
+ try {
397
+ const prdContent = await readFile(prdPath, "utf-8");
398
+ const frMatches = storyContent.match(/FR-?\d+/gi);
399
+ if (!frMatches || frMatches.length === 0) {
400
+ return "";
401
+ }
402
+ const uniqueFRs = [...new Set(frMatches.map((fr) => fr.toUpperCase()))];
403
+ const sections = [];
404
+ for (const fr of uniqueFRs) {
405
+ const normalizedFR = fr.replace("-", "-?");
406
+ const regex = new RegExp(`###+ ${normalizedFR}[:\\s][\\s\\S]*?(?=\\n###+ |$)`, "i");
407
+ const match = prdContent.match(regex);
408
+ if (match) {
409
+ sections.push(match[0].trim());
410
+ }
411
+ }
412
+ return sections.join("\n\n");
413
+ } catch (error) {
414
+ console.warn("[Athena] Failed to read PRD file:", error);
415
+ return "";
416
+ }
417
+ }
418
+ function generateImplementationInstructions(storyId) {
419
+ return `
420
+ ## Implementation Instructions for Story ${storyId}
421
+
422
+ You are implementing this story using OpenCode Athena's full capabilities.
423
+
424
+ ### Available Subagents
425
+ - **@oracle** - Use for complex debugging, architectural decisions, or strategic code choices
426
+ - **@librarian** - Use for finding implementation examples, researching patterns, exploring documentation
427
+ - **@explore** - Use for fast codebase searches and pattern matching
428
+ - **@frontend-ui-ux-engineer** - Use for UI component implementation (if applicable)
429
+
430
+ ### Available Tools
431
+ - **LSP Tools** - Use lsp_rename, lsp_find_references, lsp_code_actions for refactoring
432
+ - **AST-Grep** - Use ast_grep_search and ast_grep_replace for structural code changes
433
+
434
+ ### Quality Standards
435
+ 1. Meet ALL acceptance criteria listed in the story
436
+ 2. Follow architecture patterns exactly as specified
437
+ 3. Keep comments minimal - code should be self-documenting
438
+ 4. Run tests before marking complete
439
+ 5. Ensure no regressions in existing functionality
440
+
441
+ ### Completion
442
+ When implementation is complete:
443
+ 1. Call **athena_update_status** with status "completed" and a completion summary
444
+ 2. The sprint-status.yaml will be automatically updated
445
+ 3. Report what was implemented and any decisions made
446
+
447
+ ### If You Get Stuck
448
+ - Call @oracle for debugging help
449
+ - Call @librarian to find similar implementations
450
+ - Check the architecture document for patterns
451
+ `.trim();
452
+ }
453
+ var LOCK_EXT = ".lock";
454
+ var LOCK_TIMEOUT = 1e4;
455
+ var LOCK_RETRY_INTERVAL = 50;
456
+ var STALE_LOCK_AGE = 3e4;
457
+ async function acquireLock(filePath) {
458
+ const lockPath = `${filePath}${LOCK_EXT}`;
459
+ const startTime = Date.now();
460
+ const lockId = `${process.pid}-${Date.now()}-${Math.random().toString(36).slice(2)}`;
461
+ while (Date.now() - startTime < LOCK_TIMEOUT) {
462
+ try {
463
+ if (existsSync(lockPath)) {
464
+ const lockContent = await readFile(lockPath, "utf-8").catch(() => null);
465
+ if (lockContent) {
466
+ try {
467
+ const lockData2 = JSON.parse(lockContent);
468
+ const lockAge = Date.now() - lockData2.timestamp;
469
+ if (lockAge > STALE_LOCK_AGE) {
470
+ await rm(lockPath, { force: true });
471
+ }
472
+ } catch {
473
+ await rm(lockPath, { force: true });
474
+ }
475
+ }
476
+ }
477
+ const lockData = JSON.stringify({
478
+ id: lockId,
479
+ pid: process.pid,
480
+ timestamp: Date.now()
481
+ });
482
+ await writeFile(lockPath, lockData, { flag: "wx" });
483
+ return async () => {
484
+ try {
485
+ const currentContent = await readFile(lockPath, "utf-8").catch(() => null);
486
+ if (currentContent) {
487
+ const currentData = JSON.parse(currentContent);
488
+ if (currentData.id === lockId) {
489
+ await rm(lockPath, { force: true });
490
+ }
491
+ }
492
+ } catch {
493
+ }
494
+ };
495
+ } catch (error) {
496
+ const nodeError = error;
497
+ if (nodeError.code !== "EEXIST") {
498
+ throw error;
499
+ }
500
+ await sleep(LOCK_RETRY_INTERVAL);
501
+ }
502
+ }
503
+ throw new Error(`Failed to acquire lock for ${filePath} within ${LOCK_TIMEOUT}ms`);
504
+ }
505
+ function sleep(ms) {
506
+ return new Promise((resolve) => setTimeout(resolve, ms));
507
+ }
508
+ async function readYamlFile(filePath) {
509
+ if (!existsSync(filePath)) {
510
+ return null;
511
+ }
512
+ try {
513
+ const content = await readFile(filePath, "utf-8");
514
+ return parse(content);
515
+ } catch (error) {
516
+ console.warn(`[Athena] Failed to parse YAML file ${filePath}:`, error);
517
+ return null;
518
+ }
519
+ }
520
+ async function writeYamlFile(filePath, data) {
521
+ const content = stringify(data, {
522
+ indent: 2,
523
+ lineWidth: 120
524
+ });
525
+ await writeFile(filePath, content, "utf-8");
526
+ }
527
+ async function readSprintStatus(filePath) {
528
+ const raw = await readYamlFile(filePath);
529
+ if (!raw) {
530
+ return null;
531
+ }
532
+ return {
533
+ completed_stories: [],
534
+ pending_stories: [],
535
+ in_progress_stories: [],
536
+ blocked_stories: [],
537
+ ...raw
538
+ };
539
+ }
540
+ async function writeSprintStatus(filePath, status) {
541
+ const dir = dirname(filePath);
542
+ if (!existsSync(dir)) {
543
+ await mkdir(dir, { recursive: true });
544
+ }
545
+ const releaseLock = await acquireLock(filePath);
546
+ try {
547
+ const currentStatus = await readSprintStatus(filePath);
548
+ let statusToWrite = status;
549
+ if (currentStatus) {
550
+ if (status.last_modified && currentStatus.last_modified && currentStatus.last_modified !== status.last_modified) {
551
+ statusToWrite = mergeSprintStatus(currentStatus, status);
552
+ }
553
+ }
554
+ statusToWrite.last_modified = (/* @__PURE__ */ new Date()).toISOString();
555
+ await writeYamlFile(filePath, statusToWrite);
556
+ } finally {
557
+ await releaseLock();
558
+ }
559
+ }
560
+ function mergeSprintStatus(current, incoming) {
561
+ const merged = { ...current };
562
+ merged.story_updates = { ...current.story_updates };
563
+ if (incoming.story_updates) {
564
+ for (const [storyId, update] of Object.entries(incoming.story_updates)) {
565
+ const currentUpdate = merged.story_updates?.[storyId];
566
+ if (!currentUpdate || new Date(update.updated_at) > new Date(currentUpdate.updated_at)) {
567
+ merged.story_updates = merged.story_updates || {};
568
+ merged.story_updates[storyId] = update;
569
+ }
570
+ }
571
+ }
572
+ if (merged.story_updates) {
573
+ const statusArrays = {
574
+ completed_stories: /* @__PURE__ */ new Set(),
575
+ pending_stories: /* @__PURE__ */ new Set(),
576
+ in_progress_stories: /* @__PURE__ */ new Set(),
577
+ blocked_stories: /* @__PURE__ */ new Set(),
578
+ stories_needing_review: /* @__PURE__ */ new Set()
579
+ };
580
+ for (const story of current.completed_stories || []) {
581
+ statusArrays.completed_stories.add(story);
582
+ }
583
+ for (const story of current.pending_stories || []) {
584
+ statusArrays.pending_stories.add(story);
585
+ }
586
+ for (const story of current.in_progress_stories || []) {
587
+ statusArrays.in_progress_stories.add(story);
588
+ }
589
+ for (const story of current.blocked_stories || []) {
590
+ statusArrays.blocked_stories.add(story);
591
+ }
592
+ for (const [storyId, update] of Object.entries(merged.story_updates)) {
593
+ statusArrays.completed_stories.delete(storyId);
594
+ statusArrays.pending_stories.delete(storyId);
595
+ statusArrays.in_progress_stories.delete(storyId);
596
+ statusArrays.blocked_stories.delete(storyId);
597
+ statusArrays.stories_needing_review.delete(storyId);
598
+ switch (update.status) {
599
+ case "completed":
600
+ statusArrays.completed_stories.add(storyId);
601
+ break;
602
+ case "pending":
603
+ statusArrays.pending_stories.add(storyId);
604
+ break;
605
+ case "in_progress":
606
+ statusArrays.in_progress_stories.add(storyId);
607
+ break;
608
+ case "blocked":
609
+ statusArrays.blocked_stories.add(storyId);
610
+ break;
611
+ case "needs_review":
612
+ statusArrays.in_progress_stories.add(storyId);
613
+ statusArrays.stories_needing_review.add(storyId);
614
+ break;
615
+ }
616
+ }
617
+ merged.completed_stories = [...statusArrays.completed_stories];
618
+ merged.pending_stories = [...statusArrays.pending_stories];
619
+ merged.in_progress_stories = [...statusArrays.in_progress_stories];
620
+ merged.blocked_stories = [...statusArrays.blocked_stories];
621
+ merged.stories_needing_review = [...statusArrays.stories_needing_review];
622
+ }
623
+ if (incoming.current_story !== void 0) {
624
+ merged.current_story = incoming.current_story;
625
+ }
626
+ return merged;
627
+ }
628
+
629
+ // src/plugin/tools/get-story.ts
630
+ function createGetStoryTool(ctx, tracker, _config) {
631
+ return tool({
632
+ description: `Load the current BMAD story context for implementation.
633
+
634
+ Returns:
635
+ - Story file content with requirements and acceptance criteria
636
+ - Relevant architecture sections
637
+ - Sprint progress information
638
+ - Implementation instructions for using Sisyphus and subagents
639
+
640
+ Use this tool before starting story implementation to get full context.`,
641
+ args: {
642
+ storyId: tool.schema.string().optional().describe(
643
+ "Specific story ID (e.g., '2.3'). If omitted, loads the next pending story from sprint-status.yaml."
644
+ )
645
+ },
646
+ async execute(args) {
647
+ const result = await getStoryContext(ctx, tracker, args.storyId);
648
+ return JSON.stringify(result, null, 2);
649
+ }
650
+ });
651
+ }
652
+ async function getStoryContext(ctx, tracker, requestedStoryId) {
653
+ const paths = await getBmadPaths(ctx.directory);
654
+ if (!paths.bmadDir) {
655
+ return {
656
+ error: "No BMAD directory found",
657
+ suggestion: "Run 'npx bmad-method@alpha install' to set up BMAD in this project."
658
+ };
659
+ }
660
+ const sprint = await readSprintStatus(paths.sprintStatus);
661
+ if (!sprint) {
662
+ return {
663
+ error: "No sprint-status.yaml found",
664
+ suggestion: "Run the sprint-planning workflow with BMAD's SM agent first."
665
+ };
666
+ }
667
+ const storyId = requestedStoryId || findNextPendingStory(sprint);
668
+ if (!storyId) {
669
+ return {
670
+ error: "No pending stories found",
671
+ sprintProgress: {
672
+ completed: sprint.completed_stories.length,
673
+ total: sprint.completed_stories.length + sprint.pending_stories.length + sprint.in_progress_stories.length
674
+ },
675
+ suggestion: "All stories in current sprint are complete!"
676
+ };
677
+ }
678
+ const storyContent = await loadStoryFile(paths.storiesDir, storyId);
679
+ if (!storyContent) {
680
+ return {
681
+ error: `Story file not found for ${storyId}`,
682
+ suggestion: "Run 'create-story' workflow with BMAD's SM agent."
683
+ };
684
+ }
685
+ const archContent = await extractRelevantArchitecture(paths.architecture, storyContent);
686
+ const prdContent = await extractRelevantPRD(paths.prd, storyContent);
687
+ await tracker.setCurrentStory(storyId, {
688
+ content: storyContent,
689
+ status: "loading",
690
+ startedAt: (/* @__PURE__ */ new Date()).toISOString()
691
+ });
692
+ return {
693
+ storyId,
694
+ story: storyContent,
695
+ architecture: archContent || "No architecture document found.",
696
+ prd: prdContent || "No PRD document found.",
697
+ sprint: {
698
+ currentEpic: sprint.current_epic || "Unknown",
699
+ completedStories: sprint.completed_stories.length,
700
+ pendingStories: sprint.pending_stories.length,
701
+ blockedStories: sprint.blocked_stories.length
702
+ },
703
+ instructions: generateImplementationInstructions(storyId)
704
+ };
705
+ }
706
+ function findNextPendingStory(sprint) {
707
+ if (sprint.current_story) {
708
+ return sprint.current_story;
709
+ }
710
+ if (sprint.in_progress_stories.length > 0) {
711
+ return sprint.in_progress_stories[0];
712
+ }
713
+ if (sprint.pending_stories.length > 0) {
714
+ return sprint.pending_stories[0];
715
+ }
716
+ return null;
717
+ }
718
+ async function loadStoryFile(storiesDir, storyId) {
719
+ const possibleNames = [
720
+ `story-${storyId.replace(".", "-")}.md`,
721
+ // story-2-3.md
722
+ `story-${storyId}.md`,
723
+ // story-2.3.md
724
+ `${storyId}.md`
725
+ // 2.3.md
726
+ ];
727
+ for (const fileName of possibleNames) {
728
+ const filePath = join(storiesDir, fileName);
729
+ if (existsSync(filePath)) {
730
+ try {
731
+ return await readFile(filePath, "utf-8");
732
+ } catch {
733
+ }
734
+ }
735
+ }
736
+ return null;
737
+ }
738
+ function createParallelTool() {
739
+ return tool({
740
+ description: `Execute multiple independent stories in parallel using background agents.
741
+
742
+ NOTE: This feature is not yet implemented. It requires integration with oh-my-opencode's background agent system.
743
+
744
+ When implemented, this tool will:
745
+ 1. Validate that the specified stories have no file conflicts
746
+ 2. Mark all stories as in_progress in sprint-status.yaml
747
+ 3. Spawn background agents to implement each story
748
+ 4. Coordinate completion and report results`,
749
+ args: {
750
+ storyIds: tool.schema.array(tool.schema.string()).describe("Array of story IDs to execute in parallel"),
751
+ waitForCompletion: tool.schema.boolean().optional().describe("Whether to wait for all stories to complete (default: true)")
752
+ },
753
+ async execute(args) {
754
+ return JSON.stringify(
755
+ {
756
+ error: "Not implemented",
757
+ message: "Parallel story execution is not yet implemented. This feature requires integration with oh-my-opencode's background agent system.",
758
+ requestedStories: args.storyIds,
759
+ suggestion: "For now, implement stories sequentially using /athena-dev for each story."
760
+ },
761
+ null,
762
+ 2
763
+ );
764
+ }
765
+ });
766
+ }
767
+ function createUpdateStatusTool(ctx, tracker, config) {
768
+ return tool({
769
+ description: `Update the BMAD sprint status for a story.
770
+
771
+ Call this tool when:
772
+ - Starting a story (status: "in_progress")
773
+ - Completing a story (status: "completed") - requires completionSummary
774
+ - Blocking on an issue (status: "blocked") - requires notes explaining blocker
775
+ - Requesting review (status: "needs_review")
776
+
777
+ The sprint-status.yaml file will be automatically updated.`,
778
+ args: {
779
+ storyId: tool.schema.string().describe("The story ID (e.g., '2.3')"),
780
+ status: tool.schema.enum(["in_progress", "completed", "blocked", "needs_review"]).describe("The new status for the story"),
781
+ notes: tool.schema.string().optional().describe("Notes about the status change (required for 'blocked' status)"),
782
+ completionSummary: tool.schema.string().optional().describe("Summary of what was implemented (required for 'completed' status)")
783
+ },
784
+ async execute(args) {
785
+ const result = await updateStoryStatus(ctx, tracker, config, args);
786
+ return JSON.stringify(result, null, 2);
787
+ }
788
+ });
789
+ }
790
+ async function updateStoryStatus(ctx, tracker, config, args) {
791
+ const { storyId, status, notes, completionSummary } = args;
792
+ if (status === "completed" && !completionSummary) {
793
+ return {
794
+ error: "completionSummary is required when marking a story completed"
795
+ };
796
+ }
797
+ if (status === "blocked" && !notes) {
798
+ return {
799
+ error: "notes are required when blocking a story (explain the blocker)"
800
+ };
801
+ }
802
+ const paths = await getBmadPaths(ctx.directory);
803
+ if (!paths.bmadDir) {
804
+ return { error: "No BMAD directory found" };
805
+ }
806
+ if (!existsSync(paths.sprintStatus)) {
807
+ return { error: "No sprint-status.yaml found" };
808
+ }
809
+ const sprint = await readSprintStatus(paths.sprintStatus);
810
+ if (!sprint) {
811
+ return { error: "Failed to read sprint-status.yaml" };
812
+ }
813
+ const now = (/* @__PURE__ */ new Date()).toISOString();
814
+ removeFromAllArrays(sprint, storyId);
815
+ switch (status) {
816
+ case "in_progress":
817
+ addToArrayIfNotPresent(sprint.in_progress_stories, storyId);
818
+ sprint.current_story = storyId;
819
+ break;
820
+ case "completed":
821
+ addToArrayIfNotPresent(sprint.completed_stories, storyId);
822
+ if (sprint.current_story === storyId) {
823
+ sprint.current_story = null;
824
+ }
825
+ break;
826
+ case "blocked":
827
+ addToArrayIfNotPresent(sprint.blocked_stories, storyId);
828
+ if (sprint.current_story === storyId) {
829
+ sprint.current_story = null;
830
+ }
831
+ break;
832
+ case "needs_review":
833
+ addToArrayIfNotPresent(sprint.in_progress_stories, storyId);
834
+ sprint.stories_needing_review = sprint.stories_needing_review || [];
835
+ addToArrayIfNotPresent(sprint.stories_needing_review, storyId);
836
+ break;
837
+ }
838
+ sprint.story_updates = sprint.story_updates || {};
839
+ sprint.story_updates[storyId] = {
840
+ status,
841
+ updated_at: now,
842
+ ...notes && { notes },
843
+ ...completionSummary && { completion_summary: completionSummary }
844
+ };
845
+ await writeSprintStatus(paths.sprintStatus, sprint);
846
+ await tracker.updateStoryStatus(storyId, status);
847
+ if (config.features?.notifications && status === "completed") {
848
+ await sendNotification(`Story ${storyId} completed!`, "OpenCode Athena", ctx.$);
849
+ }
850
+ const totalStories = sprint.completed_stories.length + sprint.pending_stories.length + sprint.in_progress_stories.length + sprint.blocked_stories.length;
851
+ const percentComplete = totalStories > 0 ? Math.round(sprint.completed_stories.length / totalStories * 100) : 0;
852
+ return {
853
+ success: true,
854
+ storyId,
855
+ newStatus: status,
856
+ updatedAt: now,
857
+ sprintProgress: {
858
+ completed: sprint.completed_stories.length,
859
+ inProgress: sprint.in_progress_stories.length,
860
+ pending: sprint.pending_stories.length,
861
+ blocked: sprint.blocked_stories.length,
862
+ total: totalStories,
863
+ percentComplete
864
+ },
865
+ nextStory: status === "completed" ? sprint.pending_stories[0] || null : null
866
+ };
867
+ }
868
+ function removeFromAllArrays(sprint, storyId) {
869
+ sprint.completed_stories = [...new Set(sprint.completed_stories.filter((s) => s !== storyId))];
870
+ sprint.pending_stories = [...new Set(sprint.pending_stories.filter((s) => s !== storyId))];
871
+ sprint.in_progress_stories = [
872
+ ...new Set(sprint.in_progress_stories.filter((s) => s !== storyId))
873
+ ];
874
+ sprint.blocked_stories = [...new Set(sprint.blocked_stories.filter((s) => s !== storyId))];
875
+ if (sprint.stories_needing_review) {
876
+ sprint.stories_needing_review = [
877
+ ...new Set(sprint.stories_needing_review.filter((s) => s !== storyId))
878
+ ];
879
+ }
880
+ }
881
+ function addToArrayIfNotPresent(array, storyId) {
882
+ if (!array.includes(storyId)) {
883
+ array.push(storyId);
884
+ }
885
+ }
886
+
887
+ // src/plugin/tools/index.ts
888
+ function createTools(ctx, tracker, config) {
889
+ return {
890
+ athena_get_story: createGetStoryTool(ctx, tracker),
891
+ athena_update_status: createUpdateStatusTool(ctx, tracker, config),
892
+ athena_get_context: createGetContextTool(tracker),
893
+ athena_parallel: createParallelTool(),
894
+ athena_config: createConfigTool(config)
895
+ };
896
+ }
897
+ var StoryTracker = class {
898
+ state;
899
+ stateFilePath;
900
+ projectDir;
901
+ constructor(projectDir) {
902
+ this.projectDir = projectDir;
903
+ this.stateFilePath = CONFIG_PATHS.stateFile;
904
+ this.state = {
905
+ currentStory: null,
906
+ sessionId: crypto.randomUUID(),
907
+ projectDir,
908
+ history: []
909
+ };
910
+ }
911
+ /**
912
+ * Initialize the tracker by loading existing state
913
+ */
914
+ async initialize() {
915
+ if (existsSync(this.stateFilePath)) {
916
+ try {
917
+ const content = await readFile(this.stateFilePath, "utf-8");
918
+ const savedState = JSON.parse(content);
919
+ if (savedState.projectDir === this.projectDir) {
920
+ this.state = {
921
+ ...savedState,
922
+ sessionId: crypto.randomUUID()
923
+ // Always generate new session ID
924
+ };
925
+ }
926
+ } catch {
927
+ }
928
+ }
929
+ }
930
+ /**
931
+ * Set the current story being worked on
932
+ */
933
+ async setCurrentStory(storyId, data) {
934
+ this.state.currentStory = { id: storyId, ...data };
935
+ this.addHistoryEntry(storyId, data.status);
936
+ await this.saveState();
937
+ }
938
+ /**
939
+ * Update the status of a story
940
+ */
941
+ async updateStoryStatus(storyId, status) {
942
+ if (this.state.currentStory?.id === storyId) {
943
+ this.state.currentStory.status = status;
944
+ if (status === "completed") {
945
+ this.state.currentStory.completedAt = (/* @__PURE__ */ new Date()).toISOString();
946
+ }
947
+ }
948
+ this.addHistoryEntry(storyId, status);
949
+ await this.saveState();
950
+ }
951
+ /**
952
+ * Get the current story being tracked
953
+ */
954
+ getCurrentStory() {
955
+ return this.state.currentStory;
956
+ }
957
+ /**
958
+ * Get a formatted context string for the current story
959
+ * Used for compaction hooks to preserve context
960
+ */
961
+ async getCurrentStoryContext() {
962
+ if (!this.state.currentStory) {
963
+ return null;
964
+ }
965
+ const story = this.state.currentStory;
966
+ const recentHistory = this.state.history.slice(-5).map((h) => `- ${h.storyId}: ${h.status} at ${h.timestamp}`).join("\n");
967
+ return `
968
+ Current Story: ${story.id}
969
+ Status: ${story.status}
970
+ Started: ${story.startedAt}
971
+ ${story.completedAt ? `Completed: ${story.completedAt}` : ""}
972
+
973
+ Recent Activity:
974
+ ${recentHistory}
975
+ `.trim();
976
+ }
977
+ /**
978
+ * Clear the current story (e.g., when completed or cancelled)
979
+ */
980
+ async clearCurrentStory() {
981
+ this.state.currentStory = null;
982
+ await this.saveState();
983
+ }
984
+ /**
985
+ * Get the current session ID
986
+ */
987
+ getSessionId() {
988
+ return this.state.sessionId;
989
+ }
990
+ /**
991
+ * Get the history of status changes
992
+ */
993
+ getHistory() {
994
+ return this.state.history;
995
+ }
996
+ /**
997
+ * Add an entry to the history
998
+ */
999
+ addHistoryEntry(storyId, status) {
1000
+ this.state.history.push({
1001
+ storyId,
1002
+ status,
1003
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
1004
+ });
1005
+ if (this.state.history.length > 100) {
1006
+ this.state.history = this.state.history.slice(-100);
1007
+ }
1008
+ }
1009
+ /**
1010
+ * Save state to disk
1011
+ */
1012
+ async saveState() {
1013
+ try {
1014
+ const dir = dirname(this.stateFilePath);
1015
+ if (!existsSync(dir)) {
1016
+ await mkdir(dir, { recursive: true });
1017
+ }
1018
+ await writeFile(this.stateFilePath, JSON.stringify(this.state, null, 2), "utf-8");
1019
+ } catch (error) {
1020
+ console.warn("[Athena] Failed to save tracker state:", error);
1021
+ }
1022
+ }
1023
+ };
1024
+ var SubscriptionSchema = z.object({
1025
+ claude: z.object({
1026
+ enabled: z.boolean(),
1027
+ tier: z.enum(["max5x", "max20x", "pro", "none"])
1028
+ }),
1029
+ openai: z.object({
1030
+ enabled: z.boolean()
1031
+ }),
1032
+ google: z.object({
1033
+ enabled: z.boolean(),
1034
+ authMethod: z.enum(["antigravity", "personal", "api", "none"])
1035
+ })
1036
+ });
1037
+ var BmadConfigSchema = z.object({
1038
+ defaultTrack: z.enum(["quick-flow", "bmad-method", "enterprise"]),
1039
+ autoStatusUpdate: z.boolean(),
1040
+ parallelStoryLimit: z.number().int().min(0).max(10)
1041
+ });
1042
+ var FeaturesSchema = z.object({
1043
+ bmadBridge: z.boolean(),
1044
+ autoStatus: z.boolean(),
1045
+ parallelExecution: z.boolean(),
1046
+ notifications: z.boolean(),
1047
+ contextMonitor: z.boolean(),
1048
+ commentChecker: z.boolean(),
1049
+ lspTools: z.boolean()
1050
+ });
1051
+ var McpsSchema = z.object({
1052
+ context7: z.boolean(),
1053
+ exa: z.boolean(),
1054
+ grepApp: z.boolean()
1055
+ });
1056
+ var ModelsSchema = z.object({
1057
+ sisyphus: z.string().describe("Model for main orchestrator agent"),
1058
+ oracle: z.string().describe("Model for debugging/reasoning agent"),
1059
+ librarian: z.string().describe("Model for research/documentation agent"),
1060
+ frontend: z.string().optional().describe("Model for UI/UX agent"),
1061
+ documentWriter: z.string().optional().describe("Model for documentation generation agent"),
1062
+ multimodalLooker: z.string().optional().describe("Model for image analysis agent")
1063
+ });
1064
+ var AthenaConfigSchema = z.object({
1065
+ $schema: z.string().optional(),
1066
+ version: z.string(),
1067
+ subscriptions: SubscriptionSchema,
1068
+ models: ModelsSchema,
1069
+ bmad: BmadConfigSchema,
1070
+ features: FeaturesSchema,
1071
+ mcps: McpsSchema
1072
+ });
1073
+ var GetStoryArgsSchema = z.object({
1074
+ storyId: z.string().optional().describe(
1075
+ "Specific story ID (e.g., '2.3'). If omitted, loads the next pending story from sprint-status.yaml."
1076
+ )
1077
+ });
1078
+ var UpdateStatusArgsSchema = z.object({
1079
+ storyId: z.string().describe("The story ID (e.g., '2.3')"),
1080
+ status: z.enum(["in_progress", "completed", "blocked", "needs_review"]).describe("The new status for the story"),
1081
+ notes: z.string().optional().describe("Notes about the status change (required for 'blocked' status)"),
1082
+ completionSummary: z.string().optional().describe("Summary of what was implemented (required for 'completed' status)")
1083
+ });
1084
+ z.object({
1085
+ includeArchitecture: z.boolean().optional().default(true),
1086
+ includePrd: z.boolean().optional().default(false),
1087
+ includeSprintStatus: z.boolean().optional().default(true)
1088
+ });
1089
+ z.object({
1090
+ storyIds: z.array(z.string()).describe("Array of story IDs to implement in parallel"),
1091
+ maxConcurrent: z.number().int().min(1).max(5).optional().default(3)
1092
+ });
1093
+ z.object({
1094
+ action: z.enum(["get", "set", "reset"]).describe("Configuration action to perform"),
1095
+ key: z.string().optional().describe("Configuration key (dot notation, e.g., 'bmad.autoStatusUpdate')"),
1096
+ value: z.unknown().optional().describe("Value to set (for 'set' action)")
1097
+ });
1098
+ var StoryStatusEnum = z.enum([
1099
+ "pending",
1100
+ "in_progress",
1101
+ "completed",
1102
+ "blocked",
1103
+ "needs_review"
1104
+ ]);
1105
+ var TrackerStatusEnum = z.enum([
1106
+ "pending",
1107
+ "in_progress",
1108
+ "completed",
1109
+ "blocked",
1110
+ "needs_review",
1111
+ "loading"
1112
+ ]);
1113
+ var SprintStatusSchema = z.object({
1114
+ sprint_number: z.number().int().optional(),
1115
+ current_epic: z.string().optional(),
1116
+ current_story: z.string().nullable().optional(),
1117
+ completed_stories: z.array(z.string()).default([]),
1118
+ pending_stories: z.array(z.string()).default([]),
1119
+ in_progress_stories: z.array(z.string()).default([]),
1120
+ blocked_stories: z.array(z.string()).default([]),
1121
+ stories_needing_review: z.array(z.string()).optional(),
1122
+ story_updates: z.record(
1123
+ z.object({
1124
+ status: StoryStatusEnum,
1125
+ updated_at: z.string(),
1126
+ notes: z.string().optional(),
1127
+ completion_summary: z.string().optional()
1128
+ })
1129
+ ).optional(),
1130
+ last_modified: z.string().optional()
1131
+ });
1132
+
1133
+ // src/plugin/utils/config-loader.ts
1134
+ async function loadAthenaConfig(projectDir) {
1135
+ const localConfigPath = join(projectDir, ".opencode", "athena.json");
1136
+ const localConfig = await loadConfigFile(localConfigPath);
1137
+ const globalConfig = await loadConfigFile(CONFIG_PATHS.globalAthenaConfig);
1138
+ const merged = mergeConfigs(getDefaultConfig(), globalConfig, localConfig);
1139
+ return validateConfig(merged);
1140
+ }
1141
+ async function loadConfigFile(filePath) {
1142
+ if (!existsSync(filePath)) {
1143
+ return null;
1144
+ }
1145
+ try {
1146
+ const content = await readFile(filePath, "utf-8");
1147
+ const parsed = JSON.parse(content);
1148
+ return validatePartialConfig(parsed, filePath);
1149
+ } catch (error) {
1150
+ if (error instanceof SyntaxError) {
1151
+ console.warn(`[Athena] Invalid JSON in config file ${filePath}:`, error.message);
1152
+ } else {
1153
+ console.warn(`[Athena] Failed to load config from ${filePath}:`, error);
1154
+ }
1155
+ return null;
1156
+ }
1157
+ }
1158
+ function validatePartialConfig(config, filePath) {
1159
+ if (typeof config !== "object" || config === null) {
1160
+ console.warn(`[Athena] Config file ${filePath} must be an object`);
1161
+ return null;
1162
+ }
1163
+ const result = {};
1164
+ const configObj = config;
1165
+ if ("version" in configObj && typeof configObj.version === "string") {
1166
+ result.version = configObj.version;
1167
+ }
1168
+ if ("$schema" in configObj && typeof configObj.$schema === "string") {
1169
+ result.$schema = configObj.$schema;
1170
+ }
1171
+ if ("subscriptions" in configObj && typeof configObj.subscriptions === "object") {
1172
+ const subs = configObj.subscriptions;
1173
+ result.subscriptions = {};
1174
+ if (subs.claude && typeof subs.claude === "object") {
1175
+ const claude = subs.claude;
1176
+ result.subscriptions.claude = {
1177
+ enabled: typeof claude.enabled === "boolean" ? claude.enabled : false,
1178
+ tier: isValidTier(claude.tier) ? claude.tier : "none"
1179
+ };
1180
+ }
1181
+ if (subs.openai && typeof subs.openai === "object") {
1182
+ const openai = subs.openai;
1183
+ result.subscriptions.openai = {
1184
+ enabled: typeof openai.enabled === "boolean" ? openai.enabled : false
1185
+ };
1186
+ }
1187
+ if (subs.google && typeof subs.google === "object") {
1188
+ const google = subs.google;
1189
+ result.subscriptions.google = {
1190
+ enabled: typeof google.enabled === "boolean" ? google.enabled : false,
1191
+ authMethod: isValidAuthMethod(google.authMethod) ? google.authMethod : "none"
1192
+ };
1193
+ }
1194
+ }
1195
+ if ("models" in configObj && typeof configObj.models === "object") {
1196
+ const models = configObj.models;
1197
+ result.models = {};
1198
+ for (const key of [
1199
+ "sisyphus",
1200
+ "oracle",
1201
+ "librarian",
1202
+ "frontend",
1203
+ "documentWriter",
1204
+ "multimodalLooker"
1205
+ ]) {
1206
+ if (key in models && typeof models[key] === "string") {
1207
+ result.models[key] = models[key];
1208
+ }
1209
+ }
1210
+ }
1211
+ if ("bmad" in configObj && typeof configObj.bmad === "object") {
1212
+ const bmad = configObj.bmad;
1213
+ result.bmad = {};
1214
+ if (isValidTrack(bmad.defaultTrack)) {
1215
+ result.bmad.defaultTrack = bmad.defaultTrack;
1216
+ }
1217
+ if (typeof bmad.autoStatusUpdate === "boolean") {
1218
+ result.bmad.autoStatusUpdate = bmad.autoStatusUpdate;
1219
+ }
1220
+ if (typeof bmad.parallelStoryLimit === "number") {
1221
+ result.bmad.parallelStoryLimit = Math.max(0, Math.min(10, bmad.parallelStoryLimit));
1222
+ }
1223
+ }
1224
+ if ("features" in configObj && typeof configObj.features === "object") {
1225
+ const features = configObj.features;
1226
+ result.features = {};
1227
+ for (const key of [
1228
+ "bmadBridge",
1229
+ "autoStatus",
1230
+ "parallelExecution",
1231
+ "notifications",
1232
+ "contextMonitor",
1233
+ "commentChecker",
1234
+ "lspTools"
1235
+ ]) {
1236
+ if (key in features && typeof features[key] === "boolean") {
1237
+ result.features[key] = features[key];
1238
+ }
1239
+ }
1240
+ }
1241
+ if ("mcps" in configObj && typeof configObj.mcps === "object") {
1242
+ const mcps = configObj.mcps;
1243
+ result.mcps = {};
1244
+ for (const key of ["context7", "exa", "grepApp"]) {
1245
+ if (key in mcps && typeof mcps[key] === "boolean") {
1246
+ result.mcps[key] = mcps[key];
1247
+ }
1248
+ }
1249
+ }
1250
+ return result;
1251
+ }
1252
+ function isValidTier(value) {
1253
+ return typeof value === "string" && ["max5x", "max20x", "pro", "none"].includes(value);
1254
+ }
1255
+ function isValidAuthMethod(value) {
1256
+ return typeof value === "string" && ["antigravity", "personal", "api", "none"].includes(value);
1257
+ }
1258
+ function isValidTrack(value) {
1259
+ return typeof value === "string" && ["quick-flow", "bmad-method", "enterprise"].includes(value);
1260
+ }
1261
+ function validateConfig(config) {
1262
+ const result = AthenaConfigSchema.safeParse(config);
1263
+ if (!result.success) {
1264
+ console.warn("[Athena] Configuration validation warnings:");
1265
+ for (const error of result.error.errors) {
1266
+ console.warn(` - ${error.path.join(".")}: ${error.message}`);
1267
+ }
1268
+ return config;
1269
+ }
1270
+ return result.data;
1271
+ }
1272
+ function getDefaultConfig() {
1273
+ return {
1274
+ version: "0.0.1",
1275
+ subscriptions: {
1276
+ claude: { enabled: false, tier: "none" },
1277
+ openai: { enabled: false },
1278
+ google: { enabled: false, authMethod: "none" }
1279
+ },
1280
+ models: {
1281
+ sisyphus: "anthropic/claude-sonnet-4",
1282
+ oracle: "anthropic/claude-sonnet-4",
1283
+ librarian: "anthropic/claude-sonnet-4"
1284
+ },
1285
+ bmad: {
1286
+ defaultTrack: DEFAULTS.defaultTrack,
1287
+ autoStatusUpdate: DEFAULTS.autoStatusUpdate,
1288
+ parallelStoryLimit: DEFAULTS.parallelStoryLimit
1289
+ },
1290
+ features: { ...DEFAULTS.features },
1291
+ mcps: { ...DEFAULTS.mcps }
1292
+ };
1293
+ }
1294
+ function mergeConfigs(...configs) {
1295
+ const result = getDefaultConfig();
1296
+ for (const config of configs) {
1297
+ if (!config) continue;
1298
+ if (config.version) result.version = config.version;
1299
+ if (config.subscriptions) {
1300
+ if (config.subscriptions.claude) {
1301
+ result.subscriptions.claude = {
1302
+ ...result.subscriptions.claude,
1303
+ ...config.subscriptions.claude
1304
+ };
1305
+ }
1306
+ if (config.subscriptions.openai) {
1307
+ result.subscriptions.openai = {
1308
+ ...result.subscriptions.openai,
1309
+ ...config.subscriptions.openai
1310
+ };
1311
+ }
1312
+ if (config.subscriptions.google) {
1313
+ result.subscriptions.google = {
1314
+ ...result.subscriptions.google,
1315
+ ...config.subscriptions.google
1316
+ };
1317
+ }
1318
+ }
1319
+ if (config.models) {
1320
+ result.models = { ...result.models, ...config.models };
1321
+ }
1322
+ if (config.bmad) {
1323
+ result.bmad = { ...result.bmad, ...config.bmad };
1324
+ }
1325
+ if (config.features) {
1326
+ result.features = { ...result.features, ...config.features };
1327
+ }
1328
+ if (config.mcps) {
1329
+ result.mcps = { ...result.mcps, ...config.mcps };
1330
+ }
1331
+ }
1332
+ return result;
1333
+ }
1334
+
1335
+ // src/plugin/index.ts
1336
+ var OpenCodeAthena = async (ctx) => {
1337
+ const { directory } = ctx;
1338
+ const config = await loadAthenaConfig(directory);
1339
+ const tracker = new StoryTracker(directory);
1340
+ await tracker.initialize();
1341
+ const tools = createTools(ctx, tracker, config);
1342
+ const sessionHooks = createSessionHooks(ctx, tracker, config);
1343
+ const toolHooks = createToolHooks();
1344
+ const compactionHook = createCompactionHook(tracker);
1345
+ return {
1346
+ // Custom tools for BMAD integration
1347
+ tool: tools,
1348
+ // Session event handlers
1349
+ event: sessionHooks,
1350
+ // Tool execution hooks (stubs for now)
1351
+ "tool.execute.before": toolHooks.before,
1352
+ "tool.execute.after": toolHooks.after,
1353
+ // Compaction hook to preserve BMAD context
1354
+ "experimental.session.compacting": compactionHook
1355
+ };
1356
+ };
1357
+
1358
+ export { AthenaConfigSchema, CONFIG_PATHS, DEFAULTS, DISPLAY_NAME, GetStoryArgsSchema, OpenCodeAthena, PACKAGE_NAME, PROJECT_PATHS, SprintStatusSchema, StoryStatusEnum, TrackerStatusEnum, UpdateStatusArgsSchema, VERSION, OpenCodeAthena as default };
1359
+ //# sourceMappingURL=index.js.map
1360
+ //# sourceMappingURL=index.js.map