@agile-vibe-coding/avc 0.2.3 → 0.3.2

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 (262) hide show
  1. package/README.md +475 -3
  2. package/cli/agents/agent-selector.md +23 -0
  3. package/cli/agents/code-implementer.md +117 -0
  4. package/cli/agents/code-validator.md +80 -0
  5. package/cli/agents/context-reviewer-epic.md +101 -0
  6. package/cli/agents/context-reviewer-story.md +92 -0
  7. package/cli/agents/context-writer-epic.md +145 -0
  8. package/cli/agents/context-writer-story.md +111 -0
  9. package/cli/agents/doc-writer-epic.md +42 -0
  10. package/cli/agents/doc-writer-story.md +43 -0
  11. package/cli/agents/duplicate-detector.md +110 -0
  12. package/cli/agents/epic-story-decomposer.md +318 -39
  13. package/cli/agents/mission-scope-generator.md +68 -4
  14. package/cli/agents/mission-scope-validator.md +40 -6
  15. package/cli/agents/project-context-extractor.md +21 -6
  16. package/cli/agents/scaffolding-generator.md +99 -0
  17. package/cli/agents/seed-validator.md +71 -0
  18. package/cli/agents/story-scope-reviewer.md +147 -0
  19. package/cli/agents/story-splitter.md +83 -0
  20. package/cli/agents/validator-documentation.json +31 -0
  21. package/cli/agents/validator-documentation.md +3 -1
  22. package/cli/api-reference-tool.js +368 -0
  23. package/cli/checks/catalog.json +76 -0
  24. package/cli/checks/code/quality.json +26 -0
  25. package/cli/checks/code/testing.json +14 -0
  26. package/cli/checks/code/traceability.json +26 -0
  27. package/cli/checks/cross-refs/epic.json +171 -0
  28. package/cli/checks/cross-refs/story.json +149 -0
  29. package/cli/checks/epic/api.json +114 -0
  30. package/cli/checks/epic/backend.json +126 -0
  31. package/cli/checks/epic/cloud.json +126 -0
  32. package/cli/checks/epic/data.json +102 -0
  33. package/cli/checks/epic/database.json +114 -0
  34. package/cli/checks/epic/developer.json +182 -0
  35. package/cli/checks/epic/devops.json +174 -0
  36. package/cli/checks/epic/frontend.json +162 -0
  37. package/cli/checks/epic/mobile.json +102 -0
  38. package/cli/checks/epic/qa.json +90 -0
  39. package/cli/checks/epic/security.json +184 -0
  40. package/cli/checks/epic/solution-architect.json +192 -0
  41. package/cli/checks/epic/test-architect.json +90 -0
  42. package/cli/checks/epic/ui.json +102 -0
  43. package/cli/checks/epic/ux.json +90 -0
  44. package/cli/checks/fixes/epic-fix-template.md +10 -0
  45. package/cli/checks/fixes/story-fix-template.md +10 -0
  46. package/cli/checks/story/api.json +186 -0
  47. package/cli/checks/story/backend.json +102 -0
  48. package/cli/checks/story/cloud.json +102 -0
  49. package/cli/checks/story/data.json +210 -0
  50. package/cli/checks/story/database.json +102 -0
  51. package/cli/checks/story/developer.json +168 -0
  52. package/cli/checks/story/devops.json +102 -0
  53. package/cli/checks/story/frontend.json +174 -0
  54. package/cli/checks/story/mobile.json +102 -0
  55. package/cli/checks/story/qa.json +210 -0
  56. package/cli/checks/story/security.json +198 -0
  57. package/cli/checks/story/solution-architect.json +230 -0
  58. package/cli/checks/story/test-architect.json +210 -0
  59. package/cli/checks/story/ui.json +102 -0
  60. package/cli/checks/story/ux.json +102 -0
  61. package/cli/coding-order.js +401 -0
  62. package/cli/dependency-checker.js +72 -0
  63. package/cli/epic-story-validator.js +284 -799
  64. package/cli/index.js +0 -0
  65. package/cli/init-model-config.js +17 -10
  66. package/cli/init.js +514 -92
  67. package/cli/kanban-server-manager.js +1 -2
  68. package/cli/llm-claude.js +98 -31
  69. package/cli/llm-gemini.js +29 -5
  70. package/cli/llm-local.js +493 -0
  71. package/cli/llm-openai.js +262 -41
  72. package/cli/llm-provider.js +147 -8
  73. package/cli/llm-token-limits.js +113 -4
  74. package/cli/llm-verifier.js +209 -1
  75. package/cli/llm-xiaomi.js +143 -0
  76. package/cli/message-constants.js +3 -12
  77. package/cli/messaging-api.js +6 -12
  78. package/cli/micro-check-fixer.js +335 -0
  79. package/cli/micro-check-runner.js +449 -0
  80. package/cli/micro-check-scorer.js +148 -0
  81. package/cli/micro-check-validator.js +538 -0
  82. package/cli/model-pricing.js +23 -0
  83. package/cli/model-selector.js +3 -2
  84. package/cli/prompt-logger.js +57 -0
  85. package/cli/repl-ink.js +106 -346
  86. package/cli/repl-old.js +1 -2
  87. package/cli/seed-processor.js +194 -24
  88. package/cli/sprint-planning-processor.js +2638 -289
  89. package/cli/template-processor.js +50 -3
  90. package/cli/token-tracker.js +50 -23
  91. package/cli/tools/generate-story-validators.js +1 -1
  92. package/cli/validation-router.js +70 -8
  93. package/cli/worktree-runner.js +654 -0
  94. package/kanban/client/dist/assets/index-D_KC5EQT.css +1 -0
  95. package/kanban/client/dist/assets/index-DjY5zqW7.js +351 -0
  96. package/kanban/client/dist/index.html +2 -2
  97. package/kanban/client/src/App.jsx +43 -14
  98. package/kanban/client/src/components/ceremony/AskArchPopup.jsx +7 -3
  99. package/kanban/client/src/components/ceremony/AskModelPopup.jsx +23 -10
  100. package/kanban/client/src/components/ceremony/CeremonyWorkflowModal.jsx +320 -133
  101. package/kanban/client/src/components/ceremony/ProviderSwitcherButton.jsx +290 -0
  102. package/kanban/client/src/components/ceremony/SponsorCallModal.jsx +80 -13
  103. package/kanban/client/src/components/ceremony/SprintPlanningModal.jsx +156 -22
  104. package/kanban/client/src/components/ceremony/steps/ArchitectureStep.jsx +11 -11
  105. package/kanban/client/src/components/ceremony/steps/CompleteStep.jsx +3 -21
  106. package/kanban/client/src/components/ceremony/steps/ReviewAnswersStep.jsx +214 -10
  107. package/kanban/client/src/components/ceremony/steps/RunningStep.jsx +23 -2
  108. package/kanban/client/src/components/kanban/CardDetailModal.jsx +97 -10
  109. package/kanban/client/src/components/kanban/GroupingSelector.jsx +7 -1
  110. package/kanban/client/src/components/kanban/KanbanCard.jsx +23 -14
  111. package/kanban/client/src/components/kanban/RefineWorkItemPopup.jsx +9 -14
  112. package/kanban/client/src/components/kanban/RunButton.jsx +162 -0
  113. package/kanban/client/src/components/kanban/SeedButton.jsx +176 -0
  114. package/kanban/client/src/components/settings/AgentsTab.jsx +103 -75
  115. package/kanban/client/src/components/settings/ApiKeysTab.jsx +31 -2
  116. package/kanban/client/src/components/settings/CeremonyModelsTab.jsx +9 -2
  117. package/kanban/client/src/components/settings/CheckEditorPopup.jsx +507 -0
  118. package/kanban/client/src/components/settings/CostThresholdsTab.jsx +3 -2
  119. package/kanban/client/src/components/settings/ModelPricingTab.jsx +72 -7
  120. package/kanban/client/src/components/settings/OpenAIAuthSection.jsx +412 -0
  121. package/kanban/client/src/components/settings/SettingsModal.jsx +4 -4
  122. package/kanban/client/src/components/stats/CostModal.jsx +34 -3
  123. package/kanban/client/src/hooks/useGrouping.js +59 -0
  124. package/kanban/client/src/lib/api.js +118 -4
  125. package/kanban/client/src/lib/status-grouping.js +10 -0
  126. package/kanban/client/src/store/kanbanStore.js +8 -0
  127. package/kanban/server/index.js +23 -2
  128. package/kanban/server/routes/ceremony.js +153 -4
  129. package/kanban/server/routes/costs.js +9 -3
  130. package/kanban/server/routes/openai-oauth.js +366 -0
  131. package/kanban/server/routes/settings.js +447 -14
  132. package/kanban/server/routes/websocket.js +7 -2
  133. package/kanban/server/routes/work-items.js +141 -1
  134. package/kanban/server/services/CeremonyService.js +275 -24
  135. package/kanban/server/services/TaskRunnerService.js +261 -0
  136. package/kanban/server/workers/run-task-worker.js +121 -0
  137. package/kanban/server/workers/seed-worker.js +94 -0
  138. package/kanban/server/workers/sponsor-call-worker.js +14 -6
  139. package/kanban/server/workers/sprint-planning-worker.js +94 -12
  140. package/package.json +2 -3
  141. package/cli/agents/solver-epic-api.json +0 -15
  142. package/cli/agents/solver-epic-api.md +0 -39
  143. package/cli/agents/solver-epic-backend.json +0 -15
  144. package/cli/agents/solver-epic-backend.md +0 -39
  145. package/cli/agents/solver-epic-cloud.json +0 -15
  146. package/cli/agents/solver-epic-cloud.md +0 -39
  147. package/cli/agents/solver-epic-data.json +0 -15
  148. package/cli/agents/solver-epic-data.md +0 -39
  149. package/cli/agents/solver-epic-database.json +0 -15
  150. package/cli/agents/solver-epic-database.md +0 -39
  151. package/cli/agents/solver-epic-developer.json +0 -15
  152. package/cli/agents/solver-epic-developer.md +0 -39
  153. package/cli/agents/solver-epic-devops.json +0 -15
  154. package/cli/agents/solver-epic-devops.md +0 -39
  155. package/cli/agents/solver-epic-frontend.json +0 -15
  156. package/cli/agents/solver-epic-frontend.md +0 -39
  157. package/cli/agents/solver-epic-mobile.json +0 -15
  158. package/cli/agents/solver-epic-mobile.md +0 -39
  159. package/cli/agents/solver-epic-qa.json +0 -15
  160. package/cli/agents/solver-epic-qa.md +0 -39
  161. package/cli/agents/solver-epic-security.json +0 -15
  162. package/cli/agents/solver-epic-security.md +0 -39
  163. package/cli/agents/solver-epic-solution-architect.json +0 -15
  164. package/cli/agents/solver-epic-solution-architect.md +0 -39
  165. package/cli/agents/solver-epic-test-architect.json +0 -15
  166. package/cli/agents/solver-epic-test-architect.md +0 -39
  167. package/cli/agents/solver-epic-ui.json +0 -15
  168. package/cli/agents/solver-epic-ui.md +0 -39
  169. package/cli/agents/solver-epic-ux.json +0 -15
  170. package/cli/agents/solver-epic-ux.md +0 -39
  171. package/cli/agents/solver-story-api.json +0 -15
  172. package/cli/agents/solver-story-api.md +0 -39
  173. package/cli/agents/solver-story-backend.json +0 -15
  174. package/cli/agents/solver-story-backend.md +0 -39
  175. package/cli/agents/solver-story-cloud.json +0 -15
  176. package/cli/agents/solver-story-cloud.md +0 -39
  177. package/cli/agents/solver-story-data.json +0 -15
  178. package/cli/agents/solver-story-data.md +0 -39
  179. package/cli/agents/solver-story-database.json +0 -15
  180. package/cli/agents/solver-story-database.md +0 -39
  181. package/cli/agents/solver-story-developer.json +0 -15
  182. package/cli/agents/solver-story-developer.md +0 -39
  183. package/cli/agents/solver-story-devops.json +0 -15
  184. package/cli/agents/solver-story-devops.md +0 -39
  185. package/cli/agents/solver-story-frontend.json +0 -15
  186. package/cli/agents/solver-story-frontend.md +0 -39
  187. package/cli/agents/solver-story-mobile.json +0 -15
  188. package/cli/agents/solver-story-mobile.md +0 -39
  189. package/cli/agents/solver-story-qa.json +0 -15
  190. package/cli/agents/solver-story-qa.md +0 -39
  191. package/cli/agents/solver-story-security.json +0 -15
  192. package/cli/agents/solver-story-security.md +0 -39
  193. package/cli/agents/solver-story-solution-architect.json +0 -15
  194. package/cli/agents/solver-story-solution-architect.md +0 -39
  195. package/cli/agents/solver-story-test-architect.json +0 -15
  196. package/cli/agents/solver-story-test-architect.md +0 -39
  197. package/cli/agents/solver-story-ui.json +0 -15
  198. package/cli/agents/solver-story-ui.md +0 -39
  199. package/cli/agents/solver-story-ux.json +0 -15
  200. package/cli/agents/solver-story-ux.md +0 -39
  201. package/cli/agents/validator-epic-api.json +0 -93
  202. package/cli/agents/validator-epic-api.md +0 -137
  203. package/cli/agents/validator-epic-backend.json +0 -93
  204. package/cli/agents/validator-epic-backend.md +0 -130
  205. package/cli/agents/validator-epic-cloud.json +0 -93
  206. package/cli/agents/validator-epic-cloud.md +0 -137
  207. package/cli/agents/validator-epic-data.json +0 -93
  208. package/cli/agents/validator-epic-data.md +0 -130
  209. package/cli/agents/validator-epic-database.json +0 -93
  210. package/cli/agents/validator-epic-database.md +0 -137
  211. package/cli/agents/validator-epic-developer.json +0 -74
  212. package/cli/agents/validator-epic-developer.md +0 -153
  213. package/cli/agents/validator-epic-devops.json +0 -74
  214. package/cli/agents/validator-epic-devops.md +0 -153
  215. package/cli/agents/validator-epic-frontend.json +0 -74
  216. package/cli/agents/validator-epic-frontend.md +0 -153
  217. package/cli/agents/validator-epic-mobile.json +0 -93
  218. package/cli/agents/validator-epic-mobile.md +0 -130
  219. package/cli/agents/validator-epic-qa.json +0 -93
  220. package/cli/agents/validator-epic-qa.md +0 -130
  221. package/cli/agents/validator-epic-security.json +0 -74
  222. package/cli/agents/validator-epic-security.md +0 -154
  223. package/cli/agents/validator-epic-solution-architect.json +0 -74
  224. package/cli/agents/validator-epic-solution-architect.md +0 -156
  225. package/cli/agents/validator-epic-test-architect.json +0 -93
  226. package/cli/agents/validator-epic-test-architect.md +0 -130
  227. package/cli/agents/validator-epic-ui.json +0 -93
  228. package/cli/agents/validator-epic-ui.md +0 -130
  229. package/cli/agents/validator-epic-ux.json +0 -93
  230. package/cli/agents/validator-epic-ux.md +0 -130
  231. package/cli/agents/validator-story-api.json +0 -104
  232. package/cli/agents/validator-story-api.md +0 -152
  233. package/cli/agents/validator-story-backend.json +0 -104
  234. package/cli/agents/validator-story-backend.md +0 -152
  235. package/cli/agents/validator-story-cloud.json +0 -104
  236. package/cli/agents/validator-story-cloud.md +0 -152
  237. package/cli/agents/validator-story-data.json +0 -104
  238. package/cli/agents/validator-story-data.md +0 -152
  239. package/cli/agents/validator-story-database.json +0 -104
  240. package/cli/agents/validator-story-database.md +0 -152
  241. package/cli/agents/validator-story-developer.json +0 -104
  242. package/cli/agents/validator-story-developer.md +0 -152
  243. package/cli/agents/validator-story-devops.json +0 -104
  244. package/cli/agents/validator-story-devops.md +0 -152
  245. package/cli/agents/validator-story-frontend.json +0 -104
  246. package/cli/agents/validator-story-frontend.md +0 -152
  247. package/cli/agents/validator-story-mobile.json +0 -104
  248. package/cli/agents/validator-story-mobile.md +0 -152
  249. package/cli/agents/validator-story-qa.json +0 -104
  250. package/cli/agents/validator-story-qa.md +0 -152
  251. package/cli/agents/validator-story-security.json +0 -104
  252. package/cli/agents/validator-story-security.md +0 -152
  253. package/cli/agents/validator-story-solution-architect.json +0 -104
  254. package/cli/agents/validator-story-solution-architect.md +0 -152
  255. package/cli/agents/validator-story-test-architect.json +0 -104
  256. package/cli/agents/validator-story-test-architect.md +0 -152
  257. package/cli/agents/validator-story-ui.json +0 -104
  258. package/cli/agents/validator-story-ui.md +0 -152
  259. package/cli/agents/validator-story-ux.json +0 -104
  260. package/cli/agents/validator-story-ux.md +0 -152
  261. package/kanban/client/dist/assets/index-CiD8PS2e.js +0 -306
  262. package/kanban/client/dist/assets/index-nLh0m82Q.css +0 -1
@@ -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
+ }
@@ -96,6 +96,29 @@ export const MODEL_PRICING = {
96
96
  unit: 1000000,
97
97
  displayName: 'GPT-4o Mini',
98
98
  provider: 'openai'
99
+ },
100
+
101
+ // Xiaomi MiMo models
102
+ 'mimo-v2-flash': {
103
+ input: 0.09,
104
+ output: 0.29,
105
+ unit: 1000000,
106
+ displayName: 'MiMo V2 Flash',
107
+ provider: 'xiaomi'
108
+ },
109
+ 'mimo-v2-pro': {
110
+ input: 1.0,
111
+ output: 3.0,
112
+ unit: 1000000,
113
+ displayName: 'MiMo V2 Pro',
114
+ provider: 'xiaomi'
115
+ },
116
+ 'mimo-v2-omni': {
117
+ input: 0.40,
118
+ output: 2.0,
119
+ unit: 1000000,
120
+ displayName: 'MiMo V2 Omni',
121
+ provider: 'xiaomi'
99
122
  }
100
123
  };
101
124
 
@@ -46,8 +46,9 @@ function displayEnvironmentCheck() {
46
46
 
47
47
  const keys = {
48
48
  'ANTHROPIC_API_KEY': !!process.env.ANTHROPIC_API_KEY,
49
- 'OPENAI_API_KEY': !!process.env.OPENAI_API_KEY,
50
- 'GEMINI_API_KEY': !!process.env.GEMINI_API_KEY
49
+ 'OPENAI_API_KEY': !!(process.env.OPENAI_API_KEY || process.env.OPENAI_OAUTH_TOKEN),
50
+ 'GEMINI_API_KEY': !!process.env.GEMINI_API_KEY,
51
+ 'XIAOMI_API_KEY': !!process.env.XIAOMI_API_KEY,
51
52
  };
52
53
 
53
54
  for (const [key, available] of Object.entries(keys)) {
@@ -0,0 +1,57 @@
1
+ import fs from 'fs';
2
+ import path from 'path';
3
+
4
+ export class PromptLogger {
5
+ /**
6
+ * @param {string} projectRoot - absolute path to .avc parent
7
+ * @param {string} ceremony - e.g. 'sprint-planning', 'sponsor-call'
8
+ */
9
+ constructor(projectRoot, ceremony) {
10
+ this.ceremony = ceremony;
11
+ this.callCount = 0;
12
+ this.runDir = null;
13
+
14
+ const avcDir = path.join(projectRoot, '.avc');
15
+ if (!fs.existsSync(avcDir)) return; // not an AVC project — silently skip
16
+
17
+ const runTs = new Date().toISOString().replace(/:/g, '-').replace(/\..+/, '');
18
+ this.runDir = path.join(avcDir, 'logs', 'prompts', `${ceremony}-${runTs}`);
19
+ fs.mkdirSync(this.runDir, { recursive: true });
20
+
21
+ this._pruneOldRuns(path.join(avcDir, 'logs', 'prompts'), ceremony, 5);
22
+ }
23
+
24
+ /**
25
+ * Write one call record. Called by _trackTokens in LLMProvider.
26
+ * @param {object} payload
27
+ */
28
+ write(payload) {
29
+ if (!this.runDir) return;
30
+ this.callCount += 1;
31
+ const seq = String(this.callCount).padStart(3, '0');
32
+ const stage = (payload.stage || 'unknown').replace(/\s+/g, '-');
33
+ const timeStr = new Date().toISOString().substring(11, 19).replace(/:/g, '-');
34
+ const filename = `${seq}-${stage}-${timeStr}.json`;
35
+ try {
36
+ fs.writeFileSync(
37
+ path.join(this.runDir, filename),
38
+ JSON.stringify(payload, null, 2),
39
+ 'utf8'
40
+ );
41
+ } catch { /* non-fatal */ }
42
+ }
43
+
44
+ _pruneOldRuns(promptsDir, ceremony, keep) {
45
+ if (!fs.existsSync(promptsDir)) return;
46
+ const dirs = fs.readdirSync(promptsDir)
47
+ .filter(d => d.startsWith(`${ceremony}-`))
48
+ .map(d => ({ name: d, full: path.join(promptsDir, d) }))
49
+ .sort((a, b) => a.name.localeCompare(b.name)); // oldest first
50
+ while (dirs.length >= keep) {
51
+ const oldest = dirs.shift();
52
+ try {
53
+ fs.rmSync(oldest.full, { recursive: true, force: true });
54
+ } catch { /* non-fatal */ }
55
+ }
56
+ }
57
+ }