@agile-vibe-coding/avc 0.1.1 → 0.3.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.
Files changed (239) hide show
  1. package/cli/agent-loader.js +21 -0
  2. package/cli/agents/agent-selector.md +152 -0
  3. package/cli/agents/architecture-recommender.md +418 -0
  4. package/cli/agents/code-implementer.md +117 -0
  5. package/cli/agents/code-validator.md +80 -0
  6. package/cli/agents/context-reviewer-epic.md +101 -0
  7. package/cli/agents/context-reviewer-story.md +92 -0
  8. package/cli/agents/context-writer-epic.md +145 -0
  9. package/cli/agents/context-writer-story.md +111 -0
  10. package/cli/agents/database-deep-dive.md +470 -0
  11. package/cli/agents/database-recommender.md +634 -0
  12. package/cli/agents/doc-distributor.md +176 -0
  13. package/cli/agents/doc-writer-epic.md +42 -0
  14. package/cli/agents/doc-writer-story.md +43 -0
  15. package/cli/agents/documentation-updater.md +203 -0
  16. package/cli/agents/duplicate-detector.md +110 -0
  17. package/cli/agents/epic-story-decomposer.md +559 -0
  18. package/cli/agents/feature-context-generator.md +91 -0
  19. package/cli/agents/gap-checker-epic.md +52 -0
  20. package/cli/agents/impact-checker-story.md +51 -0
  21. package/cli/agents/migration-guide-generator.md +305 -0
  22. package/cli/agents/mission-scope-generator.md +143 -0
  23. package/cli/agents/mission-scope-validator.md +146 -0
  24. package/cli/agents/project-context-extractor.md +122 -0
  25. package/cli/agents/project-documentation-creator.json +226 -0
  26. package/cli/agents/project-documentation-creator.md +595 -0
  27. package/cli/agents/question-prefiller.md +269 -0
  28. package/cli/agents/refiner-epic.md +39 -0
  29. package/cli/agents/refiner-story.md +42 -0
  30. package/cli/agents/scaffolding-generator.md +99 -0
  31. package/cli/agents/seed-validator.md +71 -0
  32. package/cli/agents/story-doc-enricher.md +133 -0
  33. package/cli/agents/story-scope-reviewer.md +147 -0
  34. package/cli/agents/story-splitter.md +83 -0
  35. package/cli/agents/suggestion-business-analyst.md +88 -0
  36. package/cli/agents/suggestion-deployment-architect.md +263 -0
  37. package/cli/agents/suggestion-product-manager.md +129 -0
  38. package/cli/agents/suggestion-security-specialist.md +156 -0
  39. package/cli/agents/suggestion-technical-architect.md +269 -0
  40. package/cli/agents/suggestion-ux-researcher.md +93 -0
  41. package/cli/agents/task-subtask-decomposer.md +188 -0
  42. package/cli/agents/validator-documentation.json +183 -0
  43. package/cli/agents/validator-documentation.md +455 -0
  44. package/cli/agents/validator-selector.md +211 -0
  45. package/cli/ansi-colors.js +21 -0
  46. package/cli/api-reference-tool.js +368 -0
  47. package/cli/build-docs.js +29 -8
  48. package/cli/ceremony-history.js +369 -0
  49. package/cli/checks/catalog.json +76 -0
  50. package/cli/checks/code/quality.json +26 -0
  51. package/cli/checks/code/testing.json +14 -0
  52. package/cli/checks/code/traceability.json +26 -0
  53. package/cli/checks/cross-refs/epic.json +171 -0
  54. package/cli/checks/cross-refs/story.json +149 -0
  55. package/cli/checks/epic/api.json +114 -0
  56. package/cli/checks/epic/backend.json +126 -0
  57. package/cli/checks/epic/cloud.json +126 -0
  58. package/cli/checks/epic/data.json +102 -0
  59. package/cli/checks/epic/database.json +114 -0
  60. package/cli/checks/epic/developer.json +182 -0
  61. package/cli/checks/epic/devops.json +174 -0
  62. package/cli/checks/epic/frontend.json +162 -0
  63. package/cli/checks/epic/mobile.json +102 -0
  64. package/cli/checks/epic/qa.json +90 -0
  65. package/cli/checks/epic/security.json +184 -0
  66. package/cli/checks/epic/solution-architect.json +192 -0
  67. package/cli/checks/epic/test-architect.json +90 -0
  68. package/cli/checks/epic/ui.json +102 -0
  69. package/cli/checks/epic/ux.json +90 -0
  70. package/cli/checks/fixes/epic-fix-template.md +10 -0
  71. package/cli/checks/fixes/story-fix-template.md +10 -0
  72. package/cli/checks/story/api.json +186 -0
  73. package/cli/checks/story/backend.json +102 -0
  74. package/cli/checks/story/cloud.json +102 -0
  75. package/cli/checks/story/data.json +210 -0
  76. package/cli/checks/story/database.json +102 -0
  77. package/cli/checks/story/developer.json +168 -0
  78. package/cli/checks/story/devops.json +102 -0
  79. package/cli/checks/story/frontend.json +174 -0
  80. package/cli/checks/story/mobile.json +102 -0
  81. package/cli/checks/story/qa.json +210 -0
  82. package/cli/checks/story/security.json +198 -0
  83. package/cli/checks/story/solution-architect.json +230 -0
  84. package/cli/checks/story/test-architect.json +210 -0
  85. package/cli/checks/story/ui.json +102 -0
  86. package/cli/checks/story/ux.json +102 -0
  87. package/cli/coding-order.js +401 -0
  88. package/cli/command-logger.js +49 -12
  89. package/cli/components/static-output.js +63 -0
  90. package/cli/console-output-manager.js +94 -0
  91. package/cli/dependency-checker.js +72 -0
  92. package/cli/docs-sync.js +306 -0
  93. package/cli/epic-story-validator.js +659 -0
  94. package/cli/evaluation-prompts.js +1008 -0
  95. package/cli/execution-context.js +195 -0
  96. package/cli/generate-summary-table.js +340 -0
  97. package/cli/init-model-config.js +704 -0
  98. package/cli/init.js +1737 -278
  99. package/cli/kanban-server-manager.js +227 -0
  100. package/cli/llm-claude.js +150 -1
  101. package/cli/llm-gemini.js +109 -0
  102. package/cli/llm-local.js +493 -0
  103. package/cli/llm-mock.js +233 -0
  104. package/cli/llm-openai.js +454 -0
  105. package/cli/llm-provider.js +379 -3
  106. package/cli/llm-token-limits.js +211 -0
  107. package/cli/llm-verifier.js +662 -0
  108. package/cli/llm-xiaomi.js +143 -0
  109. package/cli/message-constants.js +49 -0
  110. package/cli/message-manager.js +334 -0
  111. package/cli/message-types.js +96 -0
  112. package/cli/messaging-api.js +291 -0
  113. package/cli/micro-check-fixer.js +335 -0
  114. package/cli/micro-check-runner.js +449 -0
  115. package/cli/micro-check-scorer.js +148 -0
  116. package/cli/micro-check-validator.js +538 -0
  117. package/cli/model-pricing.js +192 -0
  118. package/cli/model-query-engine.js +468 -0
  119. package/cli/model-recommendation-analyzer.js +495 -0
  120. package/cli/model-selector.js +270 -0
  121. package/cli/output-buffer.js +107 -0
  122. package/cli/process-manager.js +73 -2
  123. package/cli/prompt-logger.js +57 -0
  124. package/cli/repl-ink.js +4625 -1094
  125. package/cli/repl-old.js +3 -4
  126. package/cli/seed-processor.js +962 -0
  127. package/cli/sprint-planning-processor.js +4162 -0
  128. package/cli/template-processor.js +2149 -105
  129. package/cli/templates/project.md +25 -8
  130. package/cli/templates/vitepress-config.mts.template +5 -4
  131. package/cli/token-tracker.js +547 -0
  132. package/cli/tools/generate-story-validators.js +317 -0
  133. package/cli/tools/generate-validators.js +669 -0
  134. package/cli/update-checker.js +19 -17
  135. package/cli/update-notifier.js +4 -4
  136. package/cli/validation-router.js +667 -0
  137. package/cli/verification-tracker.js +563 -0
  138. package/cli/worktree-runner.js +654 -0
  139. package/kanban/README.md +386 -0
  140. package/kanban/client/README.md +205 -0
  141. package/kanban/client/components.json +20 -0
  142. package/kanban/client/dist/assets/index-D_KC5EQT.css +1 -0
  143. package/kanban/client/dist/assets/index-DjY5zqW7.js +351 -0
  144. package/kanban/client/dist/index.html +16 -0
  145. package/kanban/client/dist/vite.svg +1 -0
  146. package/kanban/client/index.html +15 -0
  147. package/kanban/client/package-lock.json +9442 -0
  148. package/kanban/client/package.json +44 -0
  149. package/kanban/client/postcss.config.js +6 -0
  150. package/kanban/client/public/vite.svg +1 -0
  151. package/kanban/client/src/App.jsx +651 -0
  152. package/kanban/client/src/components/ProjectFileEditorPopup.jsx +117 -0
  153. package/kanban/client/src/components/ceremony/AskArchPopup.jsx +420 -0
  154. package/kanban/client/src/components/ceremony/AskModelPopup.jsx +629 -0
  155. package/kanban/client/src/components/ceremony/CeremonyWorkflowModal.jsx +1133 -0
  156. package/kanban/client/src/components/ceremony/EpicStorySelectionModal.jsx +254 -0
  157. package/kanban/client/src/components/ceremony/ProviderSwitcherButton.jsx +290 -0
  158. package/kanban/client/src/components/ceremony/SponsorCallModal.jsx +686 -0
  159. package/kanban/client/src/components/ceremony/SprintPlanningModal.jsx +838 -0
  160. package/kanban/client/src/components/ceremony/steps/ArchitectureStep.jsx +150 -0
  161. package/kanban/client/src/components/ceremony/steps/CompleteStep.jsx +136 -0
  162. package/kanban/client/src/components/ceremony/steps/DatabaseStep.jsx +202 -0
  163. package/kanban/client/src/components/ceremony/steps/DeploymentStep.jsx +123 -0
  164. package/kanban/client/src/components/ceremony/steps/MissionStep.jsx +106 -0
  165. package/kanban/client/src/components/ceremony/steps/ReviewAnswersStep.jsx +329 -0
  166. package/kanban/client/src/components/ceremony/steps/RunningStep.jsx +249 -0
  167. package/kanban/client/src/components/kanban/CardDetailModal.jsx +646 -0
  168. package/kanban/client/src/components/kanban/EpicSection.jsx +146 -0
  169. package/kanban/client/src/components/kanban/FilterToolbar.jsx +222 -0
  170. package/kanban/client/src/components/kanban/GroupingSelector.jsx +63 -0
  171. package/kanban/client/src/components/kanban/KanbanBoard.jsx +211 -0
  172. package/kanban/client/src/components/kanban/KanbanCard.jsx +147 -0
  173. package/kanban/client/src/components/kanban/KanbanColumn.jsx +90 -0
  174. package/kanban/client/src/components/kanban/RefineWorkItemPopup.jsx +784 -0
  175. package/kanban/client/src/components/kanban/RunButton.jsx +162 -0
  176. package/kanban/client/src/components/kanban/SeedButton.jsx +176 -0
  177. package/kanban/client/src/components/layout/LoadingScreen.jsx +82 -0
  178. package/kanban/client/src/components/process/ProcessMonitorBar.jsx +80 -0
  179. package/kanban/client/src/components/settings/AgentEditorPopup.jsx +171 -0
  180. package/kanban/client/src/components/settings/AgentsTab.jsx +381 -0
  181. package/kanban/client/src/components/settings/ApiKeysTab.jsx +142 -0
  182. package/kanban/client/src/components/settings/CeremonyModelsTab.jsx +105 -0
  183. package/kanban/client/src/components/settings/CheckEditorPopup.jsx +507 -0
  184. package/kanban/client/src/components/settings/CostThresholdsTab.jsx +95 -0
  185. package/kanban/client/src/components/settings/ModelPricingTab.jsx +269 -0
  186. package/kanban/client/src/components/settings/OpenAIAuthSection.jsx +412 -0
  187. package/kanban/client/src/components/settings/ServersTab.jsx +121 -0
  188. package/kanban/client/src/components/settings/SettingsModal.jsx +84 -0
  189. package/kanban/client/src/components/stats/CostModal.jsx +384 -0
  190. package/kanban/client/src/components/ui/badge.jsx +27 -0
  191. package/kanban/client/src/components/ui/dialog.jsx +121 -0
  192. package/kanban/client/src/components/ui/tabs.jsx +85 -0
  193. package/kanban/client/src/hooks/__tests__/useGrouping.test.js +232 -0
  194. package/kanban/client/src/hooks/useGrouping.js +177 -0
  195. package/kanban/client/src/hooks/useWebSocket.js +120 -0
  196. package/kanban/client/src/lib/__tests__/api.test.js +196 -0
  197. package/kanban/client/src/lib/__tests__/status-grouping.test.js +94 -0
  198. package/kanban/client/src/lib/api.js +515 -0
  199. package/kanban/client/src/lib/status-grouping.js +154 -0
  200. package/kanban/client/src/lib/utils.js +11 -0
  201. package/kanban/client/src/main.jsx +10 -0
  202. package/kanban/client/src/store/__tests__/kanbanStore.test.js +164 -0
  203. package/kanban/client/src/store/ceremonyStore.js +172 -0
  204. package/kanban/client/src/store/filterStore.js +201 -0
  205. package/kanban/client/src/store/kanbanStore.js +123 -0
  206. package/kanban/client/src/store/processStore.js +65 -0
  207. package/kanban/client/src/store/sprintPlanningStore.js +33 -0
  208. package/kanban/client/src/styles/globals.css +59 -0
  209. package/kanban/client/tailwind.config.js +77 -0
  210. package/kanban/client/vite.config.js +28 -0
  211. package/kanban/client/vitest.config.js +28 -0
  212. package/kanban/dev-start.sh +47 -0
  213. package/kanban/package.json +12 -0
  214. package/kanban/server/index.js +537 -0
  215. package/kanban/server/routes/ceremony.js +454 -0
  216. package/kanban/server/routes/costs.js +163 -0
  217. package/kanban/server/routes/openai-oauth.js +366 -0
  218. package/kanban/server/routes/processes.js +50 -0
  219. package/kanban/server/routes/settings.js +736 -0
  220. package/kanban/server/routes/websocket.js +281 -0
  221. package/kanban/server/routes/work-items.js +487 -0
  222. package/kanban/server/services/CeremonyService.js +1441 -0
  223. package/kanban/server/services/FileSystemScanner.js +95 -0
  224. package/kanban/server/services/FileWatcher.js +144 -0
  225. package/kanban/server/services/HierarchyBuilder.js +196 -0
  226. package/kanban/server/services/ProcessRegistry.js +122 -0
  227. package/kanban/server/services/TaskRunnerService.js +261 -0
  228. package/kanban/server/services/WorkItemReader.js +123 -0
  229. package/kanban/server/services/WorkItemRefineService.js +510 -0
  230. package/kanban/server/start.js +49 -0
  231. package/kanban/server/utils/kanban-logger.js +132 -0
  232. package/kanban/server/utils/markdown.js +91 -0
  233. package/kanban/server/utils/status-grouping.js +107 -0
  234. package/kanban/server/workers/run-task-worker.js +121 -0
  235. package/kanban/server/workers/seed-worker.js +94 -0
  236. package/kanban/server/workers/sponsor-call-worker.js +92 -0
  237. package/kanban/server/workers/sprint-planning-worker.js +212 -0
  238. package/package.json +19 -7
  239. package/cli/agents/documentation.md +0 -302
@@ -0,0 +1,270 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * Model Selector - Multi-Provider Model Evaluation Tool
5
+ * Queries Claude, OpenAI, and Gemini with evaluation prompts
6
+ * to collect model recommendations for each AVC stage
7
+ */
8
+
9
+ import fs from 'fs';
10
+ import path from 'path';
11
+ import { fileURLToPath } from 'url';
12
+ import { EVALUATION_PROMPTS, getPromptStats } from './evaluation-prompts.js';
13
+ import { ModelQueryEngine } from './model-query-engine.js';
14
+ import { ModelRecommendationAnalyzer } from './model-recommendation-analyzer.js';
15
+ import { sendError, sendWarning, sendSuccess, sendInfo, sendOutput, sendIndented, sendSectionHeader } from './messaging-api.js';
16
+
17
+ const __filename = fileURLToPath(import.meta.url);
18
+ const __dirname = path.dirname(__filename);
19
+
20
+ /**
21
+ * Ensure _temp directory exists
22
+ */
23
+ function ensureTempDir() {
24
+ const tempDir = path.join(process.cwd(), '_temp');
25
+ if (!fs.existsSync(tempDir)) {
26
+ fs.mkdirSync(tempDir, { recursive: true });
27
+ }
28
+ return tempDir;
29
+ }
30
+
31
+ /**
32
+ * Display banner
33
+ */
34
+ function displayBanner() {
35
+ console.log('\n╔══════════════════════════════════════════════════════════════╗');
36
+ console.log('║ Multi-Provider Model Selection Evaluator ║');
37
+ console.log('║ Queries Claude, OpenAI, and Gemini for recommendations ║');
38
+ console.log('╚══════════════════════════════════════════════════════════════╝\n');
39
+ }
40
+
41
+ /**
42
+ * Display environment check
43
+ */
44
+ function displayEnvironmentCheck() {
45
+ sendSectionHeader('Checking API keys');
46
+
47
+ const keys = {
48
+ 'ANTHROPIC_API_KEY': !!process.env.ANTHROPIC_API_KEY,
49
+ 'OPENAI_API_KEY': !!(process.env.OPENAI_API_KEY || process.env.OPENAI_OAUTH_TOKEN),
50
+ 'GEMINI_API_KEY': !!process.env.GEMINI_API_KEY,
51
+ 'XIAOMI_API_KEY': !!process.env.XIAOMI_API_KEY,
52
+ };
53
+
54
+ for (const [key, available] of Object.entries(keys)) {
55
+ if (available) {
56
+ sendIndented(`${key}: Found`, 1);
57
+ } else {
58
+ sendIndented(`${key}: Not found`, 1);
59
+ }
60
+ }
61
+
62
+ const availableCount = Object.values(keys).filter(v => v).length;
63
+
64
+ sendOutput(`\n ${availableCount}/3 providers available`);
65
+
66
+ if (availableCount === 0) {
67
+ sendError('No API keys found. Please add at least one to your .env file.');
68
+ process.exit(1);
69
+ }
70
+
71
+ return availableCount;
72
+ }
73
+
74
+ /**
75
+ * Display prompt statistics
76
+ */
77
+ function displayPromptStats() {
78
+ const stats = getPromptStats();
79
+
80
+ sendSectionHeader('Evaluation Overview');
81
+ sendIndented(`Total prompts: ${stats.totalPrompts}`, 1);
82
+ sendIndented(`Ceremonies: ${stats.ceremonies} (${stats.ceremonyList.join(', ')})`, 1);
83
+ sendIndented(`Estimated total API calls: ${stats.estimatedTotalCalls} per provider`, 1);
84
+ sendIndented('Impact distribution:', 1);
85
+ sendIndented(`- CRITICAL: ${stats.impactDistribution.CRITICAL}`, 2);
86
+ sendIndented(`- VERY HIGH: ${stats.impactDistribution['VERY HIGH']}`, 2);
87
+ sendIndented(`- HIGH: ${stats.impactDistribution.HIGH}`, 2);
88
+ sendIndented(`- MEDIUM: ${stats.impactDistribution.MEDIUM}`, 2);
89
+ }
90
+
91
+ /**
92
+ * Progress callback for query engine
93
+ */
94
+ function createProgressCallback() {
95
+ let currentPrompt = 0;
96
+
97
+ return (progress) => {
98
+ if (progress.type === 'prompt-start') {
99
+ currentPrompt = progress.current;
100
+ sendOutput(`\n[${progress.current}/${progress.total}] Processing: ${progress.ceremony}/${progress.stage}`);
101
+ } else if (progress.type === 'prompt-complete') {
102
+ const { successCount, failureCount, totalTime } = progress;
103
+ sendIndented(`Complete: ${successCount} success, ${failureCount} failed (${totalTime.toFixed(1)}s)`, 1);
104
+ }
105
+ };
106
+ }
107
+
108
+ /**
109
+ * Write JSON output file
110
+ */
111
+ function writeJSONOutput(data, outputPath) {
112
+ sendInfo(`Writing JSON output to ${outputPath}...`);
113
+ fs.writeFileSync(outputPath, JSON.stringify(data, null, 2), 'utf-8');
114
+ sendIndented('JSON file written', 1);
115
+ }
116
+
117
+ /**
118
+ * Write Markdown output file
119
+ */
120
+ function writeMarkdownOutput(content, outputPath) {
121
+ sendInfo(`Writing Markdown report to ${outputPath}...`);
122
+ fs.writeFileSync(outputPath, content, 'utf-8');
123
+ sendIndented('Markdown report written', 1);
124
+ }
125
+
126
+ /**
127
+ * Display summary
128
+ */
129
+ function displaySummary(summary) {
130
+ console.log('\n╔══════════════════════════════════════════════════════════════╗');
131
+ console.log('║ Execution Summary ║');
132
+ console.log('╚══════════════════════════════════════════════════════════════╝\n');
133
+
134
+ console.log(`Total prompts evaluated: ${summary.totalPrompts}`);
135
+ console.log(`Successful queries: ${summary.successfulQueries}`);
136
+ console.log(`Failed queries: ${summary.failedQueries}`);
137
+ console.log(`\nExecution time: ${summary.executionTime}`);
138
+
139
+ console.log('\nCost breakdown:');
140
+ for (const [provider, cost] of Object.entries(summary.totalCost)) {
141
+ if (provider !== 'total') {
142
+ console.log(` ${provider}: $${cost.toFixed(4)}`);
143
+ }
144
+ }
145
+ console.log(` TOTAL: $${summary.totalCost.total.toFixed(4)}`);
146
+ }
147
+
148
+ /**
149
+ * Display analysis summary
150
+ */
151
+ function displayAnalysisSummary(statistics) {
152
+ console.log('\n╔══════════════════════════════════════════════════════════════╗');
153
+ console.log('║ Analysis Summary ║');
154
+ console.log('╚══════════════════════════════════════════════════════════════╝\n');
155
+
156
+ console.log('Consensus Statistics:');
157
+ console.log(` Full consensus: ${statistics.consensus.full} (${statistics.consensusRate.full})`);
158
+ console.log(` Partial consensus: ${statistics.consensus.partial} (${statistics.consensusRate.partial})`);
159
+ console.log(` No consensus: ${statistics.consensus.none} (${statistics.consensusRate.none})`);
160
+
161
+ console.log('\nDefault Alignment:');
162
+ console.log(` Perfect: ${statistics.defaultAlignment.perfect} stages`);
163
+ console.log(` Partial: ${statistics.defaultAlignment.partial} stages`);
164
+ console.log(` None: ${statistics.defaultAlignment.none} stages`);
165
+
166
+ console.log('\nRecommendations:');
167
+ console.log(` Upgrade suggestions: ${statistics.upgradeRecommendations} stages`);
168
+ console.log(` Downgrade suggestions: ${statistics.downgradeRecommendations} stages`);
169
+ }
170
+
171
+ /**
172
+ * Main execution function
173
+ */
174
+ async function main() {
175
+ try {
176
+ displayBanner();
177
+
178
+ // Check environment
179
+ const availableProviders = displayEnvironmentCheck();
180
+
181
+ // Display stats
182
+ displayPromptStats();
183
+
184
+ // Confirm execution
185
+ sendWarning('This will execute multiple API calls to all available providers.');
186
+ sendIndented('Estimated cost: $0.15-0.30 depending on available providers.', 1);
187
+ sendIndented('Press Ctrl+C to cancel, or wait 5 seconds to continue...', 1);
188
+
189
+ // Wait 5 seconds for user to cancel
190
+ await new Promise(resolve => setTimeout(resolve, 5000));
191
+
192
+ sendInfo('Starting evaluation...');
193
+
194
+ // Initialize query engine
195
+ const engine = new ModelQueryEngine();
196
+
197
+ sendInfo('Initializing providers...');
198
+ const initResult = await engine.initializeProviders();
199
+
200
+ sendIndented(`${initResult.readyCount} provider(s) initialized`, 1);
201
+
202
+ if (initResult.errors) {
203
+ sendWarning('Some providers failed to initialize:');
204
+ for (const error of initResult.errors) {
205
+ sendIndented(`- ${error.provider}: ${error.error}`, 1);
206
+ }
207
+ }
208
+
209
+ // Run evaluation
210
+ sendInfo('Querying providers (this may take several minutes)...');
211
+
212
+ const progressCallback = createProgressCallback();
213
+ const results = await engine.evaluateAll(EVALUATION_PROMPTS, progressCallback);
214
+
215
+ sendSuccess('All evaluations complete!');
216
+
217
+ // Analyze results
218
+ sendInfo('Analyzing results...');
219
+ const analyzer = new ModelRecommendationAnalyzer(results.evaluations);
220
+ const analysis = analyzer.analyze();
221
+ sendIndented('Analysis complete', 1);
222
+
223
+ // Ensure _temp directory exists
224
+ const tempDir = ensureTempDir();
225
+
226
+ // Write JSON output
227
+ const jsonPath = path.join(tempDir, 'model-recommendations.json');
228
+ const jsonOutput = {
229
+ ...results,
230
+ analysis: analysis.analyses,
231
+ statistics: analysis.statistics
232
+ };
233
+ writeJSONOutput(jsonOutput, jsonPath);
234
+
235
+ // Write Markdown report
236
+ const markdownPath = path.join(tempDir, 'MODEL_RECOMMENDATIONS_REPORT.md');
237
+ const markdownReport = analyzer.generateMarkdownReport();
238
+ writeMarkdownOutput(markdownReport, markdownPath);
239
+
240
+ // Display summaries
241
+ displaySummary(results.summary);
242
+ displayAnalysisSummary(analysis.statistics);
243
+
244
+ // Final message
245
+ sendOutput('\n╔══════════════════════════════════════════════════════════════╗');
246
+ sendOutput('║ Evaluation Complete! ║');
247
+ sendOutput('╚══════════════════════════════════════════════════════════════╝');
248
+ sendSectionHeader('Output files');
249
+ sendIndented(`- JSON: ${jsonPath}`, 1);
250
+ sendIndented(`- Report: ${markdownPath}`, 1);
251
+
252
+ sendSectionHeader('Next steps');
253
+ sendIndented('1. Review the markdown report for provider recommendations', 1);
254
+ sendIndented('2. Compare consensus vs current defaults', 1);
255
+ sendIndented('3. Consider cost vs quality trade-offs', 1);
256
+ sendIndented('4. Update model configurations in AVC if desired', 1);
257
+
258
+ } catch (error) {
259
+ sendError(`ERROR: ${error.message}`);
260
+ sendOutput(`\nStack trace: ${error.stack}`);
261
+ process.exit(1);
262
+ }
263
+ }
264
+
265
+ // Run if executed directly
266
+ if (import.meta.url === `file://${process.argv[1]}`) {
267
+ main();
268
+ }
269
+
270
+ export { main };
@@ -0,0 +1,107 @@
1
+ /**
2
+ * OutputBuffer - Manages accumulated output as item array for Static rendering
3
+ *
4
+ * Each append() call creates one item with a unique ID, compatible with
5
+ * Ink's <Static> component which requires stable keys and never erases items.
6
+ *
7
+ * Items accumulate across commands (like a natural terminal log).
8
+ * The clear() method inserts a blank separator line between commands
9
+ * instead of erasing — Static committed content cannot be removed.
10
+ */
11
+ export class OutputBuffer {
12
+ constructor() {
13
+ this.items = [];
14
+ this.itemCounter = 0;
15
+ this.listeners = [];
16
+ }
17
+
18
+ /**
19
+ * Append content as a new item
20
+ * @param {string} content - Content to append (may contain newlines)
21
+ * @param {string|null} type - Optional message type: 'ERROR'|'WARNING'|'SUCCESS'|'INFO'|null
22
+ */
23
+ append(content, type = null) {
24
+ if (!content) return;
25
+
26
+ this.items.push({ id: ++this.itemCounter, content, type });
27
+ this.notifyListeners();
28
+ }
29
+
30
+ /**
31
+ * Insert a blank separator between commands.
32
+ * Items cannot be removed from Static rendering, so this adds a
33
+ * visual separator instead of erasing previous content.
34
+ */
35
+ clear() {
36
+ if (this.items.length > 0) {
37
+ this.items.push({ id: ++this.itemCounter, content: '' });
38
+ this.notifyListeners();
39
+ }
40
+ }
41
+
42
+ /**
43
+ * Get all items (for Static rendering)
44
+ * @returns {{id: number, content: string}[]} Copy of items array
45
+ */
46
+ getItems() {
47
+ return [...this.items];
48
+ }
49
+
50
+ /**
51
+ * Get item count
52
+ * @returns {number} Number of items
53
+ */
54
+ getItemCount() {
55
+ return this.items.length;
56
+ }
57
+
58
+ /**
59
+ * Get all lines as flat string array (for test compatibility)
60
+ * @returns {string[]} All content split by newlines
61
+ */
62
+ getLines() {
63
+ return this.items.flatMap(item => item.content.split('\n'));
64
+ }
65
+
66
+ /**
67
+ * Get line count (for backward compatibility)
68
+ * @returns {number} Total number of lines across all items
69
+ */
70
+ getLineCount() {
71
+ return this.getLines().length;
72
+ }
73
+
74
+ /**
75
+ * Reset buffer completely - removes all items.
76
+ * Used in tests for isolation between test cases.
77
+ */
78
+ reset() {
79
+ this.items = [];
80
+ this.itemCounter = 0;
81
+ this.notifyListeners();
82
+ }
83
+
84
+ /**
85
+ * Subscribe to buffer changes
86
+ * @param {Function} listener - Callback function (items) => void
87
+ * @returns {Function} Unsubscribe function
88
+ */
89
+ subscribe(listener) {
90
+ this.listeners.push(listener);
91
+ return () => {
92
+ this.listeners = this.listeners.filter(l => l !== listener);
93
+ };
94
+ }
95
+
96
+ /**
97
+ * Notify all listeners of buffer change
98
+ * @private
99
+ */
100
+ notifyListeners() {
101
+ const items = this.getItems();
102
+ this.listeners.forEach(listener => listener(items));
103
+ }
104
+ }
105
+
106
+ // Singleton instance
107
+ export const outputBuffer = new OutputBuffer();
@@ -1,4 +1,4 @@
1
- import { spawn } from 'child_process';
1
+ import { spawn, fork } from 'child_process';
2
2
  import { EventEmitter } from 'events';
3
3
 
4
4
  /**
@@ -73,6 +73,63 @@ export class BackgroundProcessManager extends EventEmitter {
73
73
  return id;
74
74
  }
75
75
 
76
+ /**
77
+ * Fork a background process with an IPC channel.
78
+ * Identical metadata schema to startProcess(); also registers an IPC message handler.
79
+ * @param {string} name - Human-readable name
80
+ * @param {string} modulePath - Absolute path to the Node.js module to fork
81
+ * @param {string[]} args - Module arguments
82
+ * @param {Function} onMessage - Called with each IPC message from the child
83
+ * @returns {{ id: string, child: ChildProcess }}
84
+ */
85
+ forkProcess(name, modulePath, args = [], onMessage) {
86
+ const id = `${this.sanitizeName(name)}-${Date.now()}`;
87
+
88
+ const child = fork(modulePath, args, {
89
+ stdio: ['ignore', 'pipe', 'pipe', 'ipc'],
90
+ });
91
+
92
+ const metadata = {
93
+ id,
94
+ name,
95
+ command: modulePath,
96
+ pid: child.pid,
97
+ status: 'running',
98
+ startTime: new Date().toISOString(),
99
+ exitCode: null,
100
+ exitSignal: null,
101
+ output: [],
102
+ process: child
103
+ };
104
+
105
+ this.processes.set(id, metadata);
106
+
107
+ // Capture stdout / stderr (same as startProcess)
108
+ child.stdout?.on('data', (data) => {
109
+ this.appendOutput(id, 'stdout', data.toString());
110
+ });
111
+
112
+ child.stderr?.on('data', (data) => {
113
+ this.appendOutput(id, 'stderr', data.toString());
114
+ });
115
+
116
+ // Handle process exit / error
117
+ child.on('exit', (code, signal) => {
118
+ this.handleProcessExit(id, code, signal);
119
+ });
120
+
121
+ child.on('error', (error) => {
122
+ this.handleProcessError(id, error);
123
+ });
124
+
125
+ // Register IPC message handler
126
+ if (onMessage) child.on('message', onMessage);
127
+
128
+ this.emit('process-started', { id, name });
129
+
130
+ return { id, child };
131
+ }
132
+
76
133
  /**
77
134
  * Stop a running process
78
135
  */
@@ -175,7 +232,12 @@ export class BackgroundProcessManager extends EventEmitter {
175
232
 
176
233
  metadata.exitCode = code;
177
234
  metadata.exitSignal = signal;
178
- metadata.status = code === 0 ? 'exited' : 'crashed';
235
+
236
+ // Don't overwrite 'stopped' status (intentional termination via stopProcess)
237
+ // Only set crashed/exited if process wasn't manually stopped
238
+ if (metadata.status !== 'stopped') {
239
+ metadata.status = code === 0 ? 'exited' : 'crashed';
240
+ }
179
241
 
180
242
  this.emit('process-exited', {
181
243
  id,
@@ -184,6 +246,15 @@ export class BackgroundProcessManager extends EventEmitter {
184
246
  signal,
185
247
  status: metadata.status
186
248
  });
249
+
250
+ // Auto-cleanup finished processes after 3 seconds
251
+ // Gives user time to see final status before removal
252
+ setTimeout(() => {
253
+ if (this.processes.has(id)) {
254
+ this.processes.delete(id);
255
+ this.emit('process-removed', { id, name: metadata.name });
256
+ }
257
+ }, 3000);
187
258
  }
188
259
 
189
260
  /**
@@ -0,0 +1,57 @@
1
+ import fs from 'fs';
2
+ import path from 'path';
3
+
4
+ export class PromptLogger {
5
+ /**
6
+ * @param {string} projectRoot - absolute path to .avc parent
7
+ * @param {string} ceremony - e.g. 'sprint-planning', 'sponsor-call'
8
+ */
9
+ constructor(projectRoot, ceremony) {
10
+ this.ceremony = ceremony;
11
+ this.callCount = 0;
12
+ this.runDir = null;
13
+
14
+ const avcDir = path.join(projectRoot, '.avc');
15
+ if (!fs.existsSync(avcDir)) return; // not an AVC project — silently skip
16
+
17
+ const runTs = new Date().toISOString().replace(/:/g, '-').replace(/\..+/, '');
18
+ this.runDir = path.join(avcDir, 'logs', 'prompts', `${ceremony}-${runTs}`);
19
+ fs.mkdirSync(this.runDir, { recursive: true });
20
+
21
+ this._pruneOldRuns(path.join(avcDir, 'logs', 'prompts'), ceremony, 5);
22
+ }
23
+
24
+ /**
25
+ * Write one call record. Called by _trackTokens in LLMProvider.
26
+ * @param {object} payload
27
+ */
28
+ write(payload) {
29
+ if (!this.runDir) return;
30
+ this.callCount += 1;
31
+ const seq = String(this.callCount).padStart(3, '0');
32
+ const stage = (payload.stage || 'unknown').replace(/\s+/g, '-');
33
+ const timeStr = new Date().toISOString().substring(11, 19).replace(/:/g, '-');
34
+ const filename = `${seq}-${stage}-${timeStr}.json`;
35
+ try {
36
+ fs.writeFileSync(
37
+ path.join(this.runDir, filename),
38
+ JSON.stringify(payload, null, 2),
39
+ 'utf8'
40
+ );
41
+ } catch { /* non-fatal */ }
42
+ }
43
+
44
+ _pruneOldRuns(promptsDir, ceremony, keep) {
45
+ if (!fs.existsSync(promptsDir)) return;
46
+ const dirs = fs.readdirSync(promptsDir)
47
+ .filter(d => d.startsWith(`${ceremony}-`))
48
+ .map(d => ({ name: d, full: path.join(promptsDir, d) }))
49
+ .sort((a, b) => a.name.localeCompare(b.name)); // oldest first
50
+ while (dirs.length >= keep) {
51
+ const oldest = dirs.shift();
52
+ try {
53
+ fs.rmSync(oldest.full, { recursive: true, force: true });
54
+ } catch { /* non-fatal */ }
55
+ }
56
+ }
57
+ }