@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.
- package/README.md +2 -0
- package/cli/agent-loader.js +21 -0
- package/cli/agents/agent-selector.md +129 -0
- package/cli/agents/architecture-recommender.md +418 -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/documentation-updater.md +203 -0
- package/cli/agents/epic-story-decomposer.md +280 -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 +79 -0
- package/cli/agents/mission-scope-validator.md +112 -0
- package/cli/agents/project-context-extractor.md +107 -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/solver-epic-api.json +15 -0
- package/cli/agents/solver-epic-api.md +39 -0
- package/cli/agents/solver-epic-backend.json +15 -0
- package/cli/agents/solver-epic-backend.md +39 -0
- package/cli/agents/solver-epic-cloud.json +15 -0
- package/cli/agents/solver-epic-cloud.md +39 -0
- package/cli/agents/solver-epic-data.json +15 -0
- package/cli/agents/solver-epic-data.md +39 -0
- package/cli/agents/solver-epic-database.json +15 -0
- package/cli/agents/solver-epic-database.md +39 -0
- package/cli/agents/solver-epic-developer.json +15 -0
- package/cli/agents/solver-epic-developer.md +39 -0
- package/cli/agents/solver-epic-devops.json +15 -0
- package/cli/agents/solver-epic-devops.md +39 -0
- package/cli/agents/solver-epic-frontend.json +15 -0
- package/cli/agents/solver-epic-frontend.md +39 -0
- package/cli/agents/solver-epic-mobile.json +15 -0
- package/cli/agents/solver-epic-mobile.md +39 -0
- package/cli/agents/solver-epic-qa.json +15 -0
- package/cli/agents/solver-epic-qa.md +39 -0
- package/cli/agents/solver-epic-security.json +15 -0
- package/cli/agents/solver-epic-security.md +39 -0
- package/cli/agents/solver-epic-solution-architect.json +15 -0
- package/cli/agents/solver-epic-solution-architect.md +39 -0
- package/cli/agents/solver-epic-test-architect.json +15 -0
- package/cli/agents/solver-epic-test-architect.md +39 -0
- package/cli/agents/solver-epic-ui.json +15 -0
- package/cli/agents/solver-epic-ui.md +39 -0
- package/cli/agents/solver-epic-ux.json +15 -0
- package/cli/agents/solver-epic-ux.md +39 -0
- package/cli/agents/solver-story-api.json +15 -0
- package/cli/agents/solver-story-api.md +39 -0
- package/cli/agents/solver-story-backend.json +15 -0
- package/cli/agents/solver-story-backend.md +39 -0
- package/cli/agents/solver-story-cloud.json +15 -0
- package/cli/agents/solver-story-cloud.md +39 -0
- package/cli/agents/solver-story-data.json +15 -0
- package/cli/agents/solver-story-data.md +39 -0
- package/cli/agents/solver-story-database.json +15 -0
- package/cli/agents/solver-story-database.md +39 -0
- package/cli/agents/solver-story-developer.json +15 -0
- package/cli/agents/solver-story-developer.md +39 -0
- package/cli/agents/solver-story-devops.json +15 -0
- package/cli/agents/solver-story-devops.md +39 -0
- package/cli/agents/solver-story-frontend.json +15 -0
- package/cli/agents/solver-story-frontend.md +39 -0
- package/cli/agents/solver-story-mobile.json +15 -0
- package/cli/agents/solver-story-mobile.md +39 -0
- package/cli/agents/solver-story-qa.json +15 -0
- package/cli/agents/solver-story-qa.md +39 -0
- package/cli/agents/solver-story-security.json +15 -0
- package/cli/agents/solver-story-security.md +39 -0
- package/cli/agents/solver-story-solution-architect.json +15 -0
- package/cli/agents/solver-story-solution-architect.md +39 -0
- package/cli/agents/solver-story-test-architect.json +15 -0
- package/cli/agents/solver-story-test-architect.md +39 -0
- package/cli/agents/solver-story-ui.json +15 -0
- package/cli/agents/solver-story-ui.md +39 -0
- package/cli/agents/solver-story-ux.json +15 -0
- package/cli/agents/solver-story-ux.md +39 -0
- package/cli/agents/story-doc-enricher.md +133 -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 +152 -0
- package/cli/agents/validator-documentation.md +453 -0
- package/cli/agents/validator-epic-api.json +93 -0
- package/cli/agents/validator-epic-api.md +137 -0
- package/cli/agents/validator-epic-backend.json +93 -0
- package/cli/agents/validator-epic-backend.md +130 -0
- package/cli/agents/validator-epic-cloud.json +93 -0
- package/cli/agents/validator-epic-cloud.md +137 -0
- package/cli/agents/validator-epic-data.json +93 -0
- package/cli/agents/validator-epic-data.md +130 -0
- package/cli/agents/validator-epic-database.json +93 -0
- package/cli/agents/validator-epic-database.md +137 -0
- package/cli/agents/validator-epic-developer.json +74 -0
- package/cli/agents/validator-epic-developer.md +153 -0
- package/cli/agents/validator-epic-devops.json +74 -0
- package/cli/agents/validator-epic-devops.md +153 -0
- package/cli/agents/validator-epic-frontend.json +74 -0
- package/cli/agents/validator-epic-frontend.md +153 -0
- package/cli/agents/validator-epic-mobile.json +93 -0
- package/cli/agents/validator-epic-mobile.md +130 -0
- package/cli/agents/validator-epic-qa.json +93 -0
- package/cli/agents/validator-epic-qa.md +130 -0
- package/cli/agents/validator-epic-security.json +74 -0
- package/cli/agents/validator-epic-security.md +154 -0
- package/cli/agents/validator-epic-solution-architect.json +74 -0
- package/cli/agents/validator-epic-solution-architect.md +156 -0
- package/cli/agents/validator-epic-test-architect.json +93 -0
- package/cli/agents/validator-epic-test-architect.md +130 -0
- package/cli/agents/validator-epic-ui.json +93 -0
- package/cli/agents/validator-epic-ui.md +130 -0
- package/cli/agents/validator-epic-ux.json +93 -0
- package/cli/agents/validator-epic-ux.md +130 -0
- package/cli/agents/validator-selector.md +211 -0
- package/cli/agents/validator-story-api.json +104 -0
- package/cli/agents/validator-story-api.md +152 -0
- package/cli/agents/validator-story-backend.json +104 -0
- package/cli/agents/validator-story-backend.md +152 -0
- package/cli/agents/validator-story-cloud.json +104 -0
- package/cli/agents/validator-story-cloud.md +152 -0
- package/cli/agents/validator-story-data.json +104 -0
- package/cli/agents/validator-story-data.md +152 -0
- package/cli/agents/validator-story-database.json +104 -0
- package/cli/agents/validator-story-database.md +152 -0
- package/cli/agents/validator-story-developer.json +104 -0
- package/cli/agents/validator-story-developer.md +152 -0
- package/cli/agents/validator-story-devops.json +104 -0
- package/cli/agents/validator-story-devops.md +152 -0
- package/cli/agents/validator-story-frontend.json +104 -0
- package/cli/agents/validator-story-frontend.md +152 -0
- package/cli/agents/validator-story-mobile.json +104 -0
- package/cli/agents/validator-story-mobile.md +152 -0
- package/cli/agents/validator-story-qa.json +104 -0
- package/cli/agents/validator-story-qa.md +152 -0
- package/cli/agents/validator-story-security.json +104 -0
- package/cli/agents/validator-story-security.md +152 -0
- package/cli/agents/validator-story-solution-architect.json +104 -0
- package/cli/agents/validator-story-solution-architect.md +152 -0
- package/cli/agents/validator-story-test-architect.json +104 -0
- package/cli/agents/validator-story-test-architect.md +152 -0
- package/cli/agents/validator-story-ui.json +104 -0
- package/cli/agents/validator-story-ui.md +152 -0
- package/cli/agents/validator-story-ux.json +104 -0
- package/cli/agents/validator-story-ux.md +152 -0
- package/cli/ansi-colors.js +21 -0
- package/cli/build-docs.js +298 -0
- package/cli/ceremony-history.js +369 -0
- package/cli/command-logger.js +245 -0
- package/cli/components/static-output.js +63 -0
- package/cli/console-output-manager.js +94 -0
- package/cli/docs-sync.js +306 -0
- package/cli/epic-story-validator.js +1174 -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/index.js +3 -25
- package/cli/init-model-config.js +697 -0
- package/cli/init.js +1765 -100
- package/cli/kanban-server-manager.js +228 -0
- package/cli/llm-claude.js +109 -0
- package/cli/llm-gemini.js +115 -0
- package/cli/llm-mock.js +233 -0
- package/cli/llm-openai.js +233 -0
- package/cli/llm-provider.js +300 -0
- package/cli/llm-token-limits.js +102 -0
- package/cli/llm-verifier.js +454 -0
- package/cli/logger.js +32 -5
- package/cli/message-constants.js +58 -0
- package/cli/message-manager.js +334 -0
- package/cli/message-types.js +96 -0
- package/cli/messaging-api.js +297 -0
- package/cli/model-pricing.js +169 -0
- package/cli/model-query-engine.js +468 -0
- package/cli/model-recommendation-analyzer.js +495 -0
- package/cli/model-selector.js +269 -0
- package/cli/output-buffer.js +107 -0
- package/cli/process-manager.js +332 -0
- package/cli/repl-ink.js +5840 -504
- package/cli/repl-old.js +4 -4
- package/cli/seed-processor.js +792 -0
- package/cli/sprint-planning-processor.js +1813 -0
- package/cli/template-processor.js +2306 -108
- package/cli/templates/project.md +25 -8
- package/cli/templates/vitepress-config.mts.template +34 -0
- package/cli/token-tracker.js +520 -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 +605 -0
- package/cli/verification-tracker.js +563 -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-CiD8PS2e.js +306 -0
- package/kanban/client/dist/assets/index-nLh0m82Q.css +1 -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 +622 -0
- package/kanban/client/src/components/ProjectFileEditorPopup.jsx +117 -0
- package/kanban/client/src/components/ceremony/AskArchPopup.jsx +416 -0
- package/kanban/client/src/components/ceremony/AskModelPopup.jsx +616 -0
- package/kanban/client/src/components/ceremony/CeremonyWorkflowModal.jsx +946 -0
- package/kanban/client/src/components/ceremony/EpicStorySelectionModal.jsx +254 -0
- package/kanban/client/src/components/ceremony/SponsorCallModal.jsx +619 -0
- package/kanban/client/src/components/ceremony/SprintPlanningModal.jsx +704 -0
- package/kanban/client/src/components/ceremony/steps/ArchitectureStep.jsx +150 -0
- package/kanban/client/src/components/ceremony/steps/CompleteStep.jsx +154 -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 +125 -0
- package/kanban/client/src/components/ceremony/steps/RunningStep.jsx +228 -0
- package/kanban/client/src/components/kanban/CardDetailModal.jsx +559 -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 +57 -0
- package/kanban/client/src/components/kanban/KanbanBoard.jsx +211 -0
- package/kanban/client/src/components/kanban/KanbanCard.jsx +138 -0
- package/kanban/client/src/components/kanban/KanbanColumn.jsx +90 -0
- package/kanban/client/src/components/kanban/RefineWorkItemPopup.jsx +789 -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 +353 -0
- package/kanban/client/src/components/settings/ApiKeysTab.jsx +113 -0
- package/kanban/client/src/components/settings/CeremonyModelsTab.jsx +98 -0
- package/kanban/client/src/components/settings/CostThresholdsTab.jsx +94 -0
- package/kanban/client/src/components/settings/ModelPricingTab.jsx +204 -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 +353 -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 +118 -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 +401 -0
- package/kanban/client/src/lib/status-grouping.js +144 -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 +115 -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 +516 -0
- package/kanban/server/routes/ceremony.js +305 -0
- package/kanban/server/routes/costs.js +157 -0
- package/kanban/server/routes/processes.js +50 -0
- package/kanban/server/routes/settings.js +303 -0
- package/kanban/server/routes/websocket.js +276 -0
- package/kanban/server/routes/work-items.js +347 -0
- package/kanban/server/services/CeremonyService.js +1190 -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/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/sponsor-call-worker.js +84 -0
- package/kanban/server/workers/sprint-planning-worker.js +130 -0
- package/package.json +34 -7
|
@@ -0,0 +1,245 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Command Logger - Creates individual log files for each command execution
|
|
3
|
+
*
|
|
4
|
+
* Each command creates a timestamped log file in .avc/logs/ directory:
|
|
5
|
+
* - init-2026-01-31-13-05-23.log
|
|
6
|
+
* - sponsor-call-2026-01-31-13-10-45.log
|
|
7
|
+
* - status-2026-01-31-13-15-30.log
|
|
8
|
+
* - remove-2026-01-31-13-20-15.log
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import fs from 'fs';
|
|
12
|
+
import path from 'path';
|
|
13
|
+
|
|
14
|
+
/** Format a Date as a local ISO-8601 string (with timezone offset, e.g. 2026-03-04T18:05:16.554+01:00) */
|
|
15
|
+
function localISO(date = new Date()) {
|
|
16
|
+
const p = n => String(n).padStart(2, '0');
|
|
17
|
+
const ms = String(date.getMilliseconds()).padStart(3, '0');
|
|
18
|
+
const tz = -date.getTimezoneOffset();
|
|
19
|
+
const sign = tz >= 0 ? '+' : '-';
|
|
20
|
+
const tzH = p(Math.floor(Math.abs(tz) / 60));
|
|
21
|
+
const tzM = p(Math.abs(tz) % 60);
|
|
22
|
+
return `${date.getFullYear()}-${p(date.getMonth()+1)}-${p(date.getDate())}T${p(date.getHours())}:${p(date.getMinutes())}:${p(date.getSeconds())}.${ms}${sign}${tzH}:${tzM}`;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
class CommandLogger {
|
|
26
|
+
constructor(commandName, projectRoot = process.cwd(), inkMode = false) {
|
|
27
|
+
this.commandName = commandName;
|
|
28
|
+
this.projectRoot = projectRoot;
|
|
29
|
+
this.logsDir = path.join(projectRoot, '.avc', 'logs');
|
|
30
|
+
this.inkMode = inkMode; // Don't forward to console if in Ink mode
|
|
31
|
+
|
|
32
|
+
// Create timestamp for this command execution
|
|
33
|
+
const now = new Date();
|
|
34
|
+
const timestamp = localISO(now)
|
|
35
|
+
.replace(/T/, '-')
|
|
36
|
+
.replace(/:/g, '-')
|
|
37
|
+
.replace(/\..+/, '');
|
|
38
|
+
|
|
39
|
+
this.logFileName = `${commandName}-${timestamp}.log`;
|
|
40
|
+
this.logFilePath = path.join(this.logsDir, this.logFileName);
|
|
41
|
+
|
|
42
|
+
// Store original console methods
|
|
43
|
+
this.originalLog = console.log;
|
|
44
|
+
this.originalError = console.error;
|
|
45
|
+
this.originalWarn = console.warn;
|
|
46
|
+
this.originalInfo = console.info;
|
|
47
|
+
|
|
48
|
+
// Buffer for logs
|
|
49
|
+
this.logBuffer = [];
|
|
50
|
+
|
|
51
|
+
// Initialize log file
|
|
52
|
+
this.initializeLogFile();
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Initialize log file and directory
|
|
57
|
+
*/
|
|
58
|
+
initializeLogFile() {
|
|
59
|
+
try {
|
|
60
|
+
// Create logs directory if it doesn't exist
|
|
61
|
+
if (!fs.existsSync(this.logsDir)) {
|
|
62
|
+
fs.mkdirSync(this.logsDir, { recursive: true });
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// Write header to log file
|
|
66
|
+
const header = [
|
|
67
|
+
'='.repeat(80),
|
|
68
|
+
`AVC Command Log: ${this.commandName}`,
|
|
69
|
+
`Timestamp: ${localISO()}`,
|
|
70
|
+
`Project: ${this.projectRoot}`,
|
|
71
|
+
`Log File: ${this.logFileName}`,
|
|
72
|
+
'='.repeat(80),
|
|
73
|
+
''
|
|
74
|
+
].join('\n');
|
|
75
|
+
|
|
76
|
+
fs.writeFileSync(this.logFilePath, header, 'utf8');
|
|
77
|
+
} catch (error) {
|
|
78
|
+
// If we can't create the log file, just continue without logging
|
|
79
|
+
this.logFilePath = null;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Write a log entry to the file
|
|
85
|
+
*/
|
|
86
|
+
writeLog(level, ...args) {
|
|
87
|
+
if (!this.logFilePath) return;
|
|
88
|
+
|
|
89
|
+
try {
|
|
90
|
+
const timestamp = localISO();
|
|
91
|
+
const message = args.map(arg => {
|
|
92
|
+
if (arg === undefined) return 'undefined';
|
|
93
|
+
if (arg === null) return 'null';
|
|
94
|
+
if (typeof arg === 'object') {
|
|
95
|
+
try {
|
|
96
|
+
// Handle circular references and non-serializable objects
|
|
97
|
+
return JSON.stringify(arg, (key, value) => {
|
|
98
|
+
if (typeof value === 'function') return '[Function]';
|
|
99
|
+
if (typeof value === 'symbol') return value.toString();
|
|
100
|
+
return value;
|
|
101
|
+
}, 2);
|
|
102
|
+
} catch (jsonError) {
|
|
103
|
+
return `[Object: ${arg.constructor?.name || 'Unknown'}]`;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
return String(arg);
|
|
107
|
+
}).join(' ');
|
|
108
|
+
|
|
109
|
+
// Preserve formatting but ensure single newline at end
|
|
110
|
+
const logEntry = `[${timestamp}] [${level}] ${message}${message.endsWith('\n') ? '' : '\n'}`;
|
|
111
|
+
|
|
112
|
+
fs.appendFileSync(this.logFilePath, logEntry, 'utf8');
|
|
113
|
+
} catch (error) {
|
|
114
|
+
// Write error to stderr so it doesn't get captured in loop
|
|
115
|
+
process.stderr.write(`[CommandLogger] Failed to write log: ${error.message}\n`);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Start capturing console output
|
|
121
|
+
*/
|
|
122
|
+
start() {
|
|
123
|
+
// Intercept console.log
|
|
124
|
+
console.log = (...args) => {
|
|
125
|
+
this.writeLog('INFO', ...args);
|
|
126
|
+
// Only forward to console if NOT in Ink mode
|
|
127
|
+
if (!this.inkMode) {
|
|
128
|
+
this.originalLog(...args);
|
|
129
|
+
}
|
|
130
|
+
};
|
|
131
|
+
|
|
132
|
+
// Intercept console.error
|
|
133
|
+
console.error = (...args) => {
|
|
134
|
+
this.writeLog('ERROR', ...args);
|
|
135
|
+
// Only forward to console if NOT in Ink mode
|
|
136
|
+
if (!this.inkMode) {
|
|
137
|
+
this.originalError(...args);
|
|
138
|
+
}
|
|
139
|
+
};
|
|
140
|
+
|
|
141
|
+
// Intercept console.warn
|
|
142
|
+
console.warn = (...args) => {
|
|
143
|
+
this.writeLog('WARN', ...args);
|
|
144
|
+
// Only forward to console if NOT in Ink mode
|
|
145
|
+
if (!this.inkMode) {
|
|
146
|
+
this.originalWarn(...args);
|
|
147
|
+
}
|
|
148
|
+
};
|
|
149
|
+
|
|
150
|
+
// Intercept console.info
|
|
151
|
+
console.info = (...args) => {
|
|
152
|
+
this.writeLog('INFO', ...args);
|
|
153
|
+
// Only forward to console if NOT in Ink mode
|
|
154
|
+
if (!this.inkMode) {
|
|
155
|
+
this.originalInfo(...args);
|
|
156
|
+
}
|
|
157
|
+
};
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* Stop capturing console output and write footer
|
|
162
|
+
*/
|
|
163
|
+
stop() {
|
|
164
|
+
// Restore original console methods
|
|
165
|
+
console.log = this.originalLog;
|
|
166
|
+
console.error = this.originalError;
|
|
167
|
+
console.warn = this.originalWarn;
|
|
168
|
+
console.info = this.originalInfo;
|
|
169
|
+
|
|
170
|
+
// Write footer
|
|
171
|
+
if (this.logFilePath) {
|
|
172
|
+
try {
|
|
173
|
+
const footer = [
|
|
174
|
+
'',
|
|
175
|
+
'='.repeat(80),
|
|
176
|
+
`Command completed: ${localISO()}`,
|
|
177
|
+
`Log saved: ${this.logFilePath}`,
|
|
178
|
+
'='.repeat(80)
|
|
179
|
+
].join('\n');
|
|
180
|
+
|
|
181
|
+
fs.appendFileSync(this.logFilePath, footer, 'utf8');
|
|
182
|
+
} catch (error) {
|
|
183
|
+
// Silently fail
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* Get the path to the log file
|
|
190
|
+
*/
|
|
191
|
+
getLogPath() {
|
|
192
|
+
return this.logFilePath;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
/**
|
|
196
|
+
* Clean up old log files (keep last N logs per command)
|
|
197
|
+
*/
|
|
198
|
+
static cleanupOldLogs(projectRoot = process.cwd(), keepCount = 10) {
|
|
199
|
+
const logsDir = path.join(projectRoot, '.avc', 'logs');
|
|
200
|
+
|
|
201
|
+
if (!fs.existsSync(logsDir)) return;
|
|
202
|
+
|
|
203
|
+
try {
|
|
204
|
+
const files = fs.readdirSync(logsDir);
|
|
205
|
+
|
|
206
|
+
// Group files by command name
|
|
207
|
+
const filesByCommand = {};
|
|
208
|
+
files.forEach(file => {
|
|
209
|
+
if (!file.endsWith('.log')) return;
|
|
210
|
+
|
|
211
|
+
const commandName = file.split('-')[0];
|
|
212
|
+
if (!filesByCommand[commandName]) {
|
|
213
|
+
filesByCommand[commandName] = [];
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
filesByCommand[commandName].push({
|
|
217
|
+
name: file,
|
|
218
|
+
path: path.join(logsDir, file),
|
|
219
|
+
mtime: fs.statSync(path.join(logsDir, file)).mtime
|
|
220
|
+
});
|
|
221
|
+
});
|
|
222
|
+
|
|
223
|
+
// For each command, keep only the latest N files
|
|
224
|
+
Object.keys(filesByCommand).forEach(commandName => {
|
|
225
|
+
const files = filesByCommand[commandName];
|
|
226
|
+
|
|
227
|
+
// Sort by modification time (newest first)
|
|
228
|
+
files.sort((a, b) => b.mtime - a.mtime);
|
|
229
|
+
|
|
230
|
+
// Delete old files
|
|
231
|
+
files.slice(keepCount).forEach(file => {
|
|
232
|
+
try {
|
|
233
|
+
fs.unlinkSync(file.path);
|
|
234
|
+
} catch (error) {
|
|
235
|
+
// Ignore deletion errors
|
|
236
|
+
}
|
|
237
|
+
});
|
|
238
|
+
});
|
|
239
|
+
} catch (error) {
|
|
240
|
+
// Silently fail
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
export { CommandLogger };
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { Static, Text, Box } from 'ink';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Badge config for message types (inspired by jest example's backgroundColor badges)
|
|
6
|
+
*/
|
|
7
|
+
const BADGE = {
|
|
8
|
+
ERROR: { bg: 'red', fg: 'white', label: ' ERR ' },
|
|
9
|
+
WARNING: { bg: 'yellow', fg: 'black', label: ' WRN ' },
|
|
10
|
+
SUCCESS: { bg: 'green', fg: 'black', label: ' OK ' },
|
|
11
|
+
INFO: { bg: 'blue', fg: 'white', label: ' INF ' },
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* StaticOutput - Renders output items using Ink's built-in Static component.
|
|
16
|
+
*
|
|
17
|
+
* Ink's <Static> commits each item to the terminal once and never touches
|
|
18
|
+
* it again. Items are NOT part of Ink's height-tracking calculation, so
|
|
19
|
+
* they never cause ghost renders when the interactive section changes height.
|
|
20
|
+
*
|
|
21
|
+
* Items with a `type` field get a colored background badge prefix
|
|
22
|
+
* (ERR/WRN/OK/INF) inspired by the jest example's test status chips.
|
|
23
|
+
*
|
|
24
|
+
* @param {Object} props
|
|
25
|
+
* @param {{id: number, content: string, type?: string}[]} props.items
|
|
26
|
+
* @returns {React.Element|null}
|
|
27
|
+
*/
|
|
28
|
+
export const StaticOutput = ({ items }) => {
|
|
29
|
+
if (!items || items.length === 0) return null;
|
|
30
|
+
|
|
31
|
+
return React.createElement(Static, { items },
|
|
32
|
+
(item) => {
|
|
33
|
+
const badge = item.type ? BADGE[item.type] : null;
|
|
34
|
+
|
|
35
|
+
if (badge) {
|
|
36
|
+
// Strip the "TYPE: " prefix from content — badge already shows the type visually
|
|
37
|
+
const displayContent = item.content.replace(/^(ERROR|WARNING|SUCCESS|INFO): /, '');
|
|
38
|
+
const lines = displayContent.split('\n');
|
|
39
|
+
|
|
40
|
+
if (lines.length === 1) {
|
|
41
|
+
// Single-line: badge + text side by side
|
|
42
|
+
return React.createElement(Box, { key: item.id, flexDirection: 'row', gap: 1 },
|
|
43
|
+
React.createElement(Text, { backgroundColor: badge.bg, color: badge.fg }, badge.label),
|
|
44
|
+
React.createElement(Text, null, displayContent)
|
|
45
|
+
);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// Multi-line: badge on first line only, subsequent lines fully left-aligned
|
|
49
|
+
return React.createElement(Box, { key: item.id, flexDirection: 'column' },
|
|
50
|
+
React.createElement(Box, { flexDirection: 'row', gap: 1 },
|
|
51
|
+
React.createElement(Text, { backgroundColor: badge.bg, color: badge.fg }, badge.label),
|
|
52
|
+
React.createElement(Text, null, lines[0])
|
|
53
|
+
),
|
|
54
|
+
...lines.slice(1).map((line, i) =>
|
|
55
|
+
React.createElement(Text, { key: i }, line)
|
|
56
|
+
)
|
|
57
|
+
);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
return React.createElement(Text, { key: item.id }, item.content);
|
|
61
|
+
}
|
|
62
|
+
);
|
|
63
|
+
};
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ConsoleOutputManager - Unified console output handling for Ink REPL
|
|
3
|
+
*
|
|
4
|
+
* Provides consistent console interception across all commands:
|
|
5
|
+
* - Filters [DEBUG] messages (file only, not displayed in UI)
|
|
6
|
+
* - Streams output to OutputBuffer for Static component rendering
|
|
7
|
+
* - Works with CommandLogger for file logging (without console duplication)
|
|
8
|
+
*
|
|
9
|
+
* Usage:
|
|
10
|
+
* const manager = new ConsoleOutputManager();
|
|
11
|
+
* manager.start();
|
|
12
|
+
* try {
|
|
13
|
+
* await someOperation(); // console.log calls will be captured
|
|
14
|
+
* } finally {
|
|
15
|
+
* manager.stop();
|
|
16
|
+
* }
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
import { outputBuffer } from './output-buffer.js';
|
|
20
|
+
|
|
21
|
+
class ConsoleOutputManager {
|
|
22
|
+
constructor() {
|
|
23
|
+
this.originalLog = console.log;
|
|
24
|
+
this.originalError = console.error;
|
|
25
|
+
this.originalWarn = console.warn;
|
|
26
|
+
this.isActive = false;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Start intercepting console output
|
|
31
|
+
*/
|
|
32
|
+
start() {
|
|
33
|
+
if (this.isActive) return;
|
|
34
|
+
this.isActive = true;
|
|
35
|
+
|
|
36
|
+
// Intercept console.log
|
|
37
|
+
console.log = (...args) => {
|
|
38
|
+
const message = args.join(' ');
|
|
39
|
+
|
|
40
|
+
// Filter [DEBUG] messages - they go to file only via CommandLogger
|
|
41
|
+
// Don't display them in the UI
|
|
42
|
+
if (message.includes('[DEBUG]')) {
|
|
43
|
+
// Forward to CommandLogger for file logging
|
|
44
|
+
// CommandLogger is already intercepting, so just call its version
|
|
45
|
+
this.originalLog(...args);
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// User-facing messages: append directly to buffer
|
|
50
|
+
outputBuffer.append(message + '\n');
|
|
51
|
+
|
|
52
|
+
// Also forward to CommandLogger for file logging
|
|
53
|
+
// CommandLogger is in inkMode, so it won't forward to real console
|
|
54
|
+
this.originalLog(...args);
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
// Intercept console.error
|
|
58
|
+
console.error = (...args) => {
|
|
59
|
+
const message = args.join(' ');
|
|
60
|
+
|
|
61
|
+
// Display errors in UI with error prefix
|
|
62
|
+
outputBuffer.append(`ERROR: ${message}\n`);
|
|
63
|
+
|
|
64
|
+
// Also forward to CommandLogger for file logging
|
|
65
|
+
this.originalError(...args);
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
// Intercept console.warn
|
|
69
|
+
console.warn = (...args) => {
|
|
70
|
+
const message = args.join(' ');
|
|
71
|
+
|
|
72
|
+
// Display warnings in UI with warning prefix
|
|
73
|
+
outputBuffer.append(`WARNING: ${message}\n`);
|
|
74
|
+
|
|
75
|
+
// Also forward to CommandLogger for file logging
|
|
76
|
+
this.originalWarn(...args);
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Stop intercepting and restore original console
|
|
82
|
+
*/
|
|
83
|
+
stop() {
|
|
84
|
+
if (!this.isActive) return;
|
|
85
|
+
this.isActive = false;
|
|
86
|
+
|
|
87
|
+
// Restore original console methods
|
|
88
|
+
console.log = this.originalLog;
|
|
89
|
+
console.error = this.originalError;
|
|
90
|
+
console.warn = this.originalWarn;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
export default ConsoleOutputManager;
|
package/cli/docs-sync.js
ADDED
|
@@ -0,0 +1,306 @@
|
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import chokidar from 'chokidar';
|
|
4
|
+
|
|
5
|
+
const WORK_ITEMS_START = '// @@AVC-WORK-ITEMS-START@@';
|
|
6
|
+
const WORK_ITEMS_END = '// @@AVC-WORK-ITEMS-END@@';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Syncs .avc/project/ work-item hierarchy (epics + stories + doc.md files)
|
|
10
|
+
* into the VitePress documentation site at .avc/documentation/.
|
|
11
|
+
*
|
|
12
|
+
* Responsibilities:
|
|
13
|
+
* 1. Read .avc/project/ hierarchy via work.json files
|
|
14
|
+
* 2. Copy changed doc.md files into documentation/project/{epic}/{story}/index.md
|
|
15
|
+
* 3. Regenerate the VitePress sidebar between marker comments in config.mts
|
|
16
|
+
*/
|
|
17
|
+
export class DocsSyncProcessor {
|
|
18
|
+
constructor(projectRoot = process.cwd()) {
|
|
19
|
+
this.projectRoot = projectRoot;
|
|
20
|
+
this.avcDir = path.join(projectRoot, '.avc');
|
|
21
|
+
this.projectPath = path.join(this.avcDir, 'project');
|
|
22
|
+
this.docsDir = path.join(this.avcDir, 'documentation');
|
|
23
|
+
this.projectDocsDir = path.join(this.docsDir, 'project');
|
|
24
|
+
this.configPath = path.join(this.docsDir, '.vitepress', 'config.mts');
|
|
25
|
+
this.avcConfigPath = path.join(this.avcDir, 'avc.json');
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Read the epic/story hierarchy from .avc/project/ work.json files.
|
|
30
|
+
* Returns array sorted by ID, each entry: { id, name, docPath, stories: [{ id, name, docPath }] }
|
|
31
|
+
*/
|
|
32
|
+
readHierarchy() {
|
|
33
|
+
if (!fs.existsSync(this.projectPath)) {
|
|
34
|
+
return [];
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const entries = fs.readdirSync(this.projectPath, { withFileTypes: true });
|
|
38
|
+
const epics = [];
|
|
39
|
+
|
|
40
|
+
for (const entry of entries) {
|
|
41
|
+
if (!entry.isDirectory()) continue;
|
|
42
|
+
|
|
43
|
+
const epicId = entry.name;
|
|
44
|
+
const workJsonPath = path.join(this.projectPath, epicId, 'work.json');
|
|
45
|
+
|
|
46
|
+
if (!fs.existsSync(workJsonPath)) continue;
|
|
47
|
+
|
|
48
|
+
let workJson;
|
|
49
|
+
try {
|
|
50
|
+
workJson = JSON.parse(fs.readFileSync(workJsonPath, 'utf8'));
|
|
51
|
+
} catch {
|
|
52
|
+
continue;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
if (workJson.type !== 'epic') continue;
|
|
56
|
+
|
|
57
|
+
const epicDocPath = path.join(this.projectPath, epicId, 'doc.md');
|
|
58
|
+
const stories = [];
|
|
59
|
+
|
|
60
|
+
// Scan subdirectories for stories
|
|
61
|
+
const epicDir = path.join(this.projectPath, epicId);
|
|
62
|
+
const storyEntries = fs.readdirSync(epicDir, { withFileTypes: true });
|
|
63
|
+
|
|
64
|
+
for (const storyEntry of storyEntries) {
|
|
65
|
+
if (!storyEntry.isDirectory()) continue;
|
|
66
|
+
|
|
67
|
+
const storyId = storyEntry.name;
|
|
68
|
+
const storyWorkJsonPath = path.join(epicDir, storyId, 'work.json');
|
|
69
|
+
|
|
70
|
+
if (!fs.existsSync(storyWorkJsonPath)) continue;
|
|
71
|
+
|
|
72
|
+
let storyWorkJson;
|
|
73
|
+
try {
|
|
74
|
+
storyWorkJson = JSON.parse(fs.readFileSync(storyWorkJsonPath, 'utf8'));
|
|
75
|
+
} catch {
|
|
76
|
+
continue;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
if (storyWorkJson.type !== 'story') continue;
|
|
80
|
+
|
|
81
|
+
const storyDocPath = path.join(epicDir, storyId, 'doc.md');
|
|
82
|
+
stories.push({
|
|
83
|
+
id: storyId,
|
|
84
|
+
name: storyWorkJson.name || storyId,
|
|
85
|
+
docPath: storyDocPath,
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
stories.sort((a, b) => a.id.localeCompare(b.id));
|
|
90
|
+
|
|
91
|
+
epics.push({
|
|
92
|
+
id: epicId,
|
|
93
|
+
name: workJson.name || epicId,
|
|
94
|
+
docPath: epicDocPath,
|
|
95
|
+
stories,
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
epics.sort((a, b) => a.id.localeCompare(b.id));
|
|
100
|
+
return epics;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Copy src to dest if source is newer than dest (mtime-based incremental skip).
|
|
105
|
+
* Returns 'copied' or 'skipped'.
|
|
106
|
+
*/
|
|
107
|
+
syncFile(src, dest) {
|
|
108
|
+
if (!fs.existsSync(src)) {
|
|
109
|
+
return 'skipped';
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
if (fs.existsSync(dest)) {
|
|
113
|
+
const srcStat = fs.statSync(src);
|
|
114
|
+
const destStat = fs.statSync(dest);
|
|
115
|
+
if (destStat.mtimeMs >= srcStat.mtimeMs) {
|
|
116
|
+
return 'skipped';
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
fs.copyFileSync(src, dest);
|
|
121
|
+
return 'copied';
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Write a stub index.md for an epic/story that has no doc.md yet.
|
|
126
|
+
*/
|
|
127
|
+
_writeStub(destPath, name) {
|
|
128
|
+
const content = `# ${name}\n\n_Documentation pending._\n`;
|
|
129
|
+
fs.writeFileSync(destPath, content, 'utf8');
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Main sync orchestration. Reads hierarchy, copies files, regenerates sidebar config.
|
|
134
|
+
* @param {Function} [progressCallback] - optional (message) => void
|
|
135
|
+
* @returns {{ epics: number, stories: number, copied: number, skipped: number }}
|
|
136
|
+
*/
|
|
137
|
+
async sync(progressCallback = null) {
|
|
138
|
+
if (!fs.existsSync(this.docsDir)) {
|
|
139
|
+
throw new Error('Documentation directory not found. Run /init first to create documentation structure.');
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
const report = (msg) => { if (progressCallback) progressCallback(msg); };
|
|
143
|
+
const hierarchy = this.readHierarchy();
|
|
144
|
+
|
|
145
|
+
let epicCount = 0;
|
|
146
|
+
let storyCount = 0;
|
|
147
|
+
let copiedCount = 0;
|
|
148
|
+
let skippedCount = 0;
|
|
149
|
+
|
|
150
|
+
// Sync project root doc.md → documentation/index.md
|
|
151
|
+
const projectDocSrc = path.join(this.projectPath, 'doc.md');
|
|
152
|
+
const projectDocDest = path.join(this.docsDir, 'index.md');
|
|
153
|
+
if (fs.existsSync(projectDocSrc)) {
|
|
154
|
+
const result = this.syncFile(projectDocSrc, projectDocDest);
|
|
155
|
+
if (result === 'copied') copiedCount++;
|
|
156
|
+
else skippedCount++;
|
|
157
|
+
report(`Project brief: ${result}`);
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
// Ensure project/ subdir exists if we have epics
|
|
161
|
+
if (hierarchy.length > 0) {
|
|
162
|
+
fs.mkdirSync(this.projectDocsDir, { recursive: true });
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
for (const epic of hierarchy) {
|
|
166
|
+
epicCount++;
|
|
167
|
+
const epicDestDir = path.join(this.projectDocsDir, epic.id);
|
|
168
|
+
fs.mkdirSync(epicDestDir, { recursive: true });
|
|
169
|
+
|
|
170
|
+
const epicDestPath = path.join(epicDestDir, 'index.md');
|
|
171
|
+
if (fs.existsSync(epic.docPath)) {
|
|
172
|
+
const result = this.syncFile(epic.docPath, epicDestPath);
|
|
173
|
+
if (result === 'copied') copiedCount++;
|
|
174
|
+
else skippedCount++;
|
|
175
|
+
report(`Epic ${epic.id}: ${result}`);
|
|
176
|
+
} else {
|
|
177
|
+
this._writeStub(epicDestPath, epic.name);
|
|
178
|
+
copiedCount++;
|
|
179
|
+
report(`Epic ${epic.id}: stub written`);
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
for (const story of epic.stories) {
|
|
183
|
+
storyCount++;
|
|
184
|
+
const storyDestDir = path.join(epicDestDir, story.id);
|
|
185
|
+
fs.mkdirSync(storyDestDir, { recursive: true });
|
|
186
|
+
|
|
187
|
+
const storyDestPath = path.join(storyDestDir, 'index.md');
|
|
188
|
+
if (fs.existsSync(story.docPath)) {
|
|
189
|
+
const result = this.syncFile(story.docPath, storyDestPath);
|
|
190
|
+
if (result === 'copied') copiedCount++;
|
|
191
|
+
else skippedCount++;
|
|
192
|
+
report(`Story ${story.id}: ${result}`);
|
|
193
|
+
} else {
|
|
194
|
+
this._writeStub(storyDestPath, story.name);
|
|
195
|
+
copiedCount++;
|
|
196
|
+
report(`Story ${story.id}: stub written`);
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
// Regenerate VitePress sidebar config
|
|
202
|
+
this.generateVitePressConfig(hierarchy);
|
|
203
|
+
|
|
204
|
+
return { epics: epicCount, stories: storyCount, copied: copiedCount, skipped: skippedCount };
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
/**
|
|
208
|
+
* Regenerate the @@AVC-WORK-ITEMS-START@@ … @@AVC-WORK-ITEMS-END@@ block in config.mts.
|
|
209
|
+
* If markers are absent (existing project), appends as second sidebar array element and inserts markers.
|
|
210
|
+
*/
|
|
211
|
+
generateVitePressConfig(hierarchy) {
|
|
212
|
+
if (!fs.existsSync(this.configPath)) return;
|
|
213
|
+
|
|
214
|
+
const content = fs.readFileSync(this.configPath, 'utf8');
|
|
215
|
+
const workItemsBlock = this._buildWorkItemsBlock(hierarchy);
|
|
216
|
+
|
|
217
|
+
if (content.includes(WORK_ITEMS_START)) {
|
|
218
|
+
// Replace content between markers (inclusive)
|
|
219
|
+
const startIdx = content.indexOf(WORK_ITEMS_START);
|
|
220
|
+
const endIdx = content.indexOf(WORK_ITEMS_END);
|
|
221
|
+
if (endIdx === -1) return; // malformed markers, skip
|
|
222
|
+
|
|
223
|
+
const before = content.slice(0, startIdx);
|
|
224
|
+
const after = content.slice(endIdx + WORK_ITEMS_END.length);
|
|
225
|
+
const updated = before + workItemsBlock + after;
|
|
226
|
+
fs.writeFileSync(this.configPath, updated, 'utf8');
|
|
227
|
+
} else {
|
|
228
|
+
// Backwards compat: insert markers + work items block before the closing of the sidebar array
|
|
229
|
+
// Find the closing `]` of the sidebar array
|
|
230
|
+
const sidebarMatch = content.match(/sidebar:\s*\[/);
|
|
231
|
+
if (!sidebarMatch) return;
|
|
232
|
+
|
|
233
|
+
// Find the end of the first sidebar item block to insert after it
|
|
234
|
+
const insertionPattern = /sidebar:\s*\[\s*\{[\s\S]*?\}\s*(?=\])/;
|
|
235
|
+
const match = insertionPattern.exec(content);
|
|
236
|
+
if (!match) return;
|
|
237
|
+
|
|
238
|
+
const insertAt = match.index + match[0].length;
|
|
239
|
+
const updated = content.slice(0, insertAt) + '\n ' + workItemsBlock + '\n ' + content.slice(insertAt);
|
|
240
|
+
fs.writeFileSync(this.configPath, updated, 'utf8');
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
/**
|
|
245
|
+
* Build the sidebar work-items block string (with markers).
|
|
246
|
+
*/
|
|
247
|
+
_buildWorkItemsBlock(hierarchy) {
|
|
248
|
+
if (hierarchy.length === 0) {
|
|
249
|
+
return `${WORK_ITEMS_START}\n ${WORK_ITEMS_END}`;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
const epicItems = hierarchy.map(epic => {
|
|
253
|
+
const storyItems = epic.stories.map(story => {
|
|
254
|
+
const link = path.posix.join('/project', epic.id, story.id) + '/';
|
|
255
|
+
return ` { text: ${JSON.stringify(story.name)}, link: ${JSON.stringify(link)} }`;
|
|
256
|
+
});
|
|
257
|
+
|
|
258
|
+
const epicLink = path.posix.join('/project', epic.id) + '/';
|
|
259
|
+
if (storyItems.length > 0) {
|
|
260
|
+
return ` { text: ${JSON.stringify(epic.name)}, link: ${JSON.stringify(epicLink)}, collapsed: false, items: [\n${storyItems.join(',\n')}\n ] }`;
|
|
261
|
+
} else {
|
|
262
|
+
return ` { text: ${JSON.stringify(epic.name)}, link: ${JSON.stringify(epicLink)} }`;
|
|
263
|
+
}
|
|
264
|
+
});
|
|
265
|
+
|
|
266
|
+
const block = `,{
|
|
267
|
+
text: 'Work Items',
|
|
268
|
+
items: [
|
|
269
|
+
${epicItems.join(',\n')}
|
|
270
|
+
]
|
|
271
|
+
}`;
|
|
272
|
+
|
|
273
|
+
return `${WORK_ITEMS_START}\n ${block}\n ${WORK_ITEMS_END}`;
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
/**
|
|
277
|
+
* Watch .avc/project/ for md changes and trigger sync with debounce.
|
|
278
|
+
* Returns a chokidar watcher instance.
|
|
279
|
+
*/
|
|
280
|
+
watch(onChange = null) {
|
|
281
|
+
let debounceTimer = null;
|
|
282
|
+
|
|
283
|
+
const watcher = chokidar.watch(path.join(this.projectPath, '**', '*.md'), {
|
|
284
|
+
ignoreInitial: true,
|
|
285
|
+
awaitWriteFinish: { stabilityThreshold: 500, pollInterval: 100 },
|
|
286
|
+
});
|
|
287
|
+
|
|
288
|
+
const trigger = () => {
|
|
289
|
+
if (debounceTimer) clearTimeout(debounceTimer);
|
|
290
|
+
debounceTimer = setTimeout(async () => {
|
|
291
|
+
try {
|
|
292
|
+
const stats = await this.sync();
|
|
293
|
+
if (onChange) onChange(stats);
|
|
294
|
+
} catch {
|
|
295
|
+
// Ignore errors in watch mode
|
|
296
|
+
}
|
|
297
|
+
}, 500);
|
|
298
|
+
};
|
|
299
|
+
|
|
300
|
+
watcher.on('add', trigger);
|
|
301
|
+
watcher.on('change', trigger);
|
|
302
|
+
watcher.on('unlink', trigger);
|
|
303
|
+
|
|
304
|
+
return watcher;
|
|
305
|
+
}
|
|
306
|
+
}
|