@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,667 @@
1
+ import fs from 'fs';
2
+ import path from 'path';
3
+ import { fileURLToPath } from 'url';
4
+ import { loadAgent } from './agent-loader.js';
5
+
6
+ const __filename = fileURLToPath(import.meta.url);
7
+ const __dirname = path.dirname(__filename);
8
+
9
+ /**
10
+ * Routes epics and stories to appropriate domain validators
11
+ *
12
+ * This router determines which specialized validator agents should review
13
+ * each epic and story based on domain, features, and inferred characteristics.
14
+ *
15
+ * Hybrid Approach:
16
+ * - Uses static rule-based routing for known domains (fast path)
17
+ * - Falls back to LLM-based selection for unknown/novel domains (slow path)
18
+ */
19
+ class ValidationRouter {
20
+ constructor(llmProvider = null, useSmartSelection = false, projectContext = null) {
21
+ this.epicMatrix = this.buildEpicValidationMatrix();
22
+ this.storyMatrix = this.buildStoryValidationMatrix();
23
+ this.llmProvider = llmProvider;
24
+ this.useSmartSelection = useSmartSelection;
25
+ this.projectContext = projectContext;
26
+ this.agentsPath = path.join(__dirname, 'agents');
27
+ }
28
+
29
+ /**
30
+ * Build routing matrix for epic validation
31
+ * Maps domains and features to validator agent names
32
+ */
33
+ buildEpicValidationMatrix() {
34
+ return {
35
+ // Universal validators - always check all epics
36
+ universal: [
37
+ 'validator-epic-solution-architect',
38
+ 'validator-epic-developer',
39
+ 'validator-epic-security'
40
+ ],
41
+
42
+ // Domain-specific validators
43
+ domains: {
44
+ 'infrastructure': [
45
+ 'validator-epic-devops',
46
+ 'validator-epic-cloud',
47
+ 'validator-epic-backend'
48
+ ],
49
+ 'user-management': [
50
+ 'validator-epic-backend',
51
+ 'validator-epic-database',
52
+ 'validator-epic-security',
53
+ 'validator-epic-api'
54
+ ],
55
+ 'frontend': [
56
+ 'validator-epic-frontend',
57
+ 'validator-epic-ui',
58
+ 'validator-epic-ux'
59
+ ],
60
+ 'mobile': [
61
+ 'validator-epic-mobile',
62
+ 'validator-epic-ui',
63
+ 'validator-epic-ux',
64
+ 'validator-epic-api'
65
+ ],
66
+ 'data-processing': [
67
+ 'validator-epic-data',
68
+ 'validator-epic-database',
69
+ 'validator-epic-backend'
70
+ ],
71
+ 'api': [
72
+ 'validator-epic-api',
73
+ 'validator-epic-backend',
74
+ 'validator-epic-security'
75
+ ],
76
+ 'analytics': [
77
+ 'validator-epic-data',
78
+ 'validator-epic-backend',
79
+ 'validator-epic-database'
80
+ ],
81
+ 'communication': [
82
+ 'validator-epic-backend',
83
+ 'validator-epic-api',
84
+ 'validator-epic-security'
85
+ ]
86
+ },
87
+
88
+ // Feature-specific validators
89
+ features: {
90
+ 'authentication': ['validator-epic-security'],
91
+ 'authorization': ['validator-epic-security'],
92
+ 'database': ['validator-epic-database'],
93
+ 'testing': ['validator-epic-qa', 'validator-epic-test-architect'],
94
+ 'deployment': ['validator-epic-devops', 'validator-epic-cloud'],
95
+ 'api': ['validator-epic-api'],
96
+ 'ui': ['validator-epic-ui', 'validator-epic-ux'],
97
+ 'mobile': ['validator-epic-mobile'],
98
+ 'real-time': ['validator-epic-backend', 'validator-epic-api'],
99
+ 'data-storage': ['validator-epic-database', 'validator-epic-data'],
100
+ 'logging': ['validator-epic-devops'],
101
+ 'monitoring': ['validator-epic-devops'],
102
+ 'security': ['validator-epic-security']
103
+ }
104
+ };
105
+ }
106
+
107
+ /**
108
+ * Build routing matrix for story validation
109
+ * Similar to epic matrix but includes QA and testing focus
110
+ */
111
+ buildStoryValidationMatrix() {
112
+ return {
113
+ // Universal validators - always check all stories
114
+ universal: [
115
+ 'validator-story-developer',
116
+ 'validator-story-qa',
117
+ 'validator-story-test-architect'
118
+ ],
119
+
120
+ // Domain-specific validators
121
+ domains: {
122
+ 'infrastructure': [
123
+ 'validator-story-devops',
124
+ 'validator-story-cloud',
125
+ 'validator-story-backend'
126
+ ],
127
+ 'user-management': [
128
+ 'validator-story-backend',
129
+ 'validator-story-database',
130
+ 'validator-story-security',
131
+ 'validator-story-api',
132
+ 'validator-story-ux'
133
+ ],
134
+ 'frontend': [
135
+ 'validator-story-frontend',
136
+ 'validator-story-ui',
137
+ 'validator-story-ux'
138
+ ],
139
+ 'mobile': [
140
+ 'validator-story-mobile',
141
+ 'validator-story-ui',
142
+ 'validator-story-ux'
143
+ ],
144
+ 'data-processing': [
145
+ 'validator-story-data',
146
+ 'validator-story-database',
147
+ 'validator-story-backend'
148
+ ],
149
+ 'api': [
150
+ 'validator-story-api',
151
+ 'validator-story-backend',
152
+ 'validator-story-security'
153
+ ],
154
+ 'analytics': [
155
+ 'validator-story-data',
156
+ 'validator-story-backend',
157
+ 'validator-story-database'
158
+ ],
159
+ 'communication': [
160
+ 'validator-story-backend',
161
+ 'validator-story-api',
162
+ 'validator-story-security'
163
+ ]
164
+ },
165
+
166
+ // Feature-specific validators
167
+ features: {
168
+ 'authentication': ['validator-story-security'],
169
+ 'crud-operations': ['validator-story-database', 'validator-story-api'],
170
+ 'search': ['validator-story-database', 'validator-story-backend'],
171
+ 'real-time': ['validator-story-api', 'validator-story-backend'],
172
+ 'responsive-design': ['validator-story-ui', 'validator-story-frontend'],
173
+ 'file-upload': ['validator-story-backend', 'validator-story-api'],
174
+ 'notifications': ['validator-story-backend', 'validator-story-api'],
175
+ 'reporting': ['validator-story-data', 'validator-story-backend']
176
+ }
177
+ };
178
+ }
179
+
180
+ /**
181
+ * Get list of validator agents for an epic
182
+ * @param {Object} epic - Epic work item with domain and features
183
+ * @returns {string[]} Array of validator agent names
184
+ */
185
+ getValidatorsForEpic(epic) {
186
+ const validators = new Set();
187
+
188
+ // 1. Add universal validators (always check)
189
+ this.epicMatrix.universal.forEach(v => validators.add(v));
190
+
191
+ // 2. Add domain-specific validators
192
+ const domainValidators = this.epicMatrix.domains[epic.domain] || [];
193
+ domainValidators.forEach(v => validators.add(v));
194
+
195
+ // 3. Add feature-specific validators
196
+ (epic.features || []).forEach(feature => {
197
+ const featureNormalized = feature.toLowerCase().replace(/\s+/g, '-');
198
+ const featureValidators = this.epicMatrix.features[featureNormalized] || [];
199
+ featureValidators.forEach(v => validators.add(v));
200
+ });
201
+
202
+ // Return unique validators (2-6 agents typically), filtered by project context
203
+ return this.filterByProjectContext(Array.from(validators), 'epic');
204
+ }
205
+
206
+ /**
207
+ * Get list of validator agents for a story
208
+ * @param {Object} story - Story work item
209
+ * @param {Object} epic - Parent epic for domain/feature context
210
+ * @returns {string[]} Array of validator agent names
211
+ */
212
+ getValidatorsForStory(story, epic) {
213
+ const validators = new Set();
214
+
215
+ // 1. Add universal validators
216
+ this.storyMatrix.universal.forEach(v => validators.add(v));
217
+
218
+ // 2. Inherit domain validators from epic
219
+ const domainValidators = this.storyMatrix.domains[epic.domain] || [];
220
+ domainValidators.forEach(v => validators.add(v));
221
+
222
+ // 3. Add feature-specific validators (from epic features)
223
+ (epic.features || []).forEach(feature => {
224
+ const featureNormalized = feature.toLowerCase().replace(/\s+/g, '-');
225
+ const featureValidators = this.storyMatrix.features[featureNormalized] || [];
226
+ featureValidators.forEach(v => validators.add(v));
227
+ });
228
+
229
+ // 4. Infer features from story acceptance criteria
230
+ const inferredFeatures = this.inferFeaturesFromAcceptance(story.acceptance);
231
+ inferredFeatures.forEach(feature => {
232
+ const featureValidators = this.storyMatrix.features[feature] || [];
233
+ featureValidators.forEach(v => validators.add(v));
234
+ });
235
+
236
+ // Return unique validators (3-8 agents typically), filtered by project context
237
+ return this.filterByProjectContext(Array.from(validators), 'story');
238
+ }
239
+
240
+ /**
241
+ * Infer features from story acceptance criteria text
242
+ * Uses keyword matching to detect common patterns
243
+ * @param {string[]} acceptanceCriteria - Array of acceptance criteria
244
+ * @returns {string[]} Array of inferred feature names
245
+ */
246
+ inferFeaturesFromAcceptance(acceptanceCriteria) {
247
+ const features = [];
248
+ const text = (acceptanceCriteria || []).join(' ').toLowerCase();
249
+
250
+ // Authentication patterns
251
+ if (text.includes('login') || text.includes('authenticate') || text.includes('sign in')) {
252
+ features.push('authentication');
253
+ }
254
+
255
+ // CRUD patterns
256
+ if (text.includes('create') || text.includes('update') || text.includes('delete') || text.includes('edit')) {
257
+ features.push('crud-operations');
258
+ }
259
+
260
+ // Search patterns
261
+ if (text.includes('search') || text.includes('filter') || text.includes('find')) {
262
+ features.push('search');
263
+ }
264
+
265
+ // Real-time patterns
266
+ if (text.includes('real-time') || text.includes('websocket') || text.includes('live')) {
267
+ features.push('real-time');
268
+ }
269
+
270
+ // Responsive design patterns
271
+ if (text.includes('mobile') || text.includes('responsive') || text.includes('tablet')) {
272
+ features.push('responsive-design');
273
+ }
274
+
275
+ // File upload patterns
276
+ if (text.includes('upload') || text.includes('file') || text.includes('attachment')) {
277
+ features.push('file-upload');
278
+ }
279
+
280
+ // Notification patterns
281
+ if (text.includes('notify') || text.includes('notification') || text.includes('alert')) {
282
+ features.push('notifications');
283
+ }
284
+
285
+ // Reporting patterns
286
+ if (text.includes('report') || text.includes('analytics') || text.includes('dashboard')) {
287
+ features.push('reporting');
288
+ }
289
+
290
+ return features;
291
+ }
292
+
293
+ /**
294
+ * Use LLM to select validators for an epic (fallback for unknown domains)
295
+ * @param {Object} epic - Epic work item
296
+ * @param {string} type - 'epic' or 'story'
297
+ * @returns {Promise<string[]>} Array of validator names
298
+ */
299
+ async llmSelectValidators(workItem, type = 'epic') {
300
+ if (!this.llmProvider || !this.useSmartSelection) {
301
+ return [];
302
+ }
303
+
304
+ // Load validator selector agent
305
+ let agentInstructions;
306
+ try {
307
+ agentInstructions = loadAgent('validator-selector.md');
308
+ } catch (error) {
309
+ console.warn(`Could not load validator-selector agent: ${error.message}`);
310
+ return [];
311
+ }
312
+
313
+ // Build prompt
314
+ const prompt = this.buildValidatorSelectionPrompt(workItem, type);
315
+
316
+ try {
317
+ // Call LLM
318
+ const response = await this.llmProvider.generateJSON(prompt, agentInstructions);
319
+
320
+ // Validate response structure
321
+ if (!response.validators || !Array.isArray(response.validators)) {
322
+ console.warn(`Invalid LLM response: missing validators array`);
323
+ return [];
324
+ }
325
+
326
+ // Validate validator names exist
327
+ const validValidators = response.validators.filter(v => this.isValidValidatorName(v, type));
328
+
329
+ if (validValidators.length < response.validators.length) {
330
+ const invalid = response.validators.filter(v => !this.isValidValidatorName(v, type));
331
+ console.warn(`LLM returned invalid validator names: ${invalid.join(', ')}`);
332
+ }
333
+
334
+ // Log selection reasoning
335
+ if (response.reasoning) {
336
+ console.log(` LLM selection reasoning: ${response.reasoning}`);
337
+ }
338
+
339
+ return validValidators;
340
+ } catch (error) {
341
+ console.warn(`LLM validator selection failed: ${error.message}`);
342
+ return [];
343
+ }
344
+ }
345
+
346
+ /**
347
+ * Build prompt for LLM validator selection
348
+ * @private
349
+ */
350
+ buildValidatorSelectionPrompt(workItem, type) {
351
+ const workItemType = type.charAt(0).toUpperCase() + type.slice(1);
352
+
353
+ let prompt = `Select the most relevant validators for the following ${workItemType}:\n\n`;
354
+ prompt += `**${workItemType} Name:** ${workItem.name}\n`;
355
+ prompt += `**Domain:** ${workItem.domain}\n`;
356
+
357
+ if (type === 'epic') {
358
+ prompt += `**Description:** ${workItem.description}\n`;
359
+ prompt += `**Features:** ${(workItem.features || []).join(', ')}\n`;
360
+ } else {
361
+ prompt += `**Description:** ${workItem.description}\n`;
362
+ prompt += `**User Type:** ${workItem.userType}\n`;
363
+ prompt += `**Acceptance Criteria:**\n`;
364
+ (workItem.acceptance || []).forEach((ac, i) => {
365
+ prompt += `${i + 1}. ${ac}\n`;
366
+ });
367
+ }
368
+
369
+ prompt += `\nSelect 5-8 relevant validators from the available list and return as JSON.`;
370
+
371
+ return prompt;
372
+ }
373
+
374
+ /**
375
+ * Check if a validator name is valid
376
+ * @private
377
+ */
378
+ isValidValidatorName(validatorName, type) {
379
+ const prefix = `validator-${type}-`;
380
+ if (!validatorName.startsWith(prefix)) {
381
+ return false;
382
+ }
383
+
384
+ const role = validatorName.replace(prefix, '');
385
+ const validRoles = [
386
+ 'solution-architect', 'developer', 'security', 'devops', 'cloud',
387
+ 'backend', 'database', 'api', 'frontend', 'ui', 'ux', 'mobile',
388
+ 'data', 'qa', 'test-architect'
389
+ ];
390
+
391
+ return validRoles.includes(role);
392
+ }
393
+
394
+ /**
395
+ * Filter perspectives against project context using deterministic rules.
396
+ * Removes perspectives that are provably irrelevant based on project flags,
397
+ * regardless of what the LLM selector chose. Applied as a final pass after
398
+ * any selection method (static routing, LLM, or contextual selection).
399
+ *
400
+ * @param {string[]} validators - Full validator agent names
401
+ * @param {string} type - 'epic' or 'story'
402
+ * @returns {string[]} Filtered validators
403
+ */
404
+ filterByProjectContext(validators, type) {
405
+ const ctx = this.projectContext;
406
+ if (!ctx || Object.keys(ctx).length === 0) {
407
+ console.log(` [context-filter] Skipped: projectContext is ${ctx ? 'empty' : 'null/undefined'}`);
408
+ return validators;
409
+ }
410
+
411
+ const excluded = new Set();
412
+
413
+ // Cloud: only if project explicitly uses cloud services
414
+ if (ctx.hasCloud === false) {
415
+ excluded.add('cloud');
416
+ }
417
+
418
+ // DevOps: only if project has CI/CD or uses Kubernetes/cloud orchestration
419
+ if (ctx.hasCI_CD === false && ctx.hasCloud === false) {
420
+ excluded.add('devops');
421
+ }
422
+
423
+ // Mobile: only if project has a mobile app
424
+ if (ctx.hasMobileApp === false) {
425
+ excluded.add('mobile');
426
+ }
427
+
428
+ // Frontend/UI/UX: only if project has a frontend
429
+ if (ctx.hasFrontend === false) {
430
+ excluded.add('frontend');
431
+ excluded.add('ui');
432
+ excluded.add('ux');
433
+ }
434
+
435
+ if (excluded.size === 0) {
436
+ console.log(` [context-filter] No exclusions matched (hasCloud=${ctx.hasCloud}, hasCI_CD=${ctx.hasCI_CD}, hasMobileApp=${ctx.hasMobileApp}, hasFrontend=${ctx.hasFrontend})`);
437
+ return validators;
438
+ }
439
+
440
+ const prefix = `validator-${type}-`;
441
+ const filtered = validators.filter(v => {
442
+ const role = v.startsWith(prefix) ? v.slice(prefix.length) : v;
443
+ return !excluded.has(role);
444
+ });
445
+
446
+ // Log exclusions
447
+ const removedCount = validators.length - filtered.length;
448
+ if (removedCount > 0) {
449
+ const removedRoles = validators.filter(v => !filtered.includes(v));
450
+ console.log(` [context-filter] Removed ${removedCount} irrelevant perspective(s): ${removedRoles.map(v => v.replace(prefix, '')).join(', ')}`);
451
+ }
452
+
453
+ return filtered;
454
+ }
455
+
456
+ /**
457
+ * Check if a domain is known (has predefined routing rules)
458
+ * @private
459
+ */
460
+ isDomainKnown(domain, type = 'epic') {
461
+ const matrix = type === 'epic' ? this.epicMatrix : this.storyMatrix;
462
+ return !!matrix.domains[domain];
463
+ }
464
+
465
+ /**
466
+ * Get validators for epic with hybrid approach (static + LLM fallback)
467
+ * @param {Object} epic - Epic work item
468
+ * @returns {Promise<string[]>} Array of validator names
469
+ */
470
+ async getValidatorsForEpicWithLLM(epic) {
471
+ const validators = new Set();
472
+
473
+ // 1. Always add universal validators (static)
474
+ this.epicMatrix.universal.forEach(v => validators.add(v));
475
+
476
+ // 2. Check if domain is known
477
+ const domainKnown = this.isDomainKnown(epic.domain, 'epic');
478
+
479
+ if (domainKnown) {
480
+ // Fast path: Use static routing for known domains
481
+ const domainValidators = this.epicMatrix.domains[epic.domain];
482
+ domainValidators.forEach(v => validators.add(v));
483
+ } else if (this.useSmartSelection && this.llmProvider) {
484
+ // Slow path: Use LLM for unknown domains
485
+ console.log(` 📡 Unknown domain "${epic.domain}" - using LLM selection...`);
486
+ const llmValidators = await this.llmSelectValidators(epic, 'epic');
487
+ llmValidators.forEach(v => validators.add(v));
488
+ }
489
+
490
+ // 3. Add feature-specific validators (static)
491
+ (epic.features || []).forEach(feature => {
492
+ const featureNormalized = feature.toLowerCase().replace(/\s+/g, '-');
493
+ const featureValidators = this.epicMatrix.features[featureNormalized] || [];
494
+ featureValidators.forEach(v => validators.add(v));
495
+ });
496
+
497
+ return this.filterByProjectContext(Array.from(validators), 'epic');
498
+ }
499
+
500
+ /**
501
+ * Get validators for story with hybrid approach (static + LLM fallback)
502
+ * @param {Object} story - Story work item
503
+ * @param {Object} epic - Parent epic for context
504
+ * @returns {Promise<string[]>} Array of validator names
505
+ */
506
+ async getValidatorsForStoryWithLLM(story, epic) {
507
+ const validators = new Set();
508
+
509
+ // 1. Add universal validators (static)
510
+ this.storyMatrix.universal.forEach(v => validators.add(v));
511
+
512
+ // 2. Check if domain is known
513
+ const domainKnown = this.isDomainKnown(epic.domain, 'story');
514
+
515
+ if (domainKnown) {
516
+ // Fast path: Use static routing for known domains
517
+ const domainValidators = this.storyMatrix.domains[epic.domain];
518
+ domainValidators.forEach(v => validators.add(v));
519
+ } else if (this.useSmartSelection && this.llmProvider) {
520
+ // Slow path: Use LLM for unknown domains
521
+ console.log(` 📡 Unknown domain "${epic.domain}" - using LLM selection...`);
522
+ const llmValidators = await this.llmSelectValidators(story, 'story');
523
+ llmValidators.forEach(v => validators.add(v));
524
+ }
525
+
526
+ // 3. Add feature-specific validators from epic (static)
527
+ (epic.features || []).forEach(feature => {
528
+ const featureNormalized = feature.toLowerCase().replace(/\s+/g, '-');
529
+ const featureValidators = this.storyMatrix.features[featureNormalized] || [];
530
+ featureValidators.forEach(v => validators.add(v));
531
+ });
532
+
533
+ // 4. Infer features from acceptance criteria (static)
534
+ const inferredFeatures = this.inferFeaturesFromAcceptance(story.acceptance);
535
+ inferredFeatures.forEach(feature => {
536
+ const featureValidators = this.storyMatrix.features[feature] || [];
537
+ featureValidators.forEach(v => validators.add(v));
538
+ });
539
+
540
+ return this.filterByProjectContext(Array.from(validators), 'story');
541
+ }
542
+ /**
543
+ * Select validators using project context + per-item LLM call (contextual selection).
544
+ * Falls back to static routing if LLM call fails or returns < 2 validators.
545
+ * @param {Object} item - Epic or Story work item
546
+ * @param {string} type - 'epic' or 'story'
547
+ * @param {Object} llmProvider - LLM provider instance to use for selection
548
+ * @param {Object} [parentEpic] - Parent epic (required when type='story')
549
+ * @returns {Promise<string[]>} Array of full validator agent names
550
+ */
551
+ async selectValidatorsWithContext(item, type, llmProvider, parentEpic = null) {
552
+ const validRoles = [
553
+ 'solution-architect', 'developer', 'security', 'devops', 'cloud',
554
+ 'backend', 'database', 'api', 'frontend', 'ui', 'ux', 'mobile',
555
+ 'data', 'qa', 'test-architect'
556
+ ];
557
+
558
+ let selectorAgent;
559
+ try {
560
+ selectorAgent = loadAgent('agent-selector.md');
561
+ } catch (err) {
562
+ console.warn(` ⚠ Could not load agent-selector.md (${err.message}) — using static routing`);
563
+ return type === 'epic'
564
+ ? await this.getValidatorsForEpicWithLLM(item)
565
+ : await this.getValidatorsForStoryWithLLM(item, parentEpic ?? item);
566
+ }
567
+
568
+ if (!llmProvider) {
569
+ console.warn(` ⚠ Contextual agent selection skipped (no LLM provider) — using static routing`);
570
+ return type === 'epic'
571
+ ? await this.getValidatorsForEpicWithLLM(item)
572
+ : await this.getValidatorsForStoryWithLLM(item, parentEpic ?? item);
573
+ }
574
+
575
+ const prompt = this.buildAgentSelectionPrompt(item, type, this.projectContext || {}, parentEpic);
576
+ console.log(`[DEBUG] selectValidatorsWithContext: type=${type}, item="${item.name}", projectContext keys=${Object.keys(this.projectContext || {}).join(',') || 'none'}`);
577
+
578
+ try {
579
+ const response = await llmProvider.generateJSON(prompt, selectorAgent);
580
+
581
+ if (!response?.selected || !Array.isArray(response.selected)) {
582
+ throw new Error('missing selected array in response');
583
+ }
584
+
585
+ // Filter to known role names only
586
+ const safeSelected = response.selected.filter(r => validRoles.includes(r));
587
+
588
+ // Safety floor — always include these two
589
+ if (!safeSelected.includes('solution-architect')) safeSelected.push('solution-architect');
590
+ if (!safeSelected.includes('developer')) safeSelected.push('developer');
591
+
592
+ // Log excluded roles with reasons
593
+ const excluded = response.excluded || [];
594
+ const reasons = response.reasons || {};
595
+ excluded.forEach(role => {
596
+ const reason = reasons[role] ? `: ${reasons[role]}` : '';
597
+ console.log(` ✂ Excluding ${type} ${role}${reason}`);
598
+ });
599
+
600
+ // Map short role names to full validator agent names, then filter by project context
601
+ return this.filterByProjectContext(safeSelected.map(r => `validator-${type}-${r}`), type);
602
+ } catch (err) {
603
+ console.warn(` ⚠ Contextual agent selection failed (${err.message}) — using static routing`);
604
+ return type === 'epic'
605
+ ? await this.getValidatorsForEpicWithLLM(item)
606
+ : await this.getValidatorsForStoryWithLLM(item, parentEpic ?? item);
607
+ }
608
+ }
609
+
610
+ /**
611
+ * Build the prompt for contextual agent selection.
612
+ * @param {Object} item - Epic or Story
613
+ * @param {string} type - 'epic' or 'story'
614
+ * @param {Object} projectContext - Extracted project context
615
+ * @param {Object} [parentEpic] - Parent epic for stories
616
+ * @returns {string} Prompt text
617
+ */
618
+ buildAgentSelectionPrompt(item, type, projectContext, parentEpic = null) {
619
+ const ctx = projectContext || {};
620
+ const lines = [];
621
+
622
+ // Project context section
623
+ if (Object.keys(ctx).length > 0) {
624
+ lines.push('PROJECT CONTEXT:');
625
+ if (ctx.deploymentType) lines.push(`- Deployment: ${ctx.deploymentType}${ctx.hasCloud ? ' (cloud services present)' : ' (no cloud services)'}`);
626
+ if (ctx.techStack?.length) lines.push(`- Tech stack: ${ctx.techStack.join(', ')}`);
627
+ lines.push(`- CI/CD pipeline: ${ctx.hasCI_CD ? 'yes' : 'no'}`);
628
+ lines.push(`- Mobile app: ${ctx.hasMobileApp ? 'yes' : 'no'}`);
629
+ lines.push(`- Frontend: ${ctx.hasFrontend ? 'yes' : 'no'}`);
630
+ lines.push(`- Public API: ${ctx.hasPublicAPI ? 'yes' : 'no'}`);
631
+ if (ctx.projectType) lines.push(`- Project type: ${ctx.projectType}`);
632
+ if (ctx.teamContext) lines.push(`- Team: ${ctx.teamContext}`);
633
+ lines.push('');
634
+ }
635
+
636
+ // Work item section
637
+ if (type === 'epic') {
638
+ lines.push('ITEM TO VALIDATE (Epic):');
639
+ lines.push(`- Name: ${item.name}`);
640
+ lines.push(`- Domain: ${item.domain}`);
641
+ if (item.description) lines.push(`- Description: ${item.description}`);
642
+ if (item.features?.length) {
643
+ lines.push(`- Features:`);
644
+ item.features.forEach(f => lines.push(` * ${f}`));
645
+ }
646
+ } else {
647
+ lines.push('ITEM TO VALIDATE (Story):');
648
+ lines.push(`- Name: ${item.name}`);
649
+ if (item.userType) lines.push(`- User type: ${item.userType}`);
650
+ if (item.description) lines.push(`- Description: ${item.description}`);
651
+ if (parentEpic) {
652
+ lines.push(`- Parent Epic: ${parentEpic.name} (domain: ${parentEpic.domain})`);
653
+ }
654
+ if (item.acceptance?.length) {
655
+ lines.push(`- Acceptance Criteria:`);
656
+ item.acceptance.forEach((ac, i) => lines.push(` ${i + 1}. ${ac}`));
657
+ }
658
+ }
659
+
660
+ lines.push('');
661
+ lines.push('Select which validator roles are relevant for this specific item given the project context above.');
662
+
663
+ return lines.join('\n');
664
+ }
665
+ }
666
+
667
+ export { ValidationRouter };