@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,538 @@
1
+ import { readFileSync, existsSync } from 'fs';
2
+ import path from 'path';
3
+ import { fileURLToPath } from 'url';
4
+ import { runTier1Check, runTier2Check, runTier1Batch, runTier2Batch } from './micro-check-runner.js';
5
+ import { scoreChecks } from './micro-check-scorer.js';
6
+ import { fixFailedChecks } from './micro-check-fixer.js';
7
+
8
+ const __filename = fileURLToPath(import.meta.url);
9
+ const __dirname = path.dirname(__filename);
10
+
11
+ // ---------------------------------------------------------------------------
12
+ // Debug helper — matches existing patterns in epic-story-validator.js
13
+ // ---------------------------------------------------------------------------
14
+
15
+ // ---------------------------------------------------------------------------
16
+ // Utility
17
+ // ---------------------------------------------------------------------------
18
+
19
+ function chunkArray(array, size) {
20
+ const chunks = [];
21
+ for (let i = 0; i < array.length; i += size) {
22
+ chunks.push(array.slice(i, i + size));
23
+ }
24
+ return chunks;
25
+ }
26
+
27
+ function debug(message, data = null) {
28
+ const timestamp = new Date().toISOString();
29
+ if (data) {
30
+ console.log(`[DEBUG][${timestamp}] ${message}`, JSON.stringify(data, null, 2));
31
+ } else {
32
+ console.log(`[DEBUG][${timestamp}] ${message}`);
33
+ }
34
+ }
35
+
36
+ // ---------------------------------------------------------------------------
37
+ // Check-definition loader
38
+ // ---------------------------------------------------------------------------
39
+
40
+ /**
41
+ * Load Tier 1 and Tier 2 check definitions for the requested perspectives.
42
+ *
43
+ * Resolution order per perspective:
44
+ * 1. Project override .avc/customized-agents/checks/{workItemType}/{perspective}.json
45
+ * 2. Built-in src/cli/checks/{workItemType}/{perspective}.json
46
+ *
47
+ * @param {string} workItemType - "epic" or "story"
48
+ * @param {string[]} perspectives - List of perspective names
49
+ * @param {string} [projectRoot] - Project root for override loading
50
+ * @returns {{ tier1: Object[], tier2: Object[] }}
51
+ */
52
+ function loadCheckDefinitions(workItemType, perspectives, projectRoot) {
53
+ const checks = { tier1: [], tier2: [] };
54
+ const checksDir = path.join(__dirname, 'checks');
55
+
56
+ debug('Loading check definitions', { workItemType, perspectives, checksDir });
57
+
58
+ for (const perspective of perspectives) {
59
+ // Resolve Tier 1 file — project override takes precedence
60
+ const builtinPath = path.join(checksDir, workItemType, `${perspective}.json`);
61
+ const overridePath = projectRoot
62
+ ? path.join(projectRoot, '.avc', 'customized-agents', 'checks', workItemType, `${perspective}.json`)
63
+ : null;
64
+ const filePath = (overridePath && existsSync(overridePath)) ? overridePath : builtinPath;
65
+
66
+ if (existsSync(filePath)) {
67
+ try {
68
+ const raw = JSON.parse(readFileSync(filePath, 'utf8'));
69
+ // Support both formats: plain array or { checks: [...] } wrapper
70
+ const perspectiveChecks = Array.isArray(raw) ? raw : (Array.isArray(raw.checks) ? raw.checks : []);
71
+ debug(`Loaded ${perspectiveChecks.length} Tier 1 checks from ${filePath}`);
72
+ checks.tier1.push(...perspectiveChecks);
73
+ } catch (err) {
74
+ console.error(`[micro-check-validator] Failed to parse ${filePath}: ${err.message}`);
75
+ }
76
+ } else {
77
+ debug(`No Tier 1 check file found for perspective "${perspective}" (looked at ${builtinPath})`);
78
+ }
79
+ }
80
+
81
+ // Load Tier 2 cross-reference checks
82
+ const crossRefBuiltin = path.join(checksDir, 'cross-refs', `${workItemType}.json`);
83
+ const crossRefOverride = projectRoot
84
+ ? path.join(projectRoot, '.avc', 'customized-agents', 'checks', 'cross-refs', `${workItemType}.json`)
85
+ : null;
86
+ const crossRefPath = (crossRefOverride && existsSync(crossRefOverride)) ? crossRefOverride : crossRefBuiltin;
87
+
88
+ if (existsSync(crossRefPath)) {
89
+ try {
90
+ const raw = JSON.parse(readFileSync(crossRefPath, 'utf8'));
91
+ const crossRefChecks = Array.isArray(raw) ? raw : (Array.isArray(raw.checks) ? raw.checks : []);
92
+ // Only include cross-refs whose perspectives overlap with selected perspectives
93
+ const applicable = crossRefChecks.filter(c =>
94
+ c.perspectives && c.perspectives.some(p => perspectives.includes(p))
95
+ );
96
+ debug(`Loaded ${applicable.length}/${crossRefChecks.length} Tier 2 cross-ref checks from ${crossRefPath}`);
97
+ checks.tier2.push(...applicable);
98
+ } catch (err) {
99
+ console.error(`[micro-check-validator] Failed to parse ${crossRefPath}: ${err.message}`);
100
+ }
101
+ } else {
102
+ debug(`No Tier 2 cross-ref file found (looked at ${crossRefBuiltin})`);
103
+ }
104
+
105
+ debug('Check definitions loaded', {
106
+ tier1Count: checks.tier1.length,
107
+ tier2Count: checks.tier2.length
108
+ });
109
+
110
+ return checks;
111
+ }
112
+
113
+ // ---------------------------------------------------------------------------
114
+ // Concurrency-limited parallel runner
115
+ // ---------------------------------------------------------------------------
116
+
117
+ /**
118
+ * Run an array of async task factories with a concurrency cap.
119
+ *
120
+ * @param {Array<() => Promise>} tasks - Factory functions returning promises
121
+ * @param {number} concurrency - Max parallel tasks
122
+ * @returns {Promise<any[]>}
123
+ */
124
+ async function runWithConcurrency(tasks, concurrency) {
125
+ const results = [];
126
+ const executing = new Set();
127
+
128
+ for (const task of tasks) {
129
+ const p = task().then(result => {
130
+ executing.delete(p);
131
+ return result;
132
+ });
133
+ executing.add(p);
134
+ results.push(p);
135
+
136
+ if (executing.size >= concurrency) {
137
+ await Promise.race(executing);
138
+ }
139
+ }
140
+
141
+ return Promise.all(results);
142
+ }
143
+
144
+ // ---------------------------------------------------------------------------
145
+ // Format helpers
146
+ // ---------------------------------------------------------------------------
147
+
148
+ /**
149
+ * Convert a micro-check result into the issue format used by the existing
150
+ * aggregateValidationResults() shape.
151
+ *
152
+ * @param {Object} check - A single check result object
153
+ * @returns {Object}
154
+ */
155
+ function toIssueFormat(check) {
156
+ return {
157
+ severity: check.severity,
158
+ category: check.category,
159
+ description: check.failDescription || `Failed: ${check.id}`,
160
+ suggestion: check.failSuggestion || '',
161
+ example: ''
162
+ };
163
+ }
164
+
165
+ /**
166
+ * Compute a per-perspective score by filtering allResults down to checks
167
+ * belonging to a single perspective and scoring them independently.
168
+ *
169
+ * @param {string} perspective - Perspective name
170
+ * @param {Object[]} allResults - Combined Tier 1 + Tier 2 results
171
+ * @returns {Object} scoreChecks() output for this perspective slice
172
+ */
173
+ function perspectiveSlice(perspective, allResults) {
174
+ const slice = allResults.filter(r =>
175
+ r.perspective === perspective ||
176
+ (Array.isArray(r.perspectives) && r.perspectives.includes(perspective))
177
+ );
178
+ if (slice.length === 0) {
179
+ return { score: 100, status: 'excellent', applicableCount: 0, skippedCount: 0 };
180
+ }
181
+ return scoreChecks(slice);
182
+ }
183
+
184
+ /**
185
+ * Derive issues for a single perspective from allResults.
186
+ *
187
+ * @param {string} perspective - Perspective name
188
+ * @param {Object[]} allResults - Combined Tier 1 + Tier 2 results
189
+ * @returns {Object[]}
190
+ */
191
+ function perspectiveIssues(perspective, allResults) {
192
+ return allResults
193
+ .filter(r =>
194
+ (r.perspective === perspective ||
195
+ (Array.isArray(r.perspectives) && r.perspectives.includes(perspective))) &&
196
+ r.passed === false // excludes null (errored) and true (passed)
197
+ )
198
+ .map(toIssueFormat);
199
+ }
200
+
201
+ // ---------------------------------------------------------------------------
202
+ // Main entry point
203
+ // ---------------------------------------------------------------------------
204
+
205
+ /**
206
+ * Validate a work item using the 3-tier micro-check system.
207
+ * Single flow for ALL models — decomposed checks with programmatic scoring.
208
+ *
209
+ * @param {Object} workItem - Epic or story work.json object
210
+ * @param {string} workItemText - Work item context markdown
211
+ * @param {string} workItemType - "epic" or "story"
212
+ * @param {string[]} perspectives - Perspective names to validate
213
+ * @param {Object} llmProvider - LLM provider instance
214
+ * @param {Object} [options] - Configuration options
215
+ * @param {number} [options.concurrency=5] - Max parallel LLM calls
216
+ * @param {number} [options.maxFixAttempts=3] - Max fix iterations
217
+ * @param {Function} [options.generateContextFn] - Regenerate context markdown from work item
218
+ * @param {Function} [options.progressCallback] - Progress reporting callback
219
+ * @param {string} [options.projectRoot] - Project root for check override loading
220
+ * @returns {Promise<Object>} Result compatible with existing validateEpic()/validateStory() return shape
221
+ */
222
+ export async function validateWithMicroChecks(
223
+ workItem,
224
+ workItemText,
225
+ workItemType,
226
+ perspectives,
227
+ llmProvider,
228
+ options = {}
229
+ ) {
230
+ const {
231
+ concurrency = 5,
232
+ batchSize = 8,
233
+ maxFixAttempts = 3,
234
+ generateContextFn = null,
235
+ progressCallback = null,
236
+ projectRoot = null
237
+ } = options;
238
+
239
+ const startTime = Date.now();
240
+ debug('validateWithMicroChecks START', {
241
+ workItemType,
242
+ perspectives,
243
+ concurrency,
244
+ maxFixAttempts,
245
+ hasGenerateContextFn: !!generateContextFn
246
+ });
247
+
248
+ const progress = (msg) => {
249
+ debug(msg);
250
+ if (progressCallback) {
251
+ progressCallback(msg);
252
+ }
253
+ };
254
+
255
+ // -----------------------------------------------------------------
256
+ // Step 1 — Load check definitions
257
+ // -----------------------------------------------------------------
258
+ const checks = loadCheckDefinitions(workItemType, perspectives, projectRoot);
259
+ progress(`Loaded ${checks.tier1.length} Tier 1 + ${checks.tier2.length} Tier 2 checks for ${perspectives.length} perspective(s)`);
260
+
261
+ // -----------------------------------------------------------------
262
+ // Step 2 — Phase 1: Run Tier 1 checks in parallel
263
+ // -----------------------------------------------------------------
264
+ progress('Phase 1: Running Tier 1 checks...');
265
+ const tier1Start = Date.now();
266
+
267
+ const tier1Batches = chunkArray(checks.tier1, batchSize);
268
+ debug(`Tier 1: ${checks.tier1.length} checks in ${tier1Batches.length} batch(es) of up to ${batchSize}`);
269
+
270
+ const tier1Tasks = tier1Batches.map(batch =>
271
+ () => runTier1Batch(batch, workItemText, llmProvider, workItemType).catch(err => {
272
+ debug(`Tier 1 batch error (${batch.length} checks): ${err.message}`);
273
+ // Fallback: run each check individually
274
+ return Promise.all(batch.map(check =>
275
+ runTier1Check(check, workItemText, llmProvider, workItemType).catch(indivErr => {
276
+ debug(`Tier 1 check ${check.id} individual fallback error: ${indivErr.message}`);
277
+ return {
278
+ id: check.id, tier: 1, severity: check.severity, category: check.category,
279
+ perspective: check.perspective, universal: check.universal,
280
+ applicable: true, passed: null, evidence: `Error: ${indivErr.message}`,
281
+ failDescription: check.failDescription, failSuggestion: check.failSuggestion,
282
+ };
283
+ })
284
+ ));
285
+ })
286
+ );
287
+
288
+ const tier1BatchResults = await runWithConcurrency(tier1Tasks, concurrency);
289
+ const tier1ResultsArray = tier1BatchResults.flat();
290
+
291
+ // Build Map: checkId → result
292
+ const tier1Results = new Map();
293
+ let tier1Passed = 0;
294
+ let tier1Failed = 0;
295
+ let tier1Skipped = 0;
296
+ let tier1Errored = 0;
297
+
298
+ for (const result of tier1ResultsArray) {
299
+ tier1Results.set(result.id, result);
300
+ if (result.applicable === false) {
301
+ tier1Skipped++;
302
+ } else if (result.passed === null) {
303
+ tier1Errored++;
304
+ } else if (result.passed) {
305
+ tier1Passed++;
306
+ } else {
307
+ tier1Failed++;
308
+ }
309
+ }
310
+
311
+ const tier1Elapsed = Date.now() - tier1Start;
312
+ const errorSuffix = tier1Errored > 0 ? `, ${tier1Errored} errored` : '';
313
+ progress(
314
+ `Tier 1: ${tier1ResultsArray.length}/${checks.tier1.length} checks in ${tier1Batches.length} batch(es) ` +
315
+ `(${tier1Passed} passed, ${tier1Failed} failed, ${tier1Skipped} skipped${errorSuffix}) — ${tier1Elapsed}ms`
316
+ );
317
+
318
+ // -----------------------------------------------------------------
319
+ // Step 3 — Phase 2: Run Tier 2 cross-reference checks in parallel
320
+ // -----------------------------------------------------------------
321
+ progress('Phase 2: Running Tier 2 cross-reference checks...');
322
+ const tier2Start = Date.now();
323
+
324
+ // Only run Tier 2 checks whose dependsOn checks are ALL present in tier1Results
325
+ const applicableTier2 = checks.tier2.filter(check => {
326
+ if (!Array.isArray(check.dependsOn) || check.dependsOn.length === 0) {
327
+ return true;
328
+ }
329
+ return check.dependsOn.every(depId => tier1Results.has(depId));
330
+ });
331
+
332
+ debug(`Tier 2: ${applicableTier2.length}/${checks.tier2.length} checks have satisfied dependencies`);
333
+
334
+ const tier2Batches = chunkArray(applicableTier2, batchSize);
335
+ debug(`Tier 2: ${applicableTier2.length} checks in ${tier2Batches.length} batch(es) of up to ${batchSize}`);
336
+
337
+ const tier2Tasks = tier2Batches.map(batch =>
338
+ () => runTier2Batch(batch, workItemText, tier1Results, llmProvider, workItemType).catch(err => {
339
+ debug(`Tier 2 batch error (${batch.length} checks): ${err.message}`);
340
+ // Fallback: run each check individually
341
+ return Promise.all(batch.map(check =>
342
+ runTier2Check(check, workItemText, tier1Results, llmProvider).catch(indivErr => {
343
+ debug(`Tier 2 check ${check.id} individual fallback error: ${indivErr.message}`);
344
+ return {
345
+ id: check.id, tier: 2, severity: check.severity, category: check.category,
346
+ perspectives: check.perspectives,
347
+ applicable: true, passed: null, evidence: `Error: ${indivErr.message}`,
348
+ failDescription: check.failDescription, failSuggestion: check.failSuggestion,
349
+ };
350
+ })
351
+ ));
352
+ })
353
+ );
354
+
355
+ const tier2BatchResults = await runWithConcurrency(tier2Tasks, concurrency);
356
+ const tier2ResultsArray = tier2BatchResults.flat();
357
+
358
+ let tier2Passed = 0;
359
+ let tier2Failed = 0;
360
+ let tier2Skipped = 0;
361
+ let tier2Errored = 0;
362
+
363
+ for (const result of tier2ResultsArray) {
364
+ if (result.applicable === false) {
365
+ tier2Skipped++;
366
+ } else if (result.passed === null) {
367
+ tier2Errored++;
368
+ } else if (result.passed) {
369
+ tier2Passed++;
370
+ } else {
371
+ tier2Failed++;
372
+ }
373
+ }
374
+
375
+ const tier2Elapsed = Date.now() - tier2Start;
376
+ const tier2ErrorSuffix = tier2Errored > 0 ? `, ${tier2Errored} errored` : '';
377
+ progress(
378
+ `Tier 2: ${tier2ResultsArray.length}/${applicableTier2.length} checks in ${tier2Batches.length} batch(es) ` +
379
+ `(${tier2Passed} passed, ${tier2Failed} failed, ${tier2Skipped} skipped${tier2ErrorSuffix}) — ${tier2Elapsed}ms`
380
+ );
381
+
382
+ // -----------------------------------------------------------------
383
+ // Step 4 — Phase 3: Programmatic scoring (Tier 3 patterns included)
384
+ // -----------------------------------------------------------------
385
+ progress('Phase 3: Scoring...');
386
+
387
+ let allResults = [...tier1ResultsArray, ...tier2ResultsArray];
388
+ let scored = scoreChecks(allResults);
389
+
390
+ const criticalFails = allResults.filter(r => r.passed === false && r.severity === 'critical').length;
391
+ const majorFails = allResults.filter(r => r.passed === false && r.severity === 'major').length;
392
+ const minorFails = allResults.filter(r => r.passed === false && r.severity === 'minor').length;
393
+ const totalErrored = allResults.filter(r => r.passed === null && r.applicable !== false).length;
394
+
395
+ progress(
396
+ `Score: ${scored.score}/100 (${scored.status}) — ` +
397
+ `${criticalFails} critical, ${majorFails} major, ${minorFails} minor` +
398
+ (totalErrored > 0 ? ` (${totalErrored} checks excluded due to LLM errors)` : '')
399
+ );
400
+
401
+ // Log individual failed checks for diagnostics
402
+ const failedDetails = allResults.filter(r => r.passed === false);
403
+ if (failedDetails.length > 0) {
404
+ debug('Failed checks detail', failedDetails.map(r => ({
405
+ id: r.id,
406
+ severity: r.severity,
407
+ perspective: r.perspective || r.perspectives,
408
+ evidence: (r.evidence || '').slice(0, 200),
409
+ })));
410
+ }
411
+
412
+ // -----------------------------------------------------------------
413
+ // Step 5 — Phase 4: Fix failed checks (if critical or major)
414
+ // -----------------------------------------------------------------
415
+ let fixResults = null;
416
+
417
+ if (criticalFails > 0 || majorFails > 0) {
418
+ progress('Phase 4: Attempting fixes for critical/major failures...');
419
+ const fixStart = Date.now();
420
+
421
+ // Collect failed checks sorted by severity (critical first, then major)
422
+ const severityOrder = { critical: 0, major: 1, minor: 2 };
423
+ const failedChecks = allResults
424
+ .filter(r => r.passed === false && (r.severity === 'critical' || r.severity === 'major'))
425
+ .sort((a, b) => (severityOrder[a.severity] || 99) - (severityOrder[b.severity] || 99));
426
+
427
+ debug(`Attempting fixes for ${failedChecks.length} failed checks`, {
428
+ ids: failedChecks.map(c => c.id)
429
+ });
430
+
431
+ try {
432
+ fixResults = await fixFailedChecks({
433
+ failedChecks,
434
+ allCheckDefinitions: [...checks.tier1, ...checks.tier2],
435
+ allCheckResults: allResults,
436
+ workItem,
437
+ workItemText,
438
+ workItemType,
439
+ llmProvider,
440
+ maxFixAttempts,
441
+ generateContextFn,
442
+ progressCallback: progress,
443
+ concurrency,
444
+ tier1Results,
445
+ });
446
+
447
+ const fixElapsed = Date.now() - fixStart;
448
+ debug(`Fix phase completed in ${fixElapsed}ms`, {
449
+ fixesAttempted: fixResults?.fixResults?.length || 0,
450
+ regressionDetected: fixResults?.regressionDetected || false
451
+ });
452
+
453
+ // If fixes were applied, rescore using the rerun results
454
+ if (fixResults && fixResults.rerunResults && fixResults.rerunResults.length > 0) {
455
+ progress('Rescoring after fixes...');
456
+ allResults = fixResults.rerunResults;
457
+ scored = scoreChecks(allResults);
458
+
459
+ const postFixCritical = allResults.filter(r => r.passed === false && r.severity === 'critical').length;
460
+ const postFixMajor = allResults.filter(r => r.passed === false && r.severity === 'major').length;
461
+ const postFixMinor = allResults.filter(r => r.passed === false && r.severity === 'minor').length;
462
+
463
+ progress(
464
+ `Post-fix score: ${scored.score}/100 (${scored.status}) — ` +
465
+ `${postFixCritical} critical, ${postFixMajor} major, ${postFixMinor} minor`
466
+ );
467
+ }
468
+ } catch (fixErr) {
469
+ console.error(`[micro-check-validator] Fix phase error: ${fixErr.message}`);
470
+ debug('Fix phase failed', { error: fixErr.message, stack: fixErr.stack });
471
+ }
472
+ } else {
473
+ debug('Phase 4 skipped — no critical or major failures');
474
+ }
475
+
476
+ // -----------------------------------------------------------------
477
+ // Step 6 — Phase 5: Build result in existing format
478
+ // -----------------------------------------------------------------
479
+ progress('Phase 5: Building result...');
480
+
481
+ const finalScore = scored;
482
+
483
+ // Collect final failed checks across all severities (exclude errored/skipped)
484
+ const finalFailedChecks = allResults.filter(r => r.passed === false);
485
+
486
+ // Build per-perspective validator results
487
+ const validatorResults = perspectives.map(p => {
488
+ const pScore = perspectiveSlice(p, allResults);
489
+ const pIssues = perspectiveIssues(p, allResults);
490
+ return {
491
+ validator: `micro-check-${workItemType}-${p}`,
492
+ score: pScore.score,
493
+ status: pScore.status,
494
+ issues: pIssues
495
+ };
496
+ });
497
+
498
+ const result = {
499
+ averageScore: finalScore.score,
500
+ overallStatus: finalScore.status,
501
+ readyToPublish: finalScore.status !== 'needs-improvement',
502
+ readyForImplementation: finalScore.status !== 'needs-improvement',
503
+ criticalIssues: finalFailedChecks
504
+ .filter(c => c.severity === 'critical')
505
+ .map(toIssueFormat),
506
+ majorIssues: finalFailedChecks
507
+ .filter(c => c.severity === 'major')
508
+ .map(toIssueFormat),
509
+ minorIssues: finalFailedChecks
510
+ .filter(c => c.severity === 'minor')
511
+ .map(toIssueFormat),
512
+ validatorResults,
513
+ validatedAt: new Date().toISOString(),
514
+ microCheckDetails: {
515
+ tier1Count: tier1Results.size,
516
+ tier2Count: tier2ResultsArray.length,
517
+ applicableCount: finalScore.applicableCount,
518
+ skippedCount: finalScore.skippedCount,
519
+ splitRecommendation: finalScore.splitRecommendation,
520
+ splitReason: finalScore.splitReason,
521
+ clusterWarnings: finalScore.clusterWarnings,
522
+ fixesAttempted: fixResults?.fixResults?.length || 0,
523
+ regressionDetected: fixResults?.regressionDetected || false
524
+ }
525
+ };
526
+
527
+ const totalElapsed = Date.now() - startTime;
528
+ debug('validateWithMicroChecks COMPLETE', {
529
+ score: result.averageScore,
530
+ status: result.overallStatus,
531
+ criticalIssues: result.criticalIssues.length,
532
+ majorIssues: result.majorIssues.length,
533
+ minorIssues: result.minorIssues.length,
534
+ totalElapsedMs: totalElapsed
535
+ });
536
+
537
+ return result;
538
+ }