@agile-vibe-coding/avc 0.2.3 → 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 (261) hide show
  1. package/cli/agents/agent-selector.md +23 -0
  2. package/cli/agents/code-implementer.md +117 -0
  3. package/cli/agents/code-validator.md +80 -0
  4. package/cli/agents/context-reviewer-epic.md +101 -0
  5. package/cli/agents/context-reviewer-story.md +92 -0
  6. package/cli/agents/context-writer-epic.md +145 -0
  7. package/cli/agents/context-writer-story.md +111 -0
  8. package/cli/agents/doc-writer-epic.md +42 -0
  9. package/cli/agents/doc-writer-story.md +43 -0
  10. package/cli/agents/duplicate-detector.md +110 -0
  11. package/cli/agents/epic-story-decomposer.md +318 -39
  12. package/cli/agents/mission-scope-generator.md +68 -4
  13. package/cli/agents/mission-scope-validator.md +40 -6
  14. package/cli/agents/project-context-extractor.md +21 -6
  15. package/cli/agents/scaffolding-generator.md +99 -0
  16. package/cli/agents/seed-validator.md +71 -0
  17. package/cli/agents/story-scope-reviewer.md +147 -0
  18. package/cli/agents/story-splitter.md +83 -0
  19. package/cli/agents/validator-documentation.json +31 -0
  20. package/cli/agents/validator-documentation.md +3 -1
  21. package/cli/api-reference-tool.js +368 -0
  22. package/cli/checks/catalog.json +76 -0
  23. package/cli/checks/code/quality.json +26 -0
  24. package/cli/checks/code/testing.json +14 -0
  25. package/cli/checks/code/traceability.json +26 -0
  26. package/cli/checks/cross-refs/epic.json +171 -0
  27. package/cli/checks/cross-refs/story.json +149 -0
  28. package/cli/checks/epic/api.json +114 -0
  29. package/cli/checks/epic/backend.json +126 -0
  30. package/cli/checks/epic/cloud.json +126 -0
  31. package/cli/checks/epic/data.json +102 -0
  32. package/cli/checks/epic/database.json +114 -0
  33. package/cli/checks/epic/developer.json +182 -0
  34. package/cli/checks/epic/devops.json +174 -0
  35. package/cli/checks/epic/frontend.json +162 -0
  36. package/cli/checks/epic/mobile.json +102 -0
  37. package/cli/checks/epic/qa.json +90 -0
  38. package/cli/checks/epic/security.json +184 -0
  39. package/cli/checks/epic/solution-architect.json +192 -0
  40. package/cli/checks/epic/test-architect.json +90 -0
  41. package/cli/checks/epic/ui.json +102 -0
  42. package/cli/checks/epic/ux.json +90 -0
  43. package/cli/checks/fixes/epic-fix-template.md +10 -0
  44. package/cli/checks/fixes/story-fix-template.md +10 -0
  45. package/cli/checks/story/api.json +186 -0
  46. package/cli/checks/story/backend.json +102 -0
  47. package/cli/checks/story/cloud.json +102 -0
  48. package/cli/checks/story/data.json +210 -0
  49. package/cli/checks/story/database.json +102 -0
  50. package/cli/checks/story/developer.json +168 -0
  51. package/cli/checks/story/devops.json +102 -0
  52. package/cli/checks/story/frontend.json +174 -0
  53. package/cli/checks/story/mobile.json +102 -0
  54. package/cli/checks/story/qa.json +210 -0
  55. package/cli/checks/story/security.json +198 -0
  56. package/cli/checks/story/solution-architect.json +230 -0
  57. package/cli/checks/story/test-architect.json +210 -0
  58. package/cli/checks/story/ui.json +102 -0
  59. package/cli/checks/story/ux.json +102 -0
  60. package/cli/coding-order.js +401 -0
  61. package/cli/dependency-checker.js +72 -0
  62. package/cli/epic-story-validator.js +284 -799
  63. package/cli/index.js +0 -0
  64. package/cli/init-model-config.js +17 -10
  65. package/cli/init.js +514 -92
  66. package/cli/kanban-server-manager.js +1 -2
  67. package/cli/llm-claude.js +98 -31
  68. package/cli/llm-gemini.js +29 -5
  69. package/cli/llm-local.js +493 -0
  70. package/cli/llm-openai.js +262 -41
  71. package/cli/llm-provider.js +147 -8
  72. package/cli/llm-token-limits.js +113 -4
  73. package/cli/llm-verifier.js +209 -1
  74. package/cli/llm-xiaomi.js +143 -0
  75. package/cli/message-constants.js +3 -12
  76. package/cli/messaging-api.js +6 -12
  77. package/cli/micro-check-fixer.js +335 -0
  78. package/cli/micro-check-runner.js +449 -0
  79. package/cli/micro-check-scorer.js +148 -0
  80. package/cli/micro-check-validator.js +538 -0
  81. package/cli/model-pricing.js +23 -0
  82. package/cli/model-selector.js +3 -2
  83. package/cli/prompt-logger.js +57 -0
  84. package/cli/repl-ink.js +106 -346
  85. package/cli/repl-old.js +1 -2
  86. package/cli/seed-processor.js +194 -24
  87. package/cli/sprint-planning-processor.js +2638 -289
  88. package/cli/template-processor.js +50 -3
  89. package/cli/token-tracker.js +50 -23
  90. package/cli/tools/generate-story-validators.js +1 -1
  91. package/cli/validation-router.js +70 -8
  92. package/cli/worktree-runner.js +654 -0
  93. package/kanban/client/dist/assets/index-D_KC5EQT.css +1 -0
  94. package/kanban/client/dist/assets/index-DjY5zqW7.js +351 -0
  95. package/kanban/client/dist/index.html +2 -2
  96. package/kanban/client/src/App.jsx +43 -14
  97. package/kanban/client/src/components/ceremony/AskArchPopup.jsx +7 -3
  98. package/kanban/client/src/components/ceremony/AskModelPopup.jsx +23 -10
  99. package/kanban/client/src/components/ceremony/CeremonyWorkflowModal.jsx +320 -133
  100. package/kanban/client/src/components/ceremony/ProviderSwitcherButton.jsx +290 -0
  101. package/kanban/client/src/components/ceremony/SponsorCallModal.jsx +80 -13
  102. package/kanban/client/src/components/ceremony/SprintPlanningModal.jsx +156 -22
  103. package/kanban/client/src/components/ceremony/steps/ArchitectureStep.jsx +11 -11
  104. package/kanban/client/src/components/ceremony/steps/CompleteStep.jsx +3 -21
  105. package/kanban/client/src/components/ceremony/steps/ReviewAnswersStep.jsx +214 -10
  106. package/kanban/client/src/components/ceremony/steps/RunningStep.jsx +23 -2
  107. package/kanban/client/src/components/kanban/CardDetailModal.jsx +97 -10
  108. package/kanban/client/src/components/kanban/GroupingSelector.jsx +7 -1
  109. package/kanban/client/src/components/kanban/KanbanCard.jsx +23 -14
  110. package/kanban/client/src/components/kanban/RefineWorkItemPopup.jsx +9 -14
  111. package/kanban/client/src/components/kanban/RunButton.jsx +162 -0
  112. package/kanban/client/src/components/kanban/SeedButton.jsx +176 -0
  113. package/kanban/client/src/components/settings/AgentsTab.jsx +103 -75
  114. package/kanban/client/src/components/settings/ApiKeysTab.jsx +31 -2
  115. package/kanban/client/src/components/settings/CeremonyModelsTab.jsx +9 -2
  116. package/kanban/client/src/components/settings/CheckEditorPopup.jsx +507 -0
  117. package/kanban/client/src/components/settings/CostThresholdsTab.jsx +3 -2
  118. package/kanban/client/src/components/settings/ModelPricingTab.jsx +72 -7
  119. package/kanban/client/src/components/settings/OpenAIAuthSection.jsx +412 -0
  120. package/kanban/client/src/components/settings/SettingsModal.jsx +4 -4
  121. package/kanban/client/src/components/stats/CostModal.jsx +34 -3
  122. package/kanban/client/src/hooks/useGrouping.js +59 -0
  123. package/kanban/client/src/lib/api.js +118 -4
  124. package/kanban/client/src/lib/status-grouping.js +10 -0
  125. package/kanban/client/src/store/kanbanStore.js +8 -0
  126. package/kanban/server/index.js +23 -2
  127. package/kanban/server/routes/ceremony.js +153 -4
  128. package/kanban/server/routes/costs.js +9 -3
  129. package/kanban/server/routes/openai-oauth.js +366 -0
  130. package/kanban/server/routes/settings.js +447 -14
  131. package/kanban/server/routes/websocket.js +7 -2
  132. package/kanban/server/routes/work-items.js +141 -1
  133. package/kanban/server/services/CeremonyService.js +275 -24
  134. package/kanban/server/services/TaskRunnerService.js +261 -0
  135. package/kanban/server/workers/run-task-worker.js +121 -0
  136. package/kanban/server/workers/seed-worker.js +94 -0
  137. package/kanban/server/workers/sponsor-call-worker.js +14 -6
  138. package/kanban/server/workers/sprint-planning-worker.js +94 -12
  139. package/package.json +2 -3
  140. package/cli/agents/solver-epic-api.json +0 -15
  141. package/cli/agents/solver-epic-api.md +0 -39
  142. package/cli/agents/solver-epic-backend.json +0 -15
  143. package/cli/agents/solver-epic-backend.md +0 -39
  144. package/cli/agents/solver-epic-cloud.json +0 -15
  145. package/cli/agents/solver-epic-cloud.md +0 -39
  146. package/cli/agents/solver-epic-data.json +0 -15
  147. package/cli/agents/solver-epic-data.md +0 -39
  148. package/cli/agents/solver-epic-database.json +0 -15
  149. package/cli/agents/solver-epic-database.md +0 -39
  150. package/cli/agents/solver-epic-developer.json +0 -15
  151. package/cli/agents/solver-epic-developer.md +0 -39
  152. package/cli/agents/solver-epic-devops.json +0 -15
  153. package/cli/agents/solver-epic-devops.md +0 -39
  154. package/cli/agents/solver-epic-frontend.json +0 -15
  155. package/cli/agents/solver-epic-frontend.md +0 -39
  156. package/cli/agents/solver-epic-mobile.json +0 -15
  157. package/cli/agents/solver-epic-mobile.md +0 -39
  158. package/cli/agents/solver-epic-qa.json +0 -15
  159. package/cli/agents/solver-epic-qa.md +0 -39
  160. package/cli/agents/solver-epic-security.json +0 -15
  161. package/cli/agents/solver-epic-security.md +0 -39
  162. package/cli/agents/solver-epic-solution-architect.json +0 -15
  163. package/cli/agents/solver-epic-solution-architect.md +0 -39
  164. package/cli/agents/solver-epic-test-architect.json +0 -15
  165. package/cli/agents/solver-epic-test-architect.md +0 -39
  166. package/cli/agents/solver-epic-ui.json +0 -15
  167. package/cli/agents/solver-epic-ui.md +0 -39
  168. package/cli/agents/solver-epic-ux.json +0 -15
  169. package/cli/agents/solver-epic-ux.md +0 -39
  170. package/cli/agents/solver-story-api.json +0 -15
  171. package/cli/agents/solver-story-api.md +0 -39
  172. package/cli/agents/solver-story-backend.json +0 -15
  173. package/cli/agents/solver-story-backend.md +0 -39
  174. package/cli/agents/solver-story-cloud.json +0 -15
  175. package/cli/agents/solver-story-cloud.md +0 -39
  176. package/cli/agents/solver-story-data.json +0 -15
  177. package/cli/agents/solver-story-data.md +0 -39
  178. package/cli/agents/solver-story-database.json +0 -15
  179. package/cli/agents/solver-story-database.md +0 -39
  180. package/cli/agents/solver-story-developer.json +0 -15
  181. package/cli/agents/solver-story-developer.md +0 -39
  182. package/cli/agents/solver-story-devops.json +0 -15
  183. package/cli/agents/solver-story-devops.md +0 -39
  184. package/cli/agents/solver-story-frontend.json +0 -15
  185. package/cli/agents/solver-story-frontend.md +0 -39
  186. package/cli/agents/solver-story-mobile.json +0 -15
  187. package/cli/agents/solver-story-mobile.md +0 -39
  188. package/cli/agents/solver-story-qa.json +0 -15
  189. package/cli/agents/solver-story-qa.md +0 -39
  190. package/cli/agents/solver-story-security.json +0 -15
  191. package/cli/agents/solver-story-security.md +0 -39
  192. package/cli/agents/solver-story-solution-architect.json +0 -15
  193. package/cli/agents/solver-story-solution-architect.md +0 -39
  194. package/cli/agents/solver-story-test-architect.json +0 -15
  195. package/cli/agents/solver-story-test-architect.md +0 -39
  196. package/cli/agents/solver-story-ui.json +0 -15
  197. package/cli/agents/solver-story-ui.md +0 -39
  198. package/cli/agents/solver-story-ux.json +0 -15
  199. package/cli/agents/solver-story-ux.md +0 -39
  200. package/cli/agents/validator-epic-api.json +0 -93
  201. package/cli/agents/validator-epic-api.md +0 -137
  202. package/cli/agents/validator-epic-backend.json +0 -93
  203. package/cli/agents/validator-epic-backend.md +0 -130
  204. package/cli/agents/validator-epic-cloud.json +0 -93
  205. package/cli/agents/validator-epic-cloud.md +0 -137
  206. package/cli/agents/validator-epic-data.json +0 -93
  207. package/cli/agents/validator-epic-data.md +0 -130
  208. package/cli/agents/validator-epic-database.json +0 -93
  209. package/cli/agents/validator-epic-database.md +0 -137
  210. package/cli/agents/validator-epic-developer.json +0 -74
  211. package/cli/agents/validator-epic-developer.md +0 -153
  212. package/cli/agents/validator-epic-devops.json +0 -74
  213. package/cli/agents/validator-epic-devops.md +0 -153
  214. package/cli/agents/validator-epic-frontend.json +0 -74
  215. package/cli/agents/validator-epic-frontend.md +0 -153
  216. package/cli/agents/validator-epic-mobile.json +0 -93
  217. package/cli/agents/validator-epic-mobile.md +0 -130
  218. package/cli/agents/validator-epic-qa.json +0 -93
  219. package/cli/agents/validator-epic-qa.md +0 -130
  220. package/cli/agents/validator-epic-security.json +0 -74
  221. package/cli/agents/validator-epic-security.md +0 -154
  222. package/cli/agents/validator-epic-solution-architect.json +0 -74
  223. package/cli/agents/validator-epic-solution-architect.md +0 -156
  224. package/cli/agents/validator-epic-test-architect.json +0 -93
  225. package/cli/agents/validator-epic-test-architect.md +0 -130
  226. package/cli/agents/validator-epic-ui.json +0 -93
  227. package/cli/agents/validator-epic-ui.md +0 -130
  228. package/cli/agents/validator-epic-ux.json +0 -93
  229. package/cli/agents/validator-epic-ux.md +0 -130
  230. package/cli/agents/validator-story-api.json +0 -104
  231. package/cli/agents/validator-story-api.md +0 -152
  232. package/cli/agents/validator-story-backend.json +0 -104
  233. package/cli/agents/validator-story-backend.md +0 -152
  234. package/cli/agents/validator-story-cloud.json +0 -104
  235. package/cli/agents/validator-story-cloud.md +0 -152
  236. package/cli/agents/validator-story-data.json +0 -104
  237. package/cli/agents/validator-story-data.md +0 -152
  238. package/cli/agents/validator-story-database.json +0 -104
  239. package/cli/agents/validator-story-database.md +0 -152
  240. package/cli/agents/validator-story-developer.json +0 -104
  241. package/cli/agents/validator-story-developer.md +0 -152
  242. package/cli/agents/validator-story-devops.json +0 -104
  243. package/cli/agents/validator-story-devops.md +0 -152
  244. package/cli/agents/validator-story-frontend.json +0 -104
  245. package/cli/agents/validator-story-frontend.md +0 -152
  246. package/cli/agents/validator-story-mobile.json +0 -104
  247. package/cli/agents/validator-story-mobile.md +0 -152
  248. package/cli/agents/validator-story-qa.json +0 -104
  249. package/cli/agents/validator-story-qa.md +0 -152
  250. package/cli/agents/validator-story-security.json +0 -104
  251. package/cli/agents/validator-story-security.md +0 -152
  252. package/cli/agents/validator-story-solution-architect.json +0 -104
  253. package/cli/agents/validator-story-solution-architect.md +0 -152
  254. package/cli/agents/validator-story-test-architect.json +0 -104
  255. package/cli/agents/validator-story-test-architect.md +0 -152
  256. package/cli/agents/validator-story-ui.json +0 -104
  257. package/cli/agents/validator-story-ui.md +0 -152
  258. package/cli/agents/validator-story-ux.json +0 -104
  259. package/cli/agents/validator-story-ux.md +0 -152
  260. package/kanban/client/dist/assets/index-CiD8PS2e.js +0 -306
  261. 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
+ }