@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.
- package/cli/agent-loader.js +21 -0
- package/cli/agents/agent-selector.md +152 -0
- package/cli/agents/architecture-recommender.md +418 -0
- package/cli/agents/code-implementer.md +117 -0
- package/cli/agents/code-validator.md +80 -0
- package/cli/agents/context-reviewer-epic.md +101 -0
- package/cli/agents/context-reviewer-story.md +92 -0
- package/cli/agents/context-writer-epic.md +145 -0
- package/cli/agents/context-writer-story.md +111 -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/doc-writer-epic.md +42 -0
- package/cli/agents/doc-writer-story.md +43 -0
- package/cli/agents/documentation-updater.md +203 -0
- package/cli/agents/duplicate-detector.md +110 -0
- package/cli/agents/epic-story-decomposer.md +559 -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 +143 -0
- package/cli/agents/mission-scope-validator.md +146 -0
- package/cli/agents/project-context-extractor.md +122 -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/scaffolding-generator.md +99 -0
- package/cli/agents/seed-validator.md +71 -0
- package/cli/agents/story-doc-enricher.md +133 -0
- package/cli/agents/story-scope-reviewer.md +147 -0
- package/cli/agents/story-splitter.md +83 -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 +183 -0
- package/cli/agents/validator-documentation.md +455 -0
- package/cli/agents/validator-selector.md +211 -0
- package/cli/ansi-colors.js +21 -0
- package/cli/api-reference-tool.js +368 -0
- package/cli/build-docs.js +29 -8
- package/cli/ceremony-history.js +369 -0
- package/cli/checks/catalog.json +76 -0
- package/cli/checks/code/quality.json +26 -0
- package/cli/checks/code/testing.json +14 -0
- package/cli/checks/code/traceability.json +26 -0
- package/cli/checks/cross-refs/epic.json +171 -0
- package/cli/checks/cross-refs/story.json +149 -0
- package/cli/checks/epic/api.json +114 -0
- package/cli/checks/epic/backend.json +126 -0
- package/cli/checks/epic/cloud.json +126 -0
- package/cli/checks/epic/data.json +102 -0
- package/cli/checks/epic/database.json +114 -0
- package/cli/checks/epic/developer.json +182 -0
- package/cli/checks/epic/devops.json +174 -0
- package/cli/checks/epic/frontend.json +162 -0
- package/cli/checks/epic/mobile.json +102 -0
- package/cli/checks/epic/qa.json +90 -0
- package/cli/checks/epic/security.json +184 -0
- package/cli/checks/epic/solution-architect.json +192 -0
- package/cli/checks/epic/test-architect.json +90 -0
- package/cli/checks/epic/ui.json +102 -0
- package/cli/checks/epic/ux.json +90 -0
- package/cli/checks/fixes/epic-fix-template.md +10 -0
- package/cli/checks/fixes/story-fix-template.md +10 -0
- package/cli/checks/story/api.json +186 -0
- package/cli/checks/story/backend.json +102 -0
- package/cli/checks/story/cloud.json +102 -0
- package/cli/checks/story/data.json +210 -0
- package/cli/checks/story/database.json +102 -0
- package/cli/checks/story/developer.json +168 -0
- package/cli/checks/story/devops.json +102 -0
- package/cli/checks/story/frontend.json +174 -0
- package/cli/checks/story/mobile.json +102 -0
- package/cli/checks/story/qa.json +210 -0
- package/cli/checks/story/security.json +198 -0
- package/cli/checks/story/solution-architect.json +230 -0
- package/cli/checks/story/test-architect.json +210 -0
- package/cli/checks/story/ui.json +102 -0
- package/cli/checks/story/ux.json +102 -0
- package/cli/coding-order.js +401 -0
- package/cli/command-logger.js +49 -12
- package/cli/components/static-output.js +63 -0
- package/cli/console-output-manager.js +94 -0
- package/cli/dependency-checker.js +72 -0
- package/cli/docs-sync.js +306 -0
- package/cli/epic-story-validator.js +659 -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/init-model-config.js +704 -0
- package/cli/init.js +1737 -278
- package/cli/kanban-server-manager.js +227 -0
- package/cli/llm-claude.js +150 -1
- package/cli/llm-gemini.js +109 -0
- package/cli/llm-local.js +493 -0
- package/cli/llm-mock.js +233 -0
- package/cli/llm-openai.js +454 -0
- package/cli/llm-provider.js +379 -3
- package/cli/llm-token-limits.js +211 -0
- package/cli/llm-verifier.js +662 -0
- package/cli/llm-xiaomi.js +143 -0
- package/cli/message-constants.js +49 -0
- package/cli/message-manager.js +334 -0
- package/cli/message-types.js +96 -0
- package/cli/messaging-api.js +291 -0
- package/cli/micro-check-fixer.js +335 -0
- package/cli/micro-check-runner.js +449 -0
- package/cli/micro-check-scorer.js +148 -0
- package/cli/micro-check-validator.js +538 -0
- package/cli/model-pricing.js +192 -0
- package/cli/model-query-engine.js +468 -0
- package/cli/model-recommendation-analyzer.js +495 -0
- package/cli/model-selector.js +270 -0
- package/cli/output-buffer.js +107 -0
- package/cli/process-manager.js +73 -2
- package/cli/prompt-logger.js +57 -0
- package/cli/repl-ink.js +4625 -1094
- package/cli/repl-old.js +3 -4
- package/cli/seed-processor.js +962 -0
- package/cli/sprint-planning-processor.js +4162 -0
- package/cli/template-processor.js +2149 -105
- package/cli/templates/project.md +25 -8
- package/cli/templates/vitepress-config.mts.template +5 -4
- package/cli/token-tracker.js +547 -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 +667 -0
- package/cli/verification-tracker.js +563 -0
- package/cli/worktree-runner.js +654 -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-D_KC5EQT.css +1 -0
- package/kanban/client/dist/assets/index-DjY5zqW7.js +351 -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 +651 -0
- package/kanban/client/src/components/ProjectFileEditorPopup.jsx +117 -0
- package/kanban/client/src/components/ceremony/AskArchPopup.jsx +420 -0
- package/kanban/client/src/components/ceremony/AskModelPopup.jsx +629 -0
- package/kanban/client/src/components/ceremony/CeremonyWorkflowModal.jsx +1133 -0
- package/kanban/client/src/components/ceremony/EpicStorySelectionModal.jsx +254 -0
- package/kanban/client/src/components/ceremony/ProviderSwitcherButton.jsx +290 -0
- package/kanban/client/src/components/ceremony/SponsorCallModal.jsx +686 -0
- package/kanban/client/src/components/ceremony/SprintPlanningModal.jsx +838 -0
- package/kanban/client/src/components/ceremony/steps/ArchitectureStep.jsx +150 -0
- package/kanban/client/src/components/ceremony/steps/CompleteStep.jsx +136 -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 +329 -0
- package/kanban/client/src/components/ceremony/steps/RunningStep.jsx +249 -0
- package/kanban/client/src/components/kanban/CardDetailModal.jsx +646 -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 +63 -0
- package/kanban/client/src/components/kanban/KanbanBoard.jsx +211 -0
- package/kanban/client/src/components/kanban/KanbanCard.jsx +147 -0
- package/kanban/client/src/components/kanban/KanbanColumn.jsx +90 -0
- package/kanban/client/src/components/kanban/RefineWorkItemPopup.jsx +784 -0
- package/kanban/client/src/components/kanban/RunButton.jsx +162 -0
- package/kanban/client/src/components/kanban/SeedButton.jsx +176 -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 +381 -0
- package/kanban/client/src/components/settings/ApiKeysTab.jsx +142 -0
- package/kanban/client/src/components/settings/CeremonyModelsTab.jsx +105 -0
- package/kanban/client/src/components/settings/CheckEditorPopup.jsx +507 -0
- package/kanban/client/src/components/settings/CostThresholdsTab.jsx +95 -0
- package/kanban/client/src/components/settings/ModelPricingTab.jsx +269 -0
- package/kanban/client/src/components/settings/OpenAIAuthSection.jsx +412 -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 +384 -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 +177 -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 +515 -0
- package/kanban/client/src/lib/status-grouping.js +154 -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 +123 -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 +537 -0
- package/kanban/server/routes/ceremony.js +454 -0
- package/kanban/server/routes/costs.js +163 -0
- package/kanban/server/routes/openai-oauth.js +366 -0
- package/kanban/server/routes/processes.js +50 -0
- package/kanban/server/routes/settings.js +736 -0
- package/kanban/server/routes/websocket.js +281 -0
- package/kanban/server/routes/work-items.js +487 -0
- package/kanban/server/services/CeremonyService.js +1441 -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/TaskRunnerService.js +261 -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/run-task-worker.js +121 -0
- package/kanban/server/workers/seed-worker.js +94 -0
- package/kanban/server/workers/sponsor-call-worker.js +92 -0
- package/kanban/server/workers/sprint-planning-worker.js +212 -0
- package/package.json +19 -7
- package/cli/agents/documentation.md +0 -302
|
@@ -0,0 +1,659 @@
|
|
|
1
|
+
import { ValidationRouter } from './validation-router.js';
|
|
2
|
+
import { LLMProvider } from './llm-provider.js';
|
|
3
|
+
import path from 'path';
|
|
4
|
+
import fs from 'fs';
|
|
5
|
+
import { fileURLToPath } from 'url';
|
|
6
|
+
import { loadAgent } from './agent-loader.js';
|
|
7
|
+
import { validateWithMicroChecks } from './micro-check-validator.js';
|
|
8
|
+
|
|
9
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
10
|
+
const __dirname = path.dirname(__filename);
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Multi-Agent Epic and Story Validator
|
|
14
|
+
*
|
|
15
|
+
* Orchestrates validation via micro-checks: 370 small YES/NO LLM calls across
|
|
16
|
+
* 15 domain perspectives, deterministic scoring, and atomic per-check fixes.
|
|
17
|
+
*/
|
|
18
|
+
class EpicStoryValidator {
|
|
19
|
+
constructor(llmProvider, verificationTracker, stagesConfig = null, useSmartSelection = false, progressCallback = null, projectContext = null) {
|
|
20
|
+
this.llmProvider = llmProvider;
|
|
21
|
+
this.verificationTracker = verificationTracker;
|
|
22
|
+
this.projectContext = projectContext;
|
|
23
|
+
|
|
24
|
+
// Create router with smart selection support and project context
|
|
25
|
+
this.router = new ValidationRouter(llmProvider, useSmartSelection, projectContext);
|
|
26
|
+
|
|
27
|
+
this.agentsPath = path.join(__dirname, 'agents');
|
|
28
|
+
this.validationFeedback = new Map();
|
|
29
|
+
|
|
30
|
+
// Store validation stage configuration (solver stage removed — micro-checks handle fixes).
|
|
31
|
+
this.validationStageConfig = stagesConfig?.validation || null;
|
|
32
|
+
|
|
33
|
+
// Cache for validator-specific providers
|
|
34
|
+
this._validatorProviders = {};
|
|
35
|
+
|
|
36
|
+
// Smart selection flag
|
|
37
|
+
this.useSmartSelection = useSmartSelection;
|
|
38
|
+
|
|
39
|
+
// Progress callback for UI detail emissions
|
|
40
|
+
this.progressCallback = progressCallback;
|
|
41
|
+
|
|
42
|
+
// Per-call token callback (propagated to all created providers)
|
|
43
|
+
this._tokenCallback = null;
|
|
44
|
+
|
|
45
|
+
// Root project context.md string — prepended to all validation prompts when set
|
|
46
|
+
this.rootContextMd = null;
|
|
47
|
+
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Register a callback to be fired after every LLM API call made by this validator.
|
|
52
|
+
* Propagates to all provider instances created by this validator.
|
|
53
|
+
* @param {Function} fn - Receives { input, output, provider, model }
|
|
54
|
+
*/
|
|
55
|
+
setTokenCallback(fn) {
|
|
56
|
+
this._tokenCallback = fn;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Attach a PromptLogger so all providers created by this validator write payloads.
|
|
61
|
+
* @param {import('./prompt-logger.js').PromptLogger} logger
|
|
62
|
+
*/
|
|
63
|
+
setPromptLogger(logger) {
|
|
64
|
+
this._promptLogger = logger;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Set the root project context.md string — prepended to all validation prompts.
|
|
69
|
+
* @param {string} md - Canonical root context markdown
|
|
70
|
+
*/
|
|
71
|
+
setRootContextMd(md) {
|
|
72
|
+
this.rootContextMd = md;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Set callback invoked when a validator call fails with a quota/rate-limit error.
|
|
77
|
+
* Signature: async ({ validatorName, errMsg, provider, model }) => { newProvider?, newModel? }
|
|
78
|
+
* Returning { newProvider, newModel } switches the validation+solver stage config.
|
|
79
|
+
* Returning null/undefined retries with the same model.
|
|
80
|
+
*/
|
|
81
|
+
setQuotaExceededCallback(fn) {
|
|
82
|
+
this._quotaExceededCallback = fn;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Update validation stage config to a new provider/model and clear provider cache.
|
|
87
|
+
* Called after user selects "Switch Provider" in the quota-limit dialog.
|
|
88
|
+
*/
|
|
89
|
+
_updateValidationStageConfig(newProvider, newModel) {
|
|
90
|
+
const oldProvider = this.validationStageConfig?.provider;
|
|
91
|
+
const oldModel = this.validationStageConfig?.model;
|
|
92
|
+
if (!this.validationStageConfig) this.validationStageConfig = {};
|
|
93
|
+
this.validationStageConfig.provider = newProvider;
|
|
94
|
+
this.validationStageConfig.model = newModel;
|
|
95
|
+
// Surgical cache invalidation: only clear entries matching the old provider/model
|
|
96
|
+
// so providers that were already on the new target are preserved
|
|
97
|
+
if (oldProvider || oldModel) {
|
|
98
|
+
for (const [key, prov] of Object.entries(this._validatorProviders)) {
|
|
99
|
+
if (prov?._providerName === oldProvider || prov?._modelName === oldModel) {
|
|
100
|
+
delete this._validatorProviders[key];
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
} else {
|
|
104
|
+
// No previous config — clear everything
|
|
105
|
+
this._validatorProviders = {};
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Returns true if the error message indicates a quota or persistent rate-limit failure.
|
|
111
|
+
*/
|
|
112
|
+
_isQuotaOrRateLimit(errMsg) {
|
|
113
|
+
const m = (errMsg || '').toLowerCase();
|
|
114
|
+
return m.includes('429') || m.includes('quota') || m.includes('rate limit') ||
|
|
115
|
+
m.includes('resource exhausted') || m.includes('resource_exhausted') ||
|
|
116
|
+
m.includes('too many requests') ||
|
|
117
|
+
// Anthropic credit-balance exhausted (400 invalid_request_error)
|
|
118
|
+
m.includes('credit balance is too low') || m.includes('credit balance') ||
|
|
119
|
+
m.includes('billing') || m.includes('insufficient_quota');
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Generate canonical context.md string for an epic from its JSON fields.
|
|
124
|
+
* This is the bounded, structured format passed to validators and solvers.
|
|
125
|
+
* @param {Object} epic
|
|
126
|
+
* @returns {string}
|
|
127
|
+
*/
|
|
128
|
+
generateEpicContextMd(epic) {
|
|
129
|
+
const features = (epic.features || []).map(f => `- ${f}`).join('\n') || '- (none)';
|
|
130
|
+
const deps = epic.dependencies || [];
|
|
131
|
+
const optional = deps.filter(d => /optional/i.test(d));
|
|
132
|
+
const required = deps.filter(d => !/optional/i.test(d));
|
|
133
|
+
const reqLines = required.length ? required.map(d => `- ${d}`).join('\n') : '- (none)';
|
|
134
|
+
const storyCount = (epic.stories || []).length;
|
|
135
|
+
const lines = [
|
|
136
|
+
`# Epic: ${epic.name}`,
|
|
137
|
+
``,
|
|
138
|
+
`## Identity`,
|
|
139
|
+
`- id: ${epic.id || '(pending)'}`,
|
|
140
|
+
`- domain: ${epic.domain}`,
|
|
141
|
+
`- stories: ${storyCount}`,
|
|
142
|
+
``,
|
|
143
|
+
`## Summary`,
|
|
144
|
+
epic.description || '(no description)',
|
|
145
|
+
``,
|
|
146
|
+
`## Features`,
|
|
147
|
+
features,
|
|
148
|
+
``,
|
|
149
|
+
`## Dependencies`,
|
|
150
|
+
``,
|
|
151
|
+
`### Required`,
|
|
152
|
+
reqLines,
|
|
153
|
+
];
|
|
154
|
+
if (optional.length) {
|
|
155
|
+
lines.push('', '### Optional');
|
|
156
|
+
optional.forEach(d => lines.push(`- ${d}`));
|
|
157
|
+
}
|
|
158
|
+
return lines.join('\n');
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* Generate canonical context.md string for a story from its JSON fields.
|
|
163
|
+
* @param {Object} story
|
|
164
|
+
* @param {Object} epic - Parent epic for identity context
|
|
165
|
+
* @returns {string}
|
|
166
|
+
*/
|
|
167
|
+
generateStoryContextMd(story, epic) {
|
|
168
|
+
const ac = (story.acceptance || []).map((a, i) => `${i + 1}. ${a}`).join('\n') || '1. (none)';
|
|
169
|
+
const deps = (story.dependencies || []).map(d => `- ${d}`).join('\n') || '- (none)';
|
|
170
|
+
return [
|
|
171
|
+
`# Story: ${story.name}`,
|
|
172
|
+
``,
|
|
173
|
+
`## Identity`,
|
|
174
|
+
`- id: ${story.id || '(pending)'}`,
|
|
175
|
+
`- epic: ${epic.id || '(pending)'} (${epic.name})`,
|
|
176
|
+
`- userType: ${story.userType || 'team member'}`,
|
|
177
|
+
``,
|
|
178
|
+
`## Summary`,
|
|
179
|
+
story.description || '(no description)',
|
|
180
|
+
``,
|
|
181
|
+
`## Acceptance Criteria`,
|
|
182
|
+
ac,
|
|
183
|
+
``,
|
|
184
|
+
`## Dependencies`,
|
|
185
|
+
deps,
|
|
186
|
+
].join('\n');
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
/** Emit a Level-3 detail line to the UI (fire-and-forget safe) */
|
|
190
|
+
async _detail(msg) {
|
|
191
|
+
await this.progressCallback?.(null, null, { detail: msg });
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/**
|
|
195
|
+
* Wrap an async LLM call with a periodic elapsed-time heartbeat.
|
|
196
|
+
* Emits a detail message every `intervalMs` ms while the call runs,
|
|
197
|
+
* so the UI always shows activity during long LLM operations.
|
|
198
|
+
*/
|
|
199
|
+
_withHeartbeat(fn, getMsg, intervalMs = 5000) {
|
|
200
|
+
const startTime = Date.now();
|
|
201
|
+
let lastMsg = null;
|
|
202
|
+
const timer = setInterval(() => {
|
|
203
|
+
const elapsed = Math.round((Date.now() - startTime) / 1000);
|
|
204
|
+
const msg = getMsg(elapsed);
|
|
205
|
+
if (msg != null && msg !== lastMsg) {
|
|
206
|
+
lastMsg = msg;
|
|
207
|
+
this._detail(msg).catch(() => {});
|
|
208
|
+
// Also log to debug output so stuck calls are visible in log files
|
|
209
|
+
console.log(`[HEARTBEAT] ${msg} (${elapsed}s elapsed)`);
|
|
210
|
+
}
|
|
211
|
+
}, intervalMs);
|
|
212
|
+
return fn().finally(() => clearInterval(timer));
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
/**
|
|
216
|
+
* Determine validation type from validator name
|
|
217
|
+
* Returns: 'universal', 'domain', or 'feature'
|
|
218
|
+
*/
|
|
219
|
+
getValidationType(validatorName) {
|
|
220
|
+
const role = validatorName.replace(/^validator-(epic|story)-/, '');
|
|
221
|
+
|
|
222
|
+
// Universal validators (always applied)
|
|
223
|
+
const epicUniversal = ['solution-architect', 'developer', 'security'];
|
|
224
|
+
const storyUniversal = ['developer', 'qa', 'test-architect'];
|
|
225
|
+
|
|
226
|
+
if (epicUniversal.includes(role) || storyUniversal.includes(role)) {
|
|
227
|
+
return 'universal';
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
// Domain validators
|
|
231
|
+
const domainValidators = ['devops', 'cloud', 'backend', 'database', 'api',
|
|
232
|
+
'frontend', 'ui', 'ux', 'mobile', 'data'];
|
|
233
|
+
|
|
234
|
+
if (domainValidators.includes(role)) {
|
|
235
|
+
return 'domain';
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
// Everything else is feature-based
|
|
239
|
+
return 'feature';
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
/**
|
|
243
|
+
* Get provider for a specific validator based on validation type
|
|
244
|
+
* @param {string} validatorName - Validator name (e.g., 'validator-epic-security')
|
|
245
|
+
* @returns {Promise<LLMProvider>} LLM provider instance
|
|
246
|
+
*/
|
|
247
|
+
async getProviderForValidator(validatorName) {
|
|
248
|
+
const validationType = this.getValidationType(validatorName);
|
|
249
|
+
|
|
250
|
+
// Check validation-type specific configuration
|
|
251
|
+
const validationTypeConfig = this.validationStageConfig?.validationTypes?.[validationType];
|
|
252
|
+
|
|
253
|
+
let provider, model;
|
|
254
|
+
|
|
255
|
+
if (validationTypeConfig?.provider) {
|
|
256
|
+
// Use validation-type-specific config
|
|
257
|
+
provider = validationTypeConfig.provider;
|
|
258
|
+
model = validationTypeConfig.model;
|
|
259
|
+
} else if (this.validationStageConfig?.provider) {
|
|
260
|
+
// Fallback to validation stage default
|
|
261
|
+
provider = this.validationStageConfig.provider;
|
|
262
|
+
model = this.validationStageConfig.model;
|
|
263
|
+
} else {
|
|
264
|
+
// Fallback to global default (use default llmProvider)
|
|
265
|
+
return this.llmProvider;
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
// Check cache
|
|
269
|
+
const cacheKey = `${validatorName}:${provider}:${model}`;
|
|
270
|
+
if (this._validatorProviders[cacheKey]) {
|
|
271
|
+
return this._validatorProviders[cacheKey];
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
// Create new provider
|
|
275
|
+
const role = this.extractDomain(validatorName);
|
|
276
|
+
const providerInstance = await LLMProvider.create(provider, model);
|
|
277
|
+
if (this._tokenCallback) providerInstance.onCall((delta) => this._tokenCallback(delta, 'validation'));
|
|
278
|
+
if (this._promptLogger) providerInstance.setPromptLogger(this._promptLogger, `validation-${role}`);
|
|
279
|
+
this._validatorProviders[cacheKey] = providerInstance;
|
|
280
|
+
|
|
281
|
+
return providerInstance;
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
/**
|
|
285
|
+
* Get provider for contextual agent selection (agent-selector LLM call).
|
|
286
|
+
* Uses validation stage config; falls back to this.llmProvider.
|
|
287
|
+
* @returns {Promise<LLMProvider>}
|
|
288
|
+
*/
|
|
289
|
+
async getProviderForSelection() {
|
|
290
|
+
if (this.validationStageConfig?.provider && this.validationStageConfig?.model) {
|
|
291
|
+
const cacheKey = `selection:${this.validationStageConfig.provider}:${this.validationStageConfig.model}`;
|
|
292
|
+
if (this._validatorProviders[cacheKey]) return this._validatorProviders[cacheKey];
|
|
293
|
+
const instance = await LLMProvider.create(this.validationStageConfig.provider, this.validationStageConfig.model);
|
|
294
|
+
if (this._tokenCallback) instance.onCall((delta) => this._tokenCallback(delta, 'validation'));
|
|
295
|
+
if (this._promptLogger) instance.setPromptLogger(this._promptLogger, 'selection');
|
|
296
|
+
this._validatorProviders[cacheKey] = instance;
|
|
297
|
+
return instance;
|
|
298
|
+
}
|
|
299
|
+
return this.llmProvider;
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
/**
|
|
303
|
+
* Validate an Epic via micro-checks: parallel YES/NO domain checks,
|
|
304
|
+
* cross-reference consistency checks, deterministic scoring, and atomic fixes.
|
|
305
|
+
* @param {Object} epic - Epic work.json object
|
|
306
|
+
* @param {string} epicContext - Epic context.md content
|
|
307
|
+
* @returns {Object} Aggregated validation result
|
|
308
|
+
*/
|
|
309
|
+
async validateEpic(epic, epicContext) {
|
|
310
|
+
console.log(`\n🔍 Validating Epic (micro-checks): ${epic.name}`);
|
|
311
|
+
// Determine perspectives from router
|
|
312
|
+
let perspectives;
|
|
313
|
+
if (epic.metadata?.selectedValidators) {
|
|
314
|
+
perspectives = epic.metadata.selectedValidators.map(v => this.extractDomain(v));
|
|
315
|
+
} else {
|
|
316
|
+
const validators = this.router.getValidatorsForEpic(epic);
|
|
317
|
+
if (!epic.metadata) epic.metadata = {};
|
|
318
|
+
epic.metadata.selectedValidators = validators;
|
|
319
|
+
perspectives = validators.map(v => this.extractDomain(v));
|
|
320
|
+
}
|
|
321
|
+
const workItemText = epicContext || this.generateEpicContextMd(epic);
|
|
322
|
+
const mcResult = await validateWithMicroChecks(
|
|
323
|
+
epic, workItemText, 'epic', perspectives, this.llmProvider,
|
|
324
|
+
{
|
|
325
|
+
concurrency: this.validationStageConfig?.concurrency ?? 5,
|
|
326
|
+
batchSize: this.validationStageConfig?.batchSize ?? 8,
|
|
327
|
+
maxFixAttempts: this.validationStageConfig?.maxFixAttempts ?? 3,
|
|
328
|
+
generateContextFn: (wi) => this.generateEpicContextMd(wi),
|
|
329
|
+
progressCallback: this.progressCallback,
|
|
330
|
+
projectRoot: this.projectContext?.projectRoot,
|
|
331
|
+
}
|
|
332
|
+
);
|
|
333
|
+
epic.metadata = epic.metadata || {};
|
|
334
|
+
epic.metadata.validationResult = {
|
|
335
|
+
averageScore: mcResult.averageScore,
|
|
336
|
+
overallStatus: mcResult.overallStatus,
|
|
337
|
+
readyToPublish: mcResult.readyToPublish,
|
|
338
|
+
criticalIssues: mcResult.criticalIssues,
|
|
339
|
+
majorIssues: mcResult.majorIssues,
|
|
340
|
+
minorIssues: mcResult.minorIssues,
|
|
341
|
+
validatorResults: mcResult.validatorResults,
|
|
342
|
+
validatedAt: mcResult.validatedAt,
|
|
343
|
+
};
|
|
344
|
+
this.storeValidationFeedback(epic.id, mcResult);
|
|
345
|
+
return mcResult;
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
/**
|
|
349
|
+
* Validate a Story via micro-checks: parallel YES/NO domain checks,
|
|
350
|
+
* cross-reference consistency checks, deterministic scoring, and atomic fixes.
|
|
351
|
+
* @param {Object} story - Story work.json object
|
|
352
|
+
* @param {string} storyContext - Story context.md content
|
|
353
|
+
* @param {Object} epic - Parent epic for routing
|
|
354
|
+
* @returns {Object} Aggregated validation result
|
|
355
|
+
*/
|
|
356
|
+
async validateStory(story, storyContext, epic) {
|
|
357
|
+
console.log(`\n🔍 Validating Story (micro-checks): ${story.name}`);
|
|
358
|
+
let perspectives;
|
|
359
|
+
if (story.metadata?.selectedValidators) {
|
|
360
|
+
perspectives = story.metadata.selectedValidators.map(v => this.extractDomain(v));
|
|
361
|
+
} else {
|
|
362
|
+
const validators = this.router.getValidatorsForStory(story, epic);
|
|
363
|
+
if (!story.metadata) story.metadata = {};
|
|
364
|
+
story.metadata.selectedValidators = validators;
|
|
365
|
+
perspectives = validators.map(v => this.extractDomain(v));
|
|
366
|
+
}
|
|
367
|
+
const workItemText = storyContext || this.generateStoryContextMd(story, epic);
|
|
368
|
+
const mcResult = await validateWithMicroChecks(
|
|
369
|
+
story, workItemText, 'story', perspectives, this.llmProvider,
|
|
370
|
+
{
|
|
371
|
+
concurrency: this.validationStageConfig?.concurrency ?? 5,
|
|
372
|
+
batchSize: this.validationStageConfig?.batchSize ?? 8,
|
|
373
|
+
maxFixAttempts: this.validationStageConfig?.maxFixAttempts ?? 3,
|
|
374
|
+
generateContextFn: (wi) => this.generateStoryContextMd(wi, epic),
|
|
375
|
+
progressCallback: this.progressCallback,
|
|
376
|
+
projectRoot: this.projectContext?.projectRoot,
|
|
377
|
+
}
|
|
378
|
+
);
|
|
379
|
+
story.metadata = story.metadata || {};
|
|
380
|
+
story.metadata.validationResult = {
|
|
381
|
+
averageScore: mcResult.averageScore,
|
|
382
|
+
overallStatus: mcResult.overallStatus,
|
|
383
|
+
readyToPublish: mcResult.readyToPublish,
|
|
384
|
+
criticalIssues: mcResult.criticalIssues,
|
|
385
|
+
majorIssues: mcResult.majorIssues,
|
|
386
|
+
minorIssues: mcResult.minorIssues,
|
|
387
|
+
validatorResults: mcResult.validatorResults,
|
|
388
|
+
validatedAt: mcResult.validatedAt,
|
|
389
|
+
};
|
|
390
|
+
this.storeValidationFeedback(story.id, mcResult);
|
|
391
|
+
return mcResult;
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
/**
|
|
395
|
+
* Build the prompt for the story-splitter agent.
|
|
396
|
+
* @param {Object} story - The oversized story to split
|
|
397
|
+
* @param {Object} epic - Parent epic for context
|
|
398
|
+
* @param {Array} allIssues - MAJOR+CRITICAL issues from all validators
|
|
399
|
+
* @returns {string}
|
|
400
|
+
* @private
|
|
401
|
+
*/
|
|
402
|
+
_buildStorySplitterPrompt(story, epic, allIssues) {
|
|
403
|
+
const fullEpicContext = this.generateEpicContextMd(epic);
|
|
404
|
+
const epicContextMd = fullEpicContext.length > 3000
|
|
405
|
+
? fullEpicContext.substring(0, 3000) + '\n… (truncated)'
|
|
406
|
+
: fullEpicContext;
|
|
407
|
+
const storyContext = this.generateStoryContextMd(story, epic);
|
|
408
|
+
const critMajor = allIssues.filter(i => i.severity === 'critical' || i.severity === 'major');
|
|
409
|
+
const issueText = critMajor.map((issue, i) => {
|
|
410
|
+
const cat = issue.category ? `[${issue.category}] ` : '';
|
|
411
|
+
const sug = issue.suggestion ? `\n Required fix: ${issue.suggestion}` : '';
|
|
412
|
+
return `${i + 1}. [${(issue.severity || 'major').toUpperCase()}] ${cat}${issue.description || ''}${sug}`;
|
|
413
|
+
}).join('\n');
|
|
414
|
+
|
|
415
|
+
return `# Story to Split
|
|
416
|
+
|
|
417
|
+
## Parent Epic (canonical)
|
|
418
|
+
|
|
419
|
+
${epicContextMd}
|
|
420
|
+
|
|
421
|
+
## Original Story (too large — ${(story.acceptance || []).length} acceptance criteria)
|
|
422
|
+
|
|
423
|
+
${storyContext}
|
|
424
|
+
|
|
425
|
+
## Validator Issues That Triggered This Split
|
|
426
|
+
|
|
427
|
+
${issueText || 'Story exceeded the acceptance criteria size limit after multiple validation passes.'}
|
|
428
|
+
|
|
429
|
+
Split this story into 2-3 smaller independently deliverable stories.
|
|
430
|
+
Return a JSON array of story objects as specified in your instructions.
|
|
431
|
+
`;
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
/**
|
|
435
|
+
* Call the story-splitter agent to decompose an oversized story into 2-3 smaller stories.
|
|
436
|
+
* Returns an array of 2-3 new story objects, or null if the split failed/produced invalid output.
|
|
437
|
+
* @param {Object} workingStory - The fully-validated but too-large story
|
|
438
|
+
* @param {Object} epic - Parent epic for context
|
|
439
|
+
* @param {Array} allIssues - MAJOR+CRITICAL issues from all validators
|
|
440
|
+
* @returns {Promise<Array<Object>|null>}
|
|
441
|
+
*/
|
|
442
|
+
async _splitStory(workingStory, epic, allIssues) {
|
|
443
|
+
const agentInstructions = this.loadAgentInstructions('story-splitter.md');
|
|
444
|
+
const prompt = this._buildStorySplitterPrompt(workingStory, epic, allIssues);
|
|
445
|
+
const provider = await this.getProviderForValidator('story-splitter');
|
|
446
|
+
|
|
447
|
+
const _usageBefore = provider.getTokenUsage();
|
|
448
|
+
const _t0 = Date.now();
|
|
449
|
+
console.log(`[API-START] story-splitter (story="${workingStory.name}", ACs=${(workingStory.acceptance || []).length})`);
|
|
450
|
+
|
|
451
|
+
let result;
|
|
452
|
+
try {
|
|
453
|
+
result = await this._withHeartbeat(
|
|
454
|
+
() => provider.generateJSON(prompt, agentInstructions),
|
|
455
|
+
(elapsed) => {
|
|
456
|
+
if (elapsed < 20) return ` ✂ splitting story — analyzing scope boundaries…`;
|
|
457
|
+
if (elapsed < 40) return ` ✂ splitting story — assigning acceptance criteria…`;
|
|
458
|
+
return ` ✂ splitting story — still running…`;
|
|
459
|
+
},
|
|
460
|
+
10000
|
|
461
|
+
);
|
|
462
|
+
} catch (err) {
|
|
463
|
+
console.warn(` ⚠ story-splitter failed: ${err.message.split('\n')[0]}`);
|
|
464
|
+
return null;
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
const _elapsed = Date.now() - _t0;
|
|
468
|
+
const _usageAfter = provider.getTokenUsage();
|
|
469
|
+
const _deltaIn = _usageAfter.inputTokens - _usageBefore.inputTokens;
|
|
470
|
+
const _deltaOut = _usageAfter.outputTokens - _usageBefore.outputTokens;
|
|
471
|
+
console.log(`[API-DONE] story-splitter — ${_elapsed}ms | in=${_deltaIn} out=${_deltaOut} tokens`);
|
|
472
|
+
|
|
473
|
+
// Validate: result must be an array of 2-3 stories
|
|
474
|
+
if (!Array.isArray(result) || result.length < 2 || result.length > 3) {
|
|
475
|
+
console.warn(` ⚠ story-splitter returned unexpected shape (expected array of 2-3, got ${Array.isArray(result) ? result.length : typeof result}) — skipping split`);
|
|
476
|
+
return null;
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
// Validate each split story has required fields; cap ACs to 8
|
|
480
|
+
for (const s of result) {
|
|
481
|
+
if (!s.id || !s.name || !Array.isArray(s.acceptance)) {
|
|
482
|
+
console.warn(` ⚠ story-splitter returned malformed story (missing id/name/acceptance) — skipping split`);
|
|
483
|
+
return null;
|
|
484
|
+
}
|
|
485
|
+
if (s.acceptance.length > 8) {
|
|
486
|
+
s.acceptance = s.acceptance.slice(0, 8);
|
|
487
|
+
}
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
if (this.verificationTracker) {
|
|
491
|
+
this.verificationTracker.recordCheck('story-splitter', 'story-splitting', true);
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
return result;
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
/**
|
|
498
|
+
* Aggregate multiple validation results into a single report
|
|
499
|
+
* @private
|
|
500
|
+
*/
|
|
501
|
+
aggregateValidationResults(results, type) {
|
|
502
|
+
const aggregated = {
|
|
503
|
+
type: type, // 'epic' or 'story'
|
|
504
|
+
validatorCount: results.length,
|
|
505
|
+
validators: results.map(r => r._validatorName),
|
|
506
|
+
// Exclude errored validators (API failures) from the average — a 0 from a
|
|
507
|
+
// network failure is not a real score and would corrupt the aggregate.
|
|
508
|
+
averageScore: (() => {
|
|
509
|
+
const scorable = results.filter(r => !r._validatorError);
|
|
510
|
+
if (scorable.length === 0) return 0;
|
|
511
|
+
return Math.round(scorable.reduce((sum, r) => sum + (r.overallScore || 0), 0) / scorable.length);
|
|
512
|
+
})(),
|
|
513
|
+
|
|
514
|
+
// Aggregate issues by severity
|
|
515
|
+
criticalIssues: [],
|
|
516
|
+
majorIssues: [],
|
|
517
|
+
minorIssues: [],
|
|
518
|
+
|
|
519
|
+
// Aggregate strengths (deduplicated)
|
|
520
|
+
strengths: [],
|
|
521
|
+
|
|
522
|
+
// Aggregate improvement priorities (ranked by frequency)
|
|
523
|
+
improvementPriorities: [],
|
|
524
|
+
|
|
525
|
+
// Per-validator results summary
|
|
526
|
+
validatorResults: results.map(r => ({
|
|
527
|
+
validator: r._validatorName,
|
|
528
|
+
status: r.validationStatus,
|
|
529
|
+
score: r.overallScore || 0,
|
|
530
|
+
issueCount: (r.issues || []).length
|
|
531
|
+
}))
|
|
532
|
+
};
|
|
533
|
+
|
|
534
|
+
// Collect and categorize issues
|
|
535
|
+
results.forEach(result => {
|
|
536
|
+
(result.issues || []).forEach(issue => {
|
|
537
|
+
const enhancedIssue = {
|
|
538
|
+
...issue,
|
|
539
|
+
validator: result._validatorName,
|
|
540
|
+
domain: this.extractDomain(result._validatorName)
|
|
541
|
+
};
|
|
542
|
+
|
|
543
|
+
if (issue.severity === 'critical') {
|
|
544
|
+
aggregated.criticalIssues.push(enhancedIssue);
|
|
545
|
+
} else if (issue.severity === 'major') {
|
|
546
|
+
aggregated.majorIssues.push(enhancedIssue);
|
|
547
|
+
} else {
|
|
548
|
+
aggregated.minorIssues.push(enhancedIssue);
|
|
549
|
+
}
|
|
550
|
+
});
|
|
551
|
+
|
|
552
|
+
// Collect strengths (deduplicate similar ones)
|
|
553
|
+
(result.strengths || []).forEach(strength => {
|
|
554
|
+
if (!aggregated.strengths.some(s => this.isSimilar(s, strength))) {
|
|
555
|
+
aggregated.strengths.push(strength);
|
|
556
|
+
}
|
|
557
|
+
});
|
|
558
|
+
});
|
|
559
|
+
|
|
560
|
+
// Rank improvement priorities by frequency across validators
|
|
561
|
+
const priorityMap = new Map();
|
|
562
|
+
results.forEach(result => {
|
|
563
|
+
(result.improvementPriorities || []).forEach(priority => {
|
|
564
|
+
priorityMap.set(priority, (priorityMap.get(priority) || 0) + 1);
|
|
565
|
+
});
|
|
566
|
+
});
|
|
567
|
+
|
|
568
|
+
aggregated.improvementPriorities = Array.from(priorityMap.entries())
|
|
569
|
+
.sort((a, b) => b[1] - a[1]) // Sort by frequency
|
|
570
|
+
.slice(0, 5) // Top 5
|
|
571
|
+
.map(([priority, count]) => ({ priority, mentionedBy: count }));
|
|
572
|
+
|
|
573
|
+
return aggregated;
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
/**
|
|
577
|
+
* Determine overall status from multiple validators
|
|
578
|
+
* Uses "highest severity wins" approach
|
|
579
|
+
* @private
|
|
580
|
+
*/
|
|
581
|
+
determineOverallStatus(results) {
|
|
582
|
+
const statuses = results.map(r => r.validationStatus);
|
|
583
|
+
|
|
584
|
+
// If any validator errored (API failure), mark as needs-improvement so
|
|
585
|
+
// readyToPublish stays false — validation is incomplete, not "acceptable"
|
|
586
|
+
if (statuses.includes('error')) {
|
|
587
|
+
return 'needs-improvement';
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
// If any validator says "needs-improvement", overall is "needs-improvement"
|
|
591
|
+
if (statuses.includes('needs-improvement')) {
|
|
592
|
+
return 'needs-improvement';
|
|
593
|
+
}
|
|
594
|
+
|
|
595
|
+
// If all are "excellent", overall is "excellent"
|
|
596
|
+
if (statuses.every(s => s === 'excellent')) {
|
|
597
|
+
return 'excellent';
|
|
598
|
+
}
|
|
599
|
+
|
|
600
|
+
// Otherwise "acceptable"
|
|
601
|
+
return 'acceptable';
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
/**
|
|
605
|
+
* Load agent instructions from .md file
|
|
606
|
+
* @private
|
|
607
|
+
*/
|
|
608
|
+
loadAgentInstructions(filename) {
|
|
609
|
+
try {
|
|
610
|
+
return loadAgent(filename);
|
|
611
|
+
} catch (err) {
|
|
612
|
+
throw new Error(`Agent file not found: ${filename}`);
|
|
613
|
+
}
|
|
614
|
+
}
|
|
615
|
+
|
|
616
|
+
|
|
617
|
+
/**
|
|
618
|
+
* Extract domain name from validator or solver name
|
|
619
|
+
* @private
|
|
620
|
+
*/
|
|
621
|
+
extractDomain(validatorName) {
|
|
622
|
+
// Extract domain from validator/solver name
|
|
623
|
+
// e.g., "validator-epic-security" → "security"
|
|
624
|
+
// e.g., "solver-epic-security" → "security"
|
|
625
|
+
if (!validatorName) return 'unknown';
|
|
626
|
+
const match = validatorName.match(/(?:validator|solver)-(?:epic|story)-(.+)/);
|
|
627
|
+
return match ? match[1] : 'unknown';
|
|
628
|
+
}
|
|
629
|
+
|
|
630
|
+
/**
|
|
631
|
+
* Check if two strings are similar (for deduplication)
|
|
632
|
+
* @private
|
|
633
|
+
*/
|
|
634
|
+
isSimilar(str1, str2) {
|
|
635
|
+
// Simple similarity check (can be enhanced with fuzzy matching)
|
|
636
|
+
const s1 = str1.toLowerCase();
|
|
637
|
+
const s2 = str2.toLowerCase();
|
|
638
|
+
return s1.includes(s2) || s2.includes(s1);
|
|
639
|
+
}
|
|
640
|
+
|
|
641
|
+
/**
|
|
642
|
+
* Store validation feedback for learning/feedback loops
|
|
643
|
+
* @private
|
|
644
|
+
*/
|
|
645
|
+
storeValidationFeedback(workItemId, aggregatedResult) {
|
|
646
|
+
this.validationFeedback.set(workItemId, aggregatedResult);
|
|
647
|
+
}
|
|
648
|
+
|
|
649
|
+
/**
|
|
650
|
+
* Get validation feedback for a work item
|
|
651
|
+
* @param {string} workItemId - Work item ID
|
|
652
|
+
* @returns {Object|null} Aggregated validation result or null
|
|
653
|
+
*/
|
|
654
|
+
getValidationFeedback(workItemId) {
|
|
655
|
+
return this.validationFeedback.get(workItemId) || null;
|
|
656
|
+
}
|
|
657
|
+
}
|
|
658
|
+
|
|
659
|
+
export { EpicStoryValidator };
|