@agile-vibe-coding/avc 0.2.3 → 0.3.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (262) hide show
  1. package/README.md +475 -3
  2. package/cli/agents/agent-selector.md +23 -0
  3. package/cli/agents/code-implementer.md +117 -0
  4. package/cli/agents/code-validator.md +80 -0
  5. package/cli/agents/context-reviewer-epic.md +101 -0
  6. package/cli/agents/context-reviewer-story.md +92 -0
  7. package/cli/agents/context-writer-epic.md +145 -0
  8. package/cli/agents/context-writer-story.md +111 -0
  9. package/cli/agents/doc-writer-epic.md +42 -0
  10. package/cli/agents/doc-writer-story.md +43 -0
  11. package/cli/agents/duplicate-detector.md +110 -0
  12. package/cli/agents/epic-story-decomposer.md +318 -39
  13. package/cli/agents/mission-scope-generator.md +68 -4
  14. package/cli/agents/mission-scope-validator.md +40 -6
  15. package/cli/agents/project-context-extractor.md +21 -6
  16. package/cli/agents/scaffolding-generator.md +99 -0
  17. package/cli/agents/seed-validator.md +71 -0
  18. package/cli/agents/story-scope-reviewer.md +147 -0
  19. package/cli/agents/story-splitter.md +83 -0
  20. package/cli/agents/validator-documentation.json +31 -0
  21. package/cli/agents/validator-documentation.md +3 -1
  22. package/cli/api-reference-tool.js +368 -0
  23. package/cli/checks/catalog.json +76 -0
  24. package/cli/checks/code/quality.json +26 -0
  25. package/cli/checks/code/testing.json +14 -0
  26. package/cli/checks/code/traceability.json +26 -0
  27. package/cli/checks/cross-refs/epic.json +171 -0
  28. package/cli/checks/cross-refs/story.json +149 -0
  29. package/cli/checks/epic/api.json +114 -0
  30. package/cli/checks/epic/backend.json +126 -0
  31. package/cli/checks/epic/cloud.json +126 -0
  32. package/cli/checks/epic/data.json +102 -0
  33. package/cli/checks/epic/database.json +114 -0
  34. package/cli/checks/epic/developer.json +182 -0
  35. package/cli/checks/epic/devops.json +174 -0
  36. package/cli/checks/epic/frontend.json +162 -0
  37. package/cli/checks/epic/mobile.json +102 -0
  38. package/cli/checks/epic/qa.json +90 -0
  39. package/cli/checks/epic/security.json +184 -0
  40. package/cli/checks/epic/solution-architect.json +192 -0
  41. package/cli/checks/epic/test-architect.json +90 -0
  42. package/cli/checks/epic/ui.json +102 -0
  43. package/cli/checks/epic/ux.json +90 -0
  44. package/cli/checks/fixes/epic-fix-template.md +10 -0
  45. package/cli/checks/fixes/story-fix-template.md +10 -0
  46. package/cli/checks/story/api.json +186 -0
  47. package/cli/checks/story/backend.json +102 -0
  48. package/cli/checks/story/cloud.json +102 -0
  49. package/cli/checks/story/data.json +210 -0
  50. package/cli/checks/story/database.json +102 -0
  51. package/cli/checks/story/developer.json +168 -0
  52. package/cli/checks/story/devops.json +102 -0
  53. package/cli/checks/story/frontend.json +174 -0
  54. package/cli/checks/story/mobile.json +102 -0
  55. package/cli/checks/story/qa.json +210 -0
  56. package/cli/checks/story/security.json +198 -0
  57. package/cli/checks/story/solution-architect.json +230 -0
  58. package/cli/checks/story/test-architect.json +210 -0
  59. package/cli/checks/story/ui.json +102 -0
  60. package/cli/checks/story/ux.json +102 -0
  61. package/cli/coding-order.js +401 -0
  62. package/cli/dependency-checker.js +72 -0
  63. package/cli/epic-story-validator.js +284 -799
  64. package/cli/index.js +0 -0
  65. package/cli/init-model-config.js +17 -10
  66. package/cli/init.js +514 -92
  67. package/cli/kanban-server-manager.js +1 -2
  68. package/cli/llm-claude.js +98 -31
  69. package/cli/llm-gemini.js +29 -5
  70. package/cli/llm-local.js +493 -0
  71. package/cli/llm-openai.js +262 -41
  72. package/cli/llm-provider.js +147 -8
  73. package/cli/llm-token-limits.js +113 -4
  74. package/cli/llm-verifier.js +209 -1
  75. package/cli/llm-xiaomi.js +143 -0
  76. package/cli/message-constants.js +3 -12
  77. package/cli/messaging-api.js +6 -12
  78. package/cli/micro-check-fixer.js +335 -0
  79. package/cli/micro-check-runner.js +449 -0
  80. package/cli/micro-check-scorer.js +148 -0
  81. package/cli/micro-check-validator.js +538 -0
  82. package/cli/model-pricing.js +23 -0
  83. package/cli/model-selector.js +3 -2
  84. package/cli/prompt-logger.js +57 -0
  85. package/cli/repl-ink.js +106 -346
  86. package/cli/repl-old.js +1 -2
  87. package/cli/seed-processor.js +194 -24
  88. package/cli/sprint-planning-processor.js +2638 -289
  89. package/cli/template-processor.js +50 -3
  90. package/cli/token-tracker.js +50 -23
  91. package/cli/tools/generate-story-validators.js +1 -1
  92. package/cli/validation-router.js +70 -8
  93. package/cli/worktree-runner.js +654 -0
  94. package/kanban/client/dist/assets/index-D_KC5EQT.css +1 -0
  95. package/kanban/client/dist/assets/index-DjY5zqW7.js +351 -0
  96. package/kanban/client/dist/index.html +2 -2
  97. package/kanban/client/src/App.jsx +43 -14
  98. package/kanban/client/src/components/ceremony/AskArchPopup.jsx +7 -3
  99. package/kanban/client/src/components/ceremony/AskModelPopup.jsx +23 -10
  100. package/kanban/client/src/components/ceremony/CeremonyWorkflowModal.jsx +320 -133
  101. package/kanban/client/src/components/ceremony/ProviderSwitcherButton.jsx +290 -0
  102. package/kanban/client/src/components/ceremony/SponsorCallModal.jsx +80 -13
  103. package/kanban/client/src/components/ceremony/SprintPlanningModal.jsx +156 -22
  104. package/kanban/client/src/components/ceremony/steps/ArchitectureStep.jsx +11 -11
  105. package/kanban/client/src/components/ceremony/steps/CompleteStep.jsx +3 -21
  106. package/kanban/client/src/components/ceremony/steps/ReviewAnswersStep.jsx +214 -10
  107. package/kanban/client/src/components/ceremony/steps/RunningStep.jsx +23 -2
  108. package/kanban/client/src/components/kanban/CardDetailModal.jsx +97 -10
  109. package/kanban/client/src/components/kanban/GroupingSelector.jsx +7 -1
  110. package/kanban/client/src/components/kanban/KanbanCard.jsx +23 -14
  111. package/kanban/client/src/components/kanban/RefineWorkItemPopup.jsx +9 -14
  112. package/kanban/client/src/components/kanban/RunButton.jsx +162 -0
  113. package/kanban/client/src/components/kanban/SeedButton.jsx +176 -0
  114. package/kanban/client/src/components/settings/AgentsTab.jsx +103 -75
  115. package/kanban/client/src/components/settings/ApiKeysTab.jsx +31 -2
  116. package/kanban/client/src/components/settings/CeremonyModelsTab.jsx +9 -2
  117. package/kanban/client/src/components/settings/CheckEditorPopup.jsx +507 -0
  118. package/kanban/client/src/components/settings/CostThresholdsTab.jsx +3 -2
  119. package/kanban/client/src/components/settings/ModelPricingTab.jsx +72 -7
  120. package/kanban/client/src/components/settings/OpenAIAuthSection.jsx +412 -0
  121. package/kanban/client/src/components/settings/SettingsModal.jsx +4 -4
  122. package/kanban/client/src/components/stats/CostModal.jsx +34 -3
  123. package/kanban/client/src/hooks/useGrouping.js +59 -0
  124. package/kanban/client/src/lib/api.js +118 -4
  125. package/kanban/client/src/lib/status-grouping.js +10 -0
  126. package/kanban/client/src/store/kanbanStore.js +8 -0
  127. package/kanban/server/index.js +23 -2
  128. package/kanban/server/routes/ceremony.js +153 -4
  129. package/kanban/server/routes/costs.js +9 -3
  130. package/kanban/server/routes/openai-oauth.js +366 -0
  131. package/kanban/server/routes/settings.js +447 -14
  132. package/kanban/server/routes/websocket.js +7 -2
  133. package/kanban/server/routes/work-items.js +141 -1
  134. package/kanban/server/services/CeremonyService.js +275 -24
  135. package/kanban/server/services/TaskRunnerService.js +261 -0
  136. package/kanban/server/workers/run-task-worker.js +121 -0
  137. package/kanban/server/workers/seed-worker.js +94 -0
  138. package/kanban/server/workers/sponsor-call-worker.js +14 -6
  139. package/kanban/server/workers/sprint-planning-worker.js +94 -12
  140. package/package.json +2 -3
  141. package/cli/agents/solver-epic-api.json +0 -15
  142. package/cli/agents/solver-epic-api.md +0 -39
  143. package/cli/agents/solver-epic-backend.json +0 -15
  144. package/cli/agents/solver-epic-backend.md +0 -39
  145. package/cli/agents/solver-epic-cloud.json +0 -15
  146. package/cli/agents/solver-epic-cloud.md +0 -39
  147. package/cli/agents/solver-epic-data.json +0 -15
  148. package/cli/agents/solver-epic-data.md +0 -39
  149. package/cli/agents/solver-epic-database.json +0 -15
  150. package/cli/agents/solver-epic-database.md +0 -39
  151. package/cli/agents/solver-epic-developer.json +0 -15
  152. package/cli/agents/solver-epic-developer.md +0 -39
  153. package/cli/agents/solver-epic-devops.json +0 -15
  154. package/cli/agents/solver-epic-devops.md +0 -39
  155. package/cli/agents/solver-epic-frontend.json +0 -15
  156. package/cli/agents/solver-epic-frontend.md +0 -39
  157. package/cli/agents/solver-epic-mobile.json +0 -15
  158. package/cli/agents/solver-epic-mobile.md +0 -39
  159. package/cli/agents/solver-epic-qa.json +0 -15
  160. package/cli/agents/solver-epic-qa.md +0 -39
  161. package/cli/agents/solver-epic-security.json +0 -15
  162. package/cli/agents/solver-epic-security.md +0 -39
  163. package/cli/agents/solver-epic-solution-architect.json +0 -15
  164. package/cli/agents/solver-epic-solution-architect.md +0 -39
  165. package/cli/agents/solver-epic-test-architect.json +0 -15
  166. package/cli/agents/solver-epic-test-architect.md +0 -39
  167. package/cli/agents/solver-epic-ui.json +0 -15
  168. package/cli/agents/solver-epic-ui.md +0 -39
  169. package/cli/agents/solver-epic-ux.json +0 -15
  170. package/cli/agents/solver-epic-ux.md +0 -39
  171. package/cli/agents/solver-story-api.json +0 -15
  172. package/cli/agents/solver-story-api.md +0 -39
  173. package/cli/agents/solver-story-backend.json +0 -15
  174. package/cli/agents/solver-story-backend.md +0 -39
  175. package/cli/agents/solver-story-cloud.json +0 -15
  176. package/cli/agents/solver-story-cloud.md +0 -39
  177. package/cli/agents/solver-story-data.json +0 -15
  178. package/cli/agents/solver-story-data.md +0 -39
  179. package/cli/agents/solver-story-database.json +0 -15
  180. package/cli/agents/solver-story-database.md +0 -39
  181. package/cli/agents/solver-story-developer.json +0 -15
  182. package/cli/agents/solver-story-developer.md +0 -39
  183. package/cli/agents/solver-story-devops.json +0 -15
  184. package/cli/agents/solver-story-devops.md +0 -39
  185. package/cli/agents/solver-story-frontend.json +0 -15
  186. package/cli/agents/solver-story-frontend.md +0 -39
  187. package/cli/agents/solver-story-mobile.json +0 -15
  188. package/cli/agents/solver-story-mobile.md +0 -39
  189. package/cli/agents/solver-story-qa.json +0 -15
  190. package/cli/agents/solver-story-qa.md +0 -39
  191. package/cli/agents/solver-story-security.json +0 -15
  192. package/cli/agents/solver-story-security.md +0 -39
  193. package/cli/agents/solver-story-solution-architect.json +0 -15
  194. package/cli/agents/solver-story-solution-architect.md +0 -39
  195. package/cli/agents/solver-story-test-architect.json +0 -15
  196. package/cli/agents/solver-story-test-architect.md +0 -39
  197. package/cli/agents/solver-story-ui.json +0 -15
  198. package/cli/agents/solver-story-ui.md +0 -39
  199. package/cli/agents/solver-story-ux.json +0 -15
  200. package/cli/agents/solver-story-ux.md +0 -39
  201. package/cli/agents/validator-epic-api.json +0 -93
  202. package/cli/agents/validator-epic-api.md +0 -137
  203. package/cli/agents/validator-epic-backend.json +0 -93
  204. package/cli/agents/validator-epic-backend.md +0 -130
  205. package/cli/agents/validator-epic-cloud.json +0 -93
  206. package/cli/agents/validator-epic-cloud.md +0 -137
  207. package/cli/agents/validator-epic-data.json +0 -93
  208. package/cli/agents/validator-epic-data.md +0 -130
  209. package/cli/agents/validator-epic-database.json +0 -93
  210. package/cli/agents/validator-epic-database.md +0 -137
  211. package/cli/agents/validator-epic-developer.json +0 -74
  212. package/cli/agents/validator-epic-developer.md +0 -153
  213. package/cli/agents/validator-epic-devops.json +0 -74
  214. package/cli/agents/validator-epic-devops.md +0 -153
  215. package/cli/agents/validator-epic-frontend.json +0 -74
  216. package/cli/agents/validator-epic-frontend.md +0 -153
  217. package/cli/agents/validator-epic-mobile.json +0 -93
  218. package/cli/agents/validator-epic-mobile.md +0 -130
  219. package/cli/agents/validator-epic-qa.json +0 -93
  220. package/cli/agents/validator-epic-qa.md +0 -130
  221. package/cli/agents/validator-epic-security.json +0 -74
  222. package/cli/agents/validator-epic-security.md +0 -154
  223. package/cli/agents/validator-epic-solution-architect.json +0 -74
  224. package/cli/agents/validator-epic-solution-architect.md +0 -156
  225. package/cli/agents/validator-epic-test-architect.json +0 -93
  226. package/cli/agents/validator-epic-test-architect.md +0 -130
  227. package/cli/agents/validator-epic-ui.json +0 -93
  228. package/cli/agents/validator-epic-ui.md +0 -130
  229. package/cli/agents/validator-epic-ux.json +0 -93
  230. package/cli/agents/validator-epic-ux.md +0 -130
  231. package/cli/agents/validator-story-api.json +0 -104
  232. package/cli/agents/validator-story-api.md +0 -152
  233. package/cli/agents/validator-story-backend.json +0 -104
  234. package/cli/agents/validator-story-backend.md +0 -152
  235. package/cli/agents/validator-story-cloud.json +0 -104
  236. package/cli/agents/validator-story-cloud.md +0 -152
  237. package/cli/agents/validator-story-data.json +0 -104
  238. package/cli/agents/validator-story-data.md +0 -152
  239. package/cli/agents/validator-story-database.json +0 -104
  240. package/cli/agents/validator-story-database.md +0 -152
  241. package/cli/agents/validator-story-developer.json +0 -104
  242. package/cli/agents/validator-story-developer.md +0 -152
  243. package/cli/agents/validator-story-devops.json +0 -104
  244. package/cli/agents/validator-story-devops.md +0 -152
  245. package/cli/agents/validator-story-frontend.json +0 -104
  246. package/cli/agents/validator-story-frontend.md +0 -152
  247. package/cli/agents/validator-story-mobile.json +0 -104
  248. package/cli/agents/validator-story-mobile.md +0 -152
  249. package/cli/agents/validator-story-qa.json +0 -104
  250. package/cli/agents/validator-story-qa.md +0 -152
  251. package/cli/agents/validator-story-security.json +0 -104
  252. package/cli/agents/validator-story-security.md +0 -152
  253. package/cli/agents/validator-story-solution-architect.json +0 -104
  254. package/cli/agents/validator-story-solution-architect.md +0 -152
  255. package/cli/agents/validator-story-test-architect.json +0 -104
  256. package/cli/agents/validator-story-test-architect.md +0 -152
  257. package/cli/agents/validator-story-ui.json +0 -104
  258. package/cli/agents/validator-story-ui.md +0 -152
  259. package/cli/agents/validator-story-ux.json +0 -104
  260. package/cli/agents/validator-story-ux.md +0 -152
  261. package/kanban/client/dist/assets/index-CiD8PS2e.js +0 -306
  262. package/kanban/client/dist/assets/index-nLh0m82Q.css +0 -1
@@ -0,0 +1,449 @@
1
+ /**
2
+ * micro-check-runner.js
3
+ *
4
+ * Runs individual micro-checks (Tier 1 and Tier 2) against an LLM provider
5
+ * to evaluate work item quality.
6
+ */
7
+
8
+ const SYSTEM_INSTRUCTIONS =
9
+ "You are a work item analyzer. Answer the applicability question about whether this check is relevant to the given work item. Return JSON with 'applicable' (boolean) and 'reason' (string).";
10
+
11
+ const QUALITY_CHECK_INSTRUCTIONS =
12
+ "You are a work item quality checker. Answer YES (passed: true) or NO (passed: false) to the quality question. Provide brief evidence from the work item text supporting your answer. Return JSON with 'passed' (boolean) and 'evidence' (string).\n\n" +
13
+ "IMPORTANT calibration rules:\n" +
14
+ "- Accept SEMANTIC EQUIVALENCE: if the concept is addressed through a described mechanism, even without using the exact terminology the question uses, answer YES. Example: 'SameSite=Strict cookies' addresses CSRF even without the word 'CSRF'.\n" +
15
+ "- Match strictness to the WORK ITEM LEVEL: Epics are high-level planning artifacts — they describe WHAT and WHY, not HOW. Do not fail an epic for lacking implementation details (specific HTTP methods, error code enumerations, layer-by-layer validation). Stories should be more specific.\n" +
16
+ "- When in doubt, answer YES if the spirit of the requirement is met, even if the exact wording differs.";
17
+
18
+ // ---------------------------------------------------------------------------
19
+ // JSON parse helpers with regex fallback
20
+ // ---------------------------------------------------------------------------
21
+
22
+ /**
23
+ * Parse an applicability response from the LLM.
24
+ * Tries JSON.parse first, then regex fallback.
25
+ * Defaults to applicable = true on total failure (conservative).
26
+ * @param {string|Object} raw - Raw LLM response
27
+ * @returns {{ applicable: boolean, reason: string }}
28
+ */
29
+ function parseApplicabilityResponse(raw) {
30
+ if (raw && typeof raw === 'object') {
31
+ return {
32
+ applicable: Boolean(raw.applicable),
33
+ reason: raw.reason || '',
34
+ };
35
+ }
36
+
37
+ const text = String(raw);
38
+
39
+ try {
40
+ const parsed = JSON.parse(text);
41
+ return {
42
+ applicable: Boolean(parsed.applicable),
43
+ reason: parsed.reason || '',
44
+ };
45
+ } catch {
46
+ // Regex fallback
47
+ const applicableMatch = text.match(/"applicable"\s*:\s*(true|false)/i);
48
+ if (applicableMatch) {
49
+ return {
50
+ applicable: applicableMatch[1].toLowerCase() === 'true',
51
+ reason: '',
52
+ };
53
+ }
54
+
55
+ // Total failure — default to not-applicable (skip) to avoid phantom failures
56
+ // from unparseable LLM responses inflating failure counts
57
+ return { applicable: false, reason: 'Failed to parse LLM response, defaulting to skip' };
58
+ }
59
+ }
60
+
61
+ /**
62
+ * Parse a quality check response from the LLM.
63
+ * Tries JSON.parse first, then regex fallback.
64
+ * Defaults to passed = false on total failure (conservative).
65
+ * @param {string|Object} raw - Raw LLM response
66
+ * @returns {{ passed: boolean, evidence: string }}
67
+ */
68
+ function parseCheckResponse(raw) {
69
+ if (raw && typeof raw === 'object') {
70
+ return {
71
+ passed: Boolean(raw.passed),
72
+ evidence: raw.evidence || '',
73
+ };
74
+ }
75
+
76
+ const text = String(raw);
77
+
78
+ try {
79
+ const parsed = JSON.parse(text);
80
+ return {
81
+ passed: Boolean(parsed.passed),
82
+ evidence: parsed.evidence || '',
83
+ };
84
+ } catch {
85
+ // Regex fallback
86
+ const passedMatch = text.match(/"passed"\s*:\s*(true|false)/i);
87
+ const evidenceMatch = text.match(/"evidence"\s*:\s*"([^"]*)"/);
88
+
89
+ if (passedMatch) {
90
+ return {
91
+ passed: passedMatch[1].toLowerCase() === 'true',
92
+ evidence: evidenceMatch ? evidenceMatch[1] : '',
93
+ };
94
+ }
95
+
96
+ // Total failure — mark as indeterminate (null) so scorer can exclude it
97
+ return { passed: null, evidence: 'Failed to parse LLM response' };
98
+ }
99
+ }
100
+
101
+ // ---------------------------------------------------------------------------
102
+ // Prompt builders
103
+ // ---------------------------------------------------------------------------
104
+
105
+ function buildApplicabilityPrompt(workItemText, applicabilityQuestion) {
106
+ return (
107
+ `Given the following work item:\n\n${workItemText}\n\n` +
108
+ `Question: ${applicabilityQuestion}\n\n` +
109
+ `Respond with JSON: {"applicable": true/false, "reason": "brief reason"}`
110
+ );
111
+ }
112
+
113
+ function buildQualityCheckPrompt(workItemText, question, workItemType) {
114
+ const levelHint = workItemType === 'epic'
115
+ ? 'This is an EPIC (high-level planning document). Evaluate at epic-level granularity — do not require implementation details.\n\n'
116
+ : 'This is a STORY (implementation-level specification). Evaluate at story-level granularity — expect specific details.\n\n';
117
+ return (
118
+ levelHint +
119
+ `Given the following work item:\n\n${workItemText}\n\n` +
120
+ `Question: ${question}\n\n` +
121
+ `Respond with JSON: {"passed": true/false, "evidence": "brief quote or explanation supporting your answer"}`
122
+ );
123
+ }
124
+
125
+ // ---------------------------------------------------------------------------
126
+ // Template variable resolution (Tier 2)
127
+ // ---------------------------------------------------------------------------
128
+
129
+ /**
130
+ * Resolve template variables like {{checkId.evidence}} in a question string.
131
+ * @param {string} question - The question template
132
+ * @param {Map} tier1Results - Map of checkId -> tier1Result
133
+ * @returns {string} The resolved question
134
+ */
135
+ function resolveTemplateVariables(question, tier1Results) {
136
+ return question.replace(/\{\{([\w-]+)\.evidence\}\}/g, (_match, checkId) => {
137
+ const result = tier1Results.get(checkId);
138
+ if (result && result.evidence) {
139
+ return result.evidence;
140
+ }
141
+ return '(not available)';
142
+ });
143
+ }
144
+
145
+ // ---------------------------------------------------------------------------
146
+ // Exported check runners
147
+ // ---------------------------------------------------------------------------
148
+
149
+ /**
150
+ * Run a single Tier 1 check against a work item.
151
+ * @param {Object} check - Check definition from catalog JSON
152
+ * @param {string} workItemText - The full work item text (epic/story context markdown)
153
+ * @param {Object} llmProvider - LLM provider instance with generateJSON() method
154
+ * @param {string} [workItemType] - "epic" or "story" (falls back to check ID heuristic)
155
+ * @returns {Object} { id, tier, severity, category, perspective, universal, applicable, passed, evidence }
156
+ */
157
+ export async function runTier1Check(check, workItemText, llmProvider, workItemType) {
158
+ const baseResult = {
159
+ id: check.id,
160
+ tier: 1,
161
+ severity: check.severity,
162
+ category: check.category,
163
+ perspective: check.perspective,
164
+ universal: check.universal,
165
+ };
166
+
167
+ // Step 1: Applicability gate
168
+ if (check.universal !== true) {
169
+ const applicabilityPrompt = buildApplicabilityPrompt(workItemText, check.applicabilityQuestion);
170
+ const applicabilityRaw = await llmProvider.generateJSON(applicabilityPrompt, SYSTEM_INSTRUCTIONS);
171
+ const applicability = parseApplicabilityResponse(applicabilityRaw);
172
+
173
+ if (!applicability.applicable) {
174
+ return {
175
+ ...baseResult,
176
+ applicable: false,
177
+ passed: null,
178
+ evidence: null,
179
+ };
180
+ }
181
+ }
182
+
183
+ // Step 2: Quality check
184
+ const itemType = workItemType || (check.id.includes('-epic-') ? 'epic' : 'story');
185
+ const qualityPrompt = buildQualityCheckPrompt(workItemText, check.question, itemType);
186
+ const qualityRaw = await llmProvider.generateJSON(qualityPrompt, QUALITY_CHECK_INSTRUCTIONS);
187
+ const qualityResult = parseCheckResponse(qualityRaw);
188
+
189
+ return {
190
+ ...baseResult,
191
+ applicable: true,
192
+ passed: qualityResult.passed,
193
+ evidence: qualityResult.evidence,
194
+ failDescription: check.failDescription,
195
+ failSuggestion: check.failSuggestion,
196
+ };
197
+ }
198
+
199
+ /**
200
+ * Run a single Tier 2 cross-reference check.
201
+ * @param {Object} check - Tier 2 check definition with dependsOn and template variables
202
+ * @param {string} workItemText - The full work item text
203
+ * @param {Map} tier1Results - Map of checkId -> tier1Result (for template resolution)
204
+ * @param {Object} llmProvider - LLM provider instance
205
+ * @returns {Object} { id, tier, severity, category, perspectives, applicable: true, passed, evidence }
206
+ */
207
+ // ---------------------------------------------------------------------------
208
+ // Batch operations — run N checks in a single LLM call
209
+ // ---------------------------------------------------------------------------
210
+
211
+ const BATCH_SYSTEM_INSTRUCTIONS =
212
+ "You are a work item quality checker evaluating MULTIPLE checks in a single pass.\n\n" +
213
+ "IMPORTANT calibration rules:\n" +
214
+ "- Accept SEMANTIC EQUIVALENCE: if the concept is addressed through a described mechanism, even without using the exact terminology the question uses, answer YES. Example: 'SameSite=Strict cookies' addresses CSRF even without the word 'CSRF'.\n" +
215
+ "- Match strictness to the WORK ITEM LEVEL: Epics are high-level planning artifacts — they describe WHAT and WHY, not HOW. Do not fail an epic for lacking implementation details. Stories should be more specific.\n" +
216
+ "- When in doubt, answer YES if the spirit of the requirement is met, even if the exact wording differs.\n\n" +
217
+ "For each check, determine:\n" +
218
+ "1. If it has an applicability question: is this check relevant to the work item? If not, set applicable=false.\n" +
219
+ "2. If applicable (or universal): does the work item pass the quality question?\n\n" +
220
+ "Return a JSON object keyed by check ID. Each value must have: {\"applicable\": boolean, \"passed\": boolean|null, \"evidence\": string}.\n" +
221
+ "If not applicable, set passed=null and evidence=\"\".";
222
+
223
+ /**
224
+ * Build a single prompt that evaluates multiple checks against one work item.
225
+ * @param {Object[]} checks - Array of check definitions
226
+ * @param {string} workItemText - The full work item text
227
+ * @param {string} workItemType - "epic" or "story"
228
+ * @returns {string}
229
+ */
230
+ function buildBatchPrompt(checks, workItemText, workItemType) {
231
+ const levelHint = workItemType === 'epic'
232
+ ? 'This is an EPIC (high-level planning document). Evaluate at epic-level granularity — do not require implementation details.\n\n'
233
+ : 'This is a STORY (implementation-level specification). Evaluate at story-level granularity — expect specific details.\n\n';
234
+
235
+ let prompt = levelHint;
236
+ prompt += `Given the following work item:\n\n${workItemText}\n\n`;
237
+ prompt += `Evaluate the following ${checks.length} checks:\n\n`;
238
+
239
+ for (const check of checks) {
240
+ prompt += `--- CHECK "${check.id}" ---\n`;
241
+ if (check.universal !== true && check.applicabilityQuestion) {
242
+ prompt += `Type: CONDITIONAL (first determine applicability)\n`;
243
+ prompt += `Applicability question: ${check.applicabilityQuestion}\n`;
244
+ } else {
245
+ prompt += `Type: UNIVERSAL (always applicable)\n`;
246
+ }
247
+ prompt += `Quality question: ${check.question}\n\n`;
248
+ }
249
+
250
+ prompt += `Respond with a JSON object. Keys are check IDs, values are {\"applicable\": boolean, \"passed\": boolean|null, \"evidence\": \"...\"}.\n`;
251
+ prompt += `Example: {"check-id-1": {"applicable": true, "passed": true, "evidence": "Found in section X"}, "check-id-2": {"applicable": false, "passed": null, "evidence": ""}}`;
252
+
253
+ return prompt;
254
+ }
255
+
256
+ /**
257
+ * Parse a batch LLM response into a Map of checkId → result or null.
258
+ * Tries JSON.parse first, then regex fallback per check ID.
259
+ * @param {string|Object} raw - Raw LLM response
260
+ * @param {Object[]} checks - The checks that were batched (for ID list)
261
+ * @returns {Map<string, Object|null>}
262
+ */
263
+ function parseBatchResponse(raw, checks) {
264
+ const results = new Map();
265
+ const checkIds = checks.map(c => c.id);
266
+
267
+ // Try full JSON parse
268
+ let parsed = null;
269
+ if (raw && typeof raw === 'object') {
270
+ parsed = raw;
271
+ } else {
272
+ const text = String(raw);
273
+ try {
274
+ parsed = JSON.parse(text);
275
+ } catch {
276
+ // Fall through to regex
277
+ }
278
+ }
279
+
280
+ if (parsed && typeof parsed === 'object') {
281
+ for (const id of checkIds) {
282
+ if (parsed[id] && typeof parsed[id] === 'object') {
283
+ results.set(id, {
284
+ applicable: parsed[id].applicable !== false,
285
+ passed: parsed[id].applicable === false ? null : (parsed[id].passed === null ? null : Boolean(parsed[id].passed)),
286
+ evidence: parsed[id].applicable === false ? '' : (parsed[id].evidence || ''),
287
+ });
288
+ } else {
289
+ results.set(id, null); // Not found in response
290
+ }
291
+ }
292
+ return results;
293
+ }
294
+
295
+ // Regex fallback — try to extract per-check JSON blocks
296
+ const text = String(raw);
297
+ for (const id of checkIds) {
298
+ // Look for "check-id": { ... }
299
+ const escapedId = id.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
300
+ const pattern = new RegExp(`"${escapedId}"\\s*:\\s*\\{([^}]+)\\}`, 'i');
301
+ const match = text.match(pattern);
302
+ if (match) {
303
+ try {
304
+ const block = JSON.parse(`{${match[1]}}`);
305
+ results.set(id, {
306
+ applicable: block.applicable !== false,
307
+ passed: block.applicable === false ? null : (block.passed === null ? null : Boolean(block.passed)),
308
+ evidence: block.applicable === false ? '' : (block.evidence || ''),
309
+ });
310
+ } catch {
311
+ results.set(id, null);
312
+ }
313
+ } else {
314
+ results.set(id, null);
315
+ }
316
+ }
317
+
318
+ return results;
319
+ }
320
+
321
+ /**
322
+ * Run a batch of Tier 1 checks in a single LLM call.
323
+ * Falls back to individual runTier1Check() for any unparseable results.
324
+ * @param {Object[]} checks - Array of Tier 1 check definitions
325
+ * @param {string} workItemText - The full work item text
326
+ * @param {Object} llmProvider - LLM provider instance
327
+ * @param {string} workItemType - "epic" or "story"
328
+ * @returns {Promise<Object[]>} Array of results with same shape as runTier1Check()
329
+ */
330
+ export async function runTier1Batch(checks, workItemText, llmProvider, workItemType) {
331
+ const batchPrompt = buildBatchPrompt(checks, workItemText, workItemType);
332
+ const raw = await llmProvider.generateJSON(batchPrompt, BATCH_SYSTEM_INSTRUCTIONS);
333
+ const parsed = parseBatchResponse(raw, checks);
334
+
335
+ const results = [];
336
+ const fallbacks = [];
337
+
338
+ for (const check of checks) {
339
+ const entry = parsed.get(check.id);
340
+ if (entry) {
341
+ results.push({
342
+ id: check.id,
343
+ tier: 1,
344
+ severity: check.severity,
345
+ category: check.category,
346
+ perspective: check.perspective,
347
+ universal: check.universal,
348
+ applicable: entry.applicable,
349
+ passed: entry.passed,
350
+ evidence: entry.evidence,
351
+ failDescription: check.failDescription,
352
+ failSuggestion: check.failSuggestion,
353
+ });
354
+ } else {
355
+ fallbacks.push(check);
356
+ }
357
+ }
358
+
359
+ // Individual fallback for unparseable entries
360
+ if (fallbacks.length > 0) {
361
+ const fallbackResults = await Promise.all(
362
+ fallbacks.map(check => runTier1Check(check, workItemText, llmProvider, workItemType))
363
+ );
364
+ results.push(...fallbackResults);
365
+ }
366
+
367
+ return results;
368
+ }
369
+
370
+ /**
371
+ * Run a batch of Tier 2 checks in a single LLM call.
372
+ * Resolves template variables before building the batch prompt.
373
+ * Falls back to individual runTier2Check() for unparseable results.
374
+ * @param {Object[]} checks - Array of Tier 2 check definitions
375
+ * @param {string} workItemText - The full work item text
376
+ * @param {Map} tier1Results - Map of checkId → tier1Result
377
+ * @param {Object} llmProvider - LLM provider instance
378
+ * @param {string} workItemType - "epic" or "story"
379
+ * @returns {Promise<Object[]>} Array of results with same shape as runTier2Check()
380
+ */
381
+ export async function runTier2Batch(checks, workItemText, tier1Results, llmProvider, workItemType) {
382
+ // Resolve template variables in each check's question before batching
383
+ const resolvedChecks = checks.map(check => ({
384
+ ...check,
385
+ question: resolveTemplateVariables(check.question, tier1Results),
386
+ universal: true, // Tier 2 checks skip applicability gate
387
+ }));
388
+
389
+ const batchPrompt = buildBatchPrompt(resolvedChecks, workItemText, workItemType);
390
+ const raw = await llmProvider.generateJSON(batchPrompt, BATCH_SYSTEM_INSTRUCTIONS);
391
+ const parsed = parseBatchResponse(raw, resolvedChecks);
392
+
393
+ const results = [];
394
+ const fallbacks = [];
395
+
396
+ for (const check of checks) {
397
+ const entry = parsed.get(check.id);
398
+ if (entry) {
399
+ results.push({
400
+ id: check.id,
401
+ tier: 2,
402
+ severity: check.severity,
403
+ category: check.category,
404
+ perspectives: check.perspectives,
405
+ applicable: true,
406
+ passed: entry.passed,
407
+ evidence: entry.evidence,
408
+ failDescription: check.failDescription,
409
+ failSuggestion: check.failSuggestion,
410
+ });
411
+ } else {
412
+ fallbacks.push(check);
413
+ }
414
+ }
415
+
416
+ // Individual fallback for unparseable entries
417
+ if (fallbacks.length > 0) {
418
+ const fallbackResults = await Promise.all(
419
+ fallbacks.map(check => runTier2Check(check, workItemText, tier1Results, llmProvider))
420
+ );
421
+ results.push(...fallbackResults);
422
+ }
423
+
424
+ return results;
425
+ }
426
+
427
+ export async function runTier2Check(check, workItemText, tier1Results, llmProvider) {
428
+ // Step 1: Resolve template variables in the question
429
+ const resolvedQuestion = resolveTemplateVariables(check.question, tier1Results);
430
+
431
+ // Step 2: Run quality check with resolved question
432
+ const itemType = check.id.includes('-epic') ? 'epic' : 'story';
433
+ const qualityPrompt = buildQualityCheckPrompt(workItemText, resolvedQuestion, itemType);
434
+ const qualityRaw = await llmProvider.generateJSON(qualityPrompt, QUALITY_CHECK_INSTRUCTIONS);
435
+ const qualityResult = parseCheckResponse(qualityRaw);
436
+
437
+ return {
438
+ id: check.id,
439
+ tier: 2,
440
+ severity: check.severity,
441
+ category: check.category,
442
+ perspectives: check.perspectives,
443
+ applicable: true,
444
+ passed: qualityResult.passed,
445
+ evidence: qualityResult.evidence,
446
+ failDescription: check.failDescription,
447
+ failSuggestion: check.failSuggestion,
448
+ };
449
+ }
@@ -0,0 +1,148 @@
1
+ /**
2
+ * Score micro-check results programmatically.
3
+ * Pure JS module — no LLM involvement, no external dependencies.
4
+ *
5
+ * @param {Object[]} checkResults - Array of completed check results from runner.
6
+ * Each: { id, tier, severity, category, perspective, universal, applicable,
7
+ * passed, evidence?, failDescription?, failSuggestion? }
8
+ * @returns {Object} Scoring result with failure counts, status, and pattern detection.
9
+ */
10
+ export function scoreChecks(checkResults) {
11
+ // 1. Partition into applicable vs skipped vs errored
12
+ const applicable = [];
13
+ let skippedCount = 0;
14
+ let erroredCount = 0;
15
+
16
+ for (const check of checkResults) {
17
+ if (check.applicable === false) {
18
+ skippedCount++;
19
+ } else if (check.passed === null) {
20
+ // LLM parse error or runtime error — exclude from scoring
21
+ erroredCount++;
22
+ } else {
23
+ applicable.push(check);
24
+ }
25
+ }
26
+
27
+ const applicableCount = applicable.length;
28
+
29
+ // 2. Count failures by severity (only actual failures, not errors)
30
+ const failures = applicable.filter((c) => c.passed === false);
31
+
32
+ let criticalFails = 0;
33
+ let majorFails = 0;
34
+ let minorFails = 0;
35
+
36
+ for (const f of failures) {
37
+ switch (f.severity) {
38
+ case 'critical':
39
+ criticalFails++;
40
+ break;
41
+ case 'major':
42
+ majorFails++;
43
+ break;
44
+ case 'minor':
45
+ minorFails++;
46
+ break;
47
+ }
48
+ }
49
+
50
+ // 3. Scoring formula (matches existing validators)
51
+ let score;
52
+
53
+ if (criticalFails > 0) {
54
+ score = Math.max(0, Math.min(69, 60 - (criticalFails - 1) * 10));
55
+ } else if (majorFails > 0) {
56
+ score = Math.max(70, Math.min(89, 88 - (majorFails - 1) * 5));
57
+ } else {
58
+ score = Math.max(95, Math.min(100, 98 - minorFails));
59
+ }
60
+
61
+ // 4. Derive status
62
+ let status;
63
+
64
+ if (score >= 90) {
65
+ status = 'excellent';
66
+ } else if (score >= 70) {
67
+ status = 'acceptable';
68
+ } else {
69
+ status = 'needs-improvement';
70
+ }
71
+
72
+ // 5. Collect detailed failure info
73
+ const failedChecks = failures.map((f) => ({
74
+ id: f.id,
75
+ tier: f.tier,
76
+ severity: f.severity,
77
+ category: f.category,
78
+ perspective: f.perspective,
79
+ failDescription: f.failDescription,
80
+ failSuggestion: f.failSuggestion,
81
+ evidence: f.evidence,
82
+ }));
83
+
84
+ // 6. Tier 3 pattern detection
85
+
86
+ const result = {
87
+ score,
88
+ status,
89
+ applicableCount,
90
+ skippedCount,
91
+ erroredCount,
92
+ criticalFails,
93
+ majorFails,
94
+ minorFails,
95
+ failedChecks,
96
+ };
97
+
98
+ // 6a. Split recommendation — look for concentrated major/critical failures
99
+ // in acceptance-criteria or implementation-clarity categories
100
+ const splitTargetCategories = new Set([
101
+ 'acceptance-criteria',
102
+ 'implementation-clarity',
103
+ ]);
104
+ const severeCategoryBuckets = {};
105
+
106
+ for (const f of failures) {
107
+ if (
108
+ (f.severity === 'major' || f.severity === 'critical') &&
109
+ splitTargetCategories.has(f.category)
110
+ ) {
111
+ severeCategoryBuckets[f.category] =
112
+ (severeCategoryBuckets[f.category] || 0) + 1;
113
+ }
114
+ }
115
+
116
+ for (const [category, count] of Object.entries(severeCategoryBuckets)) {
117
+ if (count >= 3) {
118
+ result.splitRecommendation = true;
119
+ result.splitReason =
120
+ `${count} major/critical issues in ${category} suggest this work item combines too many concerns`;
121
+ break; // report the first qualifying category
122
+ }
123
+ }
124
+
125
+ // 6b. Issue clustering — perspectives with 3+ failures
126
+ const perspectiveBuckets = {};
127
+
128
+ for (const f of failures) {
129
+ perspectiveBuckets[f.perspective] =
130
+ (perspectiveBuckets[f.perspective] || 0) + 1;
131
+ }
132
+
133
+ const clusterWarnings = [];
134
+
135
+ for (const [perspective, count] of Object.entries(perspectiveBuckets)) {
136
+ if (count >= 3) {
137
+ clusterWarnings.push(
138
+ `${count} issues from ${perspective} perspective — may indicate fundamental gap in this domain`
139
+ );
140
+ }
141
+ }
142
+
143
+ if (clusterWarnings.length > 0) {
144
+ result.clusterWarnings = clusterWarnings;
145
+ }
146
+
147
+ return result;
148
+ }