@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.
- package/cli/agent-loader.js +21 -0
- package/cli/agents/agent-selector.md +152 -0
- package/cli/agents/architecture-recommender.md +418 -0
- package/cli/agents/code-implementer.md +117 -0
- package/cli/agents/code-validator.md +80 -0
- package/cli/agents/context-reviewer-epic.md +101 -0
- package/cli/agents/context-reviewer-story.md +92 -0
- package/cli/agents/context-writer-epic.md +145 -0
- package/cli/agents/context-writer-story.md +111 -0
- package/cli/agents/database-deep-dive.md +470 -0
- package/cli/agents/database-recommender.md +634 -0
- package/cli/agents/doc-distributor.md +176 -0
- package/cli/agents/doc-writer-epic.md +42 -0
- package/cli/agents/doc-writer-story.md +43 -0
- package/cli/agents/documentation-updater.md +203 -0
- package/cli/agents/duplicate-detector.md +110 -0
- package/cli/agents/epic-story-decomposer.md +559 -0
- package/cli/agents/feature-context-generator.md +91 -0
- package/cli/agents/gap-checker-epic.md +52 -0
- package/cli/agents/impact-checker-story.md +51 -0
- package/cli/agents/migration-guide-generator.md +305 -0
- package/cli/agents/mission-scope-generator.md +143 -0
- package/cli/agents/mission-scope-validator.md +146 -0
- package/cli/agents/project-context-extractor.md +122 -0
- package/cli/agents/project-documentation-creator.json +226 -0
- package/cli/agents/project-documentation-creator.md +595 -0
- package/cli/agents/question-prefiller.md +269 -0
- package/cli/agents/refiner-epic.md +39 -0
- package/cli/agents/refiner-story.md +42 -0
- package/cli/agents/scaffolding-generator.md +99 -0
- package/cli/agents/seed-validator.md +71 -0
- package/cli/agents/story-doc-enricher.md +133 -0
- package/cli/agents/story-scope-reviewer.md +147 -0
- package/cli/agents/story-splitter.md +83 -0
- package/cli/agents/suggestion-business-analyst.md +88 -0
- package/cli/agents/suggestion-deployment-architect.md +263 -0
- package/cli/agents/suggestion-product-manager.md +129 -0
- package/cli/agents/suggestion-security-specialist.md +156 -0
- package/cli/agents/suggestion-technical-architect.md +269 -0
- package/cli/agents/suggestion-ux-researcher.md +93 -0
- package/cli/agents/task-subtask-decomposer.md +188 -0
- package/cli/agents/validator-documentation.json +183 -0
- package/cli/agents/validator-documentation.md +455 -0
- package/cli/agents/validator-selector.md +211 -0
- package/cli/ansi-colors.js +21 -0
- package/cli/api-reference-tool.js +368 -0
- package/cli/build-docs.js +29 -8
- package/cli/ceremony-history.js +369 -0
- package/cli/checks/catalog.json +76 -0
- package/cli/checks/code/quality.json +26 -0
- package/cli/checks/code/testing.json +14 -0
- package/cli/checks/code/traceability.json +26 -0
- package/cli/checks/cross-refs/epic.json +171 -0
- package/cli/checks/cross-refs/story.json +149 -0
- package/cli/checks/epic/api.json +114 -0
- package/cli/checks/epic/backend.json +126 -0
- package/cli/checks/epic/cloud.json +126 -0
- package/cli/checks/epic/data.json +102 -0
- package/cli/checks/epic/database.json +114 -0
- package/cli/checks/epic/developer.json +182 -0
- package/cli/checks/epic/devops.json +174 -0
- package/cli/checks/epic/frontend.json +162 -0
- package/cli/checks/epic/mobile.json +102 -0
- package/cli/checks/epic/qa.json +90 -0
- package/cli/checks/epic/security.json +184 -0
- package/cli/checks/epic/solution-architect.json +192 -0
- package/cli/checks/epic/test-architect.json +90 -0
- package/cli/checks/epic/ui.json +102 -0
- package/cli/checks/epic/ux.json +90 -0
- package/cli/checks/fixes/epic-fix-template.md +10 -0
- package/cli/checks/fixes/story-fix-template.md +10 -0
- package/cli/checks/story/api.json +186 -0
- package/cli/checks/story/backend.json +102 -0
- package/cli/checks/story/cloud.json +102 -0
- package/cli/checks/story/data.json +210 -0
- package/cli/checks/story/database.json +102 -0
- package/cli/checks/story/developer.json +168 -0
- package/cli/checks/story/devops.json +102 -0
- package/cli/checks/story/frontend.json +174 -0
- package/cli/checks/story/mobile.json +102 -0
- package/cli/checks/story/qa.json +210 -0
- package/cli/checks/story/security.json +198 -0
- package/cli/checks/story/solution-architect.json +230 -0
- package/cli/checks/story/test-architect.json +210 -0
- package/cli/checks/story/ui.json +102 -0
- package/cli/checks/story/ux.json +102 -0
- package/cli/coding-order.js +401 -0
- package/cli/command-logger.js +49 -12
- package/cli/components/static-output.js +63 -0
- package/cli/console-output-manager.js +94 -0
- package/cli/dependency-checker.js +72 -0
- package/cli/docs-sync.js +306 -0
- package/cli/epic-story-validator.js +659 -0
- package/cli/evaluation-prompts.js +1008 -0
- package/cli/execution-context.js +195 -0
- package/cli/generate-summary-table.js +340 -0
- package/cli/init-model-config.js +704 -0
- package/cli/init.js +1737 -278
- package/cli/kanban-server-manager.js +227 -0
- package/cli/llm-claude.js +150 -1
- package/cli/llm-gemini.js +109 -0
- package/cli/llm-local.js +493 -0
- package/cli/llm-mock.js +233 -0
- package/cli/llm-openai.js +454 -0
- package/cli/llm-provider.js +379 -3
- package/cli/llm-token-limits.js +211 -0
- package/cli/llm-verifier.js +662 -0
- package/cli/llm-xiaomi.js +143 -0
- package/cli/message-constants.js +49 -0
- package/cli/message-manager.js +334 -0
- package/cli/message-types.js +96 -0
- package/cli/messaging-api.js +291 -0
- package/cli/micro-check-fixer.js +335 -0
- package/cli/micro-check-runner.js +449 -0
- package/cli/micro-check-scorer.js +148 -0
- package/cli/micro-check-validator.js +538 -0
- package/cli/model-pricing.js +192 -0
- package/cli/model-query-engine.js +468 -0
- package/cli/model-recommendation-analyzer.js +495 -0
- package/cli/model-selector.js +270 -0
- package/cli/output-buffer.js +107 -0
- package/cli/process-manager.js +73 -2
- package/cli/prompt-logger.js +57 -0
- package/cli/repl-ink.js +4625 -1094
- package/cli/repl-old.js +3 -4
- package/cli/seed-processor.js +962 -0
- package/cli/sprint-planning-processor.js +4162 -0
- package/cli/template-processor.js +2149 -105
- package/cli/templates/project.md +25 -8
- package/cli/templates/vitepress-config.mts.template +5 -4
- package/cli/token-tracker.js +547 -0
- package/cli/tools/generate-story-validators.js +317 -0
- package/cli/tools/generate-validators.js +669 -0
- package/cli/update-checker.js +19 -17
- package/cli/update-notifier.js +4 -4
- package/cli/validation-router.js +667 -0
- package/cli/verification-tracker.js +563 -0
- package/cli/worktree-runner.js +654 -0
- package/kanban/README.md +386 -0
- package/kanban/client/README.md +205 -0
- package/kanban/client/components.json +20 -0
- package/kanban/client/dist/assets/index-D_KC5EQT.css +1 -0
- package/kanban/client/dist/assets/index-DjY5zqW7.js +351 -0
- package/kanban/client/dist/index.html +16 -0
- package/kanban/client/dist/vite.svg +1 -0
- package/kanban/client/index.html +15 -0
- package/kanban/client/package-lock.json +9442 -0
- package/kanban/client/package.json +44 -0
- package/kanban/client/postcss.config.js +6 -0
- package/kanban/client/public/vite.svg +1 -0
- package/kanban/client/src/App.jsx +651 -0
- package/kanban/client/src/components/ProjectFileEditorPopup.jsx +117 -0
- package/kanban/client/src/components/ceremony/AskArchPopup.jsx +420 -0
- package/kanban/client/src/components/ceremony/AskModelPopup.jsx +629 -0
- package/kanban/client/src/components/ceremony/CeremonyWorkflowModal.jsx +1133 -0
- package/kanban/client/src/components/ceremony/EpicStorySelectionModal.jsx +254 -0
- package/kanban/client/src/components/ceremony/ProviderSwitcherButton.jsx +290 -0
- package/kanban/client/src/components/ceremony/SponsorCallModal.jsx +686 -0
- package/kanban/client/src/components/ceremony/SprintPlanningModal.jsx +838 -0
- package/kanban/client/src/components/ceremony/steps/ArchitectureStep.jsx +150 -0
- package/kanban/client/src/components/ceremony/steps/CompleteStep.jsx +136 -0
- package/kanban/client/src/components/ceremony/steps/DatabaseStep.jsx +202 -0
- package/kanban/client/src/components/ceremony/steps/DeploymentStep.jsx +123 -0
- package/kanban/client/src/components/ceremony/steps/MissionStep.jsx +106 -0
- package/kanban/client/src/components/ceremony/steps/ReviewAnswersStep.jsx +329 -0
- package/kanban/client/src/components/ceremony/steps/RunningStep.jsx +249 -0
- package/kanban/client/src/components/kanban/CardDetailModal.jsx +646 -0
- package/kanban/client/src/components/kanban/EpicSection.jsx +146 -0
- package/kanban/client/src/components/kanban/FilterToolbar.jsx +222 -0
- package/kanban/client/src/components/kanban/GroupingSelector.jsx +63 -0
- package/kanban/client/src/components/kanban/KanbanBoard.jsx +211 -0
- package/kanban/client/src/components/kanban/KanbanCard.jsx +147 -0
- package/kanban/client/src/components/kanban/KanbanColumn.jsx +90 -0
- package/kanban/client/src/components/kanban/RefineWorkItemPopup.jsx +784 -0
- package/kanban/client/src/components/kanban/RunButton.jsx +162 -0
- package/kanban/client/src/components/kanban/SeedButton.jsx +176 -0
- package/kanban/client/src/components/layout/LoadingScreen.jsx +82 -0
- package/kanban/client/src/components/process/ProcessMonitorBar.jsx +80 -0
- package/kanban/client/src/components/settings/AgentEditorPopup.jsx +171 -0
- package/kanban/client/src/components/settings/AgentsTab.jsx +381 -0
- package/kanban/client/src/components/settings/ApiKeysTab.jsx +142 -0
- package/kanban/client/src/components/settings/CeremonyModelsTab.jsx +105 -0
- package/kanban/client/src/components/settings/CheckEditorPopup.jsx +507 -0
- package/kanban/client/src/components/settings/CostThresholdsTab.jsx +95 -0
- package/kanban/client/src/components/settings/ModelPricingTab.jsx +269 -0
- package/kanban/client/src/components/settings/OpenAIAuthSection.jsx +412 -0
- package/kanban/client/src/components/settings/ServersTab.jsx +121 -0
- package/kanban/client/src/components/settings/SettingsModal.jsx +84 -0
- package/kanban/client/src/components/stats/CostModal.jsx +384 -0
- package/kanban/client/src/components/ui/badge.jsx +27 -0
- package/kanban/client/src/components/ui/dialog.jsx +121 -0
- package/kanban/client/src/components/ui/tabs.jsx +85 -0
- package/kanban/client/src/hooks/__tests__/useGrouping.test.js +232 -0
- package/kanban/client/src/hooks/useGrouping.js +177 -0
- package/kanban/client/src/hooks/useWebSocket.js +120 -0
- package/kanban/client/src/lib/__tests__/api.test.js +196 -0
- package/kanban/client/src/lib/__tests__/status-grouping.test.js +94 -0
- package/kanban/client/src/lib/api.js +515 -0
- package/kanban/client/src/lib/status-grouping.js +154 -0
- package/kanban/client/src/lib/utils.js +11 -0
- package/kanban/client/src/main.jsx +10 -0
- package/kanban/client/src/store/__tests__/kanbanStore.test.js +164 -0
- package/kanban/client/src/store/ceremonyStore.js +172 -0
- package/kanban/client/src/store/filterStore.js +201 -0
- package/kanban/client/src/store/kanbanStore.js +123 -0
- package/kanban/client/src/store/processStore.js +65 -0
- package/kanban/client/src/store/sprintPlanningStore.js +33 -0
- package/kanban/client/src/styles/globals.css +59 -0
- package/kanban/client/tailwind.config.js +77 -0
- package/kanban/client/vite.config.js +28 -0
- package/kanban/client/vitest.config.js +28 -0
- package/kanban/dev-start.sh +47 -0
- package/kanban/package.json +12 -0
- package/kanban/server/index.js +537 -0
- package/kanban/server/routes/ceremony.js +454 -0
- package/kanban/server/routes/costs.js +163 -0
- package/kanban/server/routes/openai-oauth.js +366 -0
- package/kanban/server/routes/processes.js +50 -0
- package/kanban/server/routes/settings.js +736 -0
- package/kanban/server/routes/websocket.js +281 -0
- package/kanban/server/routes/work-items.js +487 -0
- package/kanban/server/services/CeremonyService.js +1441 -0
- package/kanban/server/services/FileSystemScanner.js +95 -0
- package/kanban/server/services/FileWatcher.js +144 -0
- package/kanban/server/services/HierarchyBuilder.js +196 -0
- package/kanban/server/services/ProcessRegistry.js +122 -0
- package/kanban/server/services/TaskRunnerService.js +261 -0
- package/kanban/server/services/WorkItemReader.js +123 -0
- package/kanban/server/services/WorkItemRefineService.js +510 -0
- package/kanban/server/start.js +49 -0
- package/kanban/server/utils/kanban-logger.js +132 -0
- package/kanban/server/utils/markdown.js +91 -0
- package/kanban/server/utils/status-grouping.js +107 -0
- package/kanban/server/workers/run-task-worker.js +121 -0
- package/kanban/server/workers/seed-worker.js +94 -0
- package/kanban/server/workers/sponsor-call-worker.js +92 -0
- package/kanban/server/workers/sprint-planning-worker.js +212 -0
- package/package.json +19 -7
- package/cli/agents/documentation.md +0 -302
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import { marked } from 'marked';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Configure marked with safe defaults
|
|
5
|
+
*/
|
|
6
|
+
marked.setOptions({
|
|
7
|
+
gfm: true, // GitHub Flavored Markdown
|
|
8
|
+
breaks: true, // Line breaks as <br>
|
|
9
|
+
headerIds: true, // Add IDs to headers
|
|
10
|
+
mangle: false, // Don't mangle email addresses
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Render markdown to HTML
|
|
15
|
+
* @param {string} markdown - Markdown content
|
|
16
|
+
* @returns {string} HTML content
|
|
17
|
+
*/
|
|
18
|
+
export function renderMarkdown(markdown) {
|
|
19
|
+
if (!markdown || typeof markdown !== 'string') {
|
|
20
|
+
return '';
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
try {
|
|
24
|
+
return marked.parse(markdown);
|
|
25
|
+
} catch (error) {
|
|
26
|
+
console.error('Error rendering markdown:', error);
|
|
27
|
+
return `<p>Error rendering markdown: ${error.message}</p>`;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Sanitize markdown content (remove potentially dangerous content)
|
|
33
|
+
* Note: For MVP, we're assuming trusted content from .avc/project/
|
|
34
|
+
* In production, consider using DOMPurify or similar
|
|
35
|
+
* @param {string} html - HTML content
|
|
36
|
+
* @returns {string} Sanitized HTML
|
|
37
|
+
*/
|
|
38
|
+
export function sanitizeHtml(html) {
|
|
39
|
+
// For now, just return as-is since content is from trusted source
|
|
40
|
+
// TODO: Add proper sanitization if exposing to untrusted users
|
|
41
|
+
return html;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Extract the description (first paragraph after H1) from a doc.md string.
|
|
46
|
+
* @param {string} markdown
|
|
47
|
+
* @returns {string}
|
|
48
|
+
*/
|
|
49
|
+
export function extractDescriptionFromDoc(markdown) {
|
|
50
|
+
if (!markdown) return '';
|
|
51
|
+
const lines = markdown.split('\n');
|
|
52
|
+
let pastH1 = false;
|
|
53
|
+
const descLines = [];
|
|
54
|
+
for (const line of lines) {
|
|
55
|
+
if (!pastH1) {
|
|
56
|
+
if (line.startsWith('# ')) pastH1 = true;
|
|
57
|
+
continue;
|
|
58
|
+
}
|
|
59
|
+
if (line.startsWith('#') || line.startsWith('---')) break;
|
|
60
|
+
if (line.trim() === '' && descLines.length > 0) break;
|
|
61
|
+
if (line.trim()) descLines.push(line.trim());
|
|
62
|
+
}
|
|
63
|
+
return descLines.join(' ');
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Replace the first paragraph after H1 with newDescription.
|
|
68
|
+
* Preserves all content below the first paragraph unchanged.
|
|
69
|
+
* @param {string} markdown
|
|
70
|
+
* @param {string} newDescription
|
|
71
|
+
* @returns {string}
|
|
72
|
+
*/
|
|
73
|
+
export function updateDescriptionInDoc(markdown, newDescription) {
|
|
74
|
+
if (!markdown) return `\n\n${newDescription}\n`;
|
|
75
|
+
const lines = markdown.split('\n');
|
|
76
|
+
let h1Idx = -1, paraStart = -1, paraEnd = -1;
|
|
77
|
+
for (let i = 0; i < lines.length; i++) {
|
|
78
|
+
if (h1Idx === -1 && lines[i].startsWith('# ')) { h1Idx = i; continue; }
|
|
79
|
+
if (h1Idx !== -1 && paraStart === -1 && lines[i].trim()) { paraStart = i; continue; }
|
|
80
|
+
if (paraStart !== -1 && (!lines[i].trim() || lines[i].startsWith('#') || lines[i] === '---')) {
|
|
81
|
+
paraEnd = i; break;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
if (paraStart === -1) {
|
|
85
|
+
lines.splice(h1Idx + 1, 0, '', newDescription);
|
|
86
|
+
return lines.join('\n');
|
|
87
|
+
}
|
|
88
|
+
if (paraEnd === -1) paraEnd = lines.length;
|
|
89
|
+
lines.splice(paraStart, paraEnd - paraStart, newDescription);
|
|
90
|
+
return lines.join('\n');
|
|
91
|
+
}
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Status Column Grouping Logic
|
|
3
|
+
* Maps AVC work item statuses to logical kanban columns
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Maps status values to their display column
|
|
8
|
+
* Key: Column name
|
|
9
|
+
* Value: Array of status values that belong in that column
|
|
10
|
+
*/
|
|
11
|
+
export const STATUS_COLUMN_MAPPING = {
|
|
12
|
+
Backlog: ['planned', 'pending'],
|
|
13
|
+
Ready: ['ready'],
|
|
14
|
+
'In Progress': ['implementing', 'feedback'],
|
|
15
|
+
Review: ['implemented', 'testing'],
|
|
16
|
+
Done: ['completed'],
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Metadata for each status value
|
|
21
|
+
* Used for badges, colors, icons within grouped columns
|
|
22
|
+
*/
|
|
23
|
+
export const STATUS_METADATA = {
|
|
24
|
+
planned: { color: 'gray', icon: '📋', label: 'Planned' },
|
|
25
|
+
pending: { color: 'slate', icon: '⏸️', label: 'Pending' },
|
|
26
|
+
ready: { color: 'blue', icon: '🚀', label: 'Ready' },
|
|
27
|
+
implementing: { color: 'yellow', icon: '⚙️', label: 'Implementing' },
|
|
28
|
+
feedback: { color: 'amber', icon: '💬', label: 'Feedback' },
|
|
29
|
+
implemented: { color: 'purple', icon: '✅', label: 'Implemented' },
|
|
30
|
+
testing: { color: 'violet', icon: '🧪', label: 'Testing' },
|
|
31
|
+
completed: { color: 'green', icon: '🎉', label: 'Completed' },
|
|
32
|
+
blocked: { color: 'red', icon: '🚫', label: 'Blocked' },
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Canonical order for columns on the board
|
|
37
|
+
*/
|
|
38
|
+
export const COLUMN_ORDER = ['Backlog', 'Ready', 'In Progress', 'Review', 'Done'];
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Get the column name for a given status
|
|
42
|
+
* @param {string} status - Work item status
|
|
43
|
+
* @returns {string|null} Column name or null if not found
|
|
44
|
+
*/
|
|
45
|
+
export function getColumnForStatus(status) {
|
|
46
|
+
for (const [column, statuses] of Object.entries(STATUS_COLUMN_MAPPING)) {
|
|
47
|
+
if (statuses.includes(status)) {
|
|
48
|
+
return column;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
return null;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Get metadata for a status
|
|
56
|
+
* @param {string} status - Work item status
|
|
57
|
+
* @returns {object|null} Status metadata or null if not found
|
|
58
|
+
*/
|
|
59
|
+
export function getStatusMetadata(status) {
|
|
60
|
+
return STATUS_METADATA[status] || null;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Group work items by column
|
|
65
|
+
* @param {Array} workItems - Array of work items
|
|
66
|
+
* @returns {object} Object with column names as keys, arrays of work items as values
|
|
67
|
+
*/
|
|
68
|
+
export function groupItemsByColumn(workItems) {
|
|
69
|
+
const grouped = {};
|
|
70
|
+
|
|
71
|
+
// Initialize all columns as empty arrays
|
|
72
|
+
COLUMN_ORDER.forEach((column) => {
|
|
73
|
+
grouped[column] = [];
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
// Group items by their column
|
|
77
|
+
workItems.forEach((item) => {
|
|
78
|
+
const column = getColumnForStatus(item.status);
|
|
79
|
+
if (column && grouped[column]) {
|
|
80
|
+
grouped[column].push(item);
|
|
81
|
+
}
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
return grouped;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Get statistics for a column
|
|
89
|
+
* @param {Array} workItems - Work items in the column
|
|
90
|
+
* @returns {object} Statistics object with total count and breakdown by status
|
|
91
|
+
*/
|
|
92
|
+
export function getColumnStats(workItems) {
|
|
93
|
+
const stats = {
|
|
94
|
+
total: workItems.length,
|
|
95
|
+
byStatus: {},
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
workItems.forEach((item) => {
|
|
99
|
+
const status = item.status;
|
|
100
|
+
if (!stats.byStatus[status]) {
|
|
101
|
+
stats.byStatus[status] = 0;
|
|
102
|
+
}
|
|
103
|
+
stats.byStatus[status]++;
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
return stats;
|
|
107
|
+
}
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Run Task Worker
|
|
3
|
+
* Forked by TaskRunnerService.runTask().
|
|
4
|
+
* Creates a git worktree, implements code via LLM, runs tests, commits, merges.
|
|
5
|
+
*
|
|
6
|
+
* Parent → Worker:
|
|
7
|
+
* { type: 'init', taskId }
|
|
8
|
+
* { type: 'cancel' }
|
|
9
|
+
*
|
|
10
|
+
* Worker → Parent:
|
|
11
|
+
* { type: 'progress', message }
|
|
12
|
+
* { type: 'complete', result: { success, taskId } }
|
|
13
|
+
* { type: 'cancelled' }
|
|
14
|
+
* { type: 'error', error }
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import { CommandLogger } from '../../../cli/command-logger.js';
|
|
18
|
+
import { readFileSync, writeFileSync, existsSync } from 'fs';
|
|
19
|
+
import { join } from 'path';
|
|
20
|
+
|
|
21
|
+
let _cancelled = false;
|
|
22
|
+
|
|
23
|
+
process.on('disconnect', () => {
|
|
24
|
+
_cancelled = true;
|
|
25
|
+
setTimeout(() => process.exit(1), 5000).unref();
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
process.on('message', async (msg) => {
|
|
29
|
+
if (msg.type === 'init') {
|
|
30
|
+
run(msg.taskId);
|
|
31
|
+
} else if (msg.type === 'cancel') {
|
|
32
|
+
_cancelled = true;
|
|
33
|
+
}
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Update work.json status on disk.
|
|
38
|
+
*/
|
|
39
|
+
function updateStatus(taskId, status) {
|
|
40
|
+
const projectRoot = process.cwd();
|
|
41
|
+
// Find the work.json by walking the hierarchy
|
|
42
|
+
// Task ID: context-0001-0001-0001 → path: .avc/project/context-0001/context-0001-0001/context-0001-0001-0001/work.json
|
|
43
|
+
const idParts = taskId.replace('context-', '').split('-');
|
|
44
|
+
let dir = join(projectRoot, '.avc', 'project');
|
|
45
|
+
let current = 'context';
|
|
46
|
+
for (const part of idParts) {
|
|
47
|
+
current += `-${part}`;
|
|
48
|
+
dir = join(dir, current);
|
|
49
|
+
}
|
|
50
|
+
const workJsonPath = join(dir, 'work.json');
|
|
51
|
+
|
|
52
|
+
if (existsSync(workJsonPath)) {
|
|
53
|
+
try {
|
|
54
|
+
const workJson = JSON.parse(readFileSync(workJsonPath, 'utf8'));
|
|
55
|
+
workJson.status = status;
|
|
56
|
+
workJson.updated = new Date().toISOString();
|
|
57
|
+
writeFileSync(workJsonPath, JSON.stringify(workJson, null, 2), 'utf8');
|
|
58
|
+
} catch {}
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
async function run(taskId) {
|
|
63
|
+
const logger = new CommandLogger('run-task', process.cwd());
|
|
64
|
+
logger.start();
|
|
65
|
+
|
|
66
|
+
try {
|
|
67
|
+
const { WorktreeRunner } = await import('../../../cli/worktree-runner.js');
|
|
68
|
+
|
|
69
|
+
// Set status to implementing
|
|
70
|
+
updateStatus(taskId, 'implementing');
|
|
71
|
+
try { process.send({ type: 'progress', message: `Starting implementation of ${taskId}` }); } catch {}
|
|
72
|
+
|
|
73
|
+
const runner = new WorktreeRunner(taskId, process.cwd());
|
|
74
|
+
|
|
75
|
+
const progressCallback = (message) => {
|
|
76
|
+
if (_cancelled) return;
|
|
77
|
+
try { process.send({ type: 'progress', message }); } catch {}
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
const cancelledCheck = () => _cancelled;
|
|
81
|
+
|
|
82
|
+
const result = await runner.execute(progressCallback, cancelledCheck);
|
|
83
|
+
|
|
84
|
+
if (_cancelled) {
|
|
85
|
+
updateStatus(taskId, 'planned'); // Reset on cancel
|
|
86
|
+
try { process.send({ type: 'cancelled' }); } catch {}
|
|
87
|
+
} else if (result.success) {
|
|
88
|
+
updateStatus(taskId, 'completed');
|
|
89
|
+
try {
|
|
90
|
+
process.send({
|
|
91
|
+
type: 'complete',
|
|
92
|
+
result: { success: true, taskId },
|
|
93
|
+
});
|
|
94
|
+
} catch {}
|
|
95
|
+
} else {
|
|
96
|
+
updateStatus(taskId, 'failed');
|
|
97
|
+
try {
|
|
98
|
+
process.send({
|
|
99
|
+
type: 'error',
|
|
100
|
+
error: { message: result.error || 'Implementation failed' },
|
|
101
|
+
});
|
|
102
|
+
} catch {}
|
|
103
|
+
}
|
|
104
|
+
} catch (error) {
|
|
105
|
+
if (error.message === 'CANCELLED') {
|
|
106
|
+
updateStatus(taskId, 'planned');
|
|
107
|
+
try { process.send({ type: 'cancelled' }); } catch {}
|
|
108
|
+
} else {
|
|
109
|
+
updateStatus(taskId, 'failed');
|
|
110
|
+
try {
|
|
111
|
+
process.send({
|
|
112
|
+
type: 'error',
|
|
113
|
+
error: { message: error.message, stack: error.stack },
|
|
114
|
+
});
|
|
115
|
+
} catch {}
|
|
116
|
+
}
|
|
117
|
+
} finally {
|
|
118
|
+
logger.stop();
|
|
119
|
+
setTimeout(() => process.exit(0), 500).unref();
|
|
120
|
+
}
|
|
121
|
+
}
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Seed Worker
|
|
3
|
+
* Forked by TaskRunnerService.runSeed().
|
|
4
|
+
* Communicates with parent via IPC (process.send / process.on('message')).
|
|
5
|
+
*
|
|
6
|
+
* Parent → Worker:
|
|
7
|
+
* { type: 'init', storyId }
|
|
8
|
+
* { type: 'cancel' }
|
|
9
|
+
*
|
|
10
|
+
* Worker → Parent:
|
|
11
|
+
* { type: 'progress', message }
|
|
12
|
+
* { type: 'item-written', itemId, itemType }
|
|
13
|
+
* { type: 'complete', result: { taskCount, subtaskCount, taskIds } }
|
|
14
|
+
* { type: 'cancelled' }
|
|
15
|
+
* { type: 'error', error }
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
import { CommandLogger } from '../../../cli/command-logger.js';
|
|
19
|
+
|
|
20
|
+
let _cancelled = false;
|
|
21
|
+
|
|
22
|
+
process.on('disconnect', () => {
|
|
23
|
+
_cancelled = true;
|
|
24
|
+
setTimeout(() => process.exit(1), 5000).unref();
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
process.on('message', async (msg) => {
|
|
28
|
+
if (msg.type === 'init') {
|
|
29
|
+
run(msg.storyId);
|
|
30
|
+
} else if (msg.type === 'cancel') {
|
|
31
|
+
_cancelled = true;
|
|
32
|
+
}
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
async function run(storyId) {
|
|
36
|
+
const logger = new CommandLogger('seed', process.cwd());
|
|
37
|
+
logger.start();
|
|
38
|
+
|
|
39
|
+
try {
|
|
40
|
+
// Dynamic import to avoid loading CLI modules at fork time
|
|
41
|
+
const { SeedProcessor } = await import('../../../cli/seed-processor.js');
|
|
42
|
+
|
|
43
|
+
const processor = new SeedProcessor(storyId);
|
|
44
|
+
|
|
45
|
+
const progressCallback = (message) => {
|
|
46
|
+
if (_cancelled) return;
|
|
47
|
+
try { process.send({ type: 'progress', message }); } catch {}
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
const itemWrittenCallback = ({ itemId, itemType }) => {
|
|
51
|
+
if (_cancelled) return;
|
|
52
|
+
try { process.send({ type: 'item-written', itemId, itemType }); } catch {}
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
const cancelledCheck = () => _cancelled;
|
|
56
|
+
|
|
57
|
+
const result = await processor.executeWithCallback(
|
|
58
|
+
progressCallback,
|
|
59
|
+
itemWrittenCallback,
|
|
60
|
+
cancelledCheck
|
|
61
|
+
);
|
|
62
|
+
|
|
63
|
+
if (_cancelled) {
|
|
64
|
+
try { process.send({ type: 'cancelled' }); } catch {}
|
|
65
|
+
} else {
|
|
66
|
+
try {
|
|
67
|
+
process.send({
|
|
68
|
+
type: 'complete',
|
|
69
|
+
result: {
|
|
70
|
+
storyId,
|
|
71
|
+
taskCount: result.taskCount,
|
|
72
|
+
subtaskCount: result.subtaskCount,
|
|
73
|
+
taskIds: result.taskIds,
|
|
74
|
+
},
|
|
75
|
+
});
|
|
76
|
+
} catch {}
|
|
77
|
+
}
|
|
78
|
+
} catch (error) {
|
|
79
|
+
if (error.message === 'CEREMONY_CANCELLED') {
|
|
80
|
+
try { process.send({ type: 'cancelled' }); } catch {}
|
|
81
|
+
} else {
|
|
82
|
+
try {
|
|
83
|
+
process.send({
|
|
84
|
+
type: 'error',
|
|
85
|
+
error: { message: error.message, stack: error.stack },
|
|
86
|
+
});
|
|
87
|
+
} catch {}
|
|
88
|
+
}
|
|
89
|
+
} finally {
|
|
90
|
+
logger.stop();
|
|
91
|
+
// Give parent time to receive the final message
|
|
92
|
+
setTimeout(() => process.exit(0), 500).unref();
|
|
93
|
+
}
|
|
94
|
+
}
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Sponsor Call Worker
|
|
3
|
+
* Forked by CeremonyService.runSponsorCallInProcess().
|
|
4
|
+
* Same IPC protocol as sprint-planning-worker.js.
|
|
5
|
+
* Receives `requirements` in the init message.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { ProjectInitiator } from '../../../cli/init.js';
|
|
9
|
+
import { CommandLogger } from '../../../cli/command-logger.js';
|
|
10
|
+
|
|
11
|
+
let _paused = false;
|
|
12
|
+
let _cancelled = false;
|
|
13
|
+
let _requirements = null;
|
|
14
|
+
let _costThreshold = null;
|
|
15
|
+
let _waitingCostLimit = false;
|
|
16
|
+
|
|
17
|
+
// Parent server stopped — exit rather than running as an orphan.
|
|
18
|
+
process.on('disconnect', () => {
|
|
19
|
+
_cancelled = true;
|
|
20
|
+
// Give the current LLM call up to 5s to finish, then hard-exit.
|
|
21
|
+
setTimeout(() => process.exit(1), 5000).unref();
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
process.on('message', async (msg) => {
|
|
25
|
+
if (msg.type === 'init') {
|
|
26
|
+
_requirements = msg.requirements;
|
|
27
|
+
_costThreshold = msg.costThreshold ?? null;
|
|
28
|
+
run();
|
|
29
|
+
} else if (msg.type === 'pause') {
|
|
30
|
+
_paused = true;
|
|
31
|
+
process.send({ type: 'paused' });
|
|
32
|
+
} else if (msg.type === 'resume') {
|
|
33
|
+
_paused = false;
|
|
34
|
+
process.send({ type: 'resumed' });
|
|
35
|
+
} else if (msg.type === 'cancel') {
|
|
36
|
+
_cancelled = true;
|
|
37
|
+
} else if (msg.type === 'cost-limit-continue') {
|
|
38
|
+
_waitingCostLimit = false;
|
|
39
|
+
}
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
async function run() {
|
|
43
|
+
const logger = new CommandLogger('sponsor-call', process.cwd());
|
|
44
|
+
logger.start();
|
|
45
|
+
try {
|
|
46
|
+
const initiator = new ProjectInitiator();
|
|
47
|
+
|
|
48
|
+
const progressCallback = async (msg, substep, meta) => {
|
|
49
|
+
if (_cancelled) throw new Error('CEREMONY_CANCELLED');
|
|
50
|
+
while (_paused) {
|
|
51
|
+
await new Promise(r => setTimeout(r, 200));
|
|
52
|
+
if (_cancelled) throw new Error('CEREMONY_CANCELLED');
|
|
53
|
+
}
|
|
54
|
+
if (msg) process.send({ type: 'progress', message: msg });
|
|
55
|
+
if (substep) process.send({ type: 'substep', substep, meta: meta || {} });
|
|
56
|
+
if (meta?.detail) process.send({ type: 'detail', detail: meta.detail });
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
const costLimitReachedCallback = async (cost) => {
|
|
60
|
+
_waitingCostLimit = true;
|
|
61
|
+
process.send({ type: 'cost-limit', cost, threshold: _costThreshold });
|
|
62
|
+
while (_waitingCostLimit) {
|
|
63
|
+
await new Promise(r => setTimeout(r, 200));
|
|
64
|
+
if (_cancelled) throw new Error('CEREMONY_CANCELLED');
|
|
65
|
+
}
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
const result = await initiator.sponsorCallWithAnswers(_requirements, progressCallback, {
|
|
69
|
+
costThreshold: _costThreshold,
|
|
70
|
+
costLimitReachedCallback,
|
|
71
|
+
});
|
|
72
|
+
logger.stop();
|
|
73
|
+
// sponsorCallWithAnswers returns { error: true, message } on validation failure instead of throwing
|
|
74
|
+
if (result?.error === true) {
|
|
75
|
+
process.send({ type: 'error', error: result.message || 'Ceremony failed' });
|
|
76
|
+
} else {
|
|
77
|
+
process.send({ type: 'complete', result });
|
|
78
|
+
}
|
|
79
|
+
process.exit(0);
|
|
80
|
+
} catch (err) {
|
|
81
|
+
logger.stop();
|
|
82
|
+
const msg = err.message === 'CEREMONY_CANCELLED'
|
|
83
|
+
? { type: 'cancelled' }
|
|
84
|
+
: { type: 'error', error: err.message };
|
|
85
|
+
// Guard against ERR_IPC_CHANNEL_CLOSED if the parent already disconnected.
|
|
86
|
+
try {
|
|
87
|
+
process.send(msg, () => process.exit(0));
|
|
88
|
+
} catch (_) {
|
|
89
|
+
process.exit(0);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
}
|