@agile-vibe-coding/avc 0.1.0 → 0.2.3

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 (290) hide show
  1. package/README.md +2 -0
  2. package/cli/agent-loader.js +21 -0
  3. package/cli/agents/agent-selector.md +129 -0
  4. package/cli/agents/architecture-recommender.md +418 -0
  5. package/cli/agents/database-deep-dive.md +470 -0
  6. package/cli/agents/database-recommender.md +634 -0
  7. package/cli/agents/doc-distributor.md +176 -0
  8. package/cli/agents/documentation-updater.md +203 -0
  9. package/cli/agents/epic-story-decomposer.md +280 -0
  10. package/cli/agents/feature-context-generator.md +91 -0
  11. package/cli/agents/gap-checker-epic.md +52 -0
  12. package/cli/agents/impact-checker-story.md +51 -0
  13. package/cli/agents/migration-guide-generator.md +305 -0
  14. package/cli/agents/mission-scope-generator.md +79 -0
  15. package/cli/agents/mission-scope-validator.md +112 -0
  16. package/cli/agents/project-context-extractor.md +107 -0
  17. package/cli/agents/project-documentation-creator.json +226 -0
  18. package/cli/agents/project-documentation-creator.md +595 -0
  19. package/cli/agents/question-prefiller.md +269 -0
  20. package/cli/agents/refiner-epic.md +39 -0
  21. package/cli/agents/refiner-story.md +42 -0
  22. package/cli/agents/solver-epic-api.json +15 -0
  23. package/cli/agents/solver-epic-api.md +39 -0
  24. package/cli/agents/solver-epic-backend.json +15 -0
  25. package/cli/agents/solver-epic-backend.md +39 -0
  26. package/cli/agents/solver-epic-cloud.json +15 -0
  27. package/cli/agents/solver-epic-cloud.md +39 -0
  28. package/cli/agents/solver-epic-data.json +15 -0
  29. package/cli/agents/solver-epic-data.md +39 -0
  30. package/cli/agents/solver-epic-database.json +15 -0
  31. package/cli/agents/solver-epic-database.md +39 -0
  32. package/cli/agents/solver-epic-developer.json +15 -0
  33. package/cli/agents/solver-epic-developer.md +39 -0
  34. package/cli/agents/solver-epic-devops.json +15 -0
  35. package/cli/agents/solver-epic-devops.md +39 -0
  36. package/cli/agents/solver-epic-frontend.json +15 -0
  37. package/cli/agents/solver-epic-frontend.md +39 -0
  38. package/cli/agents/solver-epic-mobile.json +15 -0
  39. package/cli/agents/solver-epic-mobile.md +39 -0
  40. package/cli/agents/solver-epic-qa.json +15 -0
  41. package/cli/agents/solver-epic-qa.md +39 -0
  42. package/cli/agents/solver-epic-security.json +15 -0
  43. package/cli/agents/solver-epic-security.md +39 -0
  44. package/cli/agents/solver-epic-solution-architect.json +15 -0
  45. package/cli/agents/solver-epic-solution-architect.md +39 -0
  46. package/cli/agents/solver-epic-test-architect.json +15 -0
  47. package/cli/agents/solver-epic-test-architect.md +39 -0
  48. package/cli/agents/solver-epic-ui.json +15 -0
  49. package/cli/agents/solver-epic-ui.md +39 -0
  50. package/cli/agents/solver-epic-ux.json +15 -0
  51. package/cli/agents/solver-epic-ux.md +39 -0
  52. package/cli/agents/solver-story-api.json +15 -0
  53. package/cli/agents/solver-story-api.md +39 -0
  54. package/cli/agents/solver-story-backend.json +15 -0
  55. package/cli/agents/solver-story-backend.md +39 -0
  56. package/cli/agents/solver-story-cloud.json +15 -0
  57. package/cli/agents/solver-story-cloud.md +39 -0
  58. package/cli/agents/solver-story-data.json +15 -0
  59. package/cli/agents/solver-story-data.md +39 -0
  60. package/cli/agents/solver-story-database.json +15 -0
  61. package/cli/agents/solver-story-database.md +39 -0
  62. package/cli/agents/solver-story-developer.json +15 -0
  63. package/cli/agents/solver-story-developer.md +39 -0
  64. package/cli/agents/solver-story-devops.json +15 -0
  65. package/cli/agents/solver-story-devops.md +39 -0
  66. package/cli/agents/solver-story-frontend.json +15 -0
  67. package/cli/agents/solver-story-frontend.md +39 -0
  68. package/cli/agents/solver-story-mobile.json +15 -0
  69. package/cli/agents/solver-story-mobile.md +39 -0
  70. package/cli/agents/solver-story-qa.json +15 -0
  71. package/cli/agents/solver-story-qa.md +39 -0
  72. package/cli/agents/solver-story-security.json +15 -0
  73. package/cli/agents/solver-story-security.md +39 -0
  74. package/cli/agents/solver-story-solution-architect.json +15 -0
  75. package/cli/agents/solver-story-solution-architect.md +39 -0
  76. package/cli/agents/solver-story-test-architect.json +15 -0
  77. package/cli/agents/solver-story-test-architect.md +39 -0
  78. package/cli/agents/solver-story-ui.json +15 -0
  79. package/cli/agents/solver-story-ui.md +39 -0
  80. package/cli/agents/solver-story-ux.json +15 -0
  81. package/cli/agents/solver-story-ux.md +39 -0
  82. package/cli/agents/story-doc-enricher.md +133 -0
  83. package/cli/agents/suggestion-business-analyst.md +88 -0
  84. package/cli/agents/suggestion-deployment-architect.md +263 -0
  85. package/cli/agents/suggestion-product-manager.md +129 -0
  86. package/cli/agents/suggestion-security-specialist.md +156 -0
  87. package/cli/agents/suggestion-technical-architect.md +269 -0
  88. package/cli/agents/suggestion-ux-researcher.md +93 -0
  89. package/cli/agents/task-subtask-decomposer.md +188 -0
  90. package/cli/agents/validator-documentation.json +152 -0
  91. package/cli/agents/validator-documentation.md +453 -0
  92. package/cli/agents/validator-epic-api.json +93 -0
  93. package/cli/agents/validator-epic-api.md +137 -0
  94. package/cli/agents/validator-epic-backend.json +93 -0
  95. package/cli/agents/validator-epic-backend.md +130 -0
  96. package/cli/agents/validator-epic-cloud.json +93 -0
  97. package/cli/agents/validator-epic-cloud.md +137 -0
  98. package/cli/agents/validator-epic-data.json +93 -0
  99. package/cli/agents/validator-epic-data.md +130 -0
  100. package/cli/agents/validator-epic-database.json +93 -0
  101. package/cli/agents/validator-epic-database.md +137 -0
  102. package/cli/agents/validator-epic-developer.json +74 -0
  103. package/cli/agents/validator-epic-developer.md +153 -0
  104. package/cli/agents/validator-epic-devops.json +74 -0
  105. package/cli/agents/validator-epic-devops.md +153 -0
  106. package/cli/agents/validator-epic-frontend.json +74 -0
  107. package/cli/agents/validator-epic-frontend.md +153 -0
  108. package/cli/agents/validator-epic-mobile.json +93 -0
  109. package/cli/agents/validator-epic-mobile.md +130 -0
  110. package/cli/agents/validator-epic-qa.json +93 -0
  111. package/cli/agents/validator-epic-qa.md +130 -0
  112. package/cli/agents/validator-epic-security.json +74 -0
  113. package/cli/agents/validator-epic-security.md +154 -0
  114. package/cli/agents/validator-epic-solution-architect.json +74 -0
  115. package/cli/agents/validator-epic-solution-architect.md +156 -0
  116. package/cli/agents/validator-epic-test-architect.json +93 -0
  117. package/cli/agents/validator-epic-test-architect.md +130 -0
  118. package/cli/agents/validator-epic-ui.json +93 -0
  119. package/cli/agents/validator-epic-ui.md +130 -0
  120. package/cli/agents/validator-epic-ux.json +93 -0
  121. package/cli/agents/validator-epic-ux.md +130 -0
  122. package/cli/agents/validator-selector.md +211 -0
  123. package/cli/agents/validator-story-api.json +104 -0
  124. package/cli/agents/validator-story-api.md +152 -0
  125. package/cli/agents/validator-story-backend.json +104 -0
  126. package/cli/agents/validator-story-backend.md +152 -0
  127. package/cli/agents/validator-story-cloud.json +104 -0
  128. package/cli/agents/validator-story-cloud.md +152 -0
  129. package/cli/agents/validator-story-data.json +104 -0
  130. package/cli/agents/validator-story-data.md +152 -0
  131. package/cli/agents/validator-story-database.json +104 -0
  132. package/cli/agents/validator-story-database.md +152 -0
  133. package/cli/agents/validator-story-developer.json +104 -0
  134. package/cli/agents/validator-story-developer.md +152 -0
  135. package/cli/agents/validator-story-devops.json +104 -0
  136. package/cli/agents/validator-story-devops.md +152 -0
  137. package/cli/agents/validator-story-frontend.json +104 -0
  138. package/cli/agents/validator-story-frontend.md +152 -0
  139. package/cli/agents/validator-story-mobile.json +104 -0
  140. package/cli/agents/validator-story-mobile.md +152 -0
  141. package/cli/agents/validator-story-qa.json +104 -0
  142. package/cli/agents/validator-story-qa.md +152 -0
  143. package/cli/agents/validator-story-security.json +104 -0
  144. package/cli/agents/validator-story-security.md +152 -0
  145. package/cli/agents/validator-story-solution-architect.json +104 -0
  146. package/cli/agents/validator-story-solution-architect.md +152 -0
  147. package/cli/agents/validator-story-test-architect.json +104 -0
  148. package/cli/agents/validator-story-test-architect.md +152 -0
  149. package/cli/agents/validator-story-ui.json +104 -0
  150. package/cli/agents/validator-story-ui.md +152 -0
  151. package/cli/agents/validator-story-ux.json +104 -0
  152. package/cli/agents/validator-story-ux.md +152 -0
  153. package/cli/ansi-colors.js +21 -0
  154. package/cli/build-docs.js +298 -0
  155. package/cli/ceremony-history.js +369 -0
  156. package/cli/command-logger.js +245 -0
  157. package/cli/components/static-output.js +63 -0
  158. package/cli/console-output-manager.js +94 -0
  159. package/cli/docs-sync.js +306 -0
  160. package/cli/epic-story-validator.js +1174 -0
  161. package/cli/evaluation-prompts.js +1008 -0
  162. package/cli/execution-context.js +195 -0
  163. package/cli/generate-summary-table.js +340 -0
  164. package/cli/index.js +3 -25
  165. package/cli/init-model-config.js +697 -0
  166. package/cli/init.js +1765 -100
  167. package/cli/kanban-server-manager.js +228 -0
  168. package/cli/llm-claude.js +109 -0
  169. package/cli/llm-gemini.js +115 -0
  170. package/cli/llm-mock.js +233 -0
  171. package/cli/llm-openai.js +233 -0
  172. package/cli/llm-provider.js +300 -0
  173. package/cli/llm-token-limits.js +102 -0
  174. package/cli/llm-verifier.js +454 -0
  175. package/cli/logger.js +32 -5
  176. package/cli/message-constants.js +58 -0
  177. package/cli/message-manager.js +334 -0
  178. package/cli/message-types.js +96 -0
  179. package/cli/messaging-api.js +297 -0
  180. package/cli/model-pricing.js +169 -0
  181. package/cli/model-query-engine.js +468 -0
  182. package/cli/model-recommendation-analyzer.js +495 -0
  183. package/cli/model-selector.js +269 -0
  184. package/cli/output-buffer.js +107 -0
  185. package/cli/process-manager.js +332 -0
  186. package/cli/repl-ink.js +5840 -504
  187. package/cli/repl-old.js +4 -4
  188. package/cli/seed-processor.js +792 -0
  189. package/cli/sprint-planning-processor.js +1813 -0
  190. package/cli/template-processor.js +2306 -108
  191. package/cli/templates/project.md +25 -8
  192. package/cli/templates/vitepress-config.mts.template +34 -0
  193. package/cli/token-tracker.js +520 -0
  194. package/cli/tools/generate-story-validators.js +317 -0
  195. package/cli/tools/generate-validators.js +669 -0
  196. package/cli/update-checker.js +19 -17
  197. package/cli/update-notifier.js +4 -4
  198. package/cli/validation-router.js +605 -0
  199. package/cli/verification-tracker.js +563 -0
  200. package/kanban/README.md +386 -0
  201. package/kanban/client/README.md +205 -0
  202. package/kanban/client/components.json +20 -0
  203. package/kanban/client/dist/assets/index-CiD8PS2e.js +306 -0
  204. package/kanban/client/dist/assets/index-nLh0m82Q.css +1 -0
  205. package/kanban/client/dist/index.html +16 -0
  206. package/kanban/client/dist/vite.svg +1 -0
  207. package/kanban/client/index.html +15 -0
  208. package/kanban/client/package-lock.json +9442 -0
  209. package/kanban/client/package.json +44 -0
  210. package/kanban/client/postcss.config.js +6 -0
  211. package/kanban/client/public/vite.svg +1 -0
  212. package/kanban/client/src/App.jsx +622 -0
  213. package/kanban/client/src/components/ProjectFileEditorPopup.jsx +117 -0
  214. package/kanban/client/src/components/ceremony/AskArchPopup.jsx +416 -0
  215. package/kanban/client/src/components/ceremony/AskModelPopup.jsx +616 -0
  216. package/kanban/client/src/components/ceremony/CeremonyWorkflowModal.jsx +946 -0
  217. package/kanban/client/src/components/ceremony/EpicStorySelectionModal.jsx +254 -0
  218. package/kanban/client/src/components/ceremony/SponsorCallModal.jsx +619 -0
  219. package/kanban/client/src/components/ceremony/SprintPlanningModal.jsx +704 -0
  220. package/kanban/client/src/components/ceremony/steps/ArchitectureStep.jsx +150 -0
  221. package/kanban/client/src/components/ceremony/steps/CompleteStep.jsx +154 -0
  222. package/kanban/client/src/components/ceremony/steps/DatabaseStep.jsx +202 -0
  223. package/kanban/client/src/components/ceremony/steps/DeploymentStep.jsx +123 -0
  224. package/kanban/client/src/components/ceremony/steps/MissionStep.jsx +106 -0
  225. package/kanban/client/src/components/ceremony/steps/ReviewAnswersStep.jsx +125 -0
  226. package/kanban/client/src/components/ceremony/steps/RunningStep.jsx +228 -0
  227. package/kanban/client/src/components/kanban/CardDetailModal.jsx +559 -0
  228. package/kanban/client/src/components/kanban/EpicSection.jsx +146 -0
  229. package/kanban/client/src/components/kanban/FilterToolbar.jsx +222 -0
  230. package/kanban/client/src/components/kanban/GroupingSelector.jsx +57 -0
  231. package/kanban/client/src/components/kanban/KanbanBoard.jsx +211 -0
  232. package/kanban/client/src/components/kanban/KanbanCard.jsx +138 -0
  233. package/kanban/client/src/components/kanban/KanbanColumn.jsx +90 -0
  234. package/kanban/client/src/components/kanban/RefineWorkItemPopup.jsx +789 -0
  235. package/kanban/client/src/components/layout/LoadingScreen.jsx +82 -0
  236. package/kanban/client/src/components/process/ProcessMonitorBar.jsx +80 -0
  237. package/kanban/client/src/components/settings/AgentEditorPopup.jsx +171 -0
  238. package/kanban/client/src/components/settings/AgentsTab.jsx +353 -0
  239. package/kanban/client/src/components/settings/ApiKeysTab.jsx +113 -0
  240. package/kanban/client/src/components/settings/CeremonyModelsTab.jsx +98 -0
  241. package/kanban/client/src/components/settings/CostThresholdsTab.jsx +94 -0
  242. package/kanban/client/src/components/settings/ModelPricingTab.jsx +204 -0
  243. package/kanban/client/src/components/settings/ServersTab.jsx +121 -0
  244. package/kanban/client/src/components/settings/SettingsModal.jsx +84 -0
  245. package/kanban/client/src/components/stats/CostModal.jsx +353 -0
  246. package/kanban/client/src/components/ui/badge.jsx +27 -0
  247. package/kanban/client/src/components/ui/dialog.jsx +121 -0
  248. package/kanban/client/src/components/ui/tabs.jsx +85 -0
  249. package/kanban/client/src/hooks/__tests__/useGrouping.test.js +232 -0
  250. package/kanban/client/src/hooks/useGrouping.js +118 -0
  251. package/kanban/client/src/hooks/useWebSocket.js +120 -0
  252. package/kanban/client/src/lib/__tests__/api.test.js +196 -0
  253. package/kanban/client/src/lib/__tests__/status-grouping.test.js +94 -0
  254. package/kanban/client/src/lib/api.js +401 -0
  255. package/kanban/client/src/lib/status-grouping.js +144 -0
  256. package/kanban/client/src/lib/utils.js +11 -0
  257. package/kanban/client/src/main.jsx +10 -0
  258. package/kanban/client/src/store/__tests__/kanbanStore.test.js +164 -0
  259. package/kanban/client/src/store/ceremonyStore.js +172 -0
  260. package/kanban/client/src/store/filterStore.js +201 -0
  261. package/kanban/client/src/store/kanbanStore.js +115 -0
  262. package/kanban/client/src/store/processStore.js +65 -0
  263. package/kanban/client/src/store/sprintPlanningStore.js +33 -0
  264. package/kanban/client/src/styles/globals.css +59 -0
  265. package/kanban/client/tailwind.config.js +77 -0
  266. package/kanban/client/vite.config.js +28 -0
  267. package/kanban/client/vitest.config.js +28 -0
  268. package/kanban/dev-start.sh +47 -0
  269. package/kanban/package.json +12 -0
  270. package/kanban/server/index.js +516 -0
  271. package/kanban/server/routes/ceremony.js +305 -0
  272. package/kanban/server/routes/costs.js +157 -0
  273. package/kanban/server/routes/processes.js +50 -0
  274. package/kanban/server/routes/settings.js +303 -0
  275. package/kanban/server/routes/websocket.js +276 -0
  276. package/kanban/server/routes/work-items.js +347 -0
  277. package/kanban/server/services/CeremonyService.js +1190 -0
  278. package/kanban/server/services/FileSystemScanner.js +95 -0
  279. package/kanban/server/services/FileWatcher.js +144 -0
  280. package/kanban/server/services/HierarchyBuilder.js +196 -0
  281. package/kanban/server/services/ProcessRegistry.js +122 -0
  282. package/kanban/server/services/WorkItemReader.js +123 -0
  283. package/kanban/server/services/WorkItemRefineService.js +510 -0
  284. package/kanban/server/start.js +49 -0
  285. package/kanban/server/utils/kanban-logger.js +132 -0
  286. package/kanban/server/utils/markdown.js +91 -0
  287. package/kanban/server/utils/status-grouping.js +107 -0
  288. package/kanban/server/workers/sponsor-call-worker.js +84 -0
  289. package/kanban/server/workers/sprint-planning-worker.js +130 -0
  290. package/package.json +34 -7
@@ -0,0 +1,510 @@
1
+ import path from 'path';
2
+ import fs from 'fs';
3
+ import { LLMProvider } from '../../../cli/llm-provider.js';
4
+ import { extractDescriptionFromDoc, updateDescriptionInDoc } from '../utils/markdown.js';
5
+ import { EpicStoryValidator } from '../../../cli/epic-story-validator.js';
6
+ import { loadAgent } from '../../../cli/agent-loader.js';
7
+ import { KanbanLogger } from '../utils/kanban-logger.js';
8
+
9
+ /**
10
+ * WorkItemRefineService
11
+ * Orchestrates AI-powered refinement of epics and stories:
12
+ * - Principal model generates improved item (description, features/acceptance, dependencies)
13
+ * - EpicStoryValidator runs domain validators + solvers on the result
14
+ * - For epics: impact-check on all child stories + gap analysis for missing stories
15
+ * Results are proposed changes — applyChanges() writes them to disk after user approval.
16
+ */
17
+ export class WorkItemRefineService {
18
+ constructor(projectRoot) {
19
+ this.projectRoot = projectRoot;
20
+ this.avcPath = path.join(projectRoot, '.avc');
21
+ this.websocket = null; // set after WebSocket is initialised
22
+ }
23
+
24
+ /**
25
+ * Start an async refinement job.
26
+ * Returns jobId immediately; broadcasts refine:progress / refine:complete / refine:error.
27
+ * @param {string} itemId
28
+ * @param {object} item - cleaned work item (from API, includes context field)
29
+ * @param {object} options - { refinementRequest, selectedIssues, modelId, provider, validatorModelId, validatorProvider }
30
+ * @returns {string} jobId
31
+ */
32
+ async startRefine(itemId, item, options) {
33
+ const jobId = `refine-${Date.now()}`;
34
+
35
+ // Load .env for API keys
36
+ const { default: dotenv } = await import('dotenv');
37
+ dotenv.config({ path: path.join(this.projectRoot, '.env') });
38
+
39
+ // Fire-and-forget — WS events carry progress and results
40
+ this._runRefine(jobId, itemId, item, options).catch((err) => {
41
+ console.error(`[RefineService] _runRefine failed for ${itemId}:`, err.message);
42
+ this.websocket?.broadcastRefineError(itemId, jobId, err.message);
43
+ });
44
+
45
+ return jobId;
46
+ }
47
+
48
+ // ── Private: main refinement pipeline ──────────────────────────────────────
49
+
50
+ async _runRefine(jobId, itemId, item, options) {
51
+ const {
52
+ refinementRequest = '',
53
+ selectedIssues = [],
54
+ modelId,
55
+ provider,
56
+ validatorModelId,
57
+ validatorProvider,
58
+ itemDirPath = null,
59
+ } = options;
60
+
61
+ const log = new KanbanLogger('refine', this.projectRoot);
62
+ const emit = (message) => this.websocket?.broadcastRefineProgress(itemId, jobId, message);
63
+
64
+ log.info('_runRefine() started', { jobId, itemId, type: item.type, provider, modelId });
65
+
66
+ const isEpic = item.type === 'epic';
67
+
68
+ // Load principal model agent
69
+ const principalAgent = loadAgent(
70
+ isEpic ? 'refiner-epic.md' : 'refiner-story.md',
71
+ this.projectRoot
72
+ );
73
+
74
+ // Create LLM providers
75
+ const principalLLM = await LLMProvider.create(provider, modelId);
76
+ const validatorLLM = await LLMProvider.create(validatorProvider, validatorModelId);
77
+
78
+ // Build issues context string
79
+ const issuesText =
80
+ selectedIssues.length > 0
81
+ ? selectedIssues
82
+ .map(
83
+ (iss, i) =>
84
+ `${i + 1}. [${(iss.severity || 'unknown').toUpperCase()}] ${iss.description || ''}` +
85
+ (iss.suggestion ? `\n Suggestion: ${iss.suggestion}` : '')
86
+ )
87
+ .join('\n')
88
+ : 'No specific issues selected — improve overall quality based on your expertise.';
89
+
90
+ // Override description from doc.md (canonical source)
91
+ const itemDir = itemDirPath ?? this._findItemDir(itemId);
92
+ if (itemDir) {
93
+ const docPath = path.join(itemDir, 'doc.md');
94
+ if (fs.existsSync(docPath)) {
95
+ const docContent = fs.readFileSync(docPath, 'utf8');
96
+ const docDesc = extractDescriptionFromDoc(docContent);
97
+ if (docDesc) item = { ...item, description: docDesc };
98
+ }
99
+ }
100
+
101
+ // ── Step 1: Principal model generates refined item ────────────────────────
102
+ emit('Calling principal model to generate refinement…');
103
+ const principalPrompt = buildPrincipalPrompt(item, issuesText, refinementRequest);
104
+ let refined = await principalLLM.generateJSON(principalPrompt, principalAgent);
105
+ log.info('Principal model responded', { refinedId: refined?.id });
106
+
107
+ if (!refined || refined.id !== item.id) {
108
+ throw new Error(
109
+ `Principal model returned invalid item — expected id "${item.id}", got "${refined?.id}"`
110
+ );
111
+ }
112
+
113
+ // ── Step 2: Validate the refined item (validator + solver loop) ───────────
114
+ emit('Running validators on refined item…');
115
+ const validator = new EpicStoryValidator(validatorLLM, null, null, false, null);
116
+ let validationResult = null;
117
+
118
+ if (isEpic) {
119
+ const context = item.context || '';
120
+ // validateEpic mutates refined in place (description, features, dependencies, metadata.validationResult)
121
+ validationResult = await validator.validateEpic(refined, context);
122
+ } else {
123
+ const parentEpic = await this._loadParentEpic(item);
124
+ const context = item.context || '';
125
+ validationResult = await validator.validateStory(refined, context, parentEpic || { name: '', domain: '', features: [] });
126
+ }
127
+
128
+ const score = validationResult?.averageScore ?? 0;
129
+ emit(
130
+ `Validation score: ${score}/100 — ${validationResult?.readyToPublish ? 'passed' : 'needs improvement'}`
131
+ );
132
+ log.info('Validation complete', { score, readyToPublish: validationResult?.readyToPublish });
133
+
134
+ // ── Step 3 (epic only): Child story impact + gap analysis ─────────────────
135
+ let storyImpacts = [];
136
+
137
+ if (isEpic) {
138
+ const epicDir = itemDirPath ?? this._findItemDir(itemId);
139
+ const existingStories = epicDir ? await this._loadChildStories(epicDir) : [];
140
+
141
+ emit(`Checking impact on ${existingStories.length} existing stories…`);
142
+ const updateImpacts = await this._checkChildImpacts(
143
+ item, refined, existingStories, principalLLM, emit, log
144
+ );
145
+
146
+ emit('Checking for capability gaps in epic scope…');
147
+ const newStories = await this._identifyMissingStories(
148
+ refined, existingStories, principalLLM, emit, log
149
+ );
150
+
151
+ storyImpacts = [
152
+ ...updateImpacts.map((r) => ({ type: 'update', ...r })),
153
+ ...newStories.map((s) => ({ type: 'new', storyId: null, ...s })),
154
+ ];
155
+
156
+ const impactedCount = updateImpacts.filter((r) => r.impacted).length;
157
+ emit(
158
+ `Impact analysis complete: ${impactedCount} stories need updates, ${newStories.length} new stories proposed`
159
+ );
160
+ }
161
+
162
+ // ── Broadcast complete ────────────────────────────────────────────────────
163
+ const result = { jobId, itemId, originalItem: item, proposedItem: refined, validationResult, storyImpacts };
164
+ this.websocket?.broadcastRefineComplete(itemId, jobId, result);
165
+ log.info('_runRefine() completed', { jobId, itemId, storyImpactCount: storyImpacts.length });
166
+ }
167
+
168
+ // ── Private: child impact check ────────────────────────────────────────────
169
+
170
+ async _checkChildImpacts(originalEpic, refinedEpic, stories, principalLLM, emit, log) {
171
+ const impactAgent = loadAgent('impact-checker-story.md', this.projectRoot);
172
+ const impacts = [];
173
+
174
+ for (const story of stories) {
175
+ const prompt = buildImpactCheckPrompt(originalEpic, refinedEpic, story);
176
+ try {
177
+ const result = await principalLLM.generateJSON(prompt, impactAgent);
178
+ if (result?.impacted) {
179
+ emit(` Story "${story.name}" — impact detected`);
180
+ impacts.push({
181
+ storyId: story.id,
182
+ storyName: story.name,
183
+ impacted: true,
184
+ changesNeeded: result.changesNeeded || [],
185
+ proposedStory: result.proposedStory || null,
186
+ });
187
+ } else {
188
+ impacts.push({ storyId: story.id, storyName: story.name, impacted: false });
189
+ }
190
+ log.info('Impact check', { storyId: story.id, impacted: !!result?.impacted });
191
+ } catch (err) {
192
+ log.error('Impact check failed', { storyId: story.id, error: err.message });
193
+ impacts.push({ storyId: story.id, storyName: story.name, impacted: false });
194
+ }
195
+ }
196
+
197
+ return impacts;
198
+ }
199
+
200
+ // ── Private: gap analysis ──────────────────────────────────────────────────
201
+
202
+ async _identifyMissingStories(refinedEpic, existingStories, principalLLM, emit, log) {
203
+ try {
204
+ const gapAgent = loadAgent('gap-checker-epic.md', this.projectRoot);
205
+ const prompt = buildGapCheckPrompt(refinedEpic, existingStories);
206
+ const result = await principalLLM.generateJSON(prompt, gapAgent);
207
+
208
+ const gaps = result?.gaps || [];
209
+ for (const gap of gaps) {
210
+ if (gap.proposedStory) {
211
+ emit(` Gap: "${gap.missingFeature}" — proposing new story`);
212
+ }
213
+ }
214
+ log.info('Gap analysis complete', { gapCount: gaps.length });
215
+
216
+ return gaps.map((g) => ({
217
+ storyName: g.proposedStory?.name || g.missingFeature,
218
+ missingFeature: g.missingFeature,
219
+ proposedStory: g.proposedStory,
220
+ }));
221
+ } catch (err) {
222
+ log.error('Gap analysis failed', { error: err.message });
223
+ return [];
224
+ }
225
+ }
226
+
227
+ // ── Public: apply accepted changes to disk ─────────────────────────────────
228
+
229
+ /**
230
+ * Write accepted refinement changes to the filesystem.
231
+ * storyChanges: [{ type: 'update'|'new', storyId?, proposedStory }] — only accepted ones
232
+ * dirPathMap: Map<id, dirPath> — pre-resolved paths from the data store (preferred over filesystem scan)
233
+ */
234
+ async applyChanges(itemId, proposedItem, storyChanges = [], dirPathMap = new Map()) {
235
+ const isEpic = proposedItem.type === 'epic';
236
+ const itemDir = dirPathMap.get(itemId) ?? this._findItemDir(itemId);
237
+ if (!itemDir) throw new Error(`Item directory not found for "${itemId}"`);
238
+
239
+ const workJsonPath = path.join(itemDir, 'work.json');
240
+ const existing = JSON.parse(fs.readFileSync(workJsonPath, 'utf8'));
241
+
242
+ // If description changed, update doc.md first paragraph (doc.md is canonical)
243
+ if (proposedItem.description && proposedItem.description !== existing.description) {
244
+ const docPath = path.join(itemDir, 'doc.md');
245
+ if (fs.existsSync(docPath)) {
246
+ const currentDoc = fs.readFileSync(docPath, 'utf8');
247
+ fs.writeFileSync(docPath, updateDescriptionInDoc(currentDoc, proposedItem.description), 'utf8');
248
+ }
249
+ }
250
+
251
+ // Build updated item (safe fields only) — description kept as display cache
252
+ const updated = {
253
+ ...existing,
254
+ description: proposedItem.description ?? existing.description,
255
+ features: proposedItem.features ?? existing.features,
256
+ acceptance: proposedItem.acceptance ?? existing.acceptance,
257
+ dependencies: proposedItem.dependencies ?? existing.dependencies,
258
+ metadata: {
259
+ ...existing.metadata,
260
+ validationResult:
261
+ proposedItem.metadata?.validationResult ?? existing.metadata?.validationResult,
262
+ refinedAt: new Date().toISOString(),
263
+ },
264
+ };
265
+
266
+ if (isEpic) {
267
+ // Apply story updates (existing stories)
268
+ for (const change of storyChanges.filter((c) => c.type === 'update' && c.proposedStory)) {
269
+ const storyDir = dirPathMap.get(change.storyId) ?? this._findItemDir(change.storyId);
270
+ if (!storyDir) {
271
+ console.warn(`[RefineService] Story dir not found for ${change.storyId} — skipping`);
272
+ continue;
273
+ }
274
+ const storyWorkJsonPath = path.join(storyDir, 'work.json');
275
+ const existingStory = JSON.parse(fs.readFileSync(storyWorkJsonPath, 'utf8'));
276
+
277
+ // Sync description to story doc.md if changed
278
+ if (change.proposedStory.description && change.proposedStory.description !== existingStory.description) {
279
+ const storyDocPath = path.join(storyDir, 'doc.md');
280
+ if (fs.existsSync(storyDocPath)) {
281
+ const currentStoryDoc = fs.readFileSync(storyDocPath, 'utf8');
282
+ fs.writeFileSync(storyDocPath, updateDescriptionInDoc(currentStoryDoc, change.proposedStory.description), 'utf8');
283
+ }
284
+ }
285
+
286
+ const updatedStory = {
287
+ ...existingStory,
288
+ description: change.proposedStory.description ?? existingStory.description,
289
+ acceptance: change.proposedStory.acceptance ?? existingStory.acceptance,
290
+ dependencies: change.proposedStory.dependencies ?? existingStory.dependencies,
291
+ metadata: { ...existingStory.metadata, refinedAt: new Date().toISOString() },
292
+ };
293
+ fs.writeFileSync(storyWorkJsonPath, JSON.stringify(updatedStory, null, 2), 'utf8');
294
+ }
295
+
296
+ // Create new stories (gap analysis results)
297
+ const newStoryIds = [];
298
+ for (const change of storyChanges.filter((c) => c.type === 'new' && c.proposedStory)) {
299
+ const newId = this._nextStoryId(itemId, itemDir);
300
+ const newStoryDir = path.join(itemDir, newId);
301
+ fs.mkdirSync(newStoryDir, { recursive: true });
302
+
303
+ const newStoryWorkJson = {
304
+ id: newId,
305
+ name: change.proposedStory.name,
306
+ type: 'story',
307
+ userType: change.proposedStory.userType || 'user',
308
+ description: change.proposedStory.description || '',
309
+ acceptance: change.proposedStory.acceptance || [],
310
+ status: 'planned',
311
+ dependencies: change.proposedStory.dependencies || [],
312
+ children: [],
313
+ metadata: {
314
+ created: new Date().toISOString(),
315
+ ceremony: 'refinement',
316
+ },
317
+ };
318
+ fs.writeFileSync(
319
+ path.join(newStoryDir, 'work.json'),
320
+ JSON.stringify(newStoryWorkJson, null, 2),
321
+ 'utf8'
322
+ );
323
+ fs.writeFileSync(
324
+ path.join(newStoryDir, 'doc.md'),
325
+ `# ${change.proposedStory.name}\n\n${change.proposedStory.description || ''}\n`,
326
+ 'utf8'
327
+ );
328
+ newStoryIds.push(newId);
329
+ }
330
+
331
+ // Add new story ids to epic's children[]
332
+ if (newStoryIds.length > 0) {
333
+ updated.children = [...(updated.children || []), ...newStoryIds];
334
+ }
335
+ }
336
+
337
+ // Write epic/story work.json last (after all children are created)
338
+ fs.writeFileSync(workJsonPath, JSON.stringify(updated, null, 2), 'utf8');
339
+ }
340
+
341
+ // ── Private: filesystem helpers ────────────────────────────────────────────
342
+
343
+ _nextStoryId(epicId, epicDir) {
344
+ let maxNum = 0;
345
+ try {
346
+ const entries = fs.readdirSync(epicDir, { withFileTypes: true });
347
+ for (const entry of entries) {
348
+ if (!entry.isDirectory()) continue;
349
+ const parts = entry.name.split('-');
350
+ const num = parseInt(parts[parts.length - 1], 10);
351
+ if (!isNaN(num) && num > maxNum) maxNum = num;
352
+ }
353
+ } catch (_) {}
354
+ return `${epicId}-${String(maxNum + 1).padStart(4, '0')}`;
355
+ }
356
+
357
+ _findItemDir(id) {
358
+ const projectPath = path.join(this.avcPath, 'project');
359
+ return this._findDirById(projectPath, id);
360
+ }
361
+
362
+ _findDirById(dir, id) {
363
+ try {
364
+ const workJsonPath = path.join(dir, 'work.json');
365
+ if (fs.existsSync(workJsonPath)) {
366
+ const data = JSON.parse(fs.readFileSync(workJsonPath, 'utf8'));
367
+ if (data.id === id) return dir;
368
+ }
369
+ const entries = fs.readdirSync(dir, { withFileTypes: true });
370
+ for (const entry of entries) {
371
+ if (entry.isDirectory()) {
372
+ const found = this._findDirById(path.join(dir, entry.name), id);
373
+ if (found) return found;
374
+ }
375
+ }
376
+ } catch (_) {}
377
+ return null;
378
+ }
379
+
380
+ async _loadParentEpic(storyItem) {
381
+ const parentId = storyItem.id.split('-').slice(0, -1).join('-');
382
+ const parentDir = this._findItemDir(parentId);
383
+ if (!parentDir) return null;
384
+ try {
385
+ return JSON.parse(fs.readFileSync(path.join(parentDir, 'work.json'), 'utf8'));
386
+ } catch (_) {
387
+ return null;
388
+ }
389
+ }
390
+
391
+ async _loadChildStories(epicDir) {
392
+ const stories = [];
393
+ try {
394
+ const entries = fs.readdirSync(epicDir, { withFileTypes: true });
395
+ for (const entry of entries) {
396
+ if (!entry.isDirectory()) continue;
397
+ const workJsonPath = path.join(epicDir, entry.name, 'work.json');
398
+ if (!fs.existsSync(workJsonPath)) continue;
399
+ try {
400
+ const data = JSON.parse(fs.readFileSync(workJsonPath, 'utf8'));
401
+ if (data.type === 'story') {
402
+ try {
403
+ const ctxPath = path.join(epicDir, entry.name, 'context.md');
404
+ data.context = fs.existsSync(ctxPath)
405
+ ? fs.readFileSync(ctxPath, 'utf8')
406
+ : '';
407
+ } catch (_) {
408
+ data.context = '';
409
+ }
410
+ stories.push(data);
411
+ }
412
+ } catch (_) {}
413
+ }
414
+ } catch (_) {}
415
+ return stories;
416
+ }
417
+ }
418
+
419
+ // ── Prompt builders ───────────────────────────────────────────────────────────
420
+
421
+ function buildPrincipalPrompt(item, issuesText, refinementRequest) {
422
+ const isEpic = item.type === 'epic';
423
+ if (isEpic) {
424
+ return `# Epic to Refine
425
+
426
+ **Epic ID:** ${item.id}
427
+ **Epic Name:** ${item.name}
428
+ **Domain:** ${item.domain || 'general'}
429
+ **Current Description:** ${item.description || '(none)'}
430
+
431
+ **Current Features:**
432
+ ${(item.features || []).map((f) => `- ${f}`).join('\n') || '(none)'}
433
+
434
+ **Current Dependencies:**
435
+ ${(item.dependencies || []).length > 0 ? item.dependencies.join(', ') : 'None'}
436
+
437
+ ## Validation Issues to Address:
438
+
439
+ ${issuesText}
440
+
441
+ ## User Refinement Request:
442
+
443
+ ${refinementRequest?.trim() || 'No specific request — improve based on validation issues above.'}
444
+
445
+ Refine this Epic to address all issues and the user request. Return complete improved Epic JSON.`;
446
+ } else {
447
+ return `# Story to Refine
448
+
449
+ **Story ID:** ${item.id}
450
+ **Story Name:** ${item.name}
451
+ **User Type:** ${item.userType || 'user'}
452
+ **Current Description:** ${item.description || '(none)'}
453
+
454
+ **Current Acceptance Criteria:**
455
+ ${(item.acceptance || []).map((ac, i) => `${i + 1}. ${ac}`).join('\n') || '(none)'}
456
+
457
+ **Current Dependencies:**
458
+ ${(item.dependencies || []).length > 0 ? item.dependencies.join(', ') : 'None'}
459
+
460
+ ## Validation Issues to Address:
461
+
462
+ ${issuesText}
463
+
464
+ ## User Refinement Request:
465
+
466
+ ${refinementRequest?.trim() || 'No specific request — improve based on validation issues above.'}
467
+
468
+ Refine this Story to address all issues and the user request. Return complete improved Story JSON.`;
469
+ }
470
+ }
471
+
472
+ function buildImpactCheckPrompt(originalEpic, refinedEpic, story) {
473
+ return `# Epic Change Impact Analysis
474
+
475
+ ## Original Epic
476
+ **ID:** ${originalEpic.id}
477
+ **Description:** ${originalEpic.description || '(none)'}
478
+ **Features:**
479
+ ${(originalEpic.features || []).map((f) => `- ${f}`).join('\n') || '(none)'}
480
+
481
+ ## Refined Epic (proposed changes)
482
+ **Description:** ${refinedEpic.description || '(none)'}
483
+ **Features:**
484
+ ${(refinedEpic.features || []).map((f) => `- ${f}`).join('\n') || '(none)'}
485
+
486
+ ## Story to Assess
487
+ **Story ID:** ${story.id}
488
+ **Story Name:** ${story.name}
489
+ **Description:** ${story.description || '(none)'}
490
+ **Acceptance Criteria:**
491
+ ${(story.acceptance || []).map((ac, i) => `${i + 1}. ${ac}`).join('\n') || '(none)'}
492
+
493
+ Analyze whether the epic changes require any updates to this story. Return JSON following your instructions.`;
494
+ }
495
+
496
+ function buildGapCheckPrompt(refinedEpic, existingStories) {
497
+ return `# Epic Coverage Gap Analysis
498
+
499
+ ## Refined Epic
500
+ **ID:** ${refinedEpic.id}
501
+ **Name:** ${refinedEpic.name}
502
+ **Description:** ${refinedEpic.description || '(none)'}
503
+ **Features:**
504
+ ${(refinedEpic.features || []).map((f) => `- ${f}`).join('\n') || '(none)'}
505
+
506
+ ## Existing Stories (ID + name only)
507
+ ${existingStories.map((s, i) => `${i + 1}. ${s.id} — ${s.name}`).join('\n') || '(no stories yet)'}
508
+
509
+ Identify any epic features NOT covered by any existing story. Propose new stories for each gap.`;
510
+ }
@@ -0,0 +1,49 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * Kanban Server Startup Script
5
+ * Used by BackgroundProcessManager to start the kanban server
6
+ */
7
+
8
+ import { KanbanServer } from './index.js';
9
+ import path from 'path';
10
+
11
+ // Get project root from command line argument
12
+ const projectRoot = process.argv[2] || process.cwd();
13
+ const port = process.argv[3] ? parseInt(process.argv[3]) : 4174;
14
+
15
+ console.log(`Starting AVC Kanban Server...`);
16
+ console.log(`Project root: ${projectRoot}`);
17
+ console.log(`Port: ${port}`);
18
+
19
+ const server = new KanbanServer(projectRoot, { port });
20
+
21
+ // Handle graceful shutdown
22
+ const shutdown = async () => {
23
+ console.log('\nShutting down kanban server...');
24
+ await server.stop();
25
+ process.exit(0);
26
+ };
27
+
28
+ process.on('SIGINT', shutdown);
29
+ process.on('SIGTERM', shutdown);
30
+
31
+ // Start server
32
+ server.start().then(() => {
33
+ // When launched as a fork of the AVC CLI, wire incoming relay messages so that
34
+ // ceremony worker events (forwarded by the CLI) reach CeremonyService.
35
+ if (process.connected) {
36
+ process.on('message', (msg) => {
37
+ if (msg.type === 'ceremony:worker-msg') {
38
+ server.ceremonyService.handleWorkerMessage(msg.processId, msg.msg);
39
+ } else if (msg.type === 'ceremony:worker-exit') {
40
+ server.ceremonyService.handleWorkerExit(msg.processId, msg.code);
41
+ } else if (msg.type === 'ceremony:started') {
42
+ server.ceremonyService.handleWorkerStarted(msg.processId, msg.pid);
43
+ }
44
+ });
45
+ }
46
+ }).catch((error) => {
47
+ console.error('Failed to start kanban server:', error);
48
+ process.exit(1);
49
+ });
@@ -0,0 +1,132 @@
1
+ /**
2
+ * KanbanLogger
3
+ * Per-operation debug logger for the kanban server.
4
+ * Each significant operation (mission generation, ceremony run, etc.)
5
+ * creates a timestamped log file under .avc/logs/.
6
+ *
7
+ * Log files: kanban-<operation>-YYYY-MM-DD-HH-MM-SS.log
8
+ * Auto-cleanup: keeps last 10 logs per operation prefix.
9
+ */
10
+
11
+ import fs from 'fs';
12
+ import path from 'path';
13
+
14
+ export class KanbanLogger {
15
+ /**
16
+ * @param {string} operationName - e.g. 'mission', 'ceremony-run', 'analyze-db'
17
+ * @param {string} projectRoot - absolute path to the project root
18
+ */
19
+ constructor(operationName, projectRoot) {
20
+ this.operationName = operationName;
21
+ this.projectRoot = projectRoot;
22
+ this.logsDir = path.join(projectRoot, '.avc', 'logs');
23
+ this.logFile = null;
24
+ this.startTime = Date.now();
25
+
26
+ const now = new Date();
27
+ const ts = now.toISOString()
28
+ .replace('T', '-')
29
+ .replace(/:/g, '-')
30
+ .replace(/\..+/, '');
31
+
32
+ const fileName = `kanban-${operationName}-${ts}.log`;
33
+ this.logFile = path.join(this.logsDir, fileName);
34
+
35
+ try {
36
+ fs.mkdirSync(this.logsDir, { recursive: true });
37
+ const header = [
38
+ '='.repeat(80),
39
+ `Kanban Server Log: ${operationName}`,
40
+ `Started : ${now.toISOString()}`,
41
+ `Project : ${projectRoot}`,
42
+ `Log file: ${fileName}`,
43
+ '='.repeat(80),
44
+ '',
45
+ ].join('\n');
46
+ fs.writeFileSync(this.logFile, header, 'utf8');
47
+ } catch {
48
+ this.logFile = null;
49
+ }
50
+ }
51
+
52
+ // ── Core write ──────────────────────────────────────────────────────────────
53
+
54
+ _write(level, message, data) {
55
+ if (!this.logFile) return;
56
+ const ts = new Date().toISOString();
57
+ const elapsed = `+${Date.now() - this.startTime}ms`;
58
+ let line = `[${ts}] [${elapsed}] [${level}] ${message}`;
59
+ if (data !== undefined && data !== null) {
60
+ try {
61
+ line += '\n' + JSON.stringify(data, (_k, v) =>
62
+ typeof v === 'function' ? '[Function]' : v, 2);
63
+ } catch {
64
+ line += '\n[unserializable]';
65
+ }
66
+ }
67
+ line += '\n';
68
+ try {
69
+ fs.appendFileSync(this.logFile, line, 'utf8');
70
+ } catch (err) {
71
+ process.stderr.write(`[KanbanLogger] write failed: ${err.message}\n`);
72
+ }
73
+ }
74
+
75
+ // ── Public API ──────────────────────────────────────────────────────────────
76
+
77
+ debug(message, data) { this._write('DEBUG', message, data); }
78
+ info(message, data) { this._write('INFO', message, data); }
79
+ warn(message, data) { this._write('WARN', message, data); }
80
+ error(message, data) { this._write('ERROR', message, data); }
81
+
82
+ /** Call at the end of an operation to write the footer and trigger cleanup. */
83
+ finish(success = true, summary = null) {
84
+ const elapsed = Date.now() - this.startTime;
85
+ const status = success ? 'COMPLETED' : 'FAILED';
86
+ const footer = [
87
+ '',
88
+ '='.repeat(80),
89
+ `Operation ${status}: ${this.operationName}`,
90
+ `Elapsed : ${elapsed}ms`,
91
+ summary ? `Summary : ${summary}` : null,
92
+ `Finished : ${new Date().toISOString()}`,
93
+ '='.repeat(80),
94
+ ].filter(Boolean).join('\n');
95
+
96
+ try {
97
+ if (this.logFile) fs.appendFileSync(this.logFile, footer + '\n', 'utf8');
98
+ } catch {}
99
+
100
+ KanbanLogger.cleanup(this.logsDir, `kanban-${this.operationName}`);
101
+ }
102
+
103
+ /** Returns the absolute path to the log file (useful for debugging the logger itself). */
104
+ getLogPath() {
105
+ return this.logFile;
106
+ }
107
+
108
+ // ── Static helpers ──────────────────────────────────────────────────────────
109
+
110
+ /**
111
+ * Remove old logs keeping only the last `keepCount` per prefix.
112
+ * @param {string} logsDir
113
+ * @param {string} prefix - e.g. 'kanban-mission'
114
+ * @param {number} keepCount
115
+ */
116
+ static cleanup(logsDir, prefix, keepCount = 10) {
117
+ try {
118
+ const files = fs.readdirSync(logsDir)
119
+ .filter(f => f.startsWith(prefix) && f.endsWith('.log'))
120
+ .map(f => ({
121
+ name: f,
122
+ fullPath: path.join(logsDir, f),
123
+ mtime: fs.statSync(path.join(logsDir, f)).mtime,
124
+ }))
125
+ .sort((a, b) => b.mtime - a.mtime); // newest first
126
+
127
+ files.slice(keepCount).forEach(f => {
128
+ try { fs.unlinkSync(f.fullPath); } catch {}
129
+ });
130
+ } catch {}
131
+ }
132
+ }