@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
@@ -599,7 +599,16 @@ Please carefully follow the output format requirements to avoid these issues.
599
599
  * @returns {Promise<LLMProvider>} LLM provider instance
600
600
  */
601
601
  async getProviderForStageInstance(stageName) {
602
- const { provider, model } = this.getProviderForStage(stageName);
602
+ let { provider, model } = this.getProviderForStage(stageName);
603
+
604
+ // Resolve to an available provider if current one has no credentials
605
+ const resolved = await LLMProvider.resolveAvailableProvider(provider, model);
606
+ if (resolved.fellBack) {
607
+ debug(`Provider fallback for ${stageName}: ${provider}→${resolved.provider} (${resolved.model})`);
608
+ console.warn(`[WARN] ${provider} has no API key — falling back to ${resolved.provider} for stage "${stageName}"`);
609
+ provider = resolved.provider;
610
+ model = resolved.model;
611
+ }
603
612
 
604
613
  // Check if we already have a provider for this stage
605
614
  const cacheKey = `${stageName}:${provider}:${model}`;
@@ -627,7 +636,16 @@ Please carefully follow the output format requirements to avoid these issues.
627
636
  * @returns {Promise<LLMProvider>} LLM provider instance
628
637
  */
629
638
  async getValidationProviderForTypeInstance(validationType) {
630
- const { provider, model } = this.getValidationProviderForType(validationType);
639
+ let { provider, model } = this.getValidationProviderForType(validationType);
640
+
641
+ // Resolve to an available provider if current one has no credentials
642
+ const resolved = await LLMProvider.resolveAvailableProvider(provider, model);
643
+ if (resolved.fellBack) {
644
+ debug(`Validation provider fallback for ${validationType}: ${provider}→${resolved.provider} (${resolved.model})`);
645
+ console.warn(`[WARN] ${provider} has no API key — falling back to ${resolved.provider} for validation "${validationType}"`);
646
+ provider = resolved.provider;
647
+ model = resolved.model;
648
+ }
631
649
 
632
650
  // Check if we already have a provider for this validation type
633
651
  const cacheKey = `validation:${validationType}:${provider}:${model}`;
@@ -1531,7 +1549,7 @@ ${TECHNICAL_CONSIDERATIONS}
1531
1549
  **Security and Compliance Requirements:**
1532
1550
  ${SECURITY_AND_COMPLIANCE_REQUIREMENTS}
1533
1551
 
1534
- Decompose this project into Epics (3-7 domain-based groupings) and Stories (2-8 user-facing capabilities per Epic).
1552
+ Decompose this project into Epics (domain-based groupings) and Stories (user-facing capabilities per Epic) — create as many as needed to fully cover the scope.
1535
1553
 
1536
1554
  Return your response as JSON following the exact structure specified in your instructions.`;
1537
1555
  }
@@ -2564,6 +2582,35 @@ Return your response as JSON following the exact structure specified in your ins
2564
2582
  'question prefilling'
2565
2583
  );
2566
2584
 
2585
+ // Normalize common field name variants that local LLMs sometimes produce.
2586
+ // Maps known abbreviations/alternatives → canonical field names.
2587
+ const FIELD_ALIASES = {
2588
+ DEPLOY_TARGET: 'DEPLOYMENT_TARGET',
2589
+ DEPLOY: 'DEPLOYMENT_TARGET',
2590
+ DEPLOYMENT: 'DEPLOYMENT_TARGET',
2591
+ TARGET_DEPLOYMENT: 'DEPLOYMENT_TARGET',
2592
+ SECURITY_REQUIREMENTS: 'SECURITY_AND_COMPLIANCE_REQUIREMENTS',
2593
+ SECURITY_COMPLIANCE: 'SECURITY_AND_COMPLIANCE_REQUIREMENTS',
2594
+ COMPLIANCE_REQUIREMENTS: 'SECURITY_AND_COMPLIANCE_REQUIREMENTS',
2595
+ SECURITY: 'SECURITY_AND_COMPLIANCE_REQUIREMENTS',
2596
+ TECHNICAL: 'TECHNICAL_CONSIDERATIONS',
2597
+ TECH_CONSIDERATIONS: 'TECHNICAL_CONSIDERATIONS',
2598
+ TECH_STACK: 'TECHNICAL_CONSIDERATIONS',
2599
+ USERS: 'TARGET_USERS',
2600
+ TARGET_USER: 'TARGET_USERS',
2601
+ };
2602
+
2603
+ for (const [alias, canonical] of Object.entries(FIELD_ALIASES)) {
2604
+ if (result[alias] !== undefined) {
2605
+ if (!result[canonical] && result[alias]) {
2606
+ // Canonical missing or empty — use alias value
2607
+ debug(`Normalizing field name: ${alias} → ${canonical}`);
2608
+ result[canonical] = result[alias];
2609
+ }
2610
+ delete result[alias];
2611
+ }
2612
+ }
2613
+
2567
2614
  debug('Question prefilling received', {
2568
2615
  hasTargetUsers: !!result.TARGET_USERS,
2569
2616
  hasDeploymentTarget: !!result.DEPLOYMENT_TARGET,
@@ -92,31 +92,38 @@ export class TokenTracker {
92
92
 
93
93
  /**
94
94
  * Calculate cost for token usage based on model pricing
95
- * @param {number} inputTokens - Input tokens consumed
95
+ * @param {number} inputTokens - Input tokens consumed (includes cached tokens)
96
96
  * @param {number} outputTokens - Output tokens consumed
97
97
  * @param {string} modelId - Model identifier (e.g., 'claude-sonnet-4-5-20250929')
98
- * @returns {Object} Cost breakdown { input: number, output: number, total: number }
98
+ * @param {number} cachedTokens - Subset of inputTokens served from cache (billed at inputCached rate)
99
+ * @returns {Object} Cost breakdown { input, output, total, saved }
99
100
  */
100
- calculateCost(inputTokens, outputTokens, modelId) {
101
+ calculateCost(inputTokens, outputTokens, modelId, cachedTokens = 0) {
101
102
  const config = this.readConfig();
102
103
  const modelConfig = config.settings?.models?.[modelId];
103
104
 
104
105
  if (!modelConfig || !modelConfig.pricing) {
105
106
  // Model not found or no pricing - return zero cost
106
- return { input: 0, output: 0, total: 0 };
107
+ return { input: 0, output: 0, total: 0, saved: 0 };
107
108
  }
108
109
 
109
110
  const pricing = modelConfig.pricing;
110
111
  const divisor = pricing.unit === 'million' ? 1_000_000 : 1_000;
111
112
 
112
- // Calculate costs (price per unit * tokens / divisor)
113
- const inputCost = (pricing.input * inputTokens) / divisor;
113
+ // Cached tokens billed at reduced rate; fall back to full input rate if not configured
114
+ const cachedRate = pricing.inputCached ?? pricing.input;
115
+ const nonCachedInput = Math.max(0, inputTokens - cachedTokens);
116
+
117
+ const inputCost = (pricing.input * nonCachedInput + cachedRate * cachedTokens) / divisor;
114
118
  const outputCost = (pricing.output * outputTokens) / divisor;
119
+ // Savings vs. paying full rate for cached tokens
120
+ const saved = ((pricing.input - cachedRate) * cachedTokens) / divisor;
115
121
 
116
122
  return {
117
123
  input: inputCost,
118
124
  output: outputCost,
119
- total: inputCost + outputCost
125
+ total: inputCost + outputCost,
126
+ saved,
120
127
  };
121
128
  }
122
129
 
@@ -139,20 +146,25 @@ export class TokenTracker {
139
146
  const tokenData = {
140
147
  input: tokens.input || 0,
141
148
  output: tokens.output || 0,
149
+ cached: tokens.cached || 0,
150
+ cacheWrite: tokens.cacheWrite || 0,
142
151
  total: (tokens.input || 0) + (tokens.output || 0),
143
152
  provider: tokens.provider || 'unknown',
144
153
  model: tokens.model || modelId || 'unknown'
145
154
  };
146
155
 
147
156
  // Calculate cost: prefer per-model pricing from avc.json, fall back to provider estimate
157
+ // Skip cost calculation for OAuth calls (flat-rate subscription — no per-token billing)
148
158
  let costData = null;
149
- const effectiveModelId = tokens.model || modelId;
150
- if (effectiveModelId) {
151
- costData = this.calculateCost(tokenData.input, tokenData.output, effectiveModelId);
152
- }
153
- // If no per-model pricing configured but provider sent a pre-computed estimate, use it
154
- if ((!costData || costData.total === 0) && tokens.estimatedCost) {
155
- costData = { input: 0, output: 0, total: tokens.estimatedCost };
159
+ if (!tokens.skipCost) {
160
+ const effectiveModelId = tokens.model || modelId;
161
+ if (effectiveModelId) {
162
+ costData = this.calculateCost(tokenData.input, tokenData.output, effectiveModelId, tokenData.cached);
163
+ }
164
+ // If no per-model pricing configured but provider sent a pre-computed estimate, use it
165
+ if ((!costData || costData.total === 0) && tokens.estimatedCost) {
166
+ costData = { input: 0, output: 0, total: tokens.estimatedCost };
167
+ }
156
168
  }
157
169
 
158
170
  console.log(` → Tracking tokens for ${ceremonyType}: ${tokenData.input} input, ${tokenData.output} output (${tokenData.provider})`);
@@ -211,17 +223,21 @@ export class TokenTracker {
211
223
  const tokenData = {
212
224
  input: delta.input || 0,
213
225
  output: delta.output || 0,
226
+ cached: delta.cached || 0,
227
+ cacheWrite: delta.cacheWrite || 0,
214
228
  total: (delta.input || 0) + (delta.output || 0),
215
229
  provider: delta.provider || 'unknown',
216
230
  model: delta.model || 'unknown'
217
231
  };
218
232
 
219
233
  let costData = null;
220
- if (delta.model) {
221
- costData = this.calculateCost(tokenData.input, tokenData.output, delta.model);
222
- }
223
- if ((!costData || costData.total === 0) && delta.estimatedCost) {
224
- costData = { input: 0, output: 0, total: delta.estimatedCost };
234
+ if (!delta.skipCost) {
235
+ if (delta.model) {
236
+ costData = this.calculateCost(tokenData.input, tokenData.output, delta.model, tokenData.cached);
237
+ }
238
+ if ((!costData || costData.total === 0) && delta.estimatedCost) {
239
+ costData = { input: 0, output: 0, total: delta.estimatedCost };
240
+ }
225
241
  }
226
242
 
227
243
  if (!this.data[ceremonyType]) {
@@ -285,19 +301,22 @@ export class TokenTracker {
285
301
  date: dateKey,
286
302
  input: 0,
287
303
  output: 0,
304
+ cached: 0,
288
305
  total: 0,
289
306
  executions: 0,
290
- cost: { input: 0, output: 0, total: 0 }
307
+ cost: { input: 0, output: 0, total: 0, saved: 0 }
291
308
  };
292
309
  }
293
310
  scope.daily[dateKey].input += tokenData.input;
294
311
  scope.daily[dateKey].output += tokenData.output;
312
+ scope.daily[dateKey].cached = (scope.daily[dateKey].cached || 0) + (tokenData.cached || 0);
295
313
  scope.daily[dateKey].total += tokenData.total;
296
314
  if (incExec) scope.daily[dateKey].executions++;
297
315
  if (costData) {
298
316
  scope.daily[dateKey].cost.input += costData.input;
299
317
  scope.daily[dateKey].cost.output += costData.output;
300
318
  scope.daily[dateKey].cost.total += costData.total;
319
+ scope.daily[dateKey].cost.saved = (scope.daily[dateKey].cost.saved || 0) + (costData.saved || 0);
301
320
  }
302
321
 
303
322
  // Update weekly
@@ -306,19 +325,22 @@ export class TokenTracker {
306
325
  week: weekKey,
307
326
  input: 0,
308
327
  output: 0,
328
+ cached: 0,
309
329
  total: 0,
310
330
  executions: 0,
311
- cost: { input: 0, output: 0, total: 0 }
331
+ cost: { input: 0, output: 0, total: 0, saved: 0 }
312
332
  };
313
333
  }
314
334
  scope.weekly[weekKey].input += tokenData.input;
315
335
  scope.weekly[weekKey].output += tokenData.output;
336
+ scope.weekly[weekKey].cached = (scope.weekly[weekKey].cached || 0) + (tokenData.cached || 0);
316
337
  scope.weekly[weekKey].total += tokenData.total;
317
338
  if (incExec) scope.weekly[weekKey].executions++;
318
339
  if (costData) {
319
340
  scope.weekly[weekKey].cost.input += costData.input;
320
341
  scope.weekly[weekKey].cost.output += costData.output;
321
342
  scope.weekly[weekKey].cost.total += costData.total;
343
+ scope.weekly[weekKey].cost.saved = (scope.weekly[weekKey].cost.saved || 0) + (costData.saved || 0);
322
344
  }
323
345
 
324
346
  // Update monthly
@@ -327,35 +349,40 @@ export class TokenTracker {
327
349
  month: monthKey,
328
350
  input: 0,
329
351
  output: 0,
352
+ cached: 0,
330
353
  total: 0,
331
354
  executions: 0,
332
- cost: { input: 0, output: 0, total: 0 }
355
+ cost: { input: 0, output: 0, total: 0, saved: 0 }
333
356
  };
334
357
  }
335
358
  scope.monthly[monthKey].input += tokenData.input;
336
359
  scope.monthly[monthKey].output += tokenData.output;
360
+ scope.monthly[monthKey].cached = (scope.monthly[monthKey].cached || 0) + (tokenData.cached || 0);
337
361
  scope.monthly[monthKey].total += tokenData.total;
338
362
  if (incExec) scope.monthly[monthKey].executions++;
339
363
  if (costData) {
340
364
  scope.monthly[monthKey].cost.input += costData.input;
341
365
  scope.monthly[monthKey].cost.output += costData.output;
342
366
  scope.monthly[monthKey].cost.total += costData.total;
367
+ scope.monthly[monthKey].cost.saved = (scope.monthly[monthKey].cost.saved || 0) + (costData.saved || 0);
343
368
  }
344
369
 
345
370
  // Update all-time
346
371
  scope.allTime.input += tokenData.input;
347
372
  scope.allTime.output += tokenData.output;
373
+ scope.allTime.cached = (scope.allTime.cached || 0) + (tokenData.cached || 0);
348
374
  scope.allTime.total += tokenData.total;
349
375
  if (incExec) scope.allTime.executions++;
350
376
 
351
377
  // Initialize cost tracking if not present
352
378
  if (!scope.allTime.cost) {
353
- scope.allTime.cost = { input: 0, output: 0, total: 0 };
379
+ scope.allTime.cost = { input: 0, output: 0, total: 0, saved: 0 };
354
380
  }
355
381
  if (costData) {
356
382
  scope.allTime.cost.input += costData.input;
357
383
  scope.allTime.cost.output += costData.output;
358
384
  scope.allTime.cost.total += costData.total;
385
+ scope.allTime.cost.saved = (scope.allTime.cost.saved || 0) + (costData.saved || 0);
359
386
  }
360
387
 
361
388
  if (!scope.allTime.firstExecution) {
@@ -70,7 +70,7 @@ You are an expert ${domainTitle.toLowerCase()} reviewing user story implementati
70
70
  - [ ] Expected outcomes are precisely defined
71
71
 
72
72
  ### Scope & Dependencies (10 points)
73
- - [ ] Story is appropriately sized (completable in 1-3 days)
73
+ - [ ] Story is appropriately scoped (single cohesive capability, 3-8 ACs)
74
74
  - [ ] Dependencies on other stories are explicit
75
75
  - [ ] Story is independent enough to be delivered incrementally
76
76
 
@@ -199,8 +199,8 @@ class ValidationRouter {
199
199
  featureValidators.forEach(v => validators.add(v));
200
200
  });
201
201
 
202
- // Return unique validators (2-6 agents typically)
203
- return Array.from(validators);
202
+ // Return unique validators (2-6 agents typically), filtered by project context
203
+ return this.filterByProjectContext(Array.from(validators), 'epic');
204
204
  }
205
205
 
206
206
  /**
@@ -233,8 +233,8 @@ class ValidationRouter {
233
233
  featureValidators.forEach(v => validators.add(v));
234
234
  });
235
235
 
236
- // Return unique validators (3-8 agents typically)
237
- return Array.from(validators);
236
+ // Return unique validators (3-8 agents typically), filtered by project context
237
+ return this.filterByProjectContext(Array.from(validators), 'story');
238
238
  }
239
239
 
240
240
  /**
@@ -391,6 +391,68 @@ class ValidationRouter {
391
391
  return validRoles.includes(role);
392
392
  }
393
393
 
394
+ /**
395
+ * Filter perspectives against project context using deterministic rules.
396
+ * Removes perspectives that are provably irrelevant based on project flags,
397
+ * regardless of what the LLM selector chose. Applied as a final pass after
398
+ * any selection method (static routing, LLM, or contextual selection).
399
+ *
400
+ * @param {string[]} validators - Full validator agent names
401
+ * @param {string} type - 'epic' or 'story'
402
+ * @returns {string[]} Filtered validators
403
+ */
404
+ filterByProjectContext(validators, type) {
405
+ const ctx = this.projectContext;
406
+ if (!ctx || Object.keys(ctx).length === 0) {
407
+ console.log(` [context-filter] Skipped: projectContext is ${ctx ? 'empty' : 'null/undefined'}`);
408
+ return validators;
409
+ }
410
+
411
+ const excluded = new Set();
412
+
413
+ // Cloud: only if project explicitly uses cloud services
414
+ if (ctx.hasCloud === false) {
415
+ excluded.add('cloud');
416
+ }
417
+
418
+ // DevOps: only if project has CI/CD or uses Kubernetes/cloud orchestration
419
+ if (ctx.hasCI_CD === false && ctx.hasCloud === false) {
420
+ excluded.add('devops');
421
+ }
422
+
423
+ // Mobile: only if project has a mobile app
424
+ if (ctx.hasMobileApp === false) {
425
+ excluded.add('mobile');
426
+ }
427
+
428
+ // Frontend/UI/UX: only if project has a frontend
429
+ if (ctx.hasFrontend === false) {
430
+ excluded.add('frontend');
431
+ excluded.add('ui');
432
+ excluded.add('ux');
433
+ }
434
+
435
+ if (excluded.size === 0) {
436
+ console.log(` [context-filter] No exclusions matched (hasCloud=${ctx.hasCloud}, hasCI_CD=${ctx.hasCI_CD}, hasMobileApp=${ctx.hasMobileApp}, hasFrontend=${ctx.hasFrontend})`);
437
+ return validators;
438
+ }
439
+
440
+ const prefix = `validator-${type}-`;
441
+ const filtered = validators.filter(v => {
442
+ const role = v.startsWith(prefix) ? v.slice(prefix.length) : v;
443
+ return !excluded.has(role);
444
+ });
445
+
446
+ // Log exclusions
447
+ const removedCount = validators.length - filtered.length;
448
+ if (removedCount > 0) {
449
+ const removedRoles = validators.filter(v => !filtered.includes(v));
450
+ console.log(` [context-filter] Removed ${removedCount} irrelevant perspective(s): ${removedRoles.map(v => v.replace(prefix, '')).join(', ')}`);
451
+ }
452
+
453
+ return filtered;
454
+ }
455
+
394
456
  /**
395
457
  * Check if a domain is known (has predefined routing rules)
396
458
  * @private
@@ -432,7 +494,7 @@ class ValidationRouter {
432
494
  featureValidators.forEach(v => validators.add(v));
433
495
  });
434
496
 
435
- return Array.from(validators);
497
+ return this.filterByProjectContext(Array.from(validators), 'epic');
436
498
  }
437
499
 
438
500
  /**
@@ -475,7 +537,7 @@ class ValidationRouter {
475
537
  featureValidators.forEach(v => validators.add(v));
476
538
  });
477
539
 
478
- return Array.from(validators);
540
+ return this.filterByProjectContext(Array.from(validators), 'story');
479
541
  }
480
542
  /**
481
543
  * Select validators using project context + per-item LLM call (contextual selection).
@@ -535,8 +597,8 @@ class ValidationRouter {
535
597
  console.log(` ✂ Excluding ${type} ${role}${reason}`);
536
598
  });
537
599
 
538
- // Map short role names to full validator agent names
539
- return safeSelected.map(r => `validator-${type}-${r}`);
600
+ // Map short role names to full validator agent names, then filter by project context
601
+ return this.filterByProjectContext(safeSelected.map(r => `validator-${type}-${r}`), type);
540
602
  } catch (err) {
541
603
  console.warn(` ⚠ Contextual agent selection failed (${err.message}) — using static routing`);
542
604
  return type === 'epic'