@agile-vibe-coding/avc 0.1.1 → 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 (239) hide show
  1. package/cli/agent-loader.js +21 -0
  2. package/cli/agents/agent-selector.md +152 -0
  3. package/cli/agents/architecture-recommender.md +418 -0
  4. package/cli/agents/code-implementer.md +117 -0
  5. package/cli/agents/code-validator.md +80 -0
  6. package/cli/agents/context-reviewer-epic.md +101 -0
  7. package/cli/agents/context-reviewer-story.md +92 -0
  8. package/cli/agents/context-writer-epic.md +145 -0
  9. package/cli/agents/context-writer-story.md +111 -0
  10. package/cli/agents/database-deep-dive.md +470 -0
  11. package/cli/agents/database-recommender.md +634 -0
  12. package/cli/agents/doc-distributor.md +176 -0
  13. package/cli/agents/doc-writer-epic.md +42 -0
  14. package/cli/agents/doc-writer-story.md +43 -0
  15. package/cli/agents/documentation-updater.md +203 -0
  16. package/cli/agents/duplicate-detector.md +110 -0
  17. package/cli/agents/epic-story-decomposer.md +559 -0
  18. package/cli/agents/feature-context-generator.md +91 -0
  19. package/cli/agents/gap-checker-epic.md +52 -0
  20. package/cli/agents/impact-checker-story.md +51 -0
  21. package/cli/agents/migration-guide-generator.md +305 -0
  22. package/cli/agents/mission-scope-generator.md +143 -0
  23. package/cli/agents/mission-scope-validator.md +146 -0
  24. package/cli/agents/project-context-extractor.md +122 -0
  25. package/cli/agents/project-documentation-creator.json +226 -0
  26. package/cli/agents/project-documentation-creator.md +595 -0
  27. package/cli/agents/question-prefiller.md +269 -0
  28. package/cli/agents/refiner-epic.md +39 -0
  29. package/cli/agents/refiner-story.md +42 -0
  30. package/cli/agents/scaffolding-generator.md +99 -0
  31. package/cli/agents/seed-validator.md +71 -0
  32. package/cli/agents/story-doc-enricher.md +133 -0
  33. package/cli/agents/story-scope-reviewer.md +147 -0
  34. package/cli/agents/story-splitter.md +83 -0
  35. package/cli/agents/suggestion-business-analyst.md +88 -0
  36. package/cli/agents/suggestion-deployment-architect.md +263 -0
  37. package/cli/agents/suggestion-product-manager.md +129 -0
  38. package/cli/agents/suggestion-security-specialist.md +156 -0
  39. package/cli/agents/suggestion-technical-architect.md +269 -0
  40. package/cli/agents/suggestion-ux-researcher.md +93 -0
  41. package/cli/agents/task-subtask-decomposer.md +188 -0
  42. package/cli/agents/validator-documentation.json +183 -0
  43. package/cli/agents/validator-documentation.md +455 -0
  44. package/cli/agents/validator-selector.md +211 -0
  45. package/cli/ansi-colors.js +21 -0
  46. package/cli/api-reference-tool.js +368 -0
  47. package/cli/build-docs.js +29 -8
  48. package/cli/ceremony-history.js +369 -0
  49. package/cli/checks/catalog.json +76 -0
  50. package/cli/checks/code/quality.json +26 -0
  51. package/cli/checks/code/testing.json +14 -0
  52. package/cli/checks/code/traceability.json +26 -0
  53. package/cli/checks/cross-refs/epic.json +171 -0
  54. package/cli/checks/cross-refs/story.json +149 -0
  55. package/cli/checks/epic/api.json +114 -0
  56. package/cli/checks/epic/backend.json +126 -0
  57. package/cli/checks/epic/cloud.json +126 -0
  58. package/cli/checks/epic/data.json +102 -0
  59. package/cli/checks/epic/database.json +114 -0
  60. package/cli/checks/epic/developer.json +182 -0
  61. package/cli/checks/epic/devops.json +174 -0
  62. package/cli/checks/epic/frontend.json +162 -0
  63. package/cli/checks/epic/mobile.json +102 -0
  64. package/cli/checks/epic/qa.json +90 -0
  65. package/cli/checks/epic/security.json +184 -0
  66. package/cli/checks/epic/solution-architect.json +192 -0
  67. package/cli/checks/epic/test-architect.json +90 -0
  68. package/cli/checks/epic/ui.json +102 -0
  69. package/cli/checks/epic/ux.json +90 -0
  70. package/cli/checks/fixes/epic-fix-template.md +10 -0
  71. package/cli/checks/fixes/story-fix-template.md +10 -0
  72. package/cli/checks/story/api.json +186 -0
  73. package/cli/checks/story/backend.json +102 -0
  74. package/cli/checks/story/cloud.json +102 -0
  75. package/cli/checks/story/data.json +210 -0
  76. package/cli/checks/story/database.json +102 -0
  77. package/cli/checks/story/developer.json +168 -0
  78. package/cli/checks/story/devops.json +102 -0
  79. package/cli/checks/story/frontend.json +174 -0
  80. package/cli/checks/story/mobile.json +102 -0
  81. package/cli/checks/story/qa.json +210 -0
  82. package/cli/checks/story/security.json +198 -0
  83. package/cli/checks/story/solution-architect.json +230 -0
  84. package/cli/checks/story/test-architect.json +210 -0
  85. package/cli/checks/story/ui.json +102 -0
  86. package/cli/checks/story/ux.json +102 -0
  87. package/cli/coding-order.js +401 -0
  88. package/cli/command-logger.js +49 -12
  89. package/cli/components/static-output.js +63 -0
  90. package/cli/console-output-manager.js +94 -0
  91. package/cli/dependency-checker.js +72 -0
  92. package/cli/docs-sync.js +306 -0
  93. package/cli/epic-story-validator.js +659 -0
  94. package/cli/evaluation-prompts.js +1008 -0
  95. package/cli/execution-context.js +195 -0
  96. package/cli/generate-summary-table.js +340 -0
  97. package/cli/init-model-config.js +704 -0
  98. package/cli/init.js +1737 -278
  99. package/cli/kanban-server-manager.js +227 -0
  100. package/cli/llm-claude.js +150 -1
  101. package/cli/llm-gemini.js +109 -0
  102. package/cli/llm-local.js +493 -0
  103. package/cli/llm-mock.js +233 -0
  104. package/cli/llm-openai.js +454 -0
  105. package/cli/llm-provider.js +379 -3
  106. package/cli/llm-token-limits.js +211 -0
  107. package/cli/llm-verifier.js +662 -0
  108. package/cli/llm-xiaomi.js +143 -0
  109. package/cli/message-constants.js +49 -0
  110. package/cli/message-manager.js +334 -0
  111. package/cli/message-types.js +96 -0
  112. package/cli/messaging-api.js +291 -0
  113. package/cli/micro-check-fixer.js +335 -0
  114. package/cli/micro-check-runner.js +449 -0
  115. package/cli/micro-check-scorer.js +148 -0
  116. package/cli/micro-check-validator.js +538 -0
  117. package/cli/model-pricing.js +192 -0
  118. package/cli/model-query-engine.js +468 -0
  119. package/cli/model-recommendation-analyzer.js +495 -0
  120. package/cli/model-selector.js +270 -0
  121. package/cli/output-buffer.js +107 -0
  122. package/cli/process-manager.js +73 -2
  123. package/cli/prompt-logger.js +57 -0
  124. package/cli/repl-ink.js +4625 -1094
  125. package/cli/repl-old.js +3 -4
  126. package/cli/seed-processor.js +962 -0
  127. package/cli/sprint-planning-processor.js +4162 -0
  128. package/cli/template-processor.js +2149 -105
  129. package/cli/templates/project.md +25 -8
  130. package/cli/templates/vitepress-config.mts.template +5 -4
  131. package/cli/token-tracker.js +547 -0
  132. package/cli/tools/generate-story-validators.js +317 -0
  133. package/cli/tools/generate-validators.js +669 -0
  134. package/cli/update-checker.js +19 -17
  135. package/cli/update-notifier.js +4 -4
  136. package/cli/validation-router.js +667 -0
  137. package/cli/verification-tracker.js +563 -0
  138. package/cli/worktree-runner.js +654 -0
  139. package/kanban/README.md +386 -0
  140. package/kanban/client/README.md +205 -0
  141. package/kanban/client/components.json +20 -0
  142. package/kanban/client/dist/assets/index-D_KC5EQT.css +1 -0
  143. package/kanban/client/dist/assets/index-DjY5zqW7.js +351 -0
  144. package/kanban/client/dist/index.html +16 -0
  145. package/kanban/client/dist/vite.svg +1 -0
  146. package/kanban/client/index.html +15 -0
  147. package/kanban/client/package-lock.json +9442 -0
  148. package/kanban/client/package.json +44 -0
  149. package/kanban/client/postcss.config.js +6 -0
  150. package/kanban/client/public/vite.svg +1 -0
  151. package/kanban/client/src/App.jsx +651 -0
  152. package/kanban/client/src/components/ProjectFileEditorPopup.jsx +117 -0
  153. package/kanban/client/src/components/ceremony/AskArchPopup.jsx +420 -0
  154. package/kanban/client/src/components/ceremony/AskModelPopup.jsx +629 -0
  155. package/kanban/client/src/components/ceremony/CeremonyWorkflowModal.jsx +1133 -0
  156. package/kanban/client/src/components/ceremony/EpicStorySelectionModal.jsx +254 -0
  157. package/kanban/client/src/components/ceremony/ProviderSwitcherButton.jsx +290 -0
  158. package/kanban/client/src/components/ceremony/SponsorCallModal.jsx +686 -0
  159. package/kanban/client/src/components/ceremony/SprintPlanningModal.jsx +838 -0
  160. package/kanban/client/src/components/ceremony/steps/ArchitectureStep.jsx +150 -0
  161. package/kanban/client/src/components/ceremony/steps/CompleteStep.jsx +136 -0
  162. package/kanban/client/src/components/ceremony/steps/DatabaseStep.jsx +202 -0
  163. package/kanban/client/src/components/ceremony/steps/DeploymentStep.jsx +123 -0
  164. package/kanban/client/src/components/ceremony/steps/MissionStep.jsx +106 -0
  165. package/kanban/client/src/components/ceremony/steps/ReviewAnswersStep.jsx +329 -0
  166. package/kanban/client/src/components/ceremony/steps/RunningStep.jsx +249 -0
  167. package/kanban/client/src/components/kanban/CardDetailModal.jsx +646 -0
  168. package/kanban/client/src/components/kanban/EpicSection.jsx +146 -0
  169. package/kanban/client/src/components/kanban/FilterToolbar.jsx +222 -0
  170. package/kanban/client/src/components/kanban/GroupingSelector.jsx +63 -0
  171. package/kanban/client/src/components/kanban/KanbanBoard.jsx +211 -0
  172. package/kanban/client/src/components/kanban/KanbanCard.jsx +147 -0
  173. package/kanban/client/src/components/kanban/KanbanColumn.jsx +90 -0
  174. package/kanban/client/src/components/kanban/RefineWorkItemPopup.jsx +784 -0
  175. package/kanban/client/src/components/kanban/RunButton.jsx +162 -0
  176. package/kanban/client/src/components/kanban/SeedButton.jsx +176 -0
  177. package/kanban/client/src/components/layout/LoadingScreen.jsx +82 -0
  178. package/kanban/client/src/components/process/ProcessMonitorBar.jsx +80 -0
  179. package/kanban/client/src/components/settings/AgentEditorPopup.jsx +171 -0
  180. package/kanban/client/src/components/settings/AgentsTab.jsx +381 -0
  181. package/kanban/client/src/components/settings/ApiKeysTab.jsx +142 -0
  182. package/kanban/client/src/components/settings/CeremonyModelsTab.jsx +105 -0
  183. package/kanban/client/src/components/settings/CheckEditorPopup.jsx +507 -0
  184. package/kanban/client/src/components/settings/CostThresholdsTab.jsx +95 -0
  185. package/kanban/client/src/components/settings/ModelPricingTab.jsx +269 -0
  186. package/kanban/client/src/components/settings/OpenAIAuthSection.jsx +412 -0
  187. package/kanban/client/src/components/settings/ServersTab.jsx +121 -0
  188. package/kanban/client/src/components/settings/SettingsModal.jsx +84 -0
  189. package/kanban/client/src/components/stats/CostModal.jsx +384 -0
  190. package/kanban/client/src/components/ui/badge.jsx +27 -0
  191. package/kanban/client/src/components/ui/dialog.jsx +121 -0
  192. package/kanban/client/src/components/ui/tabs.jsx +85 -0
  193. package/kanban/client/src/hooks/__tests__/useGrouping.test.js +232 -0
  194. package/kanban/client/src/hooks/useGrouping.js +177 -0
  195. package/kanban/client/src/hooks/useWebSocket.js +120 -0
  196. package/kanban/client/src/lib/__tests__/api.test.js +196 -0
  197. package/kanban/client/src/lib/__tests__/status-grouping.test.js +94 -0
  198. package/kanban/client/src/lib/api.js +515 -0
  199. package/kanban/client/src/lib/status-grouping.js +154 -0
  200. package/kanban/client/src/lib/utils.js +11 -0
  201. package/kanban/client/src/main.jsx +10 -0
  202. package/kanban/client/src/store/__tests__/kanbanStore.test.js +164 -0
  203. package/kanban/client/src/store/ceremonyStore.js +172 -0
  204. package/kanban/client/src/store/filterStore.js +201 -0
  205. package/kanban/client/src/store/kanbanStore.js +123 -0
  206. package/kanban/client/src/store/processStore.js +65 -0
  207. package/kanban/client/src/store/sprintPlanningStore.js +33 -0
  208. package/kanban/client/src/styles/globals.css +59 -0
  209. package/kanban/client/tailwind.config.js +77 -0
  210. package/kanban/client/vite.config.js +28 -0
  211. package/kanban/client/vitest.config.js +28 -0
  212. package/kanban/dev-start.sh +47 -0
  213. package/kanban/package.json +12 -0
  214. package/kanban/server/index.js +537 -0
  215. package/kanban/server/routes/ceremony.js +454 -0
  216. package/kanban/server/routes/costs.js +163 -0
  217. package/kanban/server/routes/openai-oauth.js +366 -0
  218. package/kanban/server/routes/processes.js +50 -0
  219. package/kanban/server/routes/settings.js +736 -0
  220. package/kanban/server/routes/websocket.js +281 -0
  221. package/kanban/server/routes/work-items.js +487 -0
  222. package/kanban/server/services/CeremonyService.js +1441 -0
  223. package/kanban/server/services/FileSystemScanner.js +95 -0
  224. package/kanban/server/services/FileWatcher.js +144 -0
  225. package/kanban/server/services/HierarchyBuilder.js +196 -0
  226. package/kanban/server/services/ProcessRegistry.js +122 -0
  227. package/kanban/server/services/TaskRunnerService.js +261 -0
  228. package/kanban/server/services/WorkItemReader.js +123 -0
  229. package/kanban/server/services/WorkItemRefineService.js +510 -0
  230. package/kanban/server/start.js +49 -0
  231. package/kanban/server/utils/kanban-logger.js +132 -0
  232. package/kanban/server/utils/markdown.js +91 -0
  233. package/kanban/server/utils/status-grouping.js +107 -0
  234. package/kanban/server/workers/run-task-worker.js +121 -0
  235. package/kanban/server/workers/seed-worker.js +94 -0
  236. package/kanban/server/workers/sponsor-call-worker.js +92 -0
  237. package/kanban/server/workers/sprint-planning-worker.js +212 -0
  238. package/package.json +19 -7
  239. package/cli/agents/documentation.md +0 -302
@@ -0,0 +1,662 @@
1
+ import fs from 'fs';
2
+ import path from 'path';
3
+ import { fileURLToPath } from 'url';
4
+
5
+ const __filename = fileURLToPath(import.meta.url);
6
+ const __dirname = path.dirname(__filename);
7
+
8
+ /**
9
+ * LLM-based verification engine
10
+ *
11
+ * CONFIGURATION-DRIVEN VALIDATION:
12
+ * - All validation logic defined in JSON rule files
13
+ * - Fast-path optimizations configurable per rule (not hardcoded)
14
+ * - Hardcoded helpers exist (JSON parsing, regex) but triggered by JSON config
15
+ * - Each rule checks ONE thing and fixes ONE thing (atomic)
16
+ *
17
+ * Fast-Path Types Available:
18
+ * - 'json-parse': Validate JSON syntax programmatically
19
+ * - 'json-fields': Check required fields programmatically
20
+ * - 'none': Always use LLM (no fast-path)
21
+ *
22
+ * Usage:
23
+ * const verifier = new LLMVerifier(llmProvider, 'project-documentation-creator');
24
+ * const result = await verifier.verify(content, progressCallback);
25
+ */
26
+ export class LLMVerifier {
27
+ constructor(llmProvider, agentName, tracker = null) {
28
+ this.llmProvider = llmProvider;
29
+ this.agentName = agentName;
30
+ this.tracker = tracker; // Optional verification tracker
31
+ this.rules = this.loadRules();
32
+ }
33
+
34
+ /**
35
+ * Fast-path: Programmatically unwrap JSON from markdown code fence
36
+ * @param {string} content - Content that may be wrapped
37
+ * @returns {object} { isWrapped: boolean, unwrapped: string }
38
+ */
39
+ unwrapJsonCodeFence(content) {
40
+ const trimmed = content.trim();
41
+
42
+ // Pattern 1: ```json\n...\n```
43
+ const pattern1 = /^```json\s*\n([\s\S]*)\n```$/;
44
+ const match1 = trimmed.match(pattern1);
45
+ if (match1) {
46
+ return { isWrapped: true, unwrapped: match1[1].trim() };
47
+ }
48
+
49
+ // Pattern 2: ```\n...\n``` (generic code fence)
50
+ const pattern2 = /^```\s*\n([\s\S]*)\n```$/;
51
+ const match2 = trimmed.match(pattern2);
52
+ if (match2) {
53
+ // Check if content looks like JSON
54
+ const unwrapped = match2[1].trim();
55
+ if (unwrapped.startsWith('{') || unwrapped.startsWith('[')) {
56
+ return { isWrapped: true, unwrapped };
57
+ }
58
+ }
59
+
60
+ return { isWrapped: false, unwrapped: content };
61
+ }
62
+
63
+ /**
64
+ * Fast-path: Check if content is valid JSON
65
+ * @param {string} content - Content to check
66
+ * @returns {object} { canFastPath: boolean, violated: boolean, reason: string }
67
+ */
68
+ fastPathValidJson(content) {
69
+ // Check for markdown code fence
70
+ const { isWrapped, unwrapped } = this.unwrapJsonCodeFence(content);
71
+
72
+ if (isWrapped) {
73
+ return { canFastPath: true, violated: true, reason: 'markdown-fence' };
74
+ }
75
+
76
+ // Try parsing JSON
77
+ try {
78
+ JSON.parse(unwrapped);
79
+ return { canFastPath: true, violated: false };
80
+ } catch (e) {
81
+ // Parse error - might be fixable by LLM
82
+ return { canFastPath: false, violated: true, reason: e.message };
83
+ }
84
+ }
85
+
86
+ /**
87
+ * Fast-path: Check if required fields present
88
+ * @param {string} content - JSON content
89
+ * @param {array} requiredFields - Field names to check
90
+ * @returns {object} { canFastPath: boolean, violated: boolean, missingFields: array }
91
+ */
92
+ fastPathRequiredFields(content, requiredFields) {
93
+ try {
94
+ // First unwrap if needed
95
+ const { unwrapped } = this.unwrapJsonCodeFence(content);
96
+ const obj = JSON.parse(unwrapped);
97
+ const missing = requiredFields.filter(field => !(field in obj));
98
+
99
+ if (missing.length === 0) {
100
+ return { canFastPath: true, violated: false };
101
+ } else {
102
+ return { canFastPath: true, violated: true, missingFields: missing };
103
+ }
104
+ } catch (e) {
105
+ // Can't parse - let LLM handle
106
+ return { canFastPath: false };
107
+ }
108
+ }
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
+
184
+ /**
185
+ * Execute fast-path optimization if configured
186
+ * @param {string} content - Content to check
187
+ * @param {Object} rule - Verification rule with fastPath config
188
+ * @returns {Promise<Object>} { canFastPath: boolean, violated: boolean, reason: string }
189
+ */
190
+ async executeFastPath(content, rule) {
191
+ if (!rule.fastPath?.enabled) {
192
+ return { canFastPath: false };
193
+ }
194
+
195
+ const type = rule.fastPath.type;
196
+
197
+ switch (type) {
198
+ case 'json-parse':
199
+ // JSON parsing fast-path
200
+ return this.fastPathValidJson(content);
201
+
202
+ case 'json-fields':
203
+ // Required fields fast-path
204
+ const fields = rule.fastPath.requiredFields || [];
205
+ return this.fastPathRequiredFields(content, fields);
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
+
220
+ case 'none':
221
+ default:
222
+ // No fast-path, use LLM
223
+ return { canFastPath: false };
224
+ }
225
+ }
226
+
227
+ /**
228
+ * Load verification rules from JSON file
229
+ * @returns {Array} Enabled verification rules
230
+ */
231
+ loadRules() {
232
+ const rulesPath = path.join(__dirname, 'agents', `${this.agentName}.json`);
233
+
234
+ if (!fs.existsSync(rulesPath)) {
235
+ console.warn(`Warning: No verification rules found for agent: ${this.agentName}`);
236
+ return [];
237
+ }
238
+
239
+ try {
240
+ const data = JSON.parse(fs.readFileSync(rulesPath, 'utf8'));
241
+
242
+ // Filter to enabled rules only
243
+ const enabledRules = data.verifications.filter(r => r.enabled !== false);
244
+
245
+ return enabledRules;
246
+ } catch (error) {
247
+ console.error(`Error loading verification rules from ${rulesPath}:`, error.message);
248
+ return [];
249
+ }
250
+ }
251
+
252
+ /**
253
+ * Check if rule is violated
254
+ * @param {string} content - Content to check
255
+ * @param {Object} rule - Verification rule
256
+ * @returns {Promise<boolean>} True if rule is violated (needs fixing)
257
+ */
258
+ async checkRule(content, rule) {
259
+ try {
260
+ if (this.tracker) {
261
+ this.tracker.startRuleCheck(rule);
262
+ }
263
+
264
+ // Try fast-path if configured in rule
265
+ if (rule.fastPath?.enabled) {
266
+ const fastPathResult = await this.executeFastPath(content, rule);
267
+ if (fastPathResult.canFastPath) {
268
+ console.log(`[DEBUG] Fast-path used for ${rule.id}: ${fastPathResult.violated ? 'VIOLATED' : 'PASSED'}${fastPathResult.reason ? ` (${fastPathResult.reason})` : ''}${fastPathResult.missingFields ? ` (missing: ${fastPathResult.missingFields.join(', ')})` : ''}`);
269
+ if (this.tracker) {
270
+ this.tracker.endRuleCheck(fastPathResult.violated ? 'YES' : 'NO');
271
+ }
272
+ return fastPathResult.violated;
273
+ }
274
+ }
275
+
276
+ // Fallback to LLM check
277
+ console.log(`[DEBUG] Fast-path not available for ${rule.id}, using LLM`);
278
+ const prompt = rule.check.prompt.replace('{content}', content);
279
+ const maxTokens = rule.check.maxTokens || 10;
280
+
281
+ const response = await this.llmProvider.generate(prompt, maxTokens);
282
+ const answer = response.trim().toUpperCase();
283
+
284
+ // Check if response matches expected pattern (YES means violation found)
285
+ let result = false;
286
+ if (rule.check.expectedResponse === 'YES|NO') {
287
+ result = answer === 'YES';
288
+ }
289
+
290
+ if (this.tracker) {
291
+ this.tracker.endRuleCheck(answer);
292
+ }
293
+
294
+ console.log(`[DEBUG] checkRule - Rule: ${rule.id}, Result: ${answer}`);
295
+ return result;
296
+ } catch (error) {
297
+ console.error(`Error checking rule ${rule.id}:`, error.message);
298
+ if (this.tracker) {
299
+ this.tracker.endRuleCheck('ERROR');
300
+ this.tracker.completeRule();
301
+ }
302
+ return false; // Skip this rule on error
303
+ }
304
+ }
305
+
306
+ /**
307
+ * Fix content according to rule
308
+ * @param {string} content - Content to fix
309
+ * @param {Object} rule - Verification rule
310
+ * @returns {Promise<string>} Fixed content
311
+ */
312
+ async fixContent(content, rule) {
313
+ try {
314
+ if (this.tracker) {
315
+ this.tracker.startRuleFix(content.length);
316
+ }
317
+
318
+ // Fast-path fix: unwrap JSON code fences
319
+ if (rule.fastPath?.enabled && rule.fastPath.type === 'json-parse') {
320
+ const { isWrapped, unwrapped } = this.unwrapJsonCodeFence(content);
321
+ if (isWrapped) {
322
+ console.log('[DEBUG] Fast-path: Unwrapping JSON code fence (no LLM call)');
323
+ if (this.tracker) {
324
+ this.tracker.endRuleFix(unwrapped.length);
325
+ }
326
+ return unwrapped;
327
+ }
328
+ }
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
+
451
+ // Fallback to LLM fix
452
+ const prompt = rule.fix.prompt.replace('{content}', content);
453
+ const maxTokens = rule.fix.maxTokens || 4096;
454
+
455
+ console.log(`[DEBUG] fixContent - Rule: ${rule.id}, Fixing content (length: ${content.length})`);
456
+ const fixed = await this.llmProvider.generate(prompt, maxTokens);
457
+ console.log(`[DEBUG] fixContent - Rule: ${rule.id}, LLM returned ${fixed.length} chars`);
458
+ console.log(`[DEBUG] fixContent - Rule: ${rule.id}, Raw output preview:`, fixed.substring(0, 300));
459
+
460
+ const trimmed = fixed.trim();
461
+ console.log(`[DEBUG] fixContent - Rule: ${rule.id}, After trim: ${trimmed.length} chars`);
462
+
463
+ if (this.tracker) {
464
+ this.tracker.endRuleFix(trimmed.length);
465
+ }
466
+
467
+ return trimmed;
468
+ } catch (error) {
469
+ console.error(`Error fixing with rule ${rule.id}:`, error.message);
470
+ return content; // Return original content on error
471
+ }
472
+ }
473
+
474
+ /**
475
+ * Verify and fix content using all enabled rules
476
+ * @param {string} content - Content to verify
477
+ * @param {Function} progressCallback - Optional callback (mainMsg, substep)
478
+ * @returns {Promise<Object>} { content, rulesApplied }
479
+ */
480
+ async verify(content, progressCallback = null) {
481
+ // Check cache first
482
+ if (this.tracker && this.tracker.verificationCache) {
483
+ const contentHash = this.tracker.hashContent(content);
484
+ const cacheKey = `${this.agentName}-${contentHash}`;
485
+
486
+ if (this.tracker.verificationCache.has(cacheKey)) {
487
+ console.log(`[DEBUG] Cache HIT: Reusing verification for ${this.agentName} (hash: ${contentHash})`);
488
+ return this.tracker.verificationCache.get(cacheKey);
489
+ }
490
+
491
+ console.log(`[DEBUG] Cache MISS: Running verification for ${this.agentName} (hash: ${contentHash})`);
492
+ }
493
+
494
+ if (this.tracker) {
495
+ this.tracker.startSession(this.agentName, content);
496
+ }
497
+
498
+ console.log(`[DEBUG] verify - Starting verification with ${this.rules.length} rules`);
499
+ console.log(`[DEBUG] verify - Input content length: ${content.length}`);
500
+ console.log(`[DEBUG] verify - Input content preview:`, content.substring(0, 300));
501
+
502
+ let current = content;
503
+ const applied = [];
504
+
505
+ // If no rules loaded, return original content
506
+ if (this.rules.length === 0) {
507
+ return { content: current, rulesApplied: [] };
508
+ }
509
+
510
+ // PHASE 1: Check all rules in parallel
511
+ console.log(`[DEBUG] verify - Phase 1: Checking ${this.rules.length} rules in parallel`);
512
+ if (progressCallback) {
513
+ progressCallback(null, `Checking ${this.rules.length} rules...`);
514
+ await new Promise(resolve => setTimeout(resolve, 20));
515
+ }
516
+
517
+ const checkPromises = this.rules.map(async (rule) => {
518
+ // Check if rule should be skipped based on profiling
519
+ if (this.tracker && this.tracker.shouldSkipRule(this.agentName, rule.id)) {
520
+ return { rule, violated: false, error: null, skipped: true };
521
+ }
522
+
523
+ try {
524
+ const violated = await this.checkRule(current, rule);
525
+
526
+ // Update rule profile
527
+ if (this.tracker) {
528
+ this.tracker.updateRuleProfile(this.agentName, rule.id, violated);
529
+ }
530
+
531
+ return { rule, violated, error: null, skipped: false };
532
+ } catch (error) {
533
+ console.error(`Error checking rule ${rule.id}:`, error.message);
534
+ return { rule, violated: false, error: error.message, skipped: false };
535
+ }
536
+ });
537
+
538
+ const checkResults = await Promise.all(checkPromises);
539
+
540
+ // PHASE 2: Fix violations sequentially
541
+ console.log(`[DEBUG] verify - Phase 2: Fixing violations sequentially`);
542
+ const violatedRules = checkResults.filter(r => r.violated && !r.error);
543
+ console.log(`[DEBUG] verify - Found ${violatedRules.length} violations:`, violatedRules.map(r => r.rule.id));
544
+
545
+ for (const { rule, violated } of violatedRules) {
546
+ // SAFEGUARD: Double-check that rule was actually violated
547
+ if (!violated) {
548
+ console.warn(`[WARN] verify - Skipping fix for ${rule.id} - violated flag is false (defensive check)`);
549
+ if (this.tracker) {
550
+ this.tracker.completeRule();
551
+ }
552
+ continue;
553
+ }
554
+
555
+ // Report progress: fixing
556
+ if (progressCallback) {
557
+ progressCallback(null, `Fixing: ${rule.name}...`);
558
+ await new Promise(resolve => setTimeout(resolve, 20));
559
+ }
560
+
561
+ try {
562
+ const beforeLength = current.length;
563
+
564
+ // Apply fix
565
+ const fixed = await this.fixContent(current, rule);
566
+
567
+ // Only update if fix actually changed content
568
+ if (fixed !== current) {
569
+ const afterLength = fixed.length;
570
+ const changePercent = Math.abs((afterLength - beforeLength) / beforeLength * 100);
571
+ const changeChars = afterLength - beforeLength;
572
+
573
+ // SAFEGUARD: Warn on aggressive content changes
574
+ if (changePercent > 30) {
575
+ console.warn(`[WARN] verify - Rule ${rule.id} caused ${changePercent.toFixed(1)}% content change (${changeChars > 0 ? '+' : ''}${changeChars} chars)`);
576
+ console.warn(`[WARN] verify - Before: ${beforeLength} chars, After: ${afterLength} chars`);
577
+ console.warn(`[WARN] verify - This may indicate an overly aggressive fix. Review rule prompt.`);
578
+ } else {
579
+ console.log(`[DEBUG] verify - Rule ${rule.id} changed content by ${changePercent.toFixed(1)}% (${changeChars > 0 ? '+' : ''}${changeChars} chars)`);
580
+ }
581
+
582
+ current = fixed;
583
+ applied.push({
584
+ id: rule.id,
585
+ name: rule.name,
586
+ severity: rule.severity,
587
+ description: rule.description
588
+ });
589
+ } else {
590
+ console.log(`[DEBUG] verify - Rule ${rule.id} fix did not change content (no-op fix)`);
591
+ }
592
+ } catch (error) {
593
+ console.error(`Error fixing rule ${rule.id}:`, error.message);
594
+ }
595
+
596
+ if (this.tracker) {
597
+ this.tracker.completeRule();
598
+ }
599
+ }
600
+
601
+ // Complete tracking for rules that didn't violate
602
+ const passedRules = checkResults.filter(r => !r.violated || r.error);
603
+ for (const { rule } of passedRules) {
604
+ if (this.tracker) {
605
+ this.tracker.completeRule();
606
+ }
607
+ }
608
+
609
+ console.log(`[DEBUG] verify - Completed with ${applied.length} rules applied:`, applied.map(r => r.id));
610
+ console.log(`[DEBUG] verify - Final content length: ${current.length}`);
611
+ console.log(`[DEBUG] verify - Final content preview:`, current.substring(0, 300));
612
+
613
+ if (this.tracker) {
614
+ this.tracker.endSession(current, applied);
615
+ this.tracker.logSessionSummary();
616
+ }
617
+
618
+ const result = {
619
+ content: current,
620
+ rulesApplied: applied,
621
+ noViolations: applied.length === 0, // Track if this was a perfect verification
622
+ timestamp: Date.now()
623
+ };
624
+
625
+ // Store in cache
626
+ if (this.tracker && this.tracker.verificationCache) {
627
+ const contentHash = this.tracker.hashContent(content);
628
+ const cacheKey = `${this.agentName}-${contentHash}`;
629
+ this.tracker.verificationCache.set(cacheKey, result);
630
+
631
+ if (applied.length === 0) {
632
+ console.log(`[DEBUG] Cached PERFECT verification result for ${this.agentName} (hash: ${contentHash}) - no violations found`);
633
+ } else {
634
+ console.log(`[DEBUG] Cached verification result for ${this.agentName} (hash: ${contentHash}) - ${applied.length} fixes applied`);
635
+ }
636
+ }
637
+
638
+ return result;
639
+ }
640
+
641
+ /**
642
+ * Get list of all rules for this agent
643
+ * @returns {Array} Rule metadata (id, name, severity, description)
644
+ */
645
+ getRules() {
646
+ return this.rules.map(r => ({
647
+ id: r.id,
648
+ name: r.name,
649
+ severity: r.severity,
650
+ description: r.description,
651
+ enabled: r.enabled !== false
652
+ }));
653
+ }
654
+
655
+ /**
656
+ * Get count of enabled rules
657
+ * @returns {number} Number of enabled rules
658
+ */
659
+ getRuleCount() {
660
+ return this.rules.length;
661
+ }
662
+ }