@agile-vibe-coding/avc 0.1.1 → 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/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 +29 -8
- package/cli/ceremony-history.js +369 -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/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 +0 -0
- package/cli/init-model-config.js +697 -0
- package/cli/init.js +1311 -274
- package/cli/kanban-server-manager.js +228 -0
- package/cli/llm-claude.js +83 -1
- package/cli/llm-gemini.js +85 -0
- package/cli/llm-mock.js +233 -0
- package/cli/llm-openai.js +233 -0
- package/cli/llm-provider.js +240 -3
- package/cli/llm-token-limits.js +102 -0
- package/cli/llm-verifier.js +454 -0
- 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 +73 -2
- package/cli/repl-ink.js +4988 -1217
- 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 +2102 -105
- package/cli/templates/project.md +25 -8
- package/cli/templates/vitepress-config.mts.template +5 -4
- 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 +18 -5
- package/cli/agents/documentation.md +0 -302
package/cli/build-docs.js
CHANGED
|
@@ -25,6 +25,13 @@ export class DocumentationBuilder {
|
|
|
25
25
|
return fs.existsSync(this.docsDir);
|
|
26
26
|
}
|
|
27
27
|
|
|
28
|
+
/**
|
|
29
|
+
* Check if synced project docs directory exists inside documentation
|
|
30
|
+
*/
|
|
31
|
+
hasProjectDocs() {
|
|
32
|
+
return fs.existsSync(path.join(this.docsDir, 'project'));
|
|
33
|
+
}
|
|
34
|
+
|
|
28
35
|
/**
|
|
29
36
|
* Get documentation server port from avc.json config
|
|
30
37
|
* Returns default port 4173 if not configured
|
|
@@ -40,7 +47,7 @@ export class DocumentationBuilder {
|
|
|
40
47
|
const config = JSON.parse(fs.readFileSync(configPath, 'utf8'));
|
|
41
48
|
return config.settings?.documentation?.port || 4173;
|
|
42
49
|
} catch (error) {
|
|
43
|
-
console.warn(
|
|
50
|
+
console.warn(`Could not read port from avc.json: ${error.message}`);
|
|
44
51
|
return 4173;
|
|
45
52
|
}
|
|
46
53
|
}
|
|
@@ -197,13 +204,9 @@ export class DocumentationBuilder {
|
|
|
197
204
|
const port = this.getPort();
|
|
198
205
|
|
|
199
206
|
try {
|
|
200
|
-
//
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
});
|
|
204
|
-
|
|
205
|
-
// Start the preview server
|
|
206
|
-
const serverProcess = spawn('npx', ['vitepress', 'preview', '--port', String(port)], {
|
|
207
|
+
// Start the dev server — no initial build needed, vitepress dev builds on-demand
|
|
208
|
+
// and hot-reloads the browser whenever source .md files change
|
|
209
|
+
const serverProcess = spawn('npx', ['vitepress', 'dev', '--port', String(port)], {
|
|
207
210
|
cwd: this.docsDir,
|
|
208
211
|
stdio: 'pipe'
|
|
209
212
|
});
|
|
@@ -256,6 +259,22 @@ export class DocumentationBuilder {
|
|
|
256
259
|
}
|
|
257
260
|
}
|
|
258
261
|
|
|
262
|
+
/**
|
|
263
|
+
* Ensure ignoreDeadLinks: true is present in the VitePress config.
|
|
264
|
+
* Patches existing project configs that were created before this option was added.
|
|
265
|
+
*/
|
|
266
|
+
_ensureIgnoreDeadLinks() {
|
|
267
|
+
const configPath = path.join(this.docsDir, '.vitepress', 'config.mts');
|
|
268
|
+
if (!fs.existsSync(configPath)) return;
|
|
269
|
+
const content = fs.readFileSync(configPath, 'utf8');
|
|
270
|
+
if (content.includes('ignoreDeadLinks')) return;
|
|
271
|
+
const patched = content.replace(
|
|
272
|
+
/defineConfig\(\{/,
|
|
273
|
+
'defineConfig({\n ignoreDeadLinks: true,'
|
|
274
|
+
);
|
|
275
|
+
fs.writeFileSync(configPath, patched, 'utf8');
|
|
276
|
+
}
|
|
277
|
+
|
|
259
278
|
/**
|
|
260
279
|
* Build documentation without starting server
|
|
261
280
|
*/
|
|
@@ -264,6 +283,8 @@ export class DocumentationBuilder {
|
|
|
264
283
|
throw new Error('Documentation not found. Run /init first to create documentation structure.');
|
|
265
284
|
}
|
|
266
285
|
|
|
286
|
+
this._ensureIgnoreDeadLinks();
|
|
287
|
+
|
|
267
288
|
try {
|
|
268
289
|
// Build asynchronously to avoid blocking the event loop
|
|
269
290
|
await execAsync('npx vitepress build', {
|
|
@@ -0,0 +1,369 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Ceremony History Tracker - Records all ceremony executions with status and metadata
|
|
3
|
+
*
|
|
4
|
+
* Tracks when ceremonies are run, their outcomes, and preserves historical data.
|
|
5
|
+
* Helps detect abrupt terminations and provides audit trail.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import fs from 'fs';
|
|
9
|
+
import path from 'path';
|
|
10
|
+
|
|
11
|
+
export class CeremonyHistory {
|
|
12
|
+
constructor(avcPath = path.join(process.cwd(), '.avc')) {
|
|
13
|
+
this.avcPath = avcPath;
|
|
14
|
+
this.historyPath = path.join(avcPath, 'ceremonies-history.json');
|
|
15
|
+
this.data = null;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Initialize ceremony history file if it doesn't exist
|
|
20
|
+
*/
|
|
21
|
+
init() {
|
|
22
|
+
// Ensure .avc directory exists
|
|
23
|
+
if (!fs.existsSync(this.avcPath)) {
|
|
24
|
+
fs.mkdirSync(this.avcPath, { recursive: true });
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
if (!fs.existsSync(this.historyPath)) {
|
|
28
|
+
const initialData = {
|
|
29
|
+
version: "1.0",
|
|
30
|
+
lastUpdated: new Date().toISOString(),
|
|
31
|
+
ceremonies: {}
|
|
32
|
+
};
|
|
33
|
+
this._writeData(initialData);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Load ceremony history from disk
|
|
39
|
+
*/
|
|
40
|
+
load() {
|
|
41
|
+
if (fs.existsSync(this.historyPath)) {
|
|
42
|
+
this.data = JSON.parse(fs.readFileSync(this.historyPath, 'utf8'));
|
|
43
|
+
} else {
|
|
44
|
+
this.init();
|
|
45
|
+
this.data = JSON.parse(fs.readFileSync(this.historyPath, 'utf8'));
|
|
46
|
+
}
|
|
47
|
+
return this.data;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Write data to disk atomically
|
|
52
|
+
*/
|
|
53
|
+
_writeData(data) {
|
|
54
|
+
try {
|
|
55
|
+
const tempPath = this.historyPath + '.tmp';
|
|
56
|
+
fs.writeFileSync(tempPath, JSON.stringify(data, null, 2), 'utf8');
|
|
57
|
+
fs.renameSync(tempPath, this.historyPath);
|
|
58
|
+
} catch (error) {
|
|
59
|
+
// Fallback to direct write if atomic write fails
|
|
60
|
+
try {
|
|
61
|
+
fs.writeFileSync(this.historyPath, JSON.stringify(data, null, 2), 'utf8');
|
|
62
|
+
} catch (fallbackError) {
|
|
63
|
+
console.error(`Failed to write ceremony history: ${fallbackError.message}`);
|
|
64
|
+
console.error(`Path: ${this.historyPath}`);
|
|
65
|
+
throw fallbackError;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Start a new ceremony execution
|
|
72
|
+
* @param {string} ceremonyName - e.g., 'sponsor-call'
|
|
73
|
+
* @param {string} stage - Initial stage (default: 'questionnaire')
|
|
74
|
+
* @returns {string} Execution ID
|
|
75
|
+
*/
|
|
76
|
+
startExecution(ceremonyName, stage = 'questionnaire') {
|
|
77
|
+
this.load();
|
|
78
|
+
|
|
79
|
+
const now = new Date();
|
|
80
|
+
const timestamp = now.toISOString()
|
|
81
|
+
.replace(/T/, '-')
|
|
82
|
+
.replace(/:/g, '-')
|
|
83
|
+
.replace(/\..+/, '');
|
|
84
|
+
|
|
85
|
+
const executionId = `${ceremonyName}-${timestamp}`;
|
|
86
|
+
|
|
87
|
+
// Initialize ceremony entry if doesn't exist
|
|
88
|
+
if (!this.data.ceremonies[ceremonyName]) {
|
|
89
|
+
this.data.ceremonies[ceremonyName] = {
|
|
90
|
+
executions: [],
|
|
91
|
+
totalExecutions: 0,
|
|
92
|
+
lastRun: null,
|
|
93
|
+
lastSuccess: null
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// Create execution record
|
|
98
|
+
const execution = {
|
|
99
|
+
id: executionId,
|
|
100
|
+
startTime: now.toISOString(),
|
|
101
|
+
endTime: null,
|
|
102
|
+
status: 'in-progress',
|
|
103
|
+
stage: stage,
|
|
104
|
+
answers: null,
|
|
105
|
+
filesGenerated: [],
|
|
106
|
+
tokenUsage: null,
|
|
107
|
+
duration: null,
|
|
108
|
+
outcome: null
|
|
109
|
+
};
|
|
110
|
+
|
|
111
|
+
// Add to executions array
|
|
112
|
+
this.data.ceremonies[ceremonyName].executions.push(execution);
|
|
113
|
+
this.data.ceremonies[ceremonyName].lastRun = now.toISOString();
|
|
114
|
+
this.data.lastUpdated = now.toISOString();
|
|
115
|
+
|
|
116
|
+
this._writeData(this.data);
|
|
117
|
+
|
|
118
|
+
return executionId;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Update an existing execution
|
|
123
|
+
* @param {string} ceremonyName - Ceremony name
|
|
124
|
+
* @param {string} executionId - Execution ID
|
|
125
|
+
* @param {Object} updates - Fields to update
|
|
126
|
+
*/
|
|
127
|
+
updateExecution(ceremonyName, executionId, updates) {
|
|
128
|
+
this.load();
|
|
129
|
+
|
|
130
|
+
if (!this.data.ceremonies[ceremonyName]) {
|
|
131
|
+
throw new Error(`Ceremony '${ceremonyName}' not found in history`);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
const execution = this.data.ceremonies[ceremonyName].executions.find(
|
|
135
|
+
e => e.id === executionId
|
|
136
|
+
);
|
|
137
|
+
|
|
138
|
+
if (!execution) {
|
|
139
|
+
throw new Error(`Execution '${executionId}' not found for ceremony '${ceremonyName}'`);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// Update fields
|
|
143
|
+
Object.assign(execution, updates);
|
|
144
|
+
this.data.lastUpdated = new Date().toISOString();
|
|
145
|
+
|
|
146
|
+
this._writeData(this.data);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* Complete an execution
|
|
151
|
+
* @param {string} ceremonyName - Ceremony name
|
|
152
|
+
* @param {string} executionId - Execution ID
|
|
153
|
+
* @param {string} outcome - 'success', 'user-cancelled', or 'abrupt-termination'
|
|
154
|
+
* @param {Object} metadata - Additional data (answers, filesGenerated, tokenUsage, etc.)
|
|
155
|
+
*/
|
|
156
|
+
completeExecution(ceremonyName, executionId, outcome, metadata = {}) {
|
|
157
|
+
this.load();
|
|
158
|
+
|
|
159
|
+
if (!this.data.ceremonies[ceremonyName]) {
|
|
160
|
+
throw new Error(`Ceremony '${ceremonyName}' not found in history`);
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
const execution = this.data.ceremonies[ceremonyName].executions.find(
|
|
164
|
+
e => e.id === executionId
|
|
165
|
+
);
|
|
166
|
+
|
|
167
|
+
if (!execution) {
|
|
168
|
+
throw new Error(`Execution '${executionId}' not found for ceremony '${ceremonyName}'`);
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
const now = new Date();
|
|
172
|
+
const startTime = new Date(execution.startTime);
|
|
173
|
+
const duration = now - startTime;
|
|
174
|
+
|
|
175
|
+
// Determine status based on outcome
|
|
176
|
+
let status;
|
|
177
|
+
switch (outcome) {
|
|
178
|
+
case 'success':
|
|
179
|
+
status = 'completed';
|
|
180
|
+
break;
|
|
181
|
+
case 'user-cancelled':
|
|
182
|
+
status = 'cancelled';
|
|
183
|
+
break;
|
|
184
|
+
case 'abrupt-termination':
|
|
185
|
+
status = 'aborted';
|
|
186
|
+
break;
|
|
187
|
+
default:
|
|
188
|
+
status = 'completed';
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
// Update execution
|
|
192
|
+
execution.status = status;
|
|
193
|
+
execution.endTime = now.toISOString();
|
|
194
|
+
execution.outcome = outcome;
|
|
195
|
+
execution.duration = duration;
|
|
196
|
+
|
|
197
|
+
// Merge metadata
|
|
198
|
+
if (metadata.answers) execution.answers = metadata.answers;
|
|
199
|
+
if (metadata.filesGenerated) execution.filesGenerated = metadata.filesGenerated;
|
|
200
|
+
if (metadata.tokenUsage) execution.tokenUsage = metadata.tokenUsage;
|
|
201
|
+
if (metadata.cost) execution.cost = metadata.cost;
|
|
202
|
+
if (metadata.model) execution.model = metadata.model;
|
|
203
|
+
if (metadata.stage) execution.stage = metadata.stage;
|
|
204
|
+
if (metadata.error) execution.error = metadata.error;
|
|
205
|
+
if (metadata.note) execution.note = metadata.note;
|
|
206
|
+
|
|
207
|
+
// Update ceremony totals
|
|
208
|
+
this.data.ceremonies[ceremonyName].totalExecutions =
|
|
209
|
+
this.data.ceremonies[ceremonyName].executions.length;
|
|
210
|
+
|
|
211
|
+
if (outcome === 'success') {
|
|
212
|
+
this.data.ceremonies[ceremonyName].lastSuccess = now.toISOString();
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
this.data.lastUpdated = now.toISOString();
|
|
216
|
+
|
|
217
|
+
this._writeData(this.data);
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
/**
|
|
221
|
+
* Archive answers to an execution (before LLM generation starts)
|
|
222
|
+
* @param {string} ceremonyName - Ceremony name
|
|
223
|
+
* @param {string} executionId - Execution ID
|
|
224
|
+
* @param {Object} answers - Questionnaire answers
|
|
225
|
+
*/
|
|
226
|
+
archiveAnswers(ceremonyName, executionId, answers) {
|
|
227
|
+
this.updateExecution(ceremonyName, executionId, {
|
|
228
|
+
answers: { ...answers }
|
|
229
|
+
});
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
/**
|
|
233
|
+
* Get the most recent execution for a ceremony
|
|
234
|
+
* @param {string} ceremonyName - Ceremony name
|
|
235
|
+
* @returns {Object|null} Execution record or null
|
|
236
|
+
*/
|
|
237
|
+
getLastExecution(ceremonyName) {
|
|
238
|
+
this.load();
|
|
239
|
+
|
|
240
|
+
if (!this.data.ceremonies[ceremonyName]) {
|
|
241
|
+
return null;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
const executions = this.data.ceremonies[ceremonyName].executions;
|
|
245
|
+
if (executions.length === 0) {
|
|
246
|
+
return null;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
// Return most recent (last in array)
|
|
250
|
+
return executions[executions.length - 1];
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
/**
|
|
254
|
+
* Get a specific execution by ID
|
|
255
|
+
* @param {string} ceremonyName - Ceremony name
|
|
256
|
+
* @param {string} executionId - Execution ID
|
|
257
|
+
* @returns {Object|null} Execution record or null
|
|
258
|
+
*/
|
|
259
|
+
getExecutionById(ceremonyName, executionId) {
|
|
260
|
+
this.load();
|
|
261
|
+
|
|
262
|
+
if (!this.data.ceremonies[ceremonyName]) {
|
|
263
|
+
return null;
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
return this.data.ceremonies[ceremonyName].executions.find(
|
|
267
|
+
e => e.id === executionId
|
|
268
|
+
) || null;
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
/**
|
|
272
|
+
* Get all executions for a ceremony
|
|
273
|
+
* @param {string} ceremonyName - Ceremony name
|
|
274
|
+
* @returns {Array} Array of execution records (newest first)
|
|
275
|
+
*/
|
|
276
|
+
getAllExecutions(ceremonyName) {
|
|
277
|
+
this.load();
|
|
278
|
+
|
|
279
|
+
if (!this.data.ceremonies[ceremonyName]) {
|
|
280
|
+
return [];
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
// Return copy, sorted by startTime descending
|
|
284
|
+
return [...this.data.ceremonies[ceremonyName].executions]
|
|
285
|
+
.sort((a, b) => new Date(b.startTime) - new Date(a.startTime));
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
/**
|
|
289
|
+
* Detect if the last execution was abruptly terminated
|
|
290
|
+
* @param {string} ceremonyName - Ceremony name
|
|
291
|
+
* @returns {boolean} True if abrupt termination detected
|
|
292
|
+
*/
|
|
293
|
+
detectAbruptTermination(ceremonyName) {
|
|
294
|
+
const lastExecution = this.getLastExecution(ceremonyName);
|
|
295
|
+
|
|
296
|
+
if (!lastExecution) {
|
|
297
|
+
return false;
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
// Check if last execution is still in-progress and in LLM generation stage
|
|
301
|
+
return lastExecution.status === 'in-progress' &&
|
|
302
|
+
lastExecution.stage === 'llm-generation';
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
/**
|
|
306
|
+
* Clean up abrupt termination (mark as aborted)
|
|
307
|
+
* @param {string} ceremonyName - Ceremony name
|
|
308
|
+
*/
|
|
309
|
+
cleanupAbruptTermination(ceremonyName) {
|
|
310
|
+
const lastExecution = this.getLastExecution(ceremonyName);
|
|
311
|
+
|
|
312
|
+
if (!lastExecution || lastExecution.status !== 'in-progress') {
|
|
313
|
+
return;
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
this.completeExecution(ceremonyName, lastExecution.id, 'abrupt-termination', {
|
|
317
|
+
note: 'Process was interrupted during LLM generation',
|
|
318
|
+
stage: lastExecution.stage
|
|
319
|
+
});
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
/**
|
|
323
|
+
* Get ceremony statistics
|
|
324
|
+
* @param {string} ceremonyName - Ceremony name
|
|
325
|
+
* @returns {Object} Statistics object
|
|
326
|
+
*/
|
|
327
|
+
getStats(ceremonyName) {
|
|
328
|
+
this.load();
|
|
329
|
+
|
|
330
|
+
if (!this.data.ceremonies[ceremonyName]) {
|
|
331
|
+
return {
|
|
332
|
+
totalExecutions: 0,
|
|
333
|
+
successful: 0,
|
|
334
|
+
cancelled: 0,
|
|
335
|
+
aborted: 0,
|
|
336
|
+
lastRun: null,
|
|
337
|
+
lastSuccess: null
|
|
338
|
+
};
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
const ceremony = this.data.ceremonies[ceremonyName];
|
|
342
|
+
const executions = ceremony.executions;
|
|
343
|
+
|
|
344
|
+
return {
|
|
345
|
+
totalExecutions: ceremony.totalExecutions,
|
|
346
|
+
successful: executions.filter(e => e.outcome === 'success').length,
|
|
347
|
+
cancelled: executions.filter(e => e.outcome === 'user-cancelled').length,
|
|
348
|
+
aborted: executions.filter(e => e.outcome === 'abrupt-termination').length,
|
|
349
|
+
lastRun: ceremony.lastRun,
|
|
350
|
+
lastSuccess: ceremony.lastSuccess
|
|
351
|
+
};
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
/**
|
|
355
|
+
* Check if a ceremony has completed successfully at least once
|
|
356
|
+
* @param {string} ceremonyName - Ceremony name
|
|
357
|
+
* @returns {boolean} True if ceremony has a successful completion
|
|
358
|
+
*/
|
|
359
|
+
hasSuccessfulCompletion(ceremonyName) {
|
|
360
|
+
this.load();
|
|
361
|
+
|
|
362
|
+
if (!this.data.ceremonies[ceremonyName]) {
|
|
363
|
+
return false;
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
// Check if lastSuccess is set (indicates at least one successful execution)
|
|
367
|
+
return this.data.ceremonies[ceremonyName].lastSuccess !== null;
|
|
368
|
+
}
|
|
369
|
+
}
|
package/cli/command-logger.js
CHANGED
|
@@ -11,15 +11,27 @@
|
|
|
11
11
|
import fs from 'fs';
|
|
12
12
|
import path from 'path';
|
|
13
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
|
+
|
|
14
25
|
class CommandLogger {
|
|
15
|
-
constructor(commandName, projectRoot = process.cwd()) {
|
|
26
|
+
constructor(commandName, projectRoot = process.cwd(), inkMode = false) {
|
|
16
27
|
this.commandName = commandName;
|
|
17
28
|
this.projectRoot = projectRoot;
|
|
18
29
|
this.logsDir = path.join(projectRoot, '.avc', 'logs');
|
|
30
|
+
this.inkMode = inkMode; // Don't forward to console if in Ink mode
|
|
19
31
|
|
|
20
32
|
// Create timestamp for this command execution
|
|
21
33
|
const now = new Date();
|
|
22
|
-
const timestamp = now
|
|
34
|
+
const timestamp = localISO(now)
|
|
23
35
|
.replace(/T/, '-')
|
|
24
36
|
.replace(/:/g, '-')
|
|
25
37
|
.replace(/\..+/, '');
|
|
@@ -54,7 +66,7 @@ class CommandLogger {
|
|
|
54
66
|
const header = [
|
|
55
67
|
'='.repeat(80),
|
|
56
68
|
`AVC Command Log: ${this.commandName}`,
|
|
57
|
-
`Timestamp: ${
|
|
69
|
+
`Timestamp: ${localISO()}`,
|
|
58
70
|
`Project: ${this.projectRoot}`,
|
|
59
71
|
`Log File: ${this.logFileName}`,
|
|
60
72
|
'='.repeat(80),
|
|
@@ -75,19 +87,32 @@ class CommandLogger {
|
|
|
75
87
|
if (!this.logFilePath) return;
|
|
76
88
|
|
|
77
89
|
try {
|
|
78
|
-
const timestamp =
|
|
90
|
+
const timestamp = localISO();
|
|
79
91
|
const message = args.map(arg => {
|
|
92
|
+
if (arg === undefined) return 'undefined';
|
|
93
|
+
if (arg === null) return 'null';
|
|
80
94
|
if (typeof arg === 'object') {
|
|
81
|
-
|
|
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
|
+
}
|
|
82
105
|
}
|
|
83
106
|
return String(arg);
|
|
84
107
|
}).join(' ');
|
|
85
108
|
|
|
86
|
-
|
|
109
|
+
// Preserve formatting but ensure single newline at end
|
|
110
|
+
const logEntry = `[${timestamp}] [${level}] ${message}${message.endsWith('\n') ? '' : '\n'}`;
|
|
87
111
|
|
|
88
112
|
fs.appendFileSync(this.logFilePath, logEntry, 'utf8');
|
|
89
113
|
} catch (error) {
|
|
90
|
-
//
|
|
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`);
|
|
91
116
|
}
|
|
92
117
|
}
|
|
93
118
|
|
|
@@ -98,25 +123,37 @@ class CommandLogger {
|
|
|
98
123
|
// Intercept console.log
|
|
99
124
|
console.log = (...args) => {
|
|
100
125
|
this.writeLog('INFO', ...args);
|
|
101
|
-
|
|
126
|
+
// Only forward to console if NOT in Ink mode
|
|
127
|
+
if (!this.inkMode) {
|
|
128
|
+
this.originalLog(...args);
|
|
129
|
+
}
|
|
102
130
|
};
|
|
103
131
|
|
|
104
132
|
// Intercept console.error
|
|
105
133
|
console.error = (...args) => {
|
|
106
134
|
this.writeLog('ERROR', ...args);
|
|
107
|
-
|
|
135
|
+
// Only forward to console if NOT in Ink mode
|
|
136
|
+
if (!this.inkMode) {
|
|
137
|
+
this.originalError(...args);
|
|
138
|
+
}
|
|
108
139
|
};
|
|
109
140
|
|
|
110
141
|
// Intercept console.warn
|
|
111
142
|
console.warn = (...args) => {
|
|
112
143
|
this.writeLog('WARN', ...args);
|
|
113
|
-
|
|
144
|
+
// Only forward to console if NOT in Ink mode
|
|
145
|
+
if (!this.inkMode) {
|
|
146
|
+
this.originalWarn(...args);
|
|
147
|
+
}
|
|
114
148
|
};
|
|
115
149
|
|
|
116
150
|
// Intercept console.info
|
|
117
151
|
console.info = (...args) => {
|
|
118
152
|
this.writeLog('INFO', ...args);
|
|
119
|
-
|
|
153
|
+
// Only forward to console if NOT in Ink mode
|
|
154
|
+
if (!this.inkMode) {
|
|
155
|
+
this.originalInfo(...args);
|
|
156
|
+
}
|
|
120
157
|
};
|
|
121
158
|
}
|
|
122
159
|
|
|
@@ -136,7 +173,7 @@ class CommandLogger {
|
|
|
136
173
|
const footer = [
|
|
137
174
|
'',
|
|
138
175
|
'='.repeat(80),
|
|
139
|
-
`Command completed: ${
|
|
176
|
+
`Command completed: ${localISO()}`,
|
|
140
177
|
`Log saved: ${this.logFilePath}`,
|
|
141
178
|
'='.repeat(80)
|
|
142
179
|
].join('\n');
|
|
@@ -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
|
+
};
|