@agile-vibe-coding/avc 0.1.1 → 0.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/cli/agent-loader.js +21 -0
- package/cli/agents/agent-selector.md +152 -0
- package/cli/agents/architecture-recommender.md +418 -0
- package/cli/agents/code-implementer.md +117 -0
- package/cli/agents/code-validator.md +80 -0
- package/cli/agents/context-reviewer-epic.md +101 -0
- package/cli/agents/context-reviewer-story.md +92 -0
- package/cli/agents/context-writer-epic.md +145 -0
- package/cli/agents/context-writer-story.md +111 -0
- package/cli/agents/database-deep-dive.md +470 -0
- package/cli/agents/database-recommender.md +634 -0
- package/cli/agents/doc-distributor.md +176 -0
- package/cli/agents/doc-writer-epic.md +42 -0
- package/cli/agents/doc-writer-story.md +43 -0
- package/cli/agents/documentation-updater.md +203 -0
- package/cli/agents/duplicate-detector.md +110 -0
- package/cli/agents/epic-story-decomposer.md +559 -0
- package/cli/agents/feature-context-generator.md +91 -0
- package/cli/agents/gap-checker-epic.md +52 -0
- package/cli/agents/impact-checker-story.md +51 -0
- package/cli/agents/migration-guide-generator.md +305 -0
- package/cli/agents/mission-scope-generator.md +143 -0
- package/cli/agents/mission-scope-validator.md +146 -0
- package/cli/agents/project-context-extractor.md +122 -0
- package/cli/agents/project-documentation-creator.json +226 -0
- package/cli/agents/project-documentation-creator.md +595 -0
- package/cli/agents/question-prefiller.md +269 -0
- package/cli/agents/refiner-epic.md +39 -0
- package/cli/agents/refiner-story.md +42 -0
- package/cli/agents/scaffolding-generator.md +99 -0
- package/cli/agents/seed-validator.md +71 -0
- package/cli/agents/story-doc-enricher.md +133 -0
- package/cli/agents/story-scope-reviewer.md +147 -0
- package/cli/agents/story-splitter.md +83 -0
- package/cli/agents/suggestion-business-analyst.md +88 -0
- package/cli/agents/suggestion-deployment-architect.md +263 -0
- package/cli/agents/suggestion-product-manager.md +129 -0
- package/cli/agents/suggestion-security-specialist.md +156 -0
- package/cli/agents/suggestion-technical-architect.md +269 -0
- package/cli/agents/suggestion-ux-researcher.md +93 -0
- package/cli/agents/task-subtask-decomposer.md +188 -0
- package/cli/agents/validator-documentation.json +183 -0
- package/cli/agents/validator-documentation.md +455 -0
- package/cli/agents/validator-selector.md +211 -0
- package/cli/ansi-colors.js +21 -0
- package/cli/api-reference-tool.js +368 -0
- package/cli/build-docs.js +29 -8
- package/cli/ceremony-history.js +369 -0
- package/cli/checks/catalog.json +76 -0
- package/cli/checks/code/quality.json +26 -0
- package/cli/checks/code/testing.json +14 -0
- package/cli/checks/code/traceability.json +26 -0
- package/cli/checks/cross-refs/epic.json +171 -0
- package/cli/checks/cross-refs/story.json +149 -0
- package/cli/checks/epic/api.json +114 -0
- package/cli/checks/epic/backend.json +126 -0
- package/cli/checks/epic/cloud.json +126 -0
- package/cli/checks/epic/data.json +102 -0
- package/cli/checks/epic/database.json +114 -0
- package/cli/checks/epic/developer.json +182 -0
- package/cli/checks/epic/devops.json +174 -0
- package/cli/checks/epic/frontend.json +162 -0
- package/cli/checks/epic/mobile.json +102 -0
- package/cli/checks/epic/qa.json +90 -0
- package/cli/checks/epic/security.json +184 -0
- package/cli/checks/epic/solution-architect.json +192 -0
- package/cli/checks/epic/test-architect.json +90 -0
- package/cli/checks/epic/ui.json +102 -0
- package/cli/checks/epic/ux.json +90 -0
- package/cli/checks/fixes/epic-fix-template.md +10 -0
- package/cli/checks/fixes/story-fix-template.md +10 -0
- package/cli/checks/story/api.json +186 -0
- package/cli/checks/story/backend.json +102 -0
- package/cli/checks/story/cloud.json +102 -0
- package/cli/checks/story/data.json +210 -0
- package/cli/checks/story/database.json +102 -0
- package/cli/checks/story/developer.json +168 -0
- package/cli/checks/story/devops.json +102 -0
- package/cli/checks/story/frontend.json +174 -0
- package/cli/checks/story/mobile.json +102 -0
- package/cli/checks/story/qa.json +210 -0
- package/cli/checks/story/security.json +198 -0
- package/cli/checks/story/solution-architect.json +230 -0
- package/cli/checks/story/test-architect.json +210 -0
- package/cli/checks/story/ui.json +102 -0
- package/cli/checks/story/ux.json +102 -0
- package/cli/coding-order.js +401 -0
- package/cli/command-logger.js +49 -12
- package/cli/components/static-output.js +63 -0
- package/cli/console-output-manager.js +94 -0
- package/cli/dependency-checker.js +72 -0
- package/cli/docs-sync.js +306 -0
- package/cli/epic-story-validator.js +659 -0
- package/cli/evaluation-prompts.js +1008 -0
- package/cli/execution-context.js +195 -0
- package/cli/generate-summary-table.js +340 -0
- package/cli/init-model-config.js +704 -0
- package/cli/init.js +1737 -278
- package/cli/kanban-server-manager.js +227 -0
- package/cli/llm-claude.js +150 -1
- package/cli/llm-gemini.js +109 -0
- package/cli/llm-local.js +493 -0
- package/cli/llm-mock.js +233 -0
- package/cli/llm-openai.js +454 -0
- package/cli/llm-provider.js +379 -3
- package/cli/llm-token-limits.js +211 -0
- package/cli/llm-verifier.js +662 -0
- package/cli/llm-xiaomi.js +143 -0
- package/cli/message-constants.js +49 -0
- package/cli/message-manager.js +334 -0
- package/cli/message-types.js +96 -0
- package/cli/messaging-api.js +291 -0
- package/cli/micro-check-fixer.js +335 -0
- package/cli/micro-check-runner.js +449 -0
- package/cli/micro-check-scorer.js +148 -0
- package/cli/micro-check-validator.js +538 -0
- package/cli/model-pricing.js +192 -0
- package/cli/model-query-engine.js +468 -0
- package/cli/model-recommendation-analyzer.js +495 -0
- package/cli/model-selector.js +270 -0
- package/cli/output-buffer.js +107 -0
- package/cli/process-manager.js +73 -2
- package/cli/prompt-logger.js +57 -0
- package/cli/repl-ink.js +4625 -1094
- package/cli/repl-old.js +3 -4
- package/cli/seed-processor.js +962 -0
- package/cli/sprint-planning-processor.js +4162 -0
- package/cli/template-processor.js +2149 -105
- package/cli/templates/project.md +25 -8
- package/cli/templates/vitepress-config.mts.template +5 -4
- package/cli/token-tracker.js +547 -0
- package/cli/tools/generate-story-validators.js +317 -0
- package/cli/tools/generate-validators.js +669 -0
- package/cli/update-checker.js +19 -17
- package/cli/update-notifier.js +4 -4
- package/cli/validation-router.js +667 -0
- package/cli/verification-tracker.js +563 -0
- package/cli/worktree-runner.js +654 -0
- package/kanban/README.md +386 -0
- package/kanban/client/README.md +205 -0
- package/kanban/client/components.json +20 -0
- package/kanban/client/dist/assets/index-D_KC5EQT.css +1 -0
- package/kanban/client/dist/assets/index-DjY5zqW7.js +351 -0
- package/kanban/client/dist/index.html +16 -0
- package/kanban/client/dist/vite.svg +1 -0
- package/kanban/client/index.html +15 -0
- package/kanban/client/package-lock.json +9442 -0
- package/kanban/client/package.json +44 -0
- package/kanban/client/postcss.config.js +6 -0
- package/kanban/client/public/vite.svg +1 -0
- package/kanban/client/src/App.jsx +651 -0
- package/kanban/client/src/components/ProjectFileEditorPopup.jsx +117 -0
- package/kanban/client/src/components/ceremony/AskArchPopup.jsx +420 -0
- package/kanban/client/src/components/ceremony/AskModelPopup.jsx +629 -0
- package/kanban/client/src/components/ceremony/CeremonyWorkflowModal.jsx +1133 -0
- package/kanban/client/src/components/ceremony/EpicStorySelectionModal.jsx +254 -0
- package/kanban/client/src/components/ceremony/ProviderSwitcherButton.jsx +290 -0
- package/kanban/client/src/components/ceremony/SponsorCallModal.jsx +686 -0
- package/kanban/client/src/components/ceremony/SprintPlanningModal.jsx +838 -0
- package/kanban/client/src/components/ceremony/steps/ArchitectureStep.jsx +150 -0
- package/kanban/client/src/components/ceremony/steps/CompleteStep.jsx +136 -0
- package/kanban/client/src/components/ceremony/steps/DatabaseStep.jsx +202 -0
- package/kanban/client/src/components/ceremony/steps/DeploymentStep.jsx +123 -0
- package/kanban/client/src/components/ceremony/steps/MissionStep.jsx +106 -0
- package/kanban/client/src/components/ceremony/steps/ReviewAnswersStep.jsx +329 -0
- package/kanban/client/src/components/ceremony/steps/RunningStep.jsx +249 -0
- package/kanban/client/src/components/kanban/CardDetailModal.jsx +646 -0
- package/kanban/client/src/components/kanban/EpicSection.jsx +146 -0
- package/kanban/client/src/components/kanban/FilterToolbar.jsx +222 -0
- package/kanban/client/src/components/kanban/GroupingSelector.jsx +63 -0
- package/kanban/client/src/components/kanban/KanbanBoard.jsx +211 -0
- package/kanban/client/src/components/kanban/KanbanCard.jsx +147 -0
- package/kanban/client/src/components/kanban/KanbanColumn.jsx +90 -0
- package/kanban/client/src/components/kanban/RefineWorkItemPopup.jsx +784 -0
- package/kanban/client/src/components/kanban/RunButton.jsx +162 -0
- package/kanban/client/src/components/kanban/SeedButton.jsx +176 -0
- package/kanban/client/src/components/layout/LoadingScreen.jsx +82 -0
- package/kanban/client/src/components/process/ProcessMonitorBar.jsx +80 -0
- package/kanban/client/src/components/settings/AgentEditorPopup.jsx +171 -0
- package/kanban/client/src/components/settings/AgentsTab.jsx +381 -0
- package/kanban/client/src/components/settings/ApiKeysTab.jsx +142 -0
- package/kanban/client/src/components/settings/CeremonyModelsTab.jsx +105 -0
- package/kanban/client/src/components/settings/CheckEditorPopup.jsx +507 -0
- package/kanban/client/src/components/settings/CostThresholdsTab.jsx +95 -0
- package/kanban/client/src/components/settings/ModelPricingTab.jsx +269 -0
- package/kanban/client/src/components/settings/OpenAIAuthSection.jsx +412 -0
- package/kanban/client/src/components/settings/ServersTab.jsx +121 -0
- package/kanban/client/src/components/settings/SettingsModal.jsx +84 -0
- package/kanban/client/src/components/stats/CostModal.jsx +384 -0
- package/kanban/client/src/components/ui/badge.jsx +27 -0
- package/kanban/client/src/components/ui/dialog.jsx +121 -0
- package/kanban/client/src/components/ui/tabs.jsx +85 -0
- package/kanban/client/src/hooks/__tests__/useGrouping.test.js +232 -0
- package/kanban/client/src/hooks/useGrouping.js +177 -0
- package/kanban/client/src/hooks/useWebSocket.js +120 -0
- package/kanban/client/src/lib/__tests__/api.test.js +196 -0
- package/kanban/client/src/lib/__tests__/status-grouping.test.js +94 -0
- package/kanban/client/src/lib/api.js +515 -0
- package/kanban/client/src/lib/status-grouping.js +154 -0
- package/kanban/client/src/lib/utils.js +11 -0
- package/kanban/client/src/main.jsx +10 -0
- package/kanban/client/src/store/__tests__/kanbanStore.test.js +164 -0
- package/kanban/client/src/store/ceremonyStore.js +172 -0
- package/kanban/client/src/store/filterStore.js +201 -0
- package/kanban/client/src/store/kanbanStore.js +123 -0
- package/kanban/client/src/store/processStore.js +65 -0
- package/kanban/client/src/store/sprintPlanningStore.js +33 -0
- package/kanban/client/src/styles/globals.css +59 -0
- package/kanban/client/tailwind.config.js +77 -0
- package/kanban/client/vite.config.js +28 -0
- package/kanban/client/vitest.config.js +28 -0
- package/kanban/dev-start.sh +47 -0
- package/kanban/package.json +12 -0
- package/kanban/server/index.js +537 -0
- package/kanban/server/routes/ceremony.js +454 -0
- package/kanban/server/routes/costs.js +163 -0
- package/kanban/server/routes/openai-oauth.js +366 -0
- package/kanban/server/routes/processes.js +50 -0
- package/kanban/server/routes/settings.js +736 -0
- package/kanban/server/routes/websocket.js +281 -0
- package/kanban/server/routes/work-items.js +487 -0
- package/kanban/server/services/CeremonyService.js +1441 -0
- package/kanban/server/services/FileSystemScanner.js +95 -0
- package/kanban/server/services/FileWatcher.js +144 -0
- package/kanban/server/services/HierarchyBuilder.js +196 -0
- package/kanban/server/services/ProcessRegistry.js +122 -0
- package/kanban/server/services/TaskRunnerService.js +261 -0
- package/kanban/server/services/WorkItemReader.js +123 -0
- package/kanban/server/services/WorkItemRefineService.js +510 -0
- package/kanban/server/start.js +49 -0
- package/kanban/server/utils/kanban-logger.js +132 -0
- package/kanban/server/utils/markdown.js +91 -0
- package/kanban/server/utils/status-grouping.js +107 -0
- package/kanban/server/workers/run-task-worker.js +121 -0
- package/kanban/server/workers/seed-worker.js +94 -0
- package/kanban/server/workers/sponsor-call-worker.js +92 -0
- package/kanban/server/workers/sprint-planning-worker.js +212 -0
- package/package.json +19 -7
- package/cli/agents/documentation.md +0 -302
|
@@ -0,0 +1,381 @@
|
|
|
1
|
+
import { useState, useEffect } from 'react';
|
|
2
|
+
import { ChevronDown, ChevronRight, Pencil } from 'lucide-react';
|
|
3
|
+
import { getAgentList, getCheckList } from '../../lib/api';
|
|
4
|
+
import { AgentEditorPopup } from './AgentEditorPopup';
|
|
5
|
+
import { CheckEditorPopup } from './CheckEditorPopup';
|
|
6
|
+
|
|
7
|
+
const CEREMONY_STRUCTURE = [
|
|
8
|
+
{
|
|
9
|
+
ceremony: 'Sponsor Call',
|
|
10
|
+
color: 'blue',
|
|
11
|
+
phases: [
|
|
12
|
+
{ phase: 'Mission & Scope', agents: [
|
|
13
|
+
{ slug: 'mission-scope-generator', label: 'Mission Scope Generator', note: 'Generates mission & initial scope' },
|
|
14
|
+
{ slug: 'mission-scope-validator', label: 'Mission Scope Validator', note: 'Validates mission quality' },
|
|
15
|
+
]},
|
|
16
|
+
{ phase: 'Questionnaire', agents: [
|
|
17
|
+
{ slug: 'suggestion-product-manager', label: 'Product Manager', note: 'Fills Initial Scope' },
|
|
18
|
+
{ slug: 'suggestion-ux-researcher', label: 'UX Researcher', note: 'Fills Target Users' },
|
|
19
|
+
{ slug: 'suggestion-deployment-architect', label: 'Deployment Architect', note: 'Fills Deployment Target' },
|
|
20
|
+
{ slug: 'suggestion-technical-architect', label: 'Technical Architect', note: 'Fills Technical Considerations' },
|
|
21
|
+
{ slug: 'suggestion-security-specialist', label: 'Security Specialist', note: 'Fills Security & Compliance' },
|
|
22
|
+
{ slug: 'architecture-recommender', label: 'Architecture Recommender', note: 'Recommends deployment architectures' },
|
|
23
|
+
{ slug: 'database-recommender', label: 'Database Recommender', note: 'Recommends database type' },
|
|
24
|
+
{ slug: 'database-deep-dive', label: 'Database Deep Dive', note: 'Detailed database analysis' },
|
|
25
|
+
{ slug: 'question-prefiller', label: 'Question Prefiller', note: 'Pre-fills answers from architecture' },
|
|
26
|
+
]},
|
|
27
|
+
{ phase: 'Documentation', agents: [
|
|
28
|
+
{ slug: 'project-documentation-creator', label: 'Documentation Creator', note: 'Creates project documentation' },
|
|
29
|
+
{ slug: 'validator-documentation', label: 'Documentation Validator', note: 'Validates documentation quality' },
|
|
30
|
+
]},
|
|
31
|
+
{ phase: 'Context', agents: [
|
|
32
|
+
{ slug: 'migration-guide-generator', label: 'Migration Guide Generator', note: 'Generates cloud migration guide' },
|
|
33
|
+
]},
|
|
34
|
+
],
|
|
35
|
+
},
|
|
36
|
+
{
|
|
37
|
+
ceremony: 'Sprint Planning',
|
|
38
|
+
color: 'purple',
|
|
39
|
+
phases: [
|
|
40
|
+
{ phase: 'Decomposition', agents: [
|
|
41
|
+
{ slug: 'epic-story-decomposer', label: 'Epic Story Decomposer', note: 'Breaks scope into epics & stories' },
|
|
42
|
+
]},
|
|
43
|
+
{ phase: 'Contextual Selection', agents: [
|
|
44
|
+
{ slug: 'project-context-extractor', label: 'Project Context Extractor', note: 'Extracts project traits (once per run) to inform validator selection' },
|
|
45
|
+
{ slug: 'validator-selector', label: 'Validator Selector', note: 'Selects appropriate domain validators' },
|
|
46
|
+
{ slug: 'agent-selector', label: 'Agent Selector', note: 'Selects relevant validators per Epic/Story based on project context' },
|
|
47
|
+
]},
|
|
48
|
+
{ phase: 'Context Generation', agents: [
|
|
49
|
+
{ slug: 'context-writer-epic', label: 'Context Writer (Epic)', note: 'Writes complete canonical context.md — Purpose, Scope, NFRs, Success Criteria' },
|
|
50
|
+
{ slug: 'context-reviewer-epic', label: 'Context Reviewer (Epic)', note: 'Independent accuracy audit: checks all features present, no hallucinations, sections complete' },
|
|
51
|
+
{ slug: 'context-writer-story', label: 'Context Writer (Story)', note: 'Writes complete canonical context.md — User Story framing, Technical Notes, Scope boundaries' },
|
|
52
|
+
{ slug: 'context-reviewer-story', label: 'Context Reviewer (Story)', note: 'Independent accuracy audit: checks all ACs present, no hallucinations, sections complete' },
|
|
53
|
+
]},
|
|
54
|
+
{ phase: 'Documentation & Enrichment', agents: [
|
|
55
|
+
{ slug: 'doc-writer-epic', label: 'Doc Writer (Epic)', note: 'Generates narrative epic doc.md from canonical context.md' },
|
|
56
|
+
{ slug: 'doc-writer-story', label: 'Doc Writer (Story)', note: 'Generates narrative story doc.md from canonical context.md' },
|
|
57
|
+
{ slug: 'story-doc-enricher', label: 'Story Doc Enricher', note: 'Enriches story docs with API contracts, error tables, DB fields, business rules' },
|
|
58
|
+
]},
|
|
59
|
+
{ phase: 'Micro-Check Definitions — Epic', agents: [
|
|
60
|
+
{ slug: 'checks/epic/solution-architect', label: 'Solution Architect', note: 'JSON', isCheck: true },
|
|
61
|
+
{ slug: 'checks/epic/developer', label: 'Developer', note: 'JSON', isCheck: true },
|
|
62
|
+
{ slug: 'checks/epic/security', label: 'Security', note: 'JSON', isCheck: true },
|
|
63
|
+
{ slug: 'checks/epic/devops', label: 'DevOps', note: 'JSON', isCheck: true },
|
|
64
|
+
{ slug: 'checks/epic/cloud', label: 'Cloud', note: 'JSON', isCheck: true },
|
|
65
|
+
{ slug: 'checks/epic/backend', label: 'Backend', note: 'JSON', isCheck: true },
|
|
66
|
+
{ slug: 'checks/epic/database', label: 'Database', note: 'JSON', isCheck: true },
|
|
67
|
+
{ slug: 'checks/epic/api', label: 'API', note: 'JSON', isCheck: true },
|
|
68
|
+
{ slug: 'checks/epic/frontend', label: 'Frontend', note: 'JSON', isCheck: true },
|
|
69
|
+
{ slug: 'checks/epic/ui', label: 'UI', note: 'JSON', isCheck: true },
|
|
70
|
+
{ slug: 'checks/epic/ux', label: 'UX', note: 'JSON', isCheck: true },
|
|
71
|
+
{ slug: 'checks/epic/mobile', label: 'Mobile', note: 'JSON', isCheck: true },
|
|
72
|
+
{ slug: 'checks/epic/data', label: 'Data', note: 'JSON', isCheck: true },
|
|
73
|
+
{ slug: 'checks/epic/qa', label: 'QA', note: 'JSON', isCheck: true },
|
|
74
|
+
{ slug: 'checks/epic/test-architect', label: 'Test Architect', note: 'JSON', isCheck: true },
|
|
75
|
+
]},
|
|
76
|
+
{ phase: 'Micro-Check Definitions — Story', agents: [
|
|
77
|
+
{ slug: 'checks/story/solution-architect', label: 'Solution Architect', note: 'JSON', isCheck: true },
|
|
78
|
+
{ slug: 'checks/story/developer', label: 'Developer', note: 'JSON', isCheck: true },
|
|
79
|
+
{ slug: 'checks/story/security', label: 'Security', note: 'JSON', isCheck: true },
|
|
80
|
+
{ slug: 'checks/story/devops', label: 'DevOps', note: 'JSON', isCheck: true },
|
|
81
|
+
{ slug: 'checks/story/cloud', label: 'Cloud', note: 'JSON', isCheck: true },
|
|
82
|
+
{ slug: 'checks/story/backend', label: 'Backend', note: 'JSON', isCheck: true },
|
|
83
|
+
{ slug: 'checks/story/database', label: 'Database', note: 'JSON', isCheck: true },
|
|
84
|
+
{ slug: 'checks/story/api', label: 'API', note: 'JSON', isCheck: true },
|
|
85
|
+
{ slug: 'checks/story/frontend', label: 'Frontend', note: 'JSON', isCheck: true },
|
|
86
|
+
{ slug: 'checks/story/ui', label: 'UI', note: 'JSON', isCheck: true },
|
|
87
|
+
{ slug: 'checks/story/ux', label: 'UX', note: 'JSON', isCheck: true },
|
|
88
|
+
{ slug: 'checks/story/mobile', label: 'Mobile', note: 'JSON', isCheck: true },
|
|
89
|
+
{ slug: 'checks/story/data', label: 'Data', note: 'JSON', isCheck: true },
|
|
90
|
+
{ slug: 'checks/story/qa', label: 'QA', note: 'JSON', isCheck: true },
|
|
91
|
+
{ slug: 'checks/story/test-architect', label: 'Test Architect', note: 'JSON', isCheck: true },
|
|
92
|
+
{ slug: 'story-splitter', label: 'Story Splitter', note: 'Splits oversized stories (15+ ACs) into 2-3 focused stories' },
|
|
93
|
+
]},
|
|
94
|
+
{ phase: 'Cross-Reference Checks', agents: [
|
|
95
|
+
{ slug: 'checks/cross-refs/epic', label: 'Epic Cross-Refs', note: 'JSON', isCheck: true },
|
|
96
|
+
{ slug: 'checks/cross-refs/story', label: 'Story Cross-Refs', note: 'JSON', isCheck: true },
|
|
97
|
+
]},
|
|
98
|
+
],
|
|
99
|
+
},
|
|
100
|
+
{
|
|
101
|
+
ceremony: 'Seed',
|
|
102
|
+
color: 'amber',
|
|
103
|
+
phases: [
|
|
104
|
+
{ phase: 'Decomposition', agents: [
|
|
105
|
+
{ slug: 'task-subtask-decomposer', label: 'Task Decomposer', note: 'Breaks stories into tasks & subtasks' },
|
|
106
|
+
]},
|
|
107
|
+
{ phase: 'Documentation', agents: [
|
|
108
|
+
{ slug: 'doc-distributor', label: 'Doc Distributor', note: 'Moves content from story doc into task/subtask docs' },
|
|
109
|
+
{ slug: 'feature-context-generator', label: 'Feature Context Generator', note: 'Generates implementation context.md for each task/subtask' },
|
|
110
|
+
]},
|
|
111
|
+
],
|
|
112
|
+
},
|
|
113
|
+
];
|
|
114
|
+
|
|
115
|
+
const CEREMONY_COLORS = {
|
|
116
|
+
blue: { border: 'border-blue-200', header: 'bg-blue-50', accent: 'border-l-blue-400', text: 'text-blue-800' },
|
|
117
|
+
purple: { border: 'border-purple-200', header: 'bg-purple-50', accent: 'border-l-purple-400', text: 'text-purple-800' },
|
|
118
|
+
amber: { border: 'border-amber-200', header: 'bg-amber-50', accent: 'border-l-amber-400', text: 'text-amber-800' },
|
|
119
|
+
green: { border: 'border-green-200', header: 'bg-green-50', accent: 'border-l-green-400', text: 'text-green-800' },
|
|
120
|
+
slate: { border: 'border-slate-200', header: 'bg-slate-50', accent: 'border-l-slate-400', text: 'text-slate-800' },
|
|
121
|
+
};
|
|
122
|
+
|
|
123
|
+
// Flat set of all known slugs for computing "Other" group
|
|
124
|
+
const KNOWN_SLUGS = new Set(
|
|
125
|
+
CEREMONY_STRUCTURE.flatMap(c => c.phases.flatMap(p => p.agents.map(a => a.slug)))
|
|
126
|
+
);
|
|
127
|
+
|
|
128
|
+
export function AgentsTab() {
|
|
129
|
+
const [agentStatus, setAgentStatus] = useState({}); // { slug: isCustomized }
|
|
130
|
+
const [checkStatus, setCheckStatus] = useState({}); // { 'scope/perspective': { isCustomized, checkCount } }
|
|
131
|
+
const [openAgent, setOpenAgent] = useState(null); // slug | null
|
|
132
|
+
const [openCheck, setOpenCheck] = useState(null); // { scope, perspective } | null
|
|
133
|
+
const [search, setSearch] = useState('');
|
|
134
|
+
const [error, setError] = useState(null);
|
|
135
|
+
// collapsed state: absence = collapsed (default), true = open
|
|
136
|
+
const [collapsed, setCollapsed] = useState({});
|
|
137
|
+
|
|
138
|
+
useEffect(() => {
|
|
139
|
+
getAgentList()
|
|
140
|
+
.then(r => {
|
|
141
|
+
const status = {};
|
|
142
|
+
r.agents.forEach(a => {
|
|
143
|
+
const slug = a.name.replace(/\.md$/, '');
|
|
144
|
+
status[slug] = a.isCustomized;
|
|
145
|
+
});
|
|
146
|
+
setAgentStatus(status);
|
|
147
|
+
})
|
|
148
|
+
.catch(err => setError(err.message));
|
|
149
|
+
|
|
150
|
+
getCheckList()
|
|
151
|
+
.then(r => {
|
|
152
|
+
const status = {};
|
|
153
|
+
r.checks.forEach(c => {
|
|
154
|
+
const key = `checks/${c.scope}/${c.perspective}`;
|
|
155
|
+
status[key] = { isCustomized: c.isCustomized, checkCount: c.checkCount };
|
|
156
|
+
});
|
|
157
|
+
setCheckStatus(status);
|
|
158
|
+
})
|
|
159
|
+
.catch(() => {}); // silently ignore if check API not available
|
|
160
|
+
}, []);
|
|
161
|
+
|
|
162
|
+
const toggle = (key) => setCollapsed(prev => ({ ...prev, [key]: !prev[key] }));
|
|
163
|
+
const isOpen = (key) => collapsed[key] === true; // default collapsed when key absent
|
|
164
|
+
|
|
165
|
+
// Build "Other" group from agents returned by the API that aren't in any ceremony structure
|
|
166
|
+
const otherAgents = Object.keys(agentStatus)
|
|
167
|
+
.filter(slug => !KNOWN_SLUGS.has(slug))
|
|
168
|
+
.map(slug => ({ slug, label: slug, note: null }));
|
|
169
|
+
|
|
170
|
+
const allCeremonies = [
|
|
171
|
+
...CEREMONY_STRUCTURE,
|
|
172
|
+
...(otherAgents.length > 0
|
|
173
|
+
? [{ ceremony: 'Other', color: 'slate', phases: [{ phase: 'Other', agents: otherAgents }] }]
|
|
174
|
+
: []),
|
|
175
|
+
];
|
|
176
|
+
|
|
177
|
+
// Filter by search query — when searching, force everything open
|
|
178
|
+
const q = search.toLowerCase();
|
|
179
|
+
const filteredCeremonies = q
|
|
180
|
+
? allCeremonies
|
|
181
|
+
.map(c => ({
|
|
182
|
+
...c,
|
|
183
|
+
phases: c.phases
|
|
184
|
+
.map(p => ({
|
|
185
|
+
...p,
|
|
186
|
+
agents: p.agents.filter(a =>
|
|
187
|
+
a.label.toLowerCase().includes(q) ||
|
|
188
|
+
a.slug.toLowerCase().includes(q) ||
|
|
189
|
+
c.ceremony.toLowerCase().includes(q) ||
|
|
190
|
+
p.phase.toLowerCase().includes(q)
|
|
191
|
+
),
|
|
192
|
+
}))
|
|
193
|
+
.filter(p => p.agents.length > 0),
|
|
194
|
+
}))
|
|
195
|
+
.filter(c => c.phases.length > 0)
|
|
196
|
+
: allCeremonies;
|
|
197
|
+
|
|
198
|
+
const hasAgentsLoaded = Object.keys(agentStatus).length > 0 || Object.keys(checkStatus).length > 0;
|
|
199
|
+
const forceOpen = q.length > 0;
|
|
200
|
+
|
|
201
|
+
// Count customized agents per ceremony for the badge
|
|
202
|
+
const countCustomized = (ceremony) =>
|
|
203
|
+
ceremony.phases
|
|
204
|
+
.flatMap(p => p.agents)
|
|
205
|
+
.filter(a => a.isCheck ? checkStatus[a.slug]?.isCustomized : agentStatus[a.slug])
|
|
206
|
+
.length;
|
|
207
|
+
|
|
208
|
+
return (
|
|
209
|
+
<div>
|
|
210
|
+
{/* Sticky search bar */}
|
|
211
|
+
<div className="sticky top-0 bg-white z-10 px-4 py-2.5 border-b border-slate-100 flex items-center justify-between gap-3">
|
|
212
|
+
<input
|
|
213
|
+
type="search"
|
|
214
|
+
placeholder="Search agents…"
|
|
215
|
+
value={search}
|
|
216
|
+
onChange={e => setSearch(e.target.value)}
|
|
217
|
+
className="w-48 rounded border border-slate-200 px-3 py-1.5 text-xs text-slate-700 focus:outline-none focus:ring-1 focus:ring-blue-500"
|
|
218
|
+
/>
|
|
219
|
+
<span className="text-xs text-slate-400 italic flex-shrink-0">Click any agent to edit its prompt</span>
|
|
220
|
+
</div>
|
|
221
|
+
|
|
222
|
+
{/* Agent hierarchy list */}
|
|
223
|
+
<div className="px-4 py-4 flex flex-col gap-3">
|
|
224
|
+
{error && (
|
|
225
|
+
<p className="text-xs text-red-500">{error}</p>
|
|
226
|
+
)}
|
|
227
|
+
{filteredCeremonies.length === 0 && (
|
|
228
|
+
<p className="text-sm text-slate-400 py-4">No agents match your search.</p>
|
|
229
|
+
)}
|
|
230
|
+
|
|
231
|
+
{filteredCeremonies.map(ceremony => {
|
|
232
|
+
const colors = CEREMONY_COLORS[ceremony.color] || CEREMONY_COLORS.slate;
|
|
233
|
+
const ceremonyOpen = forceOpen || isOpen(ceremony.ceremony);
|
|
234
|
+
const customCount = countCustomized(ceremony);
|
|
235
|
+
|
|
236
|
+
return (
|
|
237
|
+
<div
|
|
238
|
+
key={ceremony.ceremony}
|
|
239
|
+
className={`rounded-xl border ${colors.border} overflow-hidden`}
|
|
240
|
+
>
|
|
241
|
+
{/* Ceremony header — clickable to collapse/expand */}
|
|
242
|
+
<button
|
|
243
|
+
type="button"
|
|
244
|
+
onClick={() => !forceOpen && toggle(ceremony.ceremony)}
|
|
245
|
+
className={`w-full flex items-center gap-2 px-4 py-2.5 border-l-4 ${colors.header} ${colors.accent} text-left`}
|
|
246
|
+
>
|
|
247
|
+
{ceremonyOpen
|
|
248
|
+
? <ChevronDown className="w-3.5 h-3.5 text-slate-400 flex-shrink-0" />
|
|
249
|
+
: <ChevronRight className="w-3.5 h-3.5 text-slate-400 flex-shrink-0" />
|
|
250
|
+
}
|
|
251
|
+
<span className={`text-sm font-semibold flex-1 ${colors.text}`}>
|
|
252
|
+
{ceremony.ceremony}
|
|
253
|
+
</span>
|
|
254
|
+
{customCount > 0 && (
|
|
255
|
+
<span className="text-[10px] font-medium px-1.5 py-0.5 rounded bg-amber-100 text-amber-700">
|
|
256
|
+
{customCount} custom
|
|
257
|
+
</span>
|
|
258
|
+
)}
|
|
259
|
+
</button>
|
|
260
|
+
|
|
261
|
+
{/* Ceremony body — collapsible */}
|
|
262
|
+
{ceremonyOpen && (
|
|
263
|
+
<div className="divide-y divide-slate-100">
|
|
264
|
+
{ceremony.phases.map(phase => {
|
|
265
|
+
const phaseKey = `${ceremony.ceremony}::${phase.phase}`;
|
|
266
|
+
const phaseOpen = forceOpen || isOpen(phaseKey);
|
|
267
|
+
const visibleAgents = phase.agents.filter(
|
|
268
|
+
a => !hasAgentsLoaded || a.isCheck || a.slug in agentStatus
|
|
269
|
+
);
|
|
270
|
+
if (visibleAgents.length === 0) return null;
|
|
271
|
+
|
|
272
|
+
return (
|
|
273
|
+
<div key={phase.phase}>
|
|
274
|
+
{/* Phase sub-header — indented, highlighted as non-leaf node */}
|
|
275
|
+
<button
|
|
276
|
+
type="button"
|
|
277
|
+
onClick={() => !forceOpen && toggle(phaseKey)}
|
|
278
|
+
className="w-full flex items-center gap-2 pl-6 pr-4 py-2 bg-slate-50 hover:bg-slate-100 transition-colors text-left"
|
|
279
|
+
>
|
|
280
|
+
{phaseOpen
|
|
281
|
+
? <ChevronDown className="w-3 h-3 text-slate-400 flex-shrink-0" />
|
|
282
|
+
: <ChevronRight className="w-3 h-3 text-slate-400 flex-shrink-0" />
|
|
283
|
+
}
|
|
284
|
+
<span className="text-xs font-semibold text-slate-600 uppercase tracking-wide">
|
|
285
|
+
{phase.phase}
|
|
286
|
+
</span>
|
|
287
|
+
<span className="text-xs text-slate-400 ml-1">
|
|
288
|
+
{visibleAgents.length}
|
|
289
|
+
</span>
|
|
290
|
+
</button>
|
|
291
|
+
|
|
292
|
+
{/* Agent rows — further indented */}
|
|
293
|
+
{phaseOpen && (
|
|
294
|
+
<div className="pb-1">
|
|
295
|
+
{visibleAgents.map(agent => {
|
|
296
|
+
const isCheck = agent.isCheck;
|
|
297
|
+
const isCustomized = isCheck
|
|
298
|
+
? (checkStatus[agent.slug]?.isCustomized ?? false)
|
|
299
|
+
: (agentStatus[agent.slug] ?? false);
|
|
300
|
+
const checkCount = isCheck ? (checkStatus[agent.slug]?.checkCount ?? 0) : 0;
|
|
301
|
+
const handleClick = () => {
|
|
302
|
+
if (isCheck) {
|
|
303
|
+
// Parse slug: "checks/scope/perspective"
|
|
304
|
+
const parts = agent.slug.split('/');
|
|
305
|
+
setOpenCheck({ scope: parts[1], perspective: parts[2] });
|
|
306
|
+
} else {
|
|
307
|
+
setOpenAgent(agent.slug);
|
|
308
|
+
}
|
|
309
|
+
};
|
|
310
|
+
return (
|
|
311
|
+
<button
|
|
312
|
+
key={`${ceremony.ceremony}-${agent.slug}`}
|
|
313
|
+
type="button"
|
|
314
|
+
onClick={handleClick}
|
|
315
|
+
className="w-full text-left pl-10 pr-4 py-1.5 hover:bg-slate-50 transition-colors flex items-center gap-3 group"
|
|
316
|
+
>
|
|
317
|
+
<div className="flex-1 min-w-0">
|
|
318
|
+
<span className="text-sm text-slate-700 group-hover:text-slate-900">
|
|
319
|
+
{agent.label}
|
|
320
|
+
</span>
|
|
321
|
+
{isCheck && checkCount > 0 && (
|
|
322
|
+
<span className="text-xs text-blue-500 ml-2">
|
|
323
|
+
{checkCount} checks
|
|
324
|
+
</span>
|
|
325
|
+
)}
|
|
326
|
+
{agent.note && !isCheck && (
|
|
327
|
+
<span className="text-xs text-slate-400 italic ml-2">
|
|
328
|
+
{agent.note}
|
|
329
|
+
</span>
|
|
330
|
+
)}
|
|
331
|
+
</div>
|
|
332
|
+
{isCustomized && (
|
|
333
|
+
<span className="flex-shrink-0 text-[10px] font-medium px-1.5 py-0.5 rounded bg-amber-100 text-amber-700">
|
|
334
|
+
Custom
|
|
335
|
+
</span>
|
|
336
|
+
)}
|
|
337
|
+
<Pencil className="w-3.5 h-3.5 text-slate-300 group-hover:text-slate-500 flex-shrink-0 transition-colors" />
|
|
338
|
+
</button>
|
|
339
|
+
);
|
|
340
|
+
})}
|
|
341
|
+
</div>
|
|
342
|
+
)}
|
|
343
|
+
</div>
|
|
344
|
+
);
|
|
345
|
+
})}
|
|
346
|
+
</div>
|
|
347
|
+
)}
|
|
348
|
+
</div>
|
|
349
|
+
);
|
|
350
|
+
})}
|
|
351
|
+
</div>
|
|
352
|
+
|
|
353
|
+
{/* Agent editor popup */}
|
|
354
|
+
{openAgent && (
|
|
355
|
+
<AgentEditorPopup
|
|
356
|
+
agentName={`${openAgent}.md`}
|
|
357
|
+
onClose={() => setOpenAgent(null)}
|
|
358
|
+
onSaved={() => setAgentStatus(prev => ({ ...prev, [openAgent]: true }))}
|
|
359
|
+
onReset={() => setAgentStatus(prev => ({ ...prev, [openAgent]: false }))}
|
|
360
|
+
/>
|
|
361
|
+
)}
|
|
362
|
+
|
|
363
|
+
{/* Check editor popup */}
|
|
364
|
+
{openCheck && (
|
|
365
|
+
<CheckEditorPopup
|
|
366
|
+
scope={openCheck.scope}
|
|
367
|
+
perspective={openCheck.perspective}
|
|
368
|
+
onClose={() => setOpenCheck(null)}
|
|
369
|
+
onSaved={() => {
|
|
370
|
+
const key = `checks/${openCheck.scope}/${openCheck.perspective}`;
|
|
371
|
+
setCheckStatus(prev => ({ ...prev, [key]: { ...prev[key], isCustomized: true } }));
|
|
372
|
+
}}
|
|
373
|
+
onReset={() => {
|
|
374
|
+
const key = `checks/${openCheck.scope}/${openCheck.perspective}`;
|
|
375
|
+
setCheckStatus(prev => ({ ...prev, [key]: { ...prev[key], isCustomized: false } }));
|
|
376
|
+
}}
|
|
377
|
+
/>
|
|
378
|
+
)}
|
|
379
|
+
</div>
|
|
380
|
+
);
|
|
381
|
+
}
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
import { useState } from 'react';
|
|
2
|
+
import { Eye, EyeOff } from 'lucide-react';
|
|
3
|
+
import { saveApiKeys } from '../../lib/api';
|
|
4
|
+
import { OpenAIAuthSection } from './OpenAIAuthSection';
|
|
5
|
+
|
|
6
|
+
const PROVIDERS = [
|
|
7
|
+
{ key: 'anthropic', label: 'Anthropic', envKey: 'ANTHROPIC_API_KEY', placeholder: 'sk-ant-…' },
|
|
8
|
+
{ key: 'gemini', label: 'Google (Gemini)', envKey: 'GEMINI_API_KEY', placeholder: 'AIza…' },
|
|
9
|
+
{ key: 'xiaomi', label: 'Xiaomi (MiMo)', envKey: 'XIAOMI_API_KEY', placeholder: 'sk-…' },
|
|
10
|
+
];
|
|
11
|
+
|
|
12
|
+
function ApiKeyRow({ provider, apiKeyInfo, onSaved }) {
|
|
13
|
+
const [value, setValue] = useState('');
|
|
14
|
+
const [showKey, setShowKey] = useState(false);
|
|
15
|
+
const [status, setStatus] = useState(null); // null | 'saving' | 'saved' | 'error' | 'clearing'
|
|
16
|
+
|
|
17
|
+
const handleSave = async () => {
|
|
18
|
+
setStatus('saving');
|
|
19
|
+
try {
|
|
20
|
+
await saveApiKeys({ [provider.key]: value });
|
|
21
|
+
setStatus('saved');
|
|
22
|
+
setValue('');
|
|
23
|
+
onSaved();
|
|
24
|
+
setTimeout(() => setStatus(null), 2000);
|
|
25
|
+
} catch {
|
|
26
|
+
setStatus('error');
|
|
27
|
+
setTimeout(() => setStatus(null), 2000);
|
|
28
|
+
}
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
const handleClear = async () => {
|
|
32
|
+
setStatus('clearing');
|
|
33
|
+
try {
|
|
34
|
+
await saveApiKeys({ [provider.key]: '' });
|
|
35
|
+
setStatus('saved');
|
|
36
|
+
onSaved();
|
|
37
|
+
setTimeout(() => setStatus(null), 2000);
|
|
38
|
+
} catch {
|
|
39
|
+
setStatus('error');
|
|
40
|
+
setTimeout(() => setStatus(null), 2000);
|
|
41
|
+
}
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
return (
|
|
45
|
+
<div className="flex items-center gap-3 py-3 border-b border-slate-100 last:border-0">
|
|
46
|
+
{/* Provider name */}
|
|
47
|
+
<div className="w-36 flex-shrink-0">
|
|
48
|
+
<p className="text-sm font-medium text-slate-800">{provider.label}</p>
|
|
49
|
+
<p className="text-xs text-slate-400">{provider.envKey}</p>
|
|
50
|
+
</div>
|
|
51
|
+
|
|
52
|
+
{/* Status badge */}
|
|
53
|
+
<div className="w-16 flex-shrink-0">
|
|
54
|
+
{apiKeyInfo.isSet ? (
|
|
55
|
+
<span className="inline-flex items-center gap-1 text-xs font-medium text-green-700 bg-green-50 border border-green-200 rounded-full px-2 py-0.5">
|
|
56
|
+
✓ Set
|
|
57
|
+
</span>
|
|
58
|
+
) : (
|
|
59
|
+
<span className="inline-flex items-center text-xs font-medium text-slate-400 bg-slate-50 border border-slate-200 rounded-full px-2 py-0.5">
|
|
60
|
+
Not set
|
|
61
|
+
</span>
|
|
62
|
+
)}
|
|
63
|
+
</div>
|
|
64
|
+
|
|
65
|
+
{/* Preview */}
|
|
66
|
+
{apiKeyInfo.isSet && !value && (
|
|
67
|
+
<p className="text-xs text-slate-400 font-mono flex-shrink-0">{apiKeyInfo.preview}</p>
|
|
68
|
+
)}
|
|
69
|
+
|
|
70
|
+
{/* Key input */}
|
|
71
|
+
<div className="flex-1 flex items-center gap-2 min-w-0">
|
|
72
|
+
<div className="relative flex-1">
|
|
73
|
+
<input
|
|
74
|
+
type={showKey ? 'text' : 'password'}
|
|
75
|
+
value={value}
|
|
76
|
+
onChange={(e) => setValue(e.target.value)}
|
|
77
|
+
placeholder={apiKeyInfo.isSet ? 'Enter new key to update…' : provider.placeholder}
|
|
78
|
+
className="w-full rounded-md border border-slate-300 px-2 py-1.5 pr-8 text-xs text-slate-900 placeholder:text-slate-400 focus:outline-none focus:ring-2 focus:ring-blue-500 font-mono"
|
|
79
|
+
/>
|
|
80
|
+
<button
|
|
81
|
+
type="button"
|
|
82
|
+
onClick={() => setShowKey((v) => !v)}
|
|
83
|
+
className="absolute right-2 top-1/2 -translate-y-1/2 text-slate-400 hover:text-slate-600"
|
|
84
|
+
tabIndex={-1}
|
|
85
|
+
>
|
|
86
|
+
{showKey ? <EyeOff className="w-3.5 h-3.5" /> : <Eye className="w-3.5 h-3.5" />}
|
|
87
|
+
</button>
|
|
88
|
+
</div>
|
|
89
|
+
|
|
90
|
+
{apiKeyInfo.isSet && !value && (
|
|
91
|
+
<button
|
|
92
|
+
type="button"
|
|
93
|
+
onClick={handleClear}
|
|
94
|
+
disabled={status === 'clearing'}
|
|
95
|
+
className="px-3 py-1.5 text-xs font-medium border border-red-200 text-red-600 rounded-md hover:bg-red-50 transition-colors disabled:opacity-40 flex-shrink-0"
|
|
96
|
+
>
|
|
97
|
+
{status === 'clearing' ? '…' : 'Reset'}
|
|
98
|
+
</button>
|
|
99
|
+
)}
|
|
100
|
+
|
|
101
|
+
<button
|
|
102
|
+
type="button"
|
|
103
|
+
onClick={handleSave}
|
|
104
|
+
disabled={!value.trim() || status === 'saving'}
|
|
105
|
+
className="px-3 py-1.5 text-xs font-medium bg-slate-900 text-white rounded-md hover:bg-slate-700 transition-colors disabled:opacity-40 flex-shrink-0"
|
|
106
|
+
>
|
|
107
|
+
{status === 'saving' ? (
|
|
108
|
+
<span className="inline-flex items-center gap-1">
|
|
109
|
+
<span className="w-3 h-3 border border-white/40 border-t-white rounded-full animate-spin" />
|
|
110
|
+
Saving
|
|
111
|
+
</span>
|
|
112
|
+
) : status === 'saved' ? '✓ Saved' : status === 'error' ? '✗ Error' : 'Save'}
|
|
113
|
+
</button>
|
|
114
|
+
</div>
|
|
115
|
+
</div>
|
|
116
|
+
);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
export function ApiKeysTab({ settings, onSaved }) {
|
|
120
|
+
return (
|
|
121
|
+
<div className="px-5 py-4">
|
|
122
|
+
<p className="text-xs text-slate-500 mb-4">
|
|
123
|
+
API keys are stored in your project's <code className="font-mono bg-slate-100 px-1 rounded">.env</code> file.
|
|
124
|
+
Enter a new key and click Save to update. Clear the field and save to remove a key.
|
|
125
|
+
</p>
|
|
126
|
+
<div>
|
|
127
|
+
{PROVIDERS.map((provider) => (
|
|
128
|
+
<ApiKeyRow
|
|
129
|
+
key={provider.key}
|
|
130
|
+
provider={provider}
|
|
131
|
+
apiKeyInfo={settings.apiKeys[provider.key]}
|
|
132
|
+
onSaved={onSaved}
|
|
133
|
+
/>
|
|
134
|
+
))}
|
|
135
|
+
<OpenAIAuthSection
|
|
136
|
+
apiKeyInfo={settings.apiKeys.openai}
|
|
137
|
+
onSaved={onSaved}
|
|
138
|
+
/>
|
|
139
|
+
</div>
|
|
140
|
+
</div>
|
|
141
|
+
);
|
|
142
|
+
}
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import { useState } from 'react';
|
|
2
|
+
import { Workflow } from 'lucide-react';
|
|
3
|
+
import { saveCeremonies } from '../../lib/api';
|
|
4
|
+
import { CeremonyWorkflowModal } from '../ceremony/CeremonyWorkflowModal';
|
|
5
|
+
|
|
6
|
+
function humanize(str) {
|
|
7
|
+
return str
|
|
8
|
+
.replace(/-/g, ' ')
|
|
9
|
+
.replace(/\b\w/g, (c) => c.toUpperCase());
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
const CEREMONY_DESCRIPTIONS = {
|
|
13
|
+
'sponsor-call': 'Generates mission statement and project documentation through a structured AI-guided interview.',
|
|
14
|
+
'sprint-planning': 'Decomposes project scope into epics and stories with multi-agent validation and context generation.',
|
|
15
|
+
'seed': 'Decomposes a story into implementable tasks and subtasks with documentation and context files.',
|
|
16
|
+
'run': 'Implements task code in a git worktree using AI agents with traceability rules validation.',
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
export function CeremonyModelsTab({ settings, models, onSaved }) {
|
|
20
|
+
const [ceremonies, setCeremonies] = useState(
|
|
21
|
+
() => JSON.parse(JSON.stringify(settings.ceremonies || []))
|
|
22
|
+
);
|
|
23
|
+
const [missionGenValidation, setMissionGenValidation] = useState(
|
|
24
|
+
() => JSON.parse(JSON.stringify(
|
|
25
|
+
settings.missionGenerator?.validation || { maxIterations: 3, acceptanceThreshold: 95 }
|
|
26
|
+
))
|
|
27
|
+
);
|
|
28
|
+
const [activeWorkflow, setActiveWorkflow] = useState(null);
|
|
29
|
+
|
|
30
|
+
const handleCeremonySave = async (updatedCeremony, updatedMG) => {
|
|
31
|
+
const next = ceremonies.map((c) =>
|
|
32
|
+
c.name === updatedCeremony.name ? updatedCeremony : c
|
|
33
|
+
);
|
|
34
|
+
// Always pass missionGen params; use updated value for sponsor-call, current for others
|
|
35
|
+
const missionGenArg = updatedCeremony.name === 'sponsor-call'
|
|
36
|
+
? { validation: updatedMG || missionGenValidation }
|
|
37
|
+
: { validation: missionGenValidation };
|
|
38
|
+
await saveCeremonies(next, missionGenArg);
|
|
39
|
+
setCeremonies(next);
|
|
40
|
+
if (updatedCeremony.name === 'sponsor-call' && updatedMG) {
|
|
41
|
+
setMissionGenValidation(updatedMG);
|
|
42
|
+
}
|
|
43
|
+
onSaved();
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
if (!ceremonies.length) {
|
|
47
|
+
return (
|
|
48
|
+
<div className="px-5 py-8 text-center">
|
|
49
|
+
<p className="text-sm text-slate-500">
|
|
50
|
+
No ceremony configurations found yet. Run your first ceremony from the kanban board
|
|
51
|
+
to populate settings here.
|
|
52
|
+
</p>
|
|
53
|
+
</div>
|
|
54
|
+
);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const activeWorkflowCeremony = ceremonies.find((c) => c.name === activeWorkflow);
|
|
58
|
+
|
|
59
|
+
return (
|
|
60
|
+
<div className="px-5 py-4 flex flex-col gap-3">
|
|
61
|
+
{ceremonies.map((ceremony) => {
|
|
62
|
+
const description = CEREMONY_DESCRIPTIONS[ceremony.name];
|
|
63
|
+
return (
|
|
64
|
+
<div
|
|
65
|
+
key={ceremony.name}
|
|
66
|
+
className="border border-slate-200 rounded-lg px-4 py-3 flex items-center justify-between gap-4"
|
|
67
|
+
>
|
|
68
|
+
<div className="min-w-0">
|
|
69
|
+
<p className="text-sm font-semibold text-slate-800">
|
|
70
|
+
{ceremony.displayName || humanize(ceremony.name || '')}
|
|
71
|
+
</p>
|
|
72
|
+
{description && (
|
|
73
|
+
<p className="text-xs text-slate-500 mt-0.5 leading-relaxed">{description}</p>
|
|
74
|
+
)}
|
|
75
|
+
</div>
|
|
76
|
+
<button
|
|
77
|
+
type="button"
|
|
78
|
+
onClick={() => setActiveWorkflow(ceremony.name)}
|
|
79
|
+
className="flex items-center gap-1.5 text-xs font-medium text-blue-600 hover:text-blue-700 bg-blue-50 hover:bg-blue-100 border border-blue-200 rounded-md px-3 py-1.5 transition-colors flex-shrink-0"
|
|
80
|
+
>
|
|
81
|
+
<Workflow className="w-3.5 h-3.5" />
|
|
82
|
+
Configure Models
|
|
83
|
+
</button>
|
|
84
|
+
</div>
|
|
85
|
+
);
|
|
86
|
+
})}
|
|
87
|
+
|
|
88
|
+
{activeWorkflow && activeWorkflowCeremony && (
|
|
89
|
+
<CeremonyWorkflowModal
|
|
90
|
+
ceremony={activeWorkflowCeremony}
|
|
91
|
+
allCeremonies={ceremonies}
|
|
92
|
+
apiKeys={settings.apiKeys}
|
|
93
|
+
models={models}
|
|
94
|
+
missionGenValidation={activeWorkflow === 'sponsor-call' ? missionGenValidation : null}
|
|
95
|
+
onClose={() => setActiveWorkflow(null)}
|
|
96
|
+
onSave={handleCeremonySave}
|
|
97
|
+
onCeremoniesUpdated={(updated) => {
|
|
98
|
+
setCeremonies(updated);
|
|
99
|
+
onSaved();
|
|
100
|
+
}}
|
|
101
|
+
/>
|
|
102
|
+
)}
|
|
103
|
+
</div>
|
|
104
|
+
);
|
|
105
|
+
}
|