@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
@@ -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