@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,454 @@
|
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import { fileURLToPath } from 'url';
|
|
4
|
+
|
|
5
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
6
|
+
const __dirname = path.dirname(__filename);
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* LLM-based verification engine
|
|
10
|
+
*
|
|
11
|
+
* CONFIGURATION-DRIVEN VALIDATION:
|
|
12
|
+
* - All validation logic defined in JSON rule files
|
|
13
|
+
* - Fast-path optimizations configurable per rule (not hardcoded)
|
|
14
|
+
* - Hardcoded helpers exist (JSON parsing, regex) but triggered by JSON config
|
|
15
|
+
* - Each rule checks ONE thing and fixes ONE thing (atomic)
|
|
16
|
+
*
|
|
17
|
+
* Fast-Path Types Available:
|
|
18
|
+
* - 'json-parse': Validate JSON syntax programmatically
|
|
19
|
+
* - 'json-fields': Check required fields programmatically
|
|
20
|
+
* - 'none': Always use LLM (no fast-path)
|
|
21
|
+
*
|
|
22
|
+
* Usage:
|
|
23
|
+
* const verifier = new LLMVerifier(llmProvider, 'project-documentation-creator');
|
|
24
|
+
* const result = await verifier.verify(content, progressCallback);
|
|
25
|
+
*/
|
|
26
|
+
export class LLMVerifier {
|
|
27
|
+
constructor(llmProvider, agentName, tracker = null) {
|
|
28
|
+
this.llmProvider = llmProvider;
|
|
29
|
+
this.agentName = agentName;
|
|
30
|
+
this.tracker = tracker; // Optional verification tracker
|
|
31
|
+
this.rules = this.loadRules();
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Fast-path: Programmatically unwrap JSON from markdown code fence
|
|
36
|
+
* @param {string} content - Content that may be wrapped
|
|
37
|
+
* @returns {object} { isWrapped: boolean, unwrapped: string }
|
|
38
|
+
*/
|
|
39
|
+
unwrapJsonCodeFence(content) {
|
|
40
|
+
const trimmed = content.trim();
|
|
41
|
+
|
|
42
|
+
// Pattern 1: ```json\n...\n```
|
|
43
|
+
const pattern1 = /^```json\s*\n([\s\S]*)\n```$/;
|
|
44
|
+
const match1 = trimmed.match(pattern1);
|
|
45
|
+
if (match1) {
|
|
46
|
+
return { isWrapped: true, unwrapped: match1[1].trim() };
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// Pattern 2: ```\n...\n``` (generic code fence)
|
|
50
|
+
const pattern2 = /^```\s*\n([\s\S]*)\n```$/;
|
|
51
|
+
const match2 = trimmed.match(pattern2);
|
|
52
|
+
if (match2) {
|
|
53
|
+
// Check if content looks like JSON
|
|
54
|
+
const unwrapped = match2[1].trim();
|
|
55
|
+
if (unwrapped.startsWith('{') || unwrapped.startsWith('[')) {
|
|
56
|
+
return { isWrapped: true, unwrapped };
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
return { isWrapped: false, unwrapped: content };
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Fast-path: Check if content is valid JSON
|
|
65
|
+
* @param {string} content - Content to check
|
|
66
|
+
* @returns {object} { canFastPath: boolean, violated: boolean, reason: string }
|
|
67
|
+
*/
|
|
68
|
+
fastPathValidJson(content) {
|
|
69
|
+
// Check for markdown code fence
|
|
70
|
+
const { isWrapped, unwrapped } = this.unwrapJsonCodeFence(content);
|
|
71
|
+
|
|
72
|
+
if (isWrapped) {
|
|
73
|
+
return { canFastPath: true, violated: true, reason: 'markdown-fence' };
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// Try parsing JSON
|
|
77
|
+
try {
|
|
78
|
+
JSON.parse(unwrapped);
|
|
79
|
+
return { canFastPath: true, violated: false };
|
|
80
|
+
} catch (e) {
|
|
81
|
+
// Parse error - might be fixable by LLM
|
|
82
|
+
return { canFastPath: false, violated: true, reason: e.message };
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Fast-path: Check if required fields present
|
|
88
|
+
* @param {string} content - JSON content
|
|
89
|
+
* @param {array} requiredFields - Field names to check
|
|
90
|
+
* @returns {object} { canFastPath: boolean, violated: boolean, missingFields: array }
|
|
91
|
+
*/
|
|
92
|
+
fastPathRequiredFields(content, requiredFields) {
|
|
93
|
+
try {
|
|
94
|
+
// First unwrap if needed
|
|
95
|
+
const { unwrapped } = this.unwrapJsonCodeFence(content);
|
|
96
|
+
const obj = JSON.parse(unwrapped);
|
|
97
|
+
const missing = requiredFields.filter(field => !(field in obj));
|
|
98
|
+
|
|
99
|
+
if (missing.length === 0) {
|
|
100
|
+
return { canFastPath: true, violated: false };
|
|
101
|
+
} else {
|
|
102
|
+
return { canFastPath: true, violated: true, missingFields: missing };
|
|
103
|
+
}
|
|
104
|
+
} catch (e) {
|
|
105
|
+
// Can't parse - let LLM handle
|
|
106
|
+
return { canFastPath: false };
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Execute fast-path optimization if configured
|
|
112
|
+
* @param {string} content - Content to check
|
|
113
|
+
* @param {Object} rule - Verification rule with fastPath config
|
|
114
|
+
* @returns {Promise<Object>} { canFastPath: boolean, violated: boolean, reason: string }
|
|
115
|
+
*/
|
|
116
|
+
async executeFastPath(content, rule) {
|
|
117
|
+
if (!rule.fastPath?.enabled) {
|
|
118
|
+
return { canFastPath: false };
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
const type = rule.fastPath.type;
|
|
122
|
+
|
|
123
|
+
switch (type) {
|
|
124
|
+
case 'json-parse':
|
|
125
|
+
// JSON parsing fast-path
|
|
126
|
+
return this.fastPathValidJson(content);
|
|
127
|
+
|
|
128
|
+
case 'json-fields':
|
|
129
|
+
// Required fields fast-path
|
|
130
|
+
const fields = rule.fastPath.requiredFields || [];
|
|
131
|
+
return this.fastPathRequiredFields(content, fields);
|
|
132
|
+
|
|
133
|
+
case 'none':
|
|
134
|
+
default:
|
|
135
|
+
// No fast-path, use LLM
|
|
136
|
+
return { canFastPath: false };
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* Load verification rules from JSON file
|
|
142
|
+
* @returns {Array} Enabled verification rules
|
|
143
|
+
*/
|
|
144
|
+
loadRules() {
|
|
145
|
+
const rulesPath = path.join(__dirname, 'agents', `${this.agentName}.json`);
|
|
146
|
+
|
|
147
|
+
if (!fs.existsSync(rulesPath)) {
|
|
148
|
+
console.warn(`Warning: No verification rules found for agent: ${this.agentName}`);
|
|
149
|
+
return [];
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
try {
|
|
153
|
+
const data = JSON.parse(fs.readFileSync(rulesPath, 'utf8'));
|
|
154
|
+
|
|
155
|
+
// Filter to enabled rules only
|
|
156
|
+
const enabledRules = data.verifications.filter(r => r.enabled !== false);
|
|
157
|
+
|
|
158
|
+
return enabledRules;
|
|
159
|
+
} catch (error) {
|
|
160
|
+
console.error(`Error loading verification rules from ${rulesPath}:`, error.message);
|
|
161
|
+
return [];
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* Check if rule is violated
|
|
167
|
+
* @param {string} content - Content to check
|
|
168
|
+
* @param {Object} rule - Verification rule
|
|
169
|
+
* @returns {Promise<boolean>} True if rule is violated (needs fixing)
|
|
170
|
+
*/
|
|
171
|
+
async checkRule(content, rule) {
|
|
172
|
+
try {
|
|
173
|
+
if (this.tracker) {
|
|
174
|
+
this.tracker.startRuleCheck(rule);
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
// Try fast-path if configured in rule
|
|
178
|
+
if (rule.fastPath?.enabled) {
|
|
179
|
+
const fastPathResult = await this.executeFastPath(content, rule);
|
|
180
|
+
if (fastPathResult.canFastPath) {
|
|
181
|
+
console.log(`[DEBUG] Fast-path used for ${rule.id}: ${fastPathResult.violated ? 'VIOLATED' : 'PASSED'}${fastPathResult.reason ? ` (${fastPathResult.reason})` : ''}${fastPathResult.missingFields ? ` (missing: ${fastPathResult.missingFields.join(', ')})` : ''}`);
|
|
182
|
+
if (this.tracker) {
|
|
183
|
+
this.tracker.endRuleCheck(fastPathResult.violated ? 'YES' : 'NO');
|
|
184
|
+
}
|
|
185
|
+
return fastPathResult.violated;
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
// Fallback to LLM check
|
|
190
|
+
console.log(`[DEBUG] Fast-path not available for ${rule.id}, using LLM`);
|
|
191
|
+
const prompt = rule.check.prompt.replace('{content}', content);
|
|
192
|
+
const maxTokens = rule.check.maxTokens || 10;
|
|
193
|
+
|
|
194
|
+
const response = await this.llmProvider.generate(prompt, maxTokens);
|
|
195
|
+
const answer = response.trim().toUpperCase();
|
|
196
|
+
|
|
197
|
+
// Check if response matches expected pattern (YES means violation found)
|
|
198
|
+
let result = false;
|
|
199
|
+
if (rule.check.expectedResponse === 'YES|NO') {
|
|
200
|
+
result = answer === 'YES';
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
if (this.tracker) {
|
|
204
|
+
this.tracker.endRuleCheck(answer);
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
console.log(`[DEBUG] checkRule - Rule: ${rule.id}, Result: ${answer}`);
|
|
208
|
+
return result;
|
|
209
|
+
} catch (error) {
|
|
210
|
+
console.error(`Error checking rule ${rule.id}:`, error.message);
|
|
211
|
+
if (this.tracker) {
|
|
212
|
+
this.tracker.endRuleCheck('ERROR');
|
|
213
|
+
this.tracker.completeRule();
|
|
214
|
+
}
|
|
215
|
+
return false; // Skip this rule on error
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
/**
|
|
220
|
+
* Fix content according to rule
|
|
221
|
+
* @param {string} content - Content to fix
|
|
222
|
+
* @param {Object} rule - Verification rule
|
|
223
|
+
* @returns {Promise<string>} Fixed content
|
|
224
|
+
*/
|
|
225
|
+
async fixContent(content, rule) {
|
|
226
|
+
try {
|
|
227
|
+
if (this.tracker) {
|
|
228
|
+
this.tracker.startRuleFix(content.length);
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
// Fast-path fix if configured
|
|
232
|
+
if (rule.fastPath?.enabled && rule.fastPath.type === 'json-parse') {
|
|
233
|
+
const { isWrapped, unwrapped } = this.unwrapJsonCodeFence(content);
|
|
234
|
+
if (isWrapped) {
|
|
235
|
+
console.log('[DEBUG] Fast-path: Unwrapping JSON code fence (no LLM call)');
|
|
236
|
+
if (this.tracker) {
|
|
237
|
+
this.tracker.endRuleFix(unwrapped.length);
|
|
238
|
+
}
|
|
239
|
+
return unwrapped;
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
// Fallback to LLM fix
|
|
244
|
+
const prompt = rule.fix.prompt.replace('{content}', content);
|
|
245
|
+
const maxTokens = rule.fix.maxTokens || 4096;
|
|
246
|
+
|
|
247
|
+
console.log(`[DEBUG] fixContent - Rule: ${rule.id}, Fixing content (length: ${content.length})`);
|
|
248
|
+
const fixed = await this.llmProvider.generate(prompt, maxTokens);
|
|
249
|
+
console.log(`[DEBUG] fixContent - Rule: ${rule.id}, LLM returned ${fixed.length} chars`);
|
|
250
|
+
console.log(`[DEBUG] fixContent - Rule: ${rule.id}, Raw output preview:`, fixed.substring(0, 300));
|
|
251
|
+
|
|
252
|
+
const trimmed = fixed.trim();
|
|
253
|
+
console.log(`[DEBUG] fixContent - Rule: ${rule.id}, After trim: ${trimmed.length} chars`);
|
|
254
|
+
|
|
255
|
+
if (this.tracker) {
|
|
256
|
+
this.tracker.endRuleFix(trimmed.length);
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
return trimmed;
|
|
260
|
+
} catch (error) {
|
|
261
|
+
console.error(`Error fixing with rule ${rule.id}:`, error.message);
|
|
262
|
+
return content; // Return original content on error
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
/**
|
|
267
|
+
* Verify and fix content using all enabled rules
|
|
268
|
+
* @param {string} content - Content to verify
|
|
269
|
+
* @param {Function} progressCallback - Optional callback (mainMsg, substep)
|
|
270
|
+
* @returns {Promise<Object>} { content, rulesApplied }
|
|
271
|
+
*/
|
|
272
|
+
async verify(content, progressCallback = null) {
|
|
273
|
+
// Check cache first
|
|
274
|
+
if (this.tracker && this.tracker.verificationCache) {
|
|
275
|
+
const contentHash = this.tracker.hashContent(content);
|
|
276
|
+
const cacheKey = `${this.agentName}-${contentHash}`;
|
|
277
|
+
|
|
278
|
+
if (this.tracker.verificationCache.has(cacheKey)) {
|
|
279
|
+
console.log(`[DEBUG] Cache HIT: Reusing verification for ${this.agentName} (hash: ${contentHash})`);
|
|
280
|
+
return this.tracker.verificationCache.get(cacheKey);
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
console.log(`[DEBUG] Cache MISS: Running verification for ${this.agentName} (hash: ${contentHash})`);
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
if (this.tracker) {
|
|
287
|
+
this.tracker.startSession(this.agentName, content);
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
console.log(`[DEBUG] verify - Starting verification with ${this.rules.length} rules`);
|
|
291
|
+
console.log(`[DEBUG] verify - Input content length: ${content.length}`);
|
|
292
|
+
console.log(`[DEBUG] verify - Input content preview:`, content.substring(0, 300));
|
|
293
|
+
|
|
294
|
+
let current = content;
|
|
295
|
+
const applied = [];
|
|
296
|
+
|
|
297
|
+
// If no rules loaded, return original content
|
|
298
|
+
if (this.rules.length === 0) {
|
|
299
|
+
return { content: current, rulesApplied: [] };
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
// PHASE 1: Check all rules in parallel
|
|
303
|
+
console.log(`[DEBUG] verify - Phase 1: Checking ${this.rules.length} rules in parallel`);
|
|
304
|
+
if (progressCallback) {
|
|
305
|
+
progressCallback(null, `Checking ${this.rules.length} rules...`);
|
|
306
|
+
await new Promise(resolve => setTimeout(resolve, 20));
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
const checkPromises = this.rules.map(async (rule) => {
|
|
310
|
+
// Check if rule should be skipped based on profiling
|
|
311
|
+
if (this.tracker && this.tracker.shouldSkipRule(this.agentName, rule.id)) {
|
|
312
|
+
return { rule, violated: false, error: null, skipped: true };
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
try {
|
|
316
|
+
const violated = await this.checkRule(current, rule);
|
|
317
|
+
|
|
318
|
+
// Update rule profile
|
|
319
|
+
if (this.tracker) {
|
|
320
|
+
this.tracker.updateRuleProfile(this.agentName, rule.id, violated);
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
return { rule, violated, error: null, skipped: false };
|
|
324
|
+
} catch (error) {
|
|
325
|
+
console.error(`Error checking rule ${rule.id}:`, error.message);
|
|
326
|
+
return { rule, violated: false, error: error.message, skipped: false };
|
|
327
|
+
}
|
|
328
|
+
});
|
|
329
|
+
|
|
330
|
+
const checkResults = await Promise.all(checkPromises);
|
|
331
|
+
|
|
332
|
+
// PHASE 2: Fix violations sequentially
|
|
333
|
+
console.log(`[DEBUG] verify - Phase 2: Fixing violations sequentially`);
|
|
334
|
+
const violatedRules = checkResults.filter(r => r.violated && !r.error);
|
|
335
|
+
console.log(`[DEBUG] verify - Found ${violatedRules.length} violations:`, violatedRules.map(r => r.rule.id));
|
|
336
|
+
|
|
337
|
+
for (const { rule, violated } of violatedRules) {
|
|
338
|
+
// SAFEGUARD: Double-check that rule was actually violated
|
|
339
|
+
if (!violated) {
|
|
340
|
+
console.warn(`[WARN] verify - Skipping fix for ${rule.id} - violated flag is false (defensive check)`);
|
|
341
|
+
if (this.tracker) {
|
|
342
|
+
this.tracker.completeRule();
|
|
343
|
+
}
|
|
344
|
+
continue;
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
// Report progress: fixing
|
|
348
|
+
if (progressCallback) {
|
|
349
|
+
progressCallback(null, `Fixing: ${rule.name}...`);
|
|
350
|
+
await new Promise(resolve => setTimeout(resolve, 20));
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
try {
|
|
354
|
+
const beforeLength = current.length;
|
|
355
|
+
|
|
356
|
+
// Apply fix
|
|
357
|
+
const fixed = await this.fixContent(current, rule);
|
|
358
|
+
|
|
359
|
+
// Only update if fix actually changed content
|
|
360
|
+
if (fixed !== current) {
|
|
361
|
+
const afterLength = fixed.length;
|
|
362
|
+
const changePercent = Math.abs((afterLength - beforeLength) / beforeLength * 100);
|
|
363
|
+
const changeChars = afterLength - beforeLength;
|
|
364
|
+
|
|
365
|
+
// SAFEGUARD: Warn on aggressive content changes
|
|
366
|
+
if (changePercent > 30) {
|
|
367
|
+
console.warn(`[WARN] verify - Rule ${rule.id} caused ${changePercent.toFixed(1)}% content change (${changeChars > 0 ? '+' : ''}${changeChars} chars)`);
|
|
368
|
+
console.warn(`[WARN] verify - Before: ${beforeLength} chars, After: ${afterLength} chars`);
|
|
369
|
+
console.warn(`[WARN] verify - This may indicate an overly aggressive fix. Review rule prompt.`);
|
|
370
|
+
} else {
|
|
371
|
+
console.log(`[DEBUG] verify - Rule ${rule.id} changed content by ${changePercent.toFixed(1)}% (${changeChars > 0 ? '+' : ''}${changeChars} chars)`);
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
current = fixed;
|
|
375
|
+
applied.push({
|
|
376
|
+
id: rule.id,
|
|
377
|
+
name: rule.name,
|
|
378
|
+
severity: rule.severity,
|
|
379
|
+
description: rule.description
|
|
380
|
+
});
|
|
381
|
+
} else {
|
|
382
|
+
console.log(`[DEBUG] verify - Rule ${rule.id} fix did not change content (no-op fix)`);
|
|
383
|
+
}
|
|
384
|
+
} catch (error) {
|
|
385
|
+
console.error(`Error fixing rule ${rule.id}:`, error.message);
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
if (this.tracker) {
|
|
389
|
+
this.tracker.completeRule();
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
// Complete tracking for rules that didn't violate
|
|
394
|
+
const passedRules = checkResults.filter(r => !r.violated || r.error);
|
|
395
|
+
for (const { rule } of passedRules) {
|
|
396
|
+
if (this.tracker) {
|
|
397
|
+
this.tracker.completeRule();
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
console.log(`[DEBUG] verify - Completed with ${applied.length} rules applied:`, applied.map(r => r.id));
|
|
402
|
+
console.log(`[DEBUG] verify - Final content length: ${current.length}`);
|
|
403
|
+
console.log(`[DEBUG] verify - Final content preview:`, current.substring(0, 300));
|
|
404
|
+
|
|
405
|
+
if (this.tracker) {
|
|
406
|
+
this.tracker.endSession(current, applied);
|
|
407
|
+
this.tracker.logSessionSummary();
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
const result = {
|
|
411
|
+
content: current,
|
|
412
|
+
rulesApplied: applied,
|
|
413
|
+
noViolations: applied.length === 0, // Track if this was a perfect verification
|
|
414
|
+
timestamp: Date.now()
|
|
415
|
+
};
|
|
416
|
+
|
|
417
|
+
// Store in cache
|
|
418
|
+
if (this.tracker && this.tracker.verificationCache) {
|
|
419
|
+
const contentHash = this.tracker.hashContent(content);
|
|
420
|
+
const cacheKey = `${this.agentName}-${contentHash}`;
|
|
421
|
+
this.tracker.verificationCache.set(cacheKey, result);
|
|
422
|
+
|
|
423
|
+
if (applied.length === 0) {
|
|
424
|
+
console.log(`[DEBUG] Cached PERFECT verification result for ${this.agentName} (hash: ${contentHash}) - no violations found`);
|
|
425
|
+
} else {
|
|
426
|
+
console.log(`[DEBUG] Cached verification result for ${this.agentName} (hash: ${contentHash}) - ${applied.length} fixes applied`);
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
return result;
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
/**
|
|
434
|
+
* Get list of all rules for this agent
|
|
435
|
+
* @returns {Array} Rule metadata (id, name, severity, description)
|
|
436
|
+
*/
|
|
437
|
+
getRules() {
|
|
438
|
+
return this.rules.map(r => ({
|
|
439
|
+
id: r.id,
|
|
440
|
+
name: r.name,
|
|
441
|
+
severity: r.severity,
|
|
442
|
+
description: r.description,
|
|
443
|
+
enabled: r.enabled !== false
|
|
444
|
+
}));
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
/**
|
|
448
|
+
* Get count of enabled rules
|
|
449
|
+
* @returns {number} Number of enabled rules
|
|
450
|
+
*/
|
|
451
|
+
getRuleCount() {
|
|
452
|
+
return this.rules.length;
|
|
453
|
+
}
|
|
454
|
+
}
|
package/cli/logger.js
CHANGED
|
@@ -5,31 +5,48 @@ import os from 'os';
|
|
|
5
5
|
/**
|
|
6
6
|
* Logger - Writes debug and error logs to file
|
|
7
7
|
*
|
|
8
|
-
* Log location: ~/.avc/logs/avc.log
|
|
8
|
+
* Log location: <project>/.avc/logs/avc.log (or ~/.avc/logs/avc.log if no project)
|
|
9
9
|
* Log rotation: Keeps last 10MB, rotates to avc.log.old
|
|
10
10
|
*/
|
|
11
11
|
export class Logger {
|
|
12
|
-
constructor(componentName = 'AVC') {
|
|
12
|
+
constructor(componentName = 'AVC', projectRoot = null) {
|
|
13
13
|
this.componentName = componentName;
|
|
14
|
-
|
|
14
|
+
|
|
15
|
+
// Only log if .avc folder exists - don't create it, don't use fallback
|
|
16
|
+
const baseDir = projectRoot || process.cwd();
|
|
17
|
+
const projectAvcDir = path.join(baseDir, '.avc');
|
|
18
|
+
|
|
19
|
+
if (!fs.existsSync(projectAvcDir)) {
|
|
20
|
+
// No .avc folder - disable logging
|
|
21
|
+
this.loggingDisabled = true;
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// .avc exists, set up logging
|
|
26
|
+
this.logDir = path.join(projectAvcDir, 'logs');
|
|
15
27
|
this.logFile = path.join(this.logDir, 'avc.log');
|
|
16
28
|
this.maxLogSize = 10 * 1024 * 1024; // 10MB
|
|
29
|
+
this.loggingDisabled = false;
|
|
17
30
|
|
|
18
31
|
this.ensureLogDir();
|
|
19
32
|
}
|
|
20
33
|
|
|
21
34
|
ensureLogDir() {
|
|
35
|
+
if (this.loggingDisabled) return;
|
|
36
|
+
|
|
22
37
|
try {
|
|
23
38
|
if (!fs.existsSync(this.logDir)) {
|
|
24
39
|
fs.mkdirSync(this.logDir, { recursive: true });
|
|
25
40
|
}
|
|
26
41
|
} catch (error) {
|
|
27
42
|
// Silently fail if we can't create log directory
|
|
28
|
-
|
|
43
|
+
this.loggingDisabled = true;
|
|
29
44
|
}
|
|
30
45
|
}
|
|
31
46
|
|
|
32
47
|
rotateLogIfNeeded() {
|
|
48
|
+
if (this.loggingDisabled) return;
|
|
49
|
+
|
|
33
50
|
try {
|
|
34
51
|
if (fs.existsSync(this.logFile)) {
|
|
35
52
|
const stats = fs.statSync(this.logFile);
|
|
@@ -71,13 +88,17 @@ export class Logger {
|
|
|
71
88
|
}
|
|
72
89
|
|
|
73
90
|
writeLog(level, message, data = null) {
|
|
91
|
+
// Skip logging if directory creation failed
|
|
92
|
+
if (this.loggingDisabled) return;
|
|
93
|
+
|
|
74
94
|
try {
|
|
75
95
|
this.rotateLogIfNeeded();
|
|
76
96
|
const logMessage = this.formatMessage(level, message, data);
|
|
77
97
|
fs.appendFileSync(this.logFile, logMessage, 'utf8');
|
|
78
98
|
} catch (error) {
|
|
79
99
|
// Silently fail if we can't write to log
|
|
80
|
-
|
|
100
|
+
// Disable further logging attempts to avoid repeated errors
|
|
101
|
+
this.loggingDisabled = true;
|
|
81
102
|
}
|
|
82
103
|
}
|
|
83
104
|
|
|
@@ -99,6 +120,10 @@ export class Logger {
|
|
|
99
120
|
|
|
100
121
|
// Read recent logs (last N lines)
|
|
101
122
|
readRecentLogs(lines = 50) {
|
|
123
|
+
if (this.loggingDisabled) {
|
|
124
|
+
return 'No logs available (project not initialized).';
|
|
125
|
+
}
|
|
126
|
+
|
|
102
127
|
try {
|
|
103
128
|
if (!fs.existsSync(this.logFile)) {
|
|
104
129
|
return 'No logs available yet.';
|
|
@@ -116,6 +141,8 @@ export class Logger {
|
|
|
116
141
|
|
|
117
142
|
// Clear all logs
|
|
118
143
|
clearLogs() {
|
|
144
|
+
if (this.loggingDisabled) return;
|
|
145
|
+
|
|
119
146
|
try {
|
|
120
147
|
if (fs.existsSync(this.logFile)) {
|
|
121
148
|
fs.unlinkSync(this.logFile);
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Message Constants - Reusable message definitions
|
|
3
|
+
*
|
|
4
|
+
* Centralized message strings to prevent duplication and ensure consistency.
|
|
5
|
+
* Use these constants instead of hardcoded strings throughout the codebase.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Common error messages
|
|
10
|
+
*/
|
|
11
|
+
export const MESSAGES = {
|
|
12
|
+
/**
|
|
13
|
+
* Project not initialized error
|
|
14
|
+
*/
|
|
15
|
+
PROJECT_NOT_INITIALIZED: {
|
|
16
|
+
error: 'Project not initialized',
|
|
17
|
+
help: 'Please run /init first to create the project structure.'
|
|
18
|
+
},
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Ceremony headers with titles and documentation URLs
|
|
22
|
+
*/
|
|
23
|
+
CEREMONY_HEADERS: {
|
|
24
|
+
'sponsor-call': {
|
|
25
|
+
title: 'Sponsor Call Ceremony',
|
|
26
|
+
url: 'https://agilevibecoding.org/ceremonies/sponsor-call'
|
|
27
|
+
},
|
|
28
|
+
'sprint-planning': {
|
|
29
|
+
title: 'Sprint Planning Ceremony',
|
|
30
|
+
url: 'https://agilevibecoding.org/ceremonies/sprint-planning'
|
|
31
|
+
},
|
|
32
|
+
'seed': {
|
|
33
|
+
title: 'Seed Ceremony',
|
|
34
|
+
url: 'https://agilevibecoding.org/ceremonies/seed'
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Helper function to get full "project not initialized" error message
|
|
41
|
+
* @returns {string} Complete error message with help text
|
|
42
|
+
*/
|
|
43
|
+
export function getProjectNotInitializedMessage() {
|
|
44
|
+
return `${MESSAGES.PROJECT_NOT_INITIALIZED.error}\n\n${MESSAGES.PROJECT_NOT_INITIALIZED.help}`;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Helper function to get ceremony header
|
|
49
|
+
* @param {string} ceremonyName - Name of ceremony ('sponsor-call', 'sprint-planning', 'seed')
|
|
50
|
+
* @returns {object} Object with title and url properties
|
|
51
|
+
*/
|
|
52
|
+
export function getCeremonyHeader(ceremonyName) {
|
|
53
|
+
const header = MESSAGES.CEREMONY_HEADERS[ceremonyName];
|
|
54
|
+
if (!header) {
|
|
55
|
+
throw new Error(`Unknown ceremony: ${ceremonyName}`);
|
|
56
|
+
}
|
|
57
|
+
return header;
|
|
58
|
+
}
|