@agile-vibe-coding/avc 0.1.0 → 0.2.3
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/README.md +2 -0
- package/cli/agent-loader.js +21 -0
- package/cli/agents/agent-selector.md +129 -0
- package/cli/agents/architecture-recommender.md +418 -0
- package/cli/agents/database-deep-dive.md +470 -0
- package/cli/agents/database-recommender.md +634 -0
- package/cli/agents/doc-distributor.md +176 -0
- package/cli/agents/documentation-updater.md +203 -0
- package/cli/agents/epic-story-decomposer.md +280 -0
- package/cli/agents/feature-context-generator.md +91 -0
- package/cli/agents/gap-checker-epic.md +52 -0
- package/cli/agents/impact-checker-story.md +51 -0
- package/cli/agents/migration-guide-generator.md +305 -0
- package/cli/agents/mission-scope-generator.md +79 -0
- package/cli/agents/mission-scope-validator.md +112 -0
- package/cli/agents/project-context-extractor.md +107 -0
- package/cli/agents/project-documentation-creator.json +226 -0
- package/cli/agents/project-documentation-creator.md +595 -0
- package/cli/agents/question-prefiller.md +269 -0
- package/cli/agents/refiner-epic.md +39 -0
- package/cli/agents/refiner-story.md +42 -0
- package/cli/agents/solver-epic-api.json +15 -0
- package/cli/agents/solver-epic-api.md +39 -0
- package/cli/agents/solver-epic-backend.json +15 -0
- package/cli/agents/solver-epic-backend.md +39 -0
- package/cli/agents/solver-epic-cloud.json +15 -0
- package/cli/agents/solver-epic-cloud.md +39 -0
- package/cli/agents/solver-epic-data.json +15 -0
- package/cli/agents/solver-epic-data.md +39 -0
- package/cli/agents/solver-epic-database.json +15 -0
- package/cli/agents/solver-epic-database.md +39 -0
- package/cli/agents/solver-epic-developer.json +15 -0
- package/cli/agents/solver-epic-developer.md +39 -0
- package/cli/agents/solver-epic-devops.json +15 -0
- package/cli/agents/solver-epic-devops.md +39 -0
- package/cli/agents/solver-epic-frontend.json +15 -0
- package/cli/agents/solver-epic-frontend.md +39 -0
- package/cli/agents/solver-epic-mobile.json +15 -0
- package/cli/agents/solver-epic-mobile.md +39 -0
- package/cli/agents/solver-epic-qa.json +15 -0
- package/cli/agents/solver-epic-qa.md +39 -0
- package/cli/agents/solver-epic-security.json +15 -0
- package/cli/agents/solver-epic-security.md +39 -0
- package/cli/agents/solver-epic-solution-architect.json +15 -0
- package/cli/agents/solver-epic-solution-architect.md +39 -0
- package/cli/agents/solver-epic-test-architect.json +15 -0
- package/cli/agents/solver-epic-test-architect.md +39 -0
- package/cli/agents/solver-epic-ui.json +15 -0
- package/cli/agents/solver-epic-ui.md +39 -0
- package/cli/agents/solver-epic-ux.json +15 -0
- package/cli/agents/solver-epic-ux.md +39 -0
- package/cli/agents/solver-story-api.json +15 -0
- package/cli/agents/solver-story-api.md +39 -0
- package/cli/agents/solver-story-backend.json +15 -0
- package/cli/agents/solver-story-backend.md +39 -0
- package/cli/agents/solver-story-cloud.json +15 -0
- package/cli/agents/solver-story-cloud.md +39 -0
- package/cli/agents/solver-story-data.json +15 -0
- package/cli/agents/solver-story-data.md +39 -0
- package/cli/agents/solver-story-database.json +15 -0
- package/cli/agents/solver-story-database.md +39 -0
- package/cli/agents/solver-story-developer.json +15 -0
- package/cli/agents/solver-story-developer.md +39 -0
- package/cli/agents/solver-story-devops.json +15 -0
- package/cli/agents/solver-story-devops.md +39 -0
- package/cli/agents/solver-story-frontend.json +15 -0
- package/cli/agents/solver-story-frontend.md +39 -0
- package/cli/agents/solver-story-mobile.json +15 -0
- package/cli/agents/solver-story-mobile.md +39 -0
- package/cli/agents/solver-story-qa.json +15 -0
- package/cli/agents/solver-story-qa.md +39 -0
- package/cli/agents/solver-story-security.json +15 -0
- package/cli/agents/solver-story-security.md +39 -0
- package/cli/agents/solver-story-solution-architect.json +15 -0
- package/cli/agents/solver-story-solution-architect.md +39 -0
- package/cli/agents/solver-story-test-architect.json +15 -0
- package/cli/agents/solver-story-test-architect.md +39 -0
- package/cli/agents/solver-story-ui.json +15 -0
- package/cli/agents/solver-story-ui.md +39 -0
- package/cli/agents/solver-story-ux.json +15 -0
- package/cli/agents/solver-story-ux.md +39 -0
- package/cli/agents/story-doc-enricher.md +133 -0
- package/cli/agents/suggestion-business-analyst.md +88 -0
- package/cli/agents/suggestion-deployment-architect.md +263 -0
- package/cli/agents/suggestion-product-manager.md +129 -0
- package/cli/agents/suggestion-security-specialist.md +156 -0
- package/cli/agents/suggestion-technical-architect.md +269 -0
- package/cli/agents/suggestion-ux-researcher.md +93 -0
- package/cli/agents/task-subtask-decomposer.md +188 -0
- package/cli/agents/validator-documentation.json +152 -0
- package/cli/agents/validator-documentation.md +453 -0
- package/cli/agents/validator-epic-api.json +93 -0
- package/cli/agents/validator-epic-api.md +137 -0
- package/cli/agents/validator-epic-backend.json +93 -0
- package/cli/agents/validator-epic-backend.md +130 -0
- package/cli/agents/validator-epic-cloud.json +93 -0
- package/cli/agents/validator-epic-cloud.md +137 -0
- package/cli/agents/validator-epic-data.json +93 -0
- package/cli/agents/validator-epic-data.md +130 -0
- package/cli/agents/validator-epic-database.json +93 -0
- package/cli/agents/validator-epic-database.md +137 -0
- package/cli/agents/validator-epic-developer.json +74 -0
- package/cli/agents/validator-epic-developer.md +153 -0
- package/cli/agents/validator-epic-devops.json +74 -0
- package/cli/agents/validator-epic-devops.md +153 -0
- package/cli/agents/validator-epic-frontend.json +74 -0
- package/cli/agents/validator-epic-frontend.md +153 -0
- package/cli/agents/validator-epic-mobile.json +93 -0
- package/cli/agents/validator-epic-mobile.md +130 -0
- package/cli/agents/validator-epic-qa.json +93 -0
- package/cli/agents/validator-epic-qa.md +130 -0
- package/cli/agents/validator-epic-security.json +74 -0
- package/cli/agents/validator-epic-security.md +154 -0
- package/cli/agents/validator-epic-solution-architect.json +74 -0
- package/cli/agents/validator-epic-solution-architect.md +156 -0
- package/cli/agents/validator-epic-test-architect.json +93 -0
- package/cli/agents/validator-epic-test-architect.md +130 -0
- package/cli/agents/validator-epic-ui.json +93 -0
- package/cli/agents/validator-epic-ui.md +130 -0
- package/cli/agents/validator-epic-ux.json +93 -0
- package/cli/agents/validator-epic-ux.md +130 -0
- package/cli/agents/validator-selector.md +211 -0
- package/cli/agents/validator-story-api.json +104 -0
- package/cli/agents/validator-story-api.md +152 -0
- package/cli/agents/validator-story-backend.json +104 -0
- package/cli/agents/validator-story-backend.md +152 -0
- package/cli/agents/validator-story-cloud.json +104 -0
- package/cli/agents/validator-story-cloud.md +152 -0
- package/cli/agents/validator-story-data.json +104 -0
- package/cli/agents/validator-story-data.md +152 -0
- package/cli/agents/validator-story-database.json +104 -0
- package/cli/agents/validator-story-database.md +152 -0
- package/cli/agents/validator-story-developer.json +104 -0
- package/cli/agents/validator-story-developer.md +152 -0
- package/cli/agents/validator-story-devops.json +104 -0
- package/cli/agents/validator-story-devops.md +152 -0
- package/cli/agents/validator-story-frontend.json +104 -0
- package/cli/agents/validator-story-frontend.md +152 -0
- package/cli/agents/validator-story-mobile.json +104 -0
- package/cli/agents/validator-story-mobile.md +152 -0
- package/cli/agents/validator-story-qa.json +104 -0
- package/cli/agents/validator-story-qa.md +152 -0
- package/cli/agents/validator-story-security.json +104 -0
- package/cli/agents/validator-story-security.md +152 -0
- package/cli/agents/validator-story-solution-architect.json +104 -0
- package/cli/agents/validator-story-solution-architect.md +152 -0
- package/cli/agents/validator-story-test-architect.json +104 -0
- package/cli/agents/validator-story-test-architect.md +152 -0
- package/cli/agents/validator-story-ui.json +104 -0
- package/cli/agents/validator-story-ui.md +152 -0
- package/cli/agents/validator-story-ux.json +104 -0
- package/cli/agents/validator-story-ux.md +152 -0
- package/cli/ansi-colors.js +21 -0
- package/cli/build-docs.js +298 -0
- package/cli/ceremony-history.js +369 -0
- package/cli/command-logger.js +245 -0
- package/cli/components/static-output.js +63 -0
- package/cli/console-output-manager.js +94 -0
- package/cli/docs-sync.js +306 -0
- package/cli/epic-story-validator.js +1174 -0
- package/cli/evaluation-prompts.js +1008 -0
- package/cli/execution-context.js +195 -0
- package/cli/generate-summary-table.js +340 -0
- package/cli/index.js +3 -25
- package/cli/init-model-config.js +697 -0
- package/cli/init.js +1765 -100
- package/cli/kanban-server-manager.js +228 -0
- package/cli/llm-claude.js +109 -0
- package/cli/llm-gemini.js +115 -0
- package/cli/llm-mock.js +233 -0
- package/cli/llm-openai.js +233 -0
- package/cli/llm-provider.js +300 -0
- package/cli/llm-token-limits.js +102 -0
- package/cli/llm-verifier.js +454 -0
- package/cli/logger.js +32 -5
- package/cli/message-constants.js +58 -0
- package/cli/message-manager.js +334 -0
- package/cli/message-types.js +96 -0
- package/cli/messaging-api.js +297 -0
- package/cli/model-pricing.js +169 -0
- package/cli/model-query-engine.js +468 -0
- package/cli/model-recommendation-analyzer.js +495 -0
- package/cli/model-selector.js +269 -0
- package/cli/output-buffer.js +107 -0
- package/cli/process-manager.js +332 -0
- package/cli/repl-ink.js +5840 -504
- package/cli/repl-old.js +4 -4
- package/cli/seed-processor.js +792 -0
- package/cli/sprint-planning-processor.js +1813 -0
- package/cli/template-processor.js +2306 -108
- package/cli/templates/project.md +25 -8
- package/cli/templates/vitepress-config.mts.template +34 -0
- package/cli/token-tracker.js +520 -0
- package/cli/tools/generate-story-validators.js +317 -0
- package/cli/tools/generate-validators.js +669 -0
- package/cli/update-checker.js +19 -17
- package/cli/update-notifier.js +4 -4
- package/cli/validation-router.js +605 -0
- package/cli/verification-tracker.js +563 -0
- package/kanban/README.md +386 -0
- package/kanban/client/README.md +205 -0
- package/kanban/client/components.json +20 -0
- package/kanban/client/dist/assets/index-CiD8PS2e.js +306 -0
- package/kanban/client/dist/assets/index-nLh0m82Q.css +1 -0
- package/kanban/client/dist/index.html +16 -0
- package/kanban/client/dist/vite.svg +1 -0
- package/kanban/client/index.html +15 -0
- package/kanban/client/package-lock.json +9442 -0
- package/kanban/client/package.json +44 -0
- package/kanban/client/postcss.config.js +6 -0
- package/kanban/client/public/vite.svg +1 -0
- package/kanban/client/src/App.jsx +622 -0
- package/kanban/client/src/components/ProjectFileEditorPopup.jsx +117 -0
- package/kanban/client/src/components/ceremony/AskArchPopup.jsx +416 -0
- package/kanban/client/src/components/ceremony/AskModelPopup.jsx +616 -0
- package/kanban/client/src/components/ceremony/CeremonyWorkflowModal.jsx +946 -0
- package/kanban/client/src/components/ceremony/EpicStorySelectionModal.jsx +254 -0
- package/kanban/client/src/components/ceremony/SponsorCallModal.jsx +619 -0
- package/kanban/client/src/components/ceremony/SprintPlanningModal.jsx +704 -0
- package/kanban/client/src/components/ceremony/steps/ArchitectureStep.jsx +150 -0
- package/kanban/client/src/components/ceremony/steps/CompleteStep.jsx +154 -0
- package/kanban/client/src/components/ceremony/steps/DatabaseStep.jsx +202 -0
- package/kanban/client/src/components/ceremony/steps/DeploymentStep.jsx +123 -0
- package/kanban/client/src/components/ceremony/steps/MissionStep.jsx +106 -0
- package/kanban/client/src/components/ceremony/steps/ReviewAnswersStep.jsx +125 -0
- package/kanban/client/src/components/ceremony/steps/RunningStep.jsx +228 -0
- package/kanban/client/src/components/kanban/CardDetailModal.jsx +559 -0
- package/kanban/client/src/components/kanban/EpicSection.jsx +146 -0
- package/kanban/client/src/components/kanban/FilterToolbar.jsx +222 -0
- package/kanban/client/src/components/kanban/GroupingSelector.jsx +57 -0
- package/kanban/client/src/components/kanban/KanbanBoard.jsx +211 -0
- package/kanban/client/src/components/kanban/KanbanCard.jsx +138 -0
- package/kanban/client/src/components/kanban/KanbanColumn.jsx +90 -0
- package/kanban/client/src/components/kanban/RefineWorkItemPopup.jsx +789 -0
- package/kanban/client/src/components/layout/LoadingScreen.jsx +82 -0
- package/kanban/client/src/components/process/ProcessMonitorBar.jsx +80 -0
- package/kanban/client/src/components/settings/AgentEditorPopup.jsx +171 -0
- package/kanban/client/src/components/settings/AgentsTab.jsx +353 -0
- package/kanban/client/src/components/settings/ApiKeysTab.jsx +113 -0
- package/kanban/client/src/components/settings/CeremonyModelsTab.jsx +98 -0
- package/kanban/client/src/components/settings/CostThresholdsTab.jsx +94 -0
- package/kanban/client/src/components/settings/ModelPricingTab.jsx +204 -0
- package/kanban/client/src/components/settings/ServersTab.jsx +121 -0
- package/kanban/client/src/components/settings/SettingsModal.jsx +84 -0
- package/kanban/client/src/components/stats/CostModal.jsx +353 -0
- package/kanban/client/src/components/ui/badge.jsx +27 -0
- package/kanban/client/src/components/ui/dialog.jsx +121 -0
- package/kanban/client/src/components/ui/tabs.jsx +85 -0
- package/kanban/client/src/hooks/__tests__/useGrouping.test.js +232 -0
- package/kanban/client/src/hooks/useGrouping.js +118 -0
- package/kanban/client/src/hooks/useWebSocket.js +120 -0
- package/kanban/client/src/lib/__tests__/api.test.js +196 -0
- package/kanban/client/src/lib/__tests__/status-grouping.test.js +94 -0
- package/kanban/client/src/lib/api.js +401 -0
- package/kanban/client/src/lib/status-grouping.js +144 -0
- package/kanban/client/src/lib/utils.js +11 -0
- package/kanban/client/src/main.jsx +10 -0
- package/kanban/client/src/store/__tests__/kanbanStore.test.js +164 -0
- package/kanban/client/src/store/ceremonyStore.js +172 -0
- package/kanban/client/src/store/filterStore.js +201 -0
- package/kanban/client/src/store/kanbanStore.js +115 -0
- package/kanban/client/src/store/processStore.js +65 -0
- package/kanban/client/src/store/sprintPlanningStore.js +33 -0
- package/kanban/client/src/styles/globals.css +59 -0
- package/kanban/client/tailwind.config.js +77 -0
- package/kanban/client/vite.config.js +28 -0
- package/kanban/client/vitest.config.js +28 -0
- package/kanban/dev-start.sh +47 -0
- package/kanban/package.json +12 -0
- package/kanban/server/index.js +516 -0
- package/kanban/server/routes/ceremony.js +305 -0
- package/kanban/server/routes/costs.js +157 -0
- package/kanban/server/routes/processes.js +50 -0
- package/kanban/server/routes/settings.js +303 -0
- package/kanban/server/routes/websocket.js +276 -0
- package/kanban/server/routes/work-items.js +347 -0
- package/kanban/server/services/CeremonyService.js +1190 -0
- package/kanban/server/services/FileSystemScanner.js +95 -0
- package/kanban/server/services/FileWatcher.js +144 -0
- package/kanban/server/services/HierarchyBuilder.js +196 -0
- package/kanban/server/services/ProcessRegistry.js +122 -0
- package/kanban/server/services/WorkItemReader.js +123 -0
- package/kanban/server/services/WorkItemRefineService.js +510 -0
- package/kanban/server/start.js +49 -0
- package/kanban/server/utils/kanban-logger.js +132 -0
- package/kanban/server/utils/markdown.js +91 -0
- package/kanban/server/utils/status-grouping.js +107 -0
- package/kanban/server/workers/sponsor-call-worker.js +84 -0
- package/kanban/server/workers/sprint-planning-worker.js +130 -0
- package/package.json +34 -7
|
@@ -0,0 +1,269 @@
|
|
|
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,
|
|
50
|
+
'GEMINI_API_KEY': !!process.env.GEMINI_API_KEY
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
for (const [key, available] of Object.entries(keys)) {
|
|
54
|
+
if (available) {
|
|
55
|
+
sendIndented(`${key}: Found`, 1);
|
|
56
|
+
} else {
|
|
57
|
+
sendIndented(`${key}: Not found`, 1);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const availableCount = Object.values(keys).filter(v => v).length;
|
|
62
|
+
|
|
63
|
+
sendOutput(`\n ${availableCount}/3 providers available`);
|
|
64
|
+
|
|
65
|
+
if (availableCount === 0) {
|
|
66
|
+
sendError('No API keys found. Please add at least one to your .env file.');
|
|
67
|
+
process.exit(1);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
return availableCount;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Display prompt statistics
|
|
75
|
+
*/
|
|
76
|
+
function displayPromptStats() {
|
|
77
|
+
const stats = getPromptStats();
|
|
78
|
+
|
|
79
|
+
sendSectionHeader('Evaluation Overview');
|
|
80
|
+
sendIndented(`Total prompts: ${stats.totalPrompts}`, 1);
|
|
81
|
+
sendIndented(`Ceremonies: ${stats.ceremonies} (${stats.ceremonyList.join(', ')})`, 1);
|
|
82
|
+
sendIndented(`Estimated total API calls: ${stats.estimatedTotalCalls} per provider`, 1);
|
|
83
|
+
sendIndented('Impact distribution:', 1);
|
|
84
|
+
sendIndented(`- CRITICAL: ${stats.impactDistribution.CRITICAL}`, 2);
|
|
85
|
+
sendIndented(`- VERY HIGH: ${stats.impactDistribution['VERY HIGH']}`, 2);
|
|
86
|
+
sendIndented(`- HIGH: ${stats.impactDistribution.HIGH}`, 2);
|
|
87
|
+
sendIndented(`- MEDIUM: ${stats.impactDistribution.MEDIUM}`, 2);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Progress callback for query engine
|
|
92
|
+
*/
|
|
93
|
+
function createProgressCallback() {
|
|
94
|
+
let currentPrompt = 0;
|
|
95
|
+
|
|
96
|
+
return (progress) => {
|
|
97
|
+
if (progress.type === 'prompt-start') {
|
|
98
|
+
currentPrompt = progress.current;
|
|
99
|
+
sendOutput(`\n[${progress.current}/${progress.total}] Processing: ${progress.ceremony}/${progress.stage}`);
|
|
100
|
+
} else if (progress.type === 'prompt-complete') {
|
|
101
|
+
const { successCount, failureCount, totalTime } = progress;
|
|
102
|
+
sendIndented(`Complete: ${successCount} success, ${failureCount} failed (${totalTime.toFixed(1)}s)`, 1);
|
|
103
|
+
}
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Write JSON output file
|
|
109
|
+
*/
|
|
110
|
+
function writeJSONOutput(data, outputPath) {
|
|
111
|
+
sendInfo(`Writing JSON output to ${outputPath}...`);
|
|
112
|
+
fs.writeFileSync(outputPath, JSON.stringify(data, null, 2), 'utf-8');
|
|
113
|
+
sendIndented('JSON file written', 1);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Write Markdown output file
|
|
118
|
+
*/
|
|
119
|
+
function writeMarkdownOutput(content, outputPath) {
|
|
120
|
+
sendInfo(`Writing Markdown report to ${outputPath}...`);
|
|
121
|
+
fs.writeFileSync(outputPath, content, 'utf-8');
|
|
122
|
+
sendIndented('Markdown report written', 1);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Display summary
|
|
127
|
+
*/
|
|
128
|
+
function displaySummary(summary) {
|
|
129
|
+
console.log('\n╔══════════════════════════════════════════════════════════════╗');
|
|
130
|
+
console.log('║ Execution Summary ║');
|
|
131
|
+
console.log('╚══════════════════════════════════════════════════════════════╝\n');
|
|
132
|
+
|
|
133
|
+
console.log(`Total prompts evaluated: ${summary.totalPrompts}`);
|
|
134
|
+
console.log(`Successful queries: ${summary.successfulQueries}`);
|
|
135
|
+
console.log(`Failed queries: ${summary.failedQueries}`);
|
|
136
|
+
console.log(`\nExecution time: ${summary.executionTime}`);
|
|
137
|
+
|
|
138
|
+
console.log('\nCost breakdown:');
|
|
139
|
+
for (const [provider, cost] of Object.entries(summary.totalCost)) {
|
|
140
|
+
if (provider !== 'total') {
|
|
141
|
+
console.log(` ${provider}: $${cost.toFixed(4)}`);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
console.log(` TOTAL: $${summary.totalCost.total.toFixed(4)}`);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* Display analysis summary
|
|
149
|
+
*/
|
|
150
|
+
function displayAnalysisSummary(statistics) {
|
|
151
|
+
console.log('\n╔══════════════════════════════════════════════════════════════╗');
|
|
152
|
+
console.log('║ Analysis Summary ║');
|
|
153
|
+
console.log('╚══════════════════════════════════════════════════════════════╝\n');
|
|
154
|
+
|
|
155
|
+
console.log('Consensus Statistics:');
|
|
156
|
+
console.log(` Full consensus: ${statistics.consensus.full} (${statistics.consensusRate.full})`);
|
|
157
|
+
console.log(` Partial consensus: ${statistics.consensus.partial} (${statistics.consensusRate.partial})`);
|
|
158
|
+
console.log(` No consensus: ${statistics.consensus.none} (${statistics.consensusRate.none})`);
|
|
159
|
+
|
|
160
|
+
console.log('\nDefault Alignment:');
|
|
161
|
+
console.log(` Perfect: ${statistics.defaultAlignment.perfect} stages`);
|
|
162
|
+
console.log(` Partial: ${statistics.defaultAlignment.partial} stages`);
|
|
163
|
+
console.log(` None: ${statistics.defaultAlignment.none} stages`);
|
|
164
|
+
|
|
165
|
+
console.log('\nRecommendations:');
|
|
166
|
+
console.log(` Upgrade suggestions: ${statistics.upgradeRecommendations} stages`);
|
|
167
|
+
console.log(` Downgrade suggestions: ${statistics.downgradeRecommendations} stages`);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* Main execution function
|
|
172
|
+
*/
|
|
173
|
+
async function main() {
|
|
174
|
+
try {
|
|
175
|
+
displayBanner();
|
|
176
|
+
|
|
177
|
+
// Check environment
|
|
178
|
+
const availableProviders = displayEnvironmentCheck();
|
|
179
|
+
|
|
180
|
+
// Display stats
|
|
181
|
+
displayPromptStats();
|
|
182
|
+
|
|
183
|
+
// Confirm execution
|
|
184
|
+
sendWarning('This will execute multiple API calls to all available providers.');
|
|
185
|
+
sendIndented('Estimated cost: $0.15-0.30 depending on available providers.', 1);
|
|
186
|
+
sendIndented('Press Ctrl+C to cancel, or wait 5 seconds to continue...', 1);
|
|
187
|
+
|
|
188
|
+
// Wait 5 seconds for user to cancel
|
|
189
|
+
await new Promise(resolve => setTimeout(resolve, 5000));
|
|
190
|
+
|
|
191
|
+
sendInfo('Starting evaluation...');
|
|
192
|
+
|
|
193
|
+
// Initialize query engine
|
|
194
|
+
const engine = new ModelQueryEngine();
|
|
195
|
+
|
|
196
|
+
sendInfo('Initializing providers...');
|
|
197
|
+
const initResult = await engine.initializeProviders();
|
|
198
|
+
|
|
199
|
+
sendIndented(`${initResult.readyCount} provider(s) initialized`, 1);
|
|
200
|
+
|
|
201
|
+
if (initResult.errors) {
|
|
202
|
+
sendWarning('Some providers failed to initialize:');
|
|
203
|
+
for (const error of initResult.errors) {
|
|
204
|
+
sendIndented(`- ${error.provider}: ${error.error}`, 1);
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
// Run evaluation
|
|
209
|
+
sendInfo('Querying providers (this may take several minutes)...');
|
|
210
|
+
|
|
211
|
+
const progressCallback = createProgressCallback();
|
|
212
|
+
const results = await engine.evaluateAll(EVALUATION_PROMPTS, progressCallback);
|
|
213
|
+
|
|
214
|
+
sendSuccess('All evaluations complete!');
|
|
215
|
+
|
|
216
|
+
// Analyze results
|
|
217
|
+
sendInfo('Analyzing results...');
|
|
218
|
+
const analyzer = new ModelRecommendationAnalyzer(results.evaluations);
|
|
219
|
+
const analysis = analyzer.analyze();
|
|
220
|
+
sendIndented('Analysis complete', 1);
|
|
221
|
+
|
|
222
|
+
// Ensure _temp directory exists
|
|
223
|
+
const tempDir = ensureTempDir();
|
|
224
|
+
|
|
225
|
+
// Write JSON output
|
|
226
|
+
const jsonPath = path.join(tempDir, 'model-recommendations.json');
|
|
227
|
+
const jsonOutput = {
|
|
228
|
+
...results,
|
|
229
|
+
analysis: analysis.analyses,
|
|
230
|
+
statistics: analysis.statistics
|
|
231
|
+
};
|
|
232
|
+
writeJSONOutput(jsonOutput, jsonPath);
|
|
233
|
+
|
|
234
|
+
// Write Markdown report
|
|
235
|
+
const markdownPath = path.join(tempDir, 'MODEL_RECOMMENDATIONS_REPORT.md');
|
|
236
|
+
const markdownReport = analyzer.generateMarkdownReport();
|
|
237
|
+
writeMarkdownOutput(markdownReport, markdownPath);
|
|
238
|
+
|
|
239
|
+
// Display summaries
|
|
240
|
+
displaySummary(results.summary);
|
|
241
|
+
displayAnalysisSummary(analysis.statistics);
|
|
242
|
+
|
|
243
|
+
// Final message
|
|
244
|
+
sendOutput('\n╔══════════════════════════════════════════════════════════════╗');
|
|
245
|
+
sendOutput('║ Evaluation Complete! ║');
|
|
246
|
+
sendOutput('╚══════════════════════════════════════════════════════════════╝');
|
|
247
|
+
sendSectionHeader('Output files');
|
|
248
|
+
sendIndented(`- JSON: ${jsonPath}`, 1);
|
|
249
|
+
sendIndented(`- Report: ${markdownPath}`, 1);
|
|
250
|
+
|
|
251
|
+
sendSectionHeader('Next steps');
|
|
252
|
+
sendIndented('1. Review the markdown report for provider recommendations', 1);
|
|
253
|
+
sendIndented('2. Compare consensus vs current defaults', 1);
|
|
254
|
+
sendIndented('3. Consider cost vs quality trade-offs', 1);
|
|
255
|
+
sendIndented('4. Update model configurations in AVC if desired', 1);
|
|
256
|
+
|
|
257
|
+
} catch (error) {
|
|
258
|
+
sendError(`ERROR: ${error.message}`);
|
|
259
|
+
sendOutput(`\nStack trace: ${error.stack}`);
|
|
260
|
+
process.exit(1);
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
// Run if executed directly
|
|
265
|
+
if (import.meta.url === `file://${process.argv[1]}`) {
|
|
266
|
+
main();
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
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();
|
|
@@ -0,0 +1,332 @@
|
|
|
1
|
+
import { spawn, fork } from 'child_process';
|
|
2
|
+
import { EventEmitter } from 'events';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Background Process Manager
|
|
6
|
+
* Manages lifecycle of long-running background processes
|
|
7
|
+
*/
|
|
8
|
+
export class BackgroundProcessManager extends EventEmitter {
|
|
9
|
+
constructor() {
|
|
10
|
+
super();
|
|
11
|
+
this.processes = new Map(); // id -> processMetadata
|
|
12
|
+
this.maxOutputLines = 500; // Keep last 500 lines per process
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Start a background process
|
|
17
|
+
* @param {Object} options
|
|
18
|
+
* @param {string} options.name - Human-readable name
|
|
19
|
+
* @param {string} options.command - Command to execute
|
|
20
|
+
* @param {string[]} options.args - Command arguments
|
|
21
|
+
* @param {string} options.cwd - Working directory
|
|
22
|
+
* @param {Object} options.env - Environment variables
|
|
23
|
+
* @returns {string} Process ID
|
|
24
|
+
*/
|
|
25
|
+
startProcess({ name, command, args = [], cwd, env = process.env }) {
|
|
26
|
+
const id = `${this.sanitizeName(name)}-${Date.now()}`;
|
|
27
|
+
|
|
28
|
+
const childProcess = spawn(command, args, {
|
|
29
|
+
cwd,
|
|
30
|
+
env,
|
|
31
|
+
stdio: 'pipe',
|
|
32
|
+
detached: false
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
const metadata = {
|
|
36
|
+
id,
|
|
37
|
+
name,
|
|
38
|
+
command: `${command} ${args.join(' ')}`,
|
|
39
|
+
cwd,
|
|
40
|
+
pid: childProcess.pid,
|
|
41
|
+
status: 'running',
|
|
42
|
+
startTime: new Date().toISOString(),
|
|
43
|
+
exitCode: null,
|
|
44
|
+
exitSignal: null,
|
|
45
|
+
output: [],
|
|
46
|
+
process: childProcess
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
this.processes.set(id, metadata);
|
|
50
|
+
|
|
51
|
+
// Capture stdout
|
|
52
|
+
childProcess.stdout.on('data', (data) => {
|
|
53
|
+
this.appendOutput(id, 'stdout', data.toString());
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
// Capture stderr
|
|
57
|
+
childProcess.stderr.on('data', (data) => {
|
|
58
|
+
this.appendOutput(id, 'stderr', data.toString());
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
// Handle process exit
|
|
62
|
+
childProcess.on('exit', (code, signal) => {
|
|
63
|
+
this.handleProcessExit(id, code, signal);
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
// Handle process error
|
|
67
|
+
childProcess.on('error', (error) => {
|
|
68
|
+
this.handleProcessError(id, error);
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
this.emit('process-started', { id, name });
|
|
72
|
+
|
|
73
|
+
return id;
|
|
74
|
+
}
|
|
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
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Stop a running process
|
|
135
|
+
*/
|
|
136
|
+
stopProcess(id) {
|
|
137
|
+
const metadata = this.processes.get(id);
|
|
138
|
+
if (!metadata) return false;
|
|
139
|
+
|
|
140
|
+
if (metadata.status === 'running' && metadata.process) {
|
|
141
|
+
metadata.process.kill('SIGTERM');
|
|
142
|
+
metadata.status = 'stopped';
|
|
143
|
+
this.emit('process-stopped', { id, name: metadata.name });
|
|
144
|
+
return true;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
return false;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* Find process by PID
|
|
152
|
+
* @param {number} pid - Process ID to search for
|
|
153
|
+
* @returns {Object|null} Process metadata or null if not found
|
|
154
|
+
*/
|
|
155
|
+
findProcessByPid(pid) {
|
|
156
|
+
for (const metadata of this.processes.values()) {
|
|
157
|
+
if (metadata.pid === pid) {
|
|
158
|
+
return metadata;
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
return null;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* Remove process from manager by PID
|
|
166
|
+
* Used when external kill is performed on a managed process
|
|
167
|
+
* @param {number} pid - Process ID to remove
|
|
168
|
+
* @returns {boolean} True if process was found and removed
|
|
169
|
+
*/
|
|
170
|
+
removeProcessByPid(pid) {
|
|
171
|
+
const process = this.findProcessByPid(pid);
|
|
172
|
+
if (process) {
|
|
173
|
+
this.processes.delete(process.id);
|
|
174
|
+
this.emit('process-removed', { id: process.id, name: process.name, pid });
|
|
175
|
+
return true;
|
|
176
|
+
}
|
|
177
|
+
return false;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
/**
|
|
181
|
+
* Get process metadata
|
|
182
|
+
*/
|
|
183
|
+
getProcess(id) {
|
|
184
|
+
return this.processes.get(id);
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
/**
|
|
188
|
+
* Get all processes
|
|
189
|
+
*/
|
|
190
|
+
getAllProcesses() {
|
|
191
|
+
return Array.from(this.processes.values());
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/**
|
|
195
|
+
* Get running processes only
|
|
196
|
+
*/
|
|
197
|
+
getRunningProcesses() {
|
|
198
|
+
return this.getAllProcesses().filter(p => p.status === 'running');
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
/**
|
|
202
|
+
* Append output to process buffer
|
|
203
|
+
*/
|
|
204
|
+
appendOutput(id, type, text) {
|
|
205
|
+
const metadata = this.processes.get(id);
|
|
206
|
+
if (!metadata) return;
|
|
207
|
+
|
|
208
|
+
const lines = text.split('\n').filter(line => line.trim());
|
|
209
|
+
|
|
210
|
+
for (const line of lines) {
|
|
211
|
+
metadata.output.push({
|
|
212
|
+
timestamp: new Date().toISOString(),
|
|
213
|
+
type,
|
|
214
|
+
text: line
|
|
215
|
+
});
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
// Trim to max lines
|
|
219
|
+
if (metadata.output.length > this.maxOutputLines) {
|
|
220
|
+
metadata.output = metadata.output.slice(-this.maxOutputLines);
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
this.emit('output', { id, type, text });
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
/**
|
|
227
|
+
* Handle process exit
|
|
228
|
+
*/
|
|
229
|
+
handleProcessExit(id, code, signal) {
|
|
230
|
+
const metadata = this.processes.get(id);
|
|
231
|
+
if (!metadata) return;
|
|
232
|
+
|
|
233
|
+
metadata.exitCode = code;
|
|
234
|
+
metadata.exitSignal = signal;
|
|
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
|
+
}
|
|
241
|
+
|
|
242
|
+
this.emit('process-exited', {
|
|
243
|
+
id,
|
|
244
|
+
name: metadata.name,
|
|
245
|
+
code,
|
|
246
|
+
signal,
|
|
247
|
+
status: metadata.status
|
|
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);
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
/**
|
|
261
|
+
* Handle process error
|
|
262
|
+
*/
|
|
263
|
+
handleProcessError(id, error) {
|
|
264
|
+
const metadata = this.processes.get(id);
|
|
265
|
+
if (!metadata) return;
|
|
266
|
+
|
|
267
|
+
metadata.status = 'crashed';
|
|
268
|
+
this.appendOutput(id, 'stderr', `Process error: ${error.message}`);
|
|
269
|
+
|
|
270
|
+
this.emit('process-error', { id, name: metadata.name, error });
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
/**
|
|
274
|
+
* Clean up finished processes
|
|
275
|
+
* Removes processes with status: exited, crashed, or stopped
|
|
276
|
+
*/
|
|
277
|
+
cleanupFinished() {
|
|
278
|
+
const finished = this.getAllProcesses().filter(
|
|
279
|
+
p => p.status === 'exited' || p.status === 'crashed' || p.status === 'stopped'
|
|
280
|
+
);
|
|
281
|
+
|
|
282
|
+
for (const process of finished) {
|
|
283
|
+
this.processes.delete(process.id);
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
return finished.length;
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
/**
|
|
290
|
+
* Stop all running processes
|
|
291
|
+
*/
|
|
292
|
+
stopAll() {
|
|
293
|
+
const running = this.getRunningProcesses();
|
|
294
|
+
|
|
295
|
+
for (const process of running) {
|
|
296
|
+
this.stopProcess(process.id);
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
return running.length;
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
/**
|
|
303
|
+
* Get process uptime in seconds
|
|
304
|
+
*/
|
|
305
|
+
getUptime(id) {
|
|
306
|
+
const metadata = this.processes.get(id);
|
|
307
|
+
if (!metadata) return 0;
|
|
308
|
+
|
|
309
|
+
const start = new Date(metadata.startTime);
|
|
310
|
+
const now = new Date();
|
|
311
|
+
return Math.floor((now - start) / 1000);
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
/**
|
|
315
|
+
* Format uptime as human-readable string
|
|
316
|
+
*/
|
|
317
|
+
formatUptime(seconds) {
|
|
318
|
+
if (seconds < 60) return `${seconds}s`;
|
|
319
|
+
if (seconds < 3600) return `${Math.floor(seconds / 60)}m ${seconds % 60}s`;
|
|
320
|
+
|
|
321
|
+
const hours = Math.floor(seconds / 3600);
|
|
322
|
+
const minutes = Math.floor((seconds % 3600) / 60);
|
|
323
|
+
return `${hours}h ${minutes}m`;
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
/**
|
|
327
|
+
* Sanitize name for use as ID prefix
|
|
328
|
+
*/
|
|
329
|
+
sanitizeName(name) {
|
|
330
|
+
return name.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-+|-+$/g, '');
|
|
331
|
+
}
|
|
332
|
+
}
|