@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
@@ -17,6 +17,9 @@
17
17
  * - OpenAI: https://community.openai.com/t/what-is-the-token-limit-of-the-new-version-gpt-4o/752528
18
18
  */
19
19
 
20
+ // Suppress duplicate warnings — each model warned only once per process
21
+ const _warnedModels = new Set();
22
+
20
23
  export const MODEL_MAX_TOKENS = {
21
24
  // Claude models (Anthropic)
22
25
  // Source: https://docs.cloud.google.com/vertex-ai/generative-ai/docs/partner-models/claude/
@@ -31,8 +34,14 @@ export const MODEL_MAX_TOKENS = {
31
34
 
32
35
  // OpenAI models
33
36
  // Source: https://community.openai.com/t/what-is-the-token-limit-of-the-new-version-gpt-4o/752528
34
- 'gpt-5.2-chat-latest': 16384, // Test/future model
35
- 'gpt-5.2-pro': 16384,
37
+ // GPT-5 family (32K output tokens assumed; add specific overrides as official limits are confirmed)
38
+ 'gpt-5': 32768,
39
+ 'gpt-5.1': 32768,
40
+ 'gpt-5.2': 32768,
41
+ 'gpt-5.4': 32768,
42
+ 'gpt-5-mini': 32768,
43
+ 'gpt-5.2-chat-latest': 16384, // Keep at 16384 — existing tests depend on this value
44
+ 'gpt-5.2-pro': 16384,
36
45
  'gpt-5.2-codex': 16384,
37
46
  'gpt-4o': 16384, // Correct - max 16,384 tokens
38
47
  'gpt-4o-2024-11-20': 16384,
@@ -58,6 +67,51 @@ export const MODEL_MAX_TOKENS = {
58
67
  'default': 8192
59
68
  };
60
69
 
70
+ /**
71
+ * Model context window sizes (total input + output tokens)
72
+ *
73
+ * These represent the full context window each model supports.
74
+ * For local models, we use conservative defaults since the actual
75
+ * model behind the endpoint is unknown.
76
+ */
77
+ export const MODEL_CONTEXT_WINDOWS = {
78
+ // Claude models
79
+ 'claude-sonnet-4-5': 200000,
80
+ 'claude-sonnet-4': 200000,
81
+ 'claude-opus-4-6': 200000,
82
+ 'claude-opus-4': 200000,
83
+ 'claude-haiku-4-5': 200000,
84
+ 'claude-haiku-4': 200000,
85
+
86
+ // OpenAI models
87
+ 'gpt-5': 128000,
88
+ 'gpt-5.1': 128000,
89
+ 'gpt-5.2': 128000,
90
+ 'gpt-5.4': 128000,
91
+ 'gpt-5-mini': 128000,
92
+ 'gpt-4o': 128000,
93
+ 'gpt-4o-mini': 128000,
94
+ 'gpt-4-turbo': 128000,
95
+ 'gpt-4': 8192,
96
+
97
+ // Google Gemini models
98
+ 'gemini-3.1-pro-preview': 1048576,
99
+ 'gemini-3-flash-preview': 1048576,
100
+ 'gemini-2.5-pro': 1048576,
101
+ 'gemini-2.5-flash': 1048576,
102
+ 'gemini-2.0-flash': 1048576,
103
+ 'gemini-1.5-pro': 2097152,
104
+ 'gemini-1.5-flash': 1048576,
105
+
106
+ // Default for unknown local models - conservative estimate
107
+ 'local-default': 8192,
108
+
109
+ // Xiaomi MiMo models
110
+ 'mimo-v2-flash': 262144,
111
+ 'mimo-v2-pro': 1048576,
112
+ 'mimo-v2-omni': 262144,
113
+ };
114
+
61
115
  /**
62
116
  * Get maximum output tokens for a specific model
63
117
  * @param {string} modelId - The model identifier
@@ -79,11 +133,62 @@ export function getMaxTokensForModel(modelId) {
79
133
  return MODEL_MAX_TOKENS[baseModel];
80
134
  }
81
135
 
136
+ // Prefix fallback: catch any gpt-5.x variant not explicitly listed
137
+ if (modelId.startsWith('gpt-5')) {
138
+ if (!_warnedModels.has(modelId)) {
139
+ _warnedModels.add(modelId);
140
+ console.warn(`No exact max tokens for "${modelId}", using GPT-5 family default: 32768`);
141
+ }
142
+ return 32768;
143
+ }
144
+
82
145
  // Fallback to default
83
- console.warn(`No max tokens configured for model "${modelId}", using default: ${MODEL_MAX_TOKENS['default']}`);
146
+ if (!_warnedModels.has(modelId)) {
147
+ _warnedModels.add(modelId);
148
+ console.warn(`No max tokens configured for model "${modelId}", using default: ${MODEL_MAX_TOKENS['default']}`);
149
+ }
84
150
  return MODEL_MAX_TOKENS['default'];
85
151
  }
86
152
 
153
+ /**
154
+ * Get the context window size for a specific model
155
+ * @param {string} modelId - The model identifier
156
+ * @param {string} provider - The provider name ('claude', 'openai', 'gemini', 'local')
157
+ * @returns {number|null} Context window size in tokens, or null if no limit enforcement
158
+ */
159
+ export function getContextWindowForModel(modelId, provider) {
160
+ // Local provider: use conservative default, overridable via env var
161
+ if (provider === 'local') {
162
+ const envOverride = process.env.LOCAL_LLM_CONTEXT_WINDOW;
163
+ if (envOverride) {
164
+ const parsed = parseInt(envOverride, 10);
165
+ if (!isNaN(parsed) && parsed > 0) {
166
+ return parsed;
167
+ }
168
+ console.warn(`Invalid LOCAL_LLM_CONTEXT_WINDOW value "${envOverride}", using default: ${MODEL_CONTEXT_WINDOWS['local-default']}`);
169
+ }
170
+ return MODEL_CONTEXT_WINDOWS['local-default'];
171
+ }
172
+
173
+ if (!modelId) {
174
+ return null;
175
+ }
176
+
177
+ // Direct lookup
178
+ if (MODEL_CONTEXT_WINDOWS[modelId]) {
179
+ return MODEL_CONTEXT_WINDOWS[modelId];
180
+ }
181
+
182
+ // Fuzzy match for versioned models (e.g., "claude-opus-4-latest" → "claude-opus-4")
183
+ const baseModel = modelId.split('-').slice(0, 3).join('-');
184
+ if (MODEL_CONTEXT_WINDOWS[baseModel]) {
185
+ return MODEL_CONTEXT_WINDOWS[baseModel];
186
+ }
187
+
188
+ // Unknown cloud model — no limit enforcement
189
+ return null;
190
+ }
191
+
87
192
  /**
88
193
  * Validate if requested tokens exceed model limit
89
194
  * @param {string} modelId - The model identifier
@@ -94,7 +199,11 @@ export function clampTokensToModelLimit(modelId, requestedTokens) {
94
199
  const maxTokens = getMaxTokensForModel(modelId);
95
200
 
96
201
  if (requestedTokens > maxTokens) {
97
- console.warn(`Requested ${requestedTokens} tokens for ${modelId}, clamping to model limit: ${maxTokens}`);
202
+ const clampKey = `clamp:${modelId}:${requestedTokens}`;
203
+ if (!_warnedModels.has(clampKey)) {
204
+ _warnedModels.add(clampKey);
205
+ console.warn(`Requested ${requestedTokens} tokens for ${modelId}, clamping to model limit: ${maxTokens}`);
206
+ }
98
207
  return maxTokens;
99
208
  }
100
209
 
@@ -107,6 +107,80 @@ export class LLMVerifier {
107
107
  }
108
108
  }
109
109
 
110
+ /**
111
+ * Fast-path: Check if a numeric field is within range
112
+ */
113
+ fastPathScoreRange(content, field, min, max) {
114
+ try {
115
+ const { unwrapped } = this.unwrapJsonCodeFence(content);
116
+ const obj = JSON.parse(unwrapped);
117
+ const val = obj[field];
118
+ if (typeof val === 'number' && Number.isInteger(val) && val >= min && val <= max) {
119
+ return { canFastPath: true, violated: false };
120
+ }
121
+ return { canFastPath: true, violated: true, reason: `${field}=${val} (expected integer ${min}-${max})` };
122
+ } catch {
123
+ return { canFastPath: false };
124
+ }
125
+ }
126
+
127
+ /**
128
+ * Fast-path: Check if a field value is one of allowed enum values
129
+ */
130
+ fastPathEnum(content, field, allowedValues) {
131
+ try {
132
+ const { unwrapped } = this.unwrapJsonCodeFence(content);
133
+ const obj = JSON.parse(unwrapped);
134
+ if (allowedValues.includes(obj[field])) {
135
+ return { canFastPath: true, violated: false };
136
+ }
137
+ return { canFastPath: true, violated: true, reason: `${field}="${obj[field]}" not in [${allowedValues.join(',')}]` };
138
+ } catch {
139
+ return { canFastPath: false };
140
+ }
141
+ }
142
+
143
+ /**
144
+ * Fast-path: Check enum values inside nested objects within arrays
145
+ */
146
+ fastPathEnumDeep(content, field, allowedValues, searchArrays) {
147
+ try {
148
+ const { unwrapped } = this.unwrapJsonCodeFence(content);
149
+ const obj = JSON.parse(unwrapped);
150
+ const invalid = [];
151
+ for (const arrName of searchArrays) {
152
+ if (Array.isArray(obj[arrName])) {
153
+ for (const item of obj[arrName]) {
154
+ if (item[field] && !allowedValues.includes(item[field])) {
155
+ invalid.push(`${arrName}.${field}="${item[field]}"`);
156
+ }
157
+ }
158
+ }
159
+ }
160
+ if (invalid.length === 0) return { canFastPath: true, violated: false };
161
+ return { canFastPath: true, violated: true, reason: invalid.join(', ') };
162
+ } catch {
163
+ return { canFastPath: false };
164
+ }
165
+ }
166
+
167
+ /**
168
+ * Fast-path: Check if specified fields are arrays (not strings/objects/null)
169
+ */
170
+ fastPathArrayFields(content, arrayFields) {
171
+ try {
172
+ const { unwrapped } = this.unwrapJsonCodeFence(content);
173
+ const obj = JSON.parse(unwrapped);
174
+ const nonArrays = arrayFields.filter(f => f in obj && !Array.isArray(obj[f]));
175
+ if (nonArrays.length === 0) {
176
+ return { canFastPath: true, violated: false };
177
+ }
178
+ return { canFastPath: true, violated: true, reason: `non-array fields: ${nonArrays.join(', ')}` };
179
+ } catch {
180
+ return { canFastPath: false };
181
+ }
182
+ }
183
+
110
184
  /**
111
185
  * Execute fast-path optimization if configured
112
186
  * @param {string} content - Content to check
@@ -130,6 +204,19 @@ export class LLMVerifier {
130
204
  const fields = rule.fastPath.requiredFields || [];
131
205
  return this.fastPathRequiredFields(content, fields);
132
206
 
207
+ case 'json-arrays':
208
+ // Array fields fast-path — check that specified fields are arrays
209
+ return this.fastPathArrayFields(content, rule.fastPath.arrayFields || []);
210
+
211
+ case 'json-score-range':
212
+ return this.fastPathScoreRange(content, rule.fastPath.field, rule.fastPath.min, rule.fastPath.max);
213
+
214
+ case 'json-enum':
215
+ return this.fastPathEnum(content, rule.fastPath.field, rule.fastPath.allowedValues || []);
216
+
217
+ case 'json-enum-deep':
218
+ return this.fastPathEnumDeep(content, rule.fastPath.field, rule.fastPath.allowedValues || [], rule.fastPath.searchArrays || []);
219
+
133
220
  case 'none':
134
221
  default:
135
222
  // No fast-path, use LLM
@@ -228,7 +315,7 @@ export class LLMVerifier {
228
315
  this.tracker.startRuleFix(content.length);
229
316
  }
230
317
 
231
- // Fast-path fix if configured
318
+ // Fast-path fix: unwrap JSON code fences
232
319
  if (rule.fastPath?.enabled && rule.fastPath.type === 'json-parse') {
233
320
  const { isWrapped, unwrapped } = this.unwrapJsonCodeFence(content);
234
321
  if (isWrapped) {
@@ -240,6 +327,127 @@ export class LLMVerifier {
240
327
  }
241
328
  }
242
329
 
330
+ // Fast-path fix: clamp score to valid range
331
+ if (rule.fastPath?.enabled && rule.fastPath.type === 'json-score-range') {
332
+ try {
333
+ const { unwrapped } = this.unwrapJsonCodeFence(content);
334
+ const obj = JSON.parse(unwrapped);
335
+ const { field, min, max } = rule.fastPath;
336
+ let val = obj[field];
337
+ if (typeof val !== 'number' || isNaN(val)) val = 50;
338
+ obj[field] = Math.round(Math.min(max, Math.max(min, val)));
339
+ const result = JSON.stringify(obj, null, 2);
340
+ console.log(`[DEBUG] Fast-path fix for ${rule.id}: clamped ${field} to ${obj[field]} (no LLM call)`);
341
+ if (this.tracker) this.tracker.endRuleFix(result.length);
342
+ return result;
343
+ } catch { /* fall through */ }
344
+ }
345
+
346
+ // Fast-path fix: correct enum value based on score
347
+ if (rule.fastPath?.enabled && rule.fastPath.type === 'json-enum' && rule.fastPath.field === 'validationStatus') {
348
+ try {
349
+ const { unwrapped } = this.unwrapJsonCodeFence(content);
350
+ const obj = JSON.parse(unwrapped);
351
+ const score = obj.overallScore;
352
+ if (typeof score === 'number') {
353
+ obj.validationStatus = score >= 90 ? 'excellent' : score >= 75 ? 'acceptable' : 'needs-improvement';
354
+ } else {
355
+ obj.validationStatus = 'needs-improvement';
356
+ }
357
+ const result = JSON.stringify(obj, null, 2);
358
+ console.log(`[DEBUG] Fast-path fix for ${rule.id}: set validationStatus="${obj.validationStatus}" (no LLM call)`);
359
+ if (this.tracker) this.tracker.endRuleFix(result.length);
360
+ return result;
361
+ } catch { /* fall through */ }
362
+ }
363
+
364
+ // Fast-path fix: correct severity enum values in nested arrays
365
+ if (rule.fastPath?.enabled && rule.fastPath.type === 'json-enum-deep') {
366
+ try {
367
+ const { unwrapped } = this.unwrapJsonCodeFence(content);
368
+ const obj = JSON.parse(unwrapped);
369
+ const { field, allowedValues, searchArrays } = rule.fastPath;
370
+ const severityMap = { high: 'critical', medium: 'major', low: 'minor' };
371
+ for (const arrName of searchArrays) {
372
+ if (Array.isArray(obj[arrName])) {
373
+ for (const item of obj[arrName]) {
374
+ if (item[field] && !allowedValues.includes(item[field])) {
375
+ item[field] = severityMap[item[field]] || 'major';
376
+ }
377
+ }
378
+ }
379
+ }
380
+ const result = JSON.stringify(obj, null, 2);
381
+ console.log(`[DEBUG] Fast-path fix for ${rule.id}: corrected ${field} values (no LLM call)`);
382
+ if (this.tracker) this.tracker.endRuleFix(result.length);
383
+ return result;
384
+ } catch { /* fall through */ }
385
+ }
386
+
387
+ // Fast-path fix: convert non-array fields to arrays
388
+ if (rule.fastPath?.enabled && rule.fastPath.type === 'json-arrays') {
389
+ try {
390
+ const { unwrapped } = this.unwrapJsonCodeFence(content);
391
+ const obj = JSON.parse(unwrapped);
392
+ const arrayFields = rule.fastPath.arrayFields || [];
393
+ let fixed = false;
394
+ for (const field of arrayFields) {
395
+ if (field in obj && !Array.isArray(obj[field])) {
396
+ const val = obj[field];
397
+ if (val === null || val === undefined) {
398
+ obj[field] = [];
399
+ } else if (typeof val === 'string') {
400
+ obj[field] = val ? [val] : [];
401
+ } else if (typeof val === 'object') {
402
+ obj[field] = [val];
403
+ } else {
404
+ obj[field] = [];
405
+ }
406
+ fixed = true;
407
+ }
408
+ }
409
+ if (fixed) {
410
+ const result = JSON.stringify(obj, null, 2);
411
+ console.log(`[DEBUG] Fast-path fix for ${rule.id}: converted non-array fields to arrays (no LLM call)`);
412
+ if (this.tracker) {
413
+ this.tracker.endRuleFix(result.length);
414
+ }
415
+ return result;
416
+ }
417
+ } catch { /* fall through to LLM */ }
418
+ }
419
+
420
+ // Fast-path fix: add missing required fields with defaults from fix prompt
421
+ if (rule.fastPath?.enabled && rule.fastPath.type === 'json-fields') {
422
+ try {
423
+ const { unwrapped } = this.unwrapJsonCodeFence(content);
424
+ const obj = JSON.parse(unwrapped);
425
+ const missing = (rule.fastPath.requiredFields || []).filter(f => !(f in obj));
426
+ if (missing.length > 0) {
427
+ // Parse defaults from fix prompt — use sensible fallbacks
428
+ const defaults = {
429
+ validationStatus: 'needs-improvement',
430
+ overallScore: 50,
431
+ structuralIssues: [],
432
+ contentIssues: [],
433
+ applicationFlowGaps: [],
434
+ strengths: ['Document structure present'],
435
+ improvementPriorities: [],
436
+ readyForPublication: false,
437
+ };
438
+ for (const field of missing) {
439
+ obj[field] = defaults[field] !== undefined ? defaults[field] : null;
440
+ }
441
+ const fixed = JSON.stringify(obj, null, 2);
442
+ console.log(`[DEBUG] Fast-path fix for ${rule.id}: added missing fields [${missing.join(', ')}] (no LLM call)`);
443
+ if (this.tracker) {
444
+ this.tracker.endRuleFix(fixed.length);
445
+ }
446
+ return fixed;
447
+ }
448
+ } catch { /* JSON parse failed — fall through to LLM */ }
449
+ }
450
+
243
451
  // Fallback to LLM fix
244
452
  const prompt = rule.fix.prompt.replace('{content}', content);
245
453
  const maxTokens = rule.fix.maxTokens || 4096;
@@ -0,0 +1,143 @@
1
+ /**
2
+ * llm-xiaomi.js — Xiaomi MiMo LLM provider.
3
+ *
4
+ * Uses the OpenAI-compatible API at https://api.xiaomimimo.com/v1.
5
+ * Supports: mimo-v2-flash, mimo-v2-pro, mimo-v2-omni.
6
+ *
7
+ * Requires XIAOMI_API_KEY environment variable.
8
+ */
9
+
10
+ import OpenAI from 'openai';
11
+ import { LLMProvider } from './llm-provider.js';
12
+
13
+ const XIAOMI_BASE_URL = 'https://api.xiaomimimo.com/v1';
14
+
15
+ function cleanResponse(text) {
16
+ if (!text) return '';
17
+ // Strip markdown code fences
18
+ return text.replace(/^```(?:json)?\s*\n?/i, '').replace(/\n?```\s*$/i, '').trim();
19
+ }
20
+
21
+ function extractJSON(text) {
22
+ // Try to find JSON object or array in the response
23
+ const jsonMatch = text.match(/(\{[\s\S]*\}|\[[\s\S]*\])/);
24
+ return jsonMatch ? jsonMatch[1] : text;
25
+ }
26
+
27
+ export class XiaomiProvider extends LLMProvider {
28
+ constructor(model) {
29
+ super('xiaomi', model || 'mimo-v2-flash');
30
+ }
31
+
32
+ _createClient() {
33
+ const apiKey = process.env.XIAOMI_API_KEY;
34
+ if (!apiKey) {
35
+ throw new Error('XIAOMI_API_KEY not set. Add it to your .env file. Get a key at https://platform.xiaomimimo.com/#/console/api-keys');
36
+ }
37
+ return new OpenAI({
38
+ apiKey,
39
+ baseURL: XIAOMI_BASE_URL,
40
+ timeout: 10 * 60_000, // 10 min
41
+ maxRetries: 0,
42
+ });
43
+ }
44
+
45
+ async _callProvider(prompt, maxTokens, systemInstructions) {
46
+ if (!this._client) this._client = this._createClient();
47
+
48
+ const messages = [];
49
+ if (systemInstructions) {
50
+ messages.push({ role: 'system', content: systemInstructions });
51
+ }
52
+ messages.push({ role: 'user', content: prompt });
53
+
54
+ const response = await this._client.chat.completions.create({
55
+ model: this.model,
56
+ messages,
57
+ max_tokens: maxTokens,
58
+ });
59
+
60
+ const usage = response.usage;
61
+ this._trackTokens(usage, { prompt, agentInstructions: systemInstructions, response: response.choices?.[0]?.message?.content });
62
+
63
+ return response.choices?.[0]?.message?.content || '';
64
+ }
65
+
66
+ async generateJSON(prompt, agentInstructions = null, cachedContext = null) {
67
+ if (!this._client) this._client = this._createClient();
68
+
69
+ const JSON_SYSTEM = 'You are a helpful assistant that always returns valid JSON. Your response must be a valid JSON object or array, nothing else. Do not include any thinking, reasoning, or explanation — only the JSON.';
70
+ const systemParts = [JSON_SYSTEM];
71
+ if (agentInstructions) systemParts.push(agentInstructions);
72
+ if (cachedContext) systemParts.push(`---\n\n${cachedContext}`);
73
+
74
+ const messages = [
75
+ { role: 'system', content: systemParts.join('\n\n') },
76
+ { role: 'user', content: prompt },
77
+ ];
78
+
79
+ const _t0 = Date.now();
80
+ const response = await this._withRetry(
81
+ () => this._client.chat.completions.create({
82
+ model: this.model,
83
+ messages,
84
+ response_format: { type: 'json_object' },
85
+ }),
86
+ 'JSON generation (Xiaomi)'
87
+ );
88
+
89
+ const content = response.choices?.[0]?.message?.content || '';
90
+ this._trackTokens(response.usage, {
91
+ prompt,
92
+ agentInstructions: agentInstructions ?? null,
93
+ response: content,
94
+ elapsed: Date.now() - _t0,
95
+ });
96
+
97
+ const jsonStr = extractJSON(cleanResponse(content));
98
+ try {
99
+ return JSON.parse(jsonStr);
100
+ } catch (firstError) {
101
+ // Try jsonrepair as fallback
102
+ try {
103
+ const { jsonrepair } = await import('jsonrepair');
104
+ return JSON.parse(jsonrepair(jsonStr));
105
+ } catch {
106
+ throw new Error(`Failed to parse JSON from Xiaomi (${this.model}): ${firstError.message}\n\nResponse:\n${content.slice(0, 500)}`);
107
+ }
108
+ }
109
+ }
110
+
111
+ async generateText(prompt, agentInstructions = null, cachedContext = null) {
112
+ if (!this._client) this._client = this._createClient();
113
+
114
+ const systemParts = [];
115
+ if (agentInstructions) systemParts.push(agentInstructions);
116
+ if (cachedContext) systemParts.push(cachedContext);
117
+
118
+ const messages = [];
119
+ if (systemParts.length > 0) {
120
+ messages.push({ role: 'system', content: systemParts.join('\n\n') });
121
+ }
122
+ messages.push({ role: 'user', content: prompt });
123
+
124
+ const _t0 = Date.now();
125
+ const response = await this._withRetry(
126
+ () => this._client.chat.completions.create({
127
+ model: this.model,
128
+ messages,
129
+ }),
130
+ 'Text generation (Xiaomi)'
131
+ );
132
+
133
+ const content = response.choices?.[0]?.message?.content || '';
134
+ this._trackTokens(response.usage, {
135
+ prompt,
136
+ agentInstructions: agentInstructions ?? null,
137
+ response: content,
138
+ elapsed: Date.now() - _t0,
139
+ });
140
+
141
+ return content;
142
+ }
143
+ }
@@ -21,18 +21,9 @@ export const MESSAGES = {
21
21
  * Ceremony headers with titles and documentation URLs
22
22
  */
23
23
  CEREMONY_HEADERS: {
24
- 'sponsor-call': {
25
- title: 'Sponsor Call Ceremony',
26
- url: 'https://agilevibecoding.org/ceremonies/sponsor-call'
27
- },
28
- 'sprint-planning': {
29
- title: 'Sprint Planning Ceremony',
30
- url: 'https://agilevibecoding.org/ceremonies/sprint-planning'
31
- },
32
- 'seed': {
33
- title: 'Seed Ceremony',
34
- url: 'https://agilevibecoding.org/ceremonies/seed'
35
- }
24
+ 'sponsor-call': { title: 'Sponsor Call Ceremony' },
25
+ 'sprint-planning': { title: 'Sprint Planning Ceremony' },
26
+ 'seed': { title: 'Seed Ceremony' },
36
27
  }
37
28
  };
38
29
 
@@ -67,22 +67,16 @@ export function cancelCommand() {
67
67
  * Send a ceremony header (title + documentation URL)
68
68
  * Automatically prevents duplicate headers within same command execution
69
69
  *
70
- * @param {string} title - Ceremony title (without emoji)
71
- * @param {string} url - Documentation URL
72
- *
73
- * @example
74
- * sendCeremonyHeader('Sponsor Call Ceremony', 'https://agilevibecoding.org/ceremonies/sponsor-call');
70
+ * @param {string} title - Ceremony title
75
71
  */
76
- export function sendCeremonyHeader(title, url) {
77
- const key = `${title}|${url}`;
78
-
79
- // Prevent duplicate ceremony headers
80
- if (ceremonySent.has(key)) {
72
+ export function sendCeremonyHeader(title) {
73
+ // Prevent duplicate ceremony headers within the same command execution
74
+ if (ceremonySent.has(title)) {
81
75
  return;
82
76
  }
83
77
 
84
- ceremonySent.add(key);
85
- const content = `${boldCyan(title)}\n${gray('Documentation:')} ${cyan(url)}`;
78
+ ceremonySent.add(title);
79
+ const content = boldCyan(title);
86
80
  messageManager.send(MessageType.CEREMONY_HEADER, content);
87
81
  }
88
82