@agile-vibe-coding/avc 0.2.3 → 0.3.2
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 +475 -3
- package/cli/agents/agent-selector.md +23 -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/doc-writer-epic.md +42 -0
- package/cli/agents/doc-writer-story.md +43 -0
- package/cli/agents/duplicate-detector.md +110 -0
- package/cli/agents/epic-story-decomposer.md +318 -39
- package/cli/agents/mission-scope-generator.md +68 -4
- package/cli/agents/mission-scope-validator.md +40 -6
- package/cli/agents/project-context-extractor.md +21 -6
- package/cli/agents/scaffolding-generator.md +99 -0
- package/cli/agents/seed-validator.md +71 -0
- package/cli/agents/story-scope-reviewer.md +147 -0
- package/cli/agents/story-splitter.md +83 -0
- package/cli/agents/validator-documentation.json +31 -0
- package/cli/agents/validator-documentation.md +3 -1
- package/cli/api-reference-tool.js +368 -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/dependency-checker.js +72 -0
- package/cli/epic-story-validator.js +284 -799
- package/cli/index.js +0 -0
- package/cli/init-model-config.js +17 -10
- package/cli/init.js +514 -92
- package/cli/kanban-server-manager.js +1 -2
- package/cli/llm-claude.js +98 -31
- package/cli/llm-gemini.js +29 -5
- package/cli/llm-local.js +493 -0
- package/cli/llm-openai.js +262 -41
- package/cli/llm-provider.js +147 -8
- package/cli/llm-token-limits.js +113 -4
- package/cli/llm-verifier.js +209 -1
- package/cli/llm-xiaomi.js +143 -0
- package/cli/message-constants.js +3 -12
- package/cli/messaging-api.js +6 -12
- 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 +23 -0
- package/cli/model-selector.js +3 -2
- package/cli/prompt-logger.js +57 -0
- package/cli/repl-ink.js +106 -346
- package/cli/repl-old.js +1 -2
- package/cli/seed-processor.js +194 -24
- package/cli/sprint-planning-processor.js +2638 -289
- package/cli/template-processor.js +50 -3
- package/cli/token-tracker.js +50 -23
- package/cli/tools/generate-story-validators.js +1 -1
- package/cli/validation-router.js +70 -8
- package/cli/worktree-runner.js +654 -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 +2 -2
- package/kanban/client/src/App.jsx +43 -14
- package/kanban/client/src/components/ceremony/AskArchPopup.jsx +7 -3
- package/kanban/client/src/components/ceremony/AskModelPopup.jsx +23 -10
- package/kanban/client/src/components/ceremony/CeremonyWorkflowModal.jsx +320 -133
- package/kanban/client/src/components/ceremony/ProviderSwitcherButton.jsx +290 -0
- package/kanban/client/src/components/ceremony/SponsorCallModal.jsx +80 -13
- package/kanban/client/src/components/ceremony/SprintPlanningModal.jsx +156 -22
- package/kanban/client/src/components/ceremony/steps/ArchitectureStep.jsx +11 -11
- package/kanban/client/src/components/ceremony/steps/CompleteStep.jsx +3 -21
- package/kanban/client/src/components/ceremony/steps/ReviewAnswersStep.jsx +214 -10
- package/kanban/client/src/components/ceremony/steps/RunningStep.jsx +23 -2
- package/kanban/client/src/components/kanban/CardDetailModal.jsx +97 -10
- package/kanban/client/src/components/kanban/GroupingSelector.jsx +7 -1
- package/kanban/client/src/components/kanban/KanbanCard.jsx +23 -14
- package/kanban/client/src/components/kanban/RefineWorkItemPopup.jsx +9 -14
- 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/settings/AgentsTab.jsx +103 -75
- package/kanban/client/src/components/settings/ApiKeysTab.jsx +31 -2
- package/kanban/client/src/components/settings/CeremonyModelsTab.jsx +9 -2
- package/kanban/client/src/components/settings/CheckEditorPopup.jsx +507 -0
- package/kanban/client/src/components/settings/CostThresholdsTab.jsx +3 -2
- package/kanban/client/src/components/settings/ModelPricingTab.jsx +72 -7
- package/kanban/client/src/components/settings/OpenAIAuthSection.jsx +412 -0
- package/kanban/client/src/components/settings/SettingsModal.jsx +4 -4
- package/kanban/client/src/components/stats/CostModal.jsx +34 -3
- package/kanban/client/src/hooks/useGrouping.js +59 -0
- package/kanban/client/src/lib/api.js +118 -4
- package/kanban/client/src/lib/status-grouping.js +10 -0
- package/kanban/client/src/store/kanbanStore.js +8 -0
- package/kanban/server/index.js +23 -2
- package/kanban/server/routes/ceremony.js +153 -4
- package/kanban/server/routes/costs.js +9 -3
- package/kanban/server/routes/openai-oauth.js +366 -0
- package/kanban/server/routes/settings.js +447 -14
- package/kanban/server/routes/websocket.js +7 -2
- package/kanban/server/routes/work-items.js +141 -1
- package/kanban/server/services/CeremonyService.js +275 -24
- package/kanban/server/services/TaskRunnerService.js +261 -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 +14 -6
- package/kanban/server/workers/sprint-planning-worker.js +94 -12
- package/package.json +2 -3
- package/cli/agents/solver-epic-api.json +0 -15
- package/cli/agents/solver-epic-api.md +0 -39
- package/cli/agents/solver-epic-backend.json +0 -15
- package/cli/agents/solver-epic-backend.md +0 -39
- package/cli/agents/solver-epic-cloud.json +0 -15
- package/cli/agents/solver-epic-cloud.md +0 -39
- package/cli/agents/solver-epic-data.json +0 -15
- package/cli/agents/solver-epic-data.md +0 -39
- package/cli/agents/solver-epic-database.json +0 -15
- package/cli/agents/solver-epic-database.md +0 -39
- package/cli/agents/solver-epic-developer.json +0 -15
- package/cli/agents/solver-epic-developer.md +0 -39
- package/cli/agents/solver-epic-devops.json +0 -15
- package/cli/agents/solver-epic-devops.md +0 -39
- package/cli/agents/solver-epic-frontend.json +0 -15
- package/cli/agents/solver-epic-frontend.md +0 -39
- package/cli/agents/solver-epic-mobile.json +0 -15
- package/cli/agents/solver-epic-mobile.md +0 -39
- package/cli/agents/solver-epic-qa.json +0 -15
- package/cli/agents/solver-epic-qa.md +0 -39
- package/cli/agents/solver-epic-security.json +0 -15
- package/cli/agents/solver-epic-security.md +0 -39
- package/cli/agents/solver-epic-solution-architect.json +0 -15
- package/cli/agents/solver-epic-solution-architect.md +0 -39
- package/cli/agents/solver-epic-test-architect.json +0 -15
- package/cli/agents/solver-epic-test-architect.md +0 -39
- package/cli/agents/solver-epic-ui.json +0 -15
- package/cli/agents/solver-epic-ui.md +0 -39
- package/cli/agents/solver-epic-ux.json +0 -15
- package/cli/agents/solver-epic-ux.md +0 -39
- package/cli/agents/solver-story-api.json +0 -15
- package/cli/agents/solver-story-api.md +0 -39
- package/cli/agents/solver-story-backend.json +0 -15
- package/cli/agents/solver-story-backend.md +0 -39
- package/cli/agents/solver-story-cloud.json +0 -15
- package/cli/agents/solver-story-cloud.md +0 -39
- package/cli/agents/solver-story-data.json +0 -15
- package/cli/agents/solver-story-data.md +0 -39
- package/cli/agents/solver-story-database.json +0 -15
- package/cli/agents/solver-story-database.md +0 -39
- package/cli/agents/solver-story-developer.json +0 -15
- package/cli/agents/solver-story-developer.md +0 -39
- package/cli/agents/solver-story-devops.json +0 -15
- package/cli/agents/solver-story-devops.md +0 -39
- package/cli/agents/solver-story-frontend.json +0 -15
- package/cli/agents/solver-story-frontend.md +0 -39
- package/cli/agents/solver-story-mobile.json +0 -15
- package/cli/agents/solver-story-mobile.md +0 -39
- package/cli/agents/solver-story-qa.json +0 -15
- package/cli/agents/solver-story-qa.md +0 -39
- package/cli/agents/solver-story-security.json +0 -15
- package/cli/agents/solver-story-security.md +0 -39
- package/cli/agents/solver-story-solution-architect.json +0 -15
- package/cli/agents/solver-story-solution-architect.md +0 -39
- package/cli/agents/solver-story-test-architect.json +0 -15
- package/cli/agents/solver-story-test-architect.md +0 -39
- package/cli/agents/solver-story-ui.json +0 -15
- package/cli/agents/solver-story-ui.md +0 -39
- package/cli/agents/solver-story-ux.json +0 -15
- package/cli/agents/solver-story-ux.md +0 -39
- package/cli/agents/validator-epic-api.json +0 -93
- package/cli/agents/validator-epic-api.md +0 -137
- package/cli/agents/validator-epic-backend.json +0 -93
- package/cli/agents/validator-epic-backend.md +0 -130
- package/cli/agents/validator-epic-cloud.json +0 -93
- package/cli/agents/validator-epic-cloud.md +0 -137
- package/cli/agents/validator-epic-data.json +0 -93
- package/cli/agents/validator-epic-data.md +0 -130
- package/cli/agents/validator-epic-database.json +0 -93
- package/cli/agents/validator-epic-database.md +0 -137
- package/cli/agents/validator-epic-developer.json +0 -74
- package/cli/agents/validator-epic-developer.md +0 -153
- package/cli/agents/validator-epic-devops.json +0 -74
- package/cli/agents/validator-epic-devops.md +0 -153
- package/cli/agents/validator-epic-frontend.json +0 -74
- package/cli/agents/validator-epic-frontend.md +0 -153
- package/cli/agents/validator-epic-mobile.json +0 -93
- package/cli/agents/validator-epic-mobile.md +0 -130
- package/cli/agents/validator-epic-qa.json +0 -93
- package/cli/agents/validator-epic-qa.md +0 -130
- package/cli/agents/validator-epic-security.json +0 -74
- package/cli/agents/validator-epic-security.md +0 -154
- package/cli/agents/validator-epic-solution-architect.json +0 -74
- package/cli/agents/validator-epic-solution-architect.md +0 -156
- package/cli/agents/validator-epic-test-architect.json +0 -93
- package/cli/agents/validator-epic-test-architect.md +0 -130
- package/cli/agents/validator-epic-ui.json +0 -93
- package/cli/agents/validator-epic-ui.md +0 -130
- package/cli/agents/validator-epic-ux.json +0 -93
- package/cli/agents/validator-epic-ux.md +0 -130
- package/cli/agents/validator-story-api.json +0 -104
- package/cli/agents/validator-story-api.md +0 -152
- package/cli/agents/validator-story-backend.json +0 -104
- package/cli/agents/validator-story-backend.md +0 -152
- package/cli/agents/validator-story-cloud.json +0 -104
- package/cli/agents/validator-story-cloud.md +0 -152
- package/cli/agents/validator-story-data.json +0 -104
- package/cli/agents/validator-story-data.md +0 -152
- package/cli/agents/validator-story-database.json +0 -104
- package/cli/agents/validator-story-database.md +0 -152
- package/cli/agents/validator-story-developer.json +0 -104
- package/cli/agents/validator-story-developer.md +0 -152
- package/cli/agents/validator-story-devops.json +0 -104
- package/cli/agents/validator-story-devops.md +0 -152
- package/cli/agents/validator-story-frontend.json +0 -104
- package/cli/agents/validator-story-frontend.md +0 -152
- package/cli/agents/validator-story-mobile.json +0 -104
- package/cli/agents/validator-story-mobile.md +0 -152
- package/cli/agents/validator-story-qa.json +0 -104
- package/cli/agents/validator-story-qa.md +0 -152
- package/cli/agents/validator-story-security.json +0 -104
- package/cli/agents/validator-story-security.md +0 -152
- package/cli/agents/validator-story-solution-architect.json +0 -104
- package/cli/agents/validator-story-solution-architect.md +0 -152
- package/cli/agents/validator-story-test-architect.json +0 -104
- package/cli/agents/validator-story-test-architect.md +0 -152
- package/cli/agents/validator-story-ui.json +0 -104
- package/cli/agents/validator-story-ui.md +0 -152
- package/cli/agents/validator-story-ux.json +0 -104
- package/cli/agents/validator-story-ux.md +0 -152
- package/kanban/client/dist/assets/index-CiD8PS2e.js +0 -306
- package/kanban/client/dist/assets/index-nLh0m82Q.css +0 -1
package/cli/llm-token-limits.js
CHANGED
|
@@ -17,6 +17,9 @@
|
|
|
17
17
|
* - OpenAI: https://community.openai.com/t/what-is-the-token-limit-of-the-new-version-gpt-4o/752528
|
|
18
18
|
*/
|
|
19
19
|
|
|
20
|
+
// Suppress duplicate warnings — each model warned only once per process
|
|
21
|
+
const _warnedModels = new Set();
|
|
22
|
+
|
|
20
23
|
export const MODEL_MAX_TOKENS = {
|
|
21
24
|
// Claude models (Anthropic)
|
|
22
25
|
// Source: https://docs.cloud.google.com/vertex-ai/generative-ai/docs/partner-models/claude/
|
|
@@ -31,8 +34,14 @@ export const MODEL_MAX_TOKENS = {
|
|
|
31
34
|
|
|
32
35
|
// OpenAI models
|
|
33
36
|
// Source: https://community.openai.com/t/what-is-the-token-limit-of-the-new-version-gpt-4o/752528
|
|
34
|
-
|
|
35
|
-
'gpt-5
|
|
37
|
+
// GPT-5 family (32K output tokens assumed; add specific overrides as official limits are confirmed)
|
|
38
|
+
'gpt-5': 32768,
|
|
39
|
+
'gpt-5.1': 32768,
|
|
40
|
+
'gpt-5.2': 32768,
|
|
41
|
+
'gpt-5.4': 32768,
|
|
42
|
+
'gpt-5-mini': 32768,
|
|
43
|
+
'gpt-5.2-chat-latest': 16384, // Keep at 16384 — existing tests depend on this value
|
|
44
|
+
'gpt-5.2-pro': 16384,
|
|
36
45
|
'gpt-5.2-codex': 16384,
|
|
37
46
|
'gpt-4o': 16384, // Correct - max 16,384 tokens
|
|
38
47
|
'gpt-4o-2024-11-20': 16384,
|
|
@@ -58,6 +67,51 @@ export const MODEL_MAX_TOKENS = {
|
|
|
58
67
|
'default': 8192
|
|
59
68
|
};
|
|
60
69
|
|
|
70
|
+
/**
|
|
71
|
+
* Model context window sizes (total input + output tokens)
|
|
72
|
+
*
|
|
73
|
+
* These represent the full context window each model supports.
|
|
74
|
+
* For local models, we use conservative defaults since the actual
|
|
75
|
+
* model behind the endpoint is unknown.
|
|
76
|
+
*/
|
|
77
|
+
export const MODEL_CONTEXT_WINDOWS = {
|
|
78
|
+
// Claude models
|
|
79
|
+
'claude-sonnet-4-5': 200000,
|
|
80
|
+
'claude-sonnet-4': 200000,
|
|
81
|
+
'claude-opus-4-6': 200000,
|
|
82
|
+
'claude-opus-4': 200000,
|
|
83
|
+
'claude-haiku-4-5': 200000,
|
|
84
|
+
'claude-haiku-4': 200000,
|
|
85
|
+
|
|
86
|
+
// OpenAI models
|
|
87
|
+
'gpt-5': 128000,
|
|
88
|
+
'gpt-5.1': 128000,
|
|
89
|
+
'gpt-5.2': 128000,
|
|
90
|
+
'gpt-5.4': 128000,
|
|
91
|
+
'gpt-5-mini': 128000,
|
|
92
|
+
'gpt-4o': 128000,
|
|
93
|
+
'gpt-4o-mini': 128000,
|
|
94
|
+
'gpt-4-turbo': 128000,
|
|
95
|
+
'gpt-4': 8192,
|
|
96
|
+
|
|
97
|
+
// Google Gemini models
|
|
98
|
+
'gemini-3.1-pro-preview': 1048576,
|
|
99
|
+
'gemini-3-flash-preview': 1048576,
|
|
100
|
+
'gemini-2.5-pro': 1048576,
|
|
101
|
+
'gemini-2.5-flash': 1048576,
|
|
102
|
+
'gemini-2.0-flash': 1048576,
|
|
103
|
+
'gemini-1.5-pro': 2097152,
|
|
104
|
+
'gemini-1.5-flash': 1048576,
|
|
105
|
+
|
|
106
|
+
// Default for unknown local models - conservative estimate
|
|
107
|
+
'local-default': 8192,
|
|
108
|
+
|
|
109
|
+
// Xiaomi MiMo models
|
|
110
|
+
'mimo-v2-flash': 262144,
|
|
111
|
+
'mimo-v2-pro': 1048576,
|
|
112
|
+
'mimo-v2-omni': 262144,
|
|
113
|
+
};
|
|
114
|
+
|
|
61
115
|
/**
|
|
62
116
|
* Get maximum output tokens for a specific model
|
|
63
117
|
* @param {string} modelId - The model identifier
|
|
@@ -79,11 +133,62 @@ export function getMaxTokensForModel(modelId) {
|
|
|
79
133
|
return MODEL_MAX_TOKENS[baseModel];
|
|
80
134
|
}
|
|
81
135
|
|
|
136
|
+
// Prefix fallback: catch any gpt-5.x variant not explicitly listed
|
|
137
|
+
if (modelId.startsWith('gpt-5')) {
|
|
138
|
+
if (!_warnedModels.has(modelId)) {
|
|
139
|
+
_warnedModels.add(modelId);
|
|
140
|
+
console.warn(`No exact max tokens for "${modelId}", using GPT-5 family default: 32768`);
|
|
141
|
+
}
|
|
142
|
+
return 32768;
|
|
143
|
+
}
|
|
144
|
+
|
|
82
145
|
// Fallback to default
|
|
83
|
-
|
|
146
|
+
if (!_warnedModels.has(modelId)) {
|
|
147
|
+
_warnedModels.add(modelId);
|
|
148
|
+
console.warn(`No max tokens configured for model "${modelId}", using default: ${MODEL_MAX_TOKENS['default']}`);
|
|
149
|
+
}
|
|
84
150
|
return MODEL_MAX_TOKENS['default'];
|
|
85
151
|
}
|
|
86
152
|
|
|
153
|
+
/**
|
|
154
|
+
* Get the context window size for a specific model
|
|
155
|
+
* @param {string} modelId - The model identifier
|
|
156
|
+
* @param {string} provider - The provider name ('claude', 'openai', 'gemini', 'local')
|
|
157
|
+
* @returns {number|null} Context window size in tokens, or null if no limit enforcement
|
|
158
|
+
*/
|
|
159
|
+
export function getContextWindowForModel(modelId, provider) {
|
|
160
|
+
// Local provider: use conservative default, overridable via env var
|
|
161
|
+
if (provider === 'local') {
|
|
162
|
+
const envOverride = process.env.LOCAL_LLM_CONTEXT_WINDOW;
|
|
163
|
+
if (envOverride) {
|
|
164
|
+
const parsed = parseInt(envOverride, 10);
|
|
165
|
+
if (!isNaN(parsed) && parsed > 0) {
|
|
166
|
+
return parsed;
|
|
167
|
+
}
|
|
168
|
+
console.warn(`Invalid LOCAL_LLM_CONTEXT_WINDOW value "${envOverride}", using default: ${MODEL_CONTEXT_WINDOWS['local-default']}`);
|
|
169
|
+
}
|
|
170
|
+
return MODEL_CONTEXT_WINDOWS['local-default'];
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
if (!modelId) {
|
|
174
|
+
return null;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
// Direct lookup
|
|
178
|
+
if (MODEL_CONTEXT_WINDOWS[modelId]) {
|
|
179
|
+
return MODEL_CONTEXT_WINDOWS[modelId];
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
// Fuzzy match for versioned models (e.g., "claude-opus-4-latest" → "claude-opus-4")
|
|
183
|
+
const baseModel = modelId.split('-').slice(0, 3).join('-');
|
|
184
|
+
if (MODEL_CONTEXT_WINDOWS[baseModel]) {
|
|
185
|
+
return MODEL_CONTEXT_WINDOWS[baseModel];
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
// Unknown cloud model — no limit enforcement
|
|
189
|
+
return null;
|
|
190
|
+
}
|
|
191
|
+
|
|
87
192
|
/**
|
|
88
193
|
* Validate if requested tokens exceed model limit
|
|
89
194
|
* @param {string} modelId - The model identifier
|
|
@@ -94,7 +199,11 @@ export function clampTokensToModelLimit(modelId, requestedTokens) {
|
|
|
94
199
|
const maxTokens = getMaxTokensForModel(modelId);
|
|
95
200
|
|
|
96
201
|
if (requestedTokens > maxTokens) {
|
|
97
|
-
|
|
202
|
+
const clampKey = `clamp:${modelId}:${requestedTokens}`;
|
|
203
|
+
if (!_warnedModels.has(clampKey)) {
|
|
204
|
+
_warnedModels.add(clampKey);
|
|
205
|
+
console.warn(`Requested ${requestedTokens} tokens for ${modelId}, clamping to model limit: ${maxTokens}`);
|
|
206
|
+
}
|
|
98
207
|
return maxTokens;
|
|
99
208
|
}
|
|
100
209
|
|
package/cli/llm-verifier.js
CHANGED
|
@@ -107,6 +107,80 @@ export class LLMVerifier {
|
|
|
107
107
|
}
|
|
108
108
|
}
|
|
109
109
|
|
|
110
|
+
/**
|
|
111
|
+
* Fast-path: Check if a numeric field is within range
|
|
112
|
+
*/
|
|
113
|
+
fastPathScoreRange(content, field, min, max) {
|
|
114
|
+
try {
|
|
115
|
+
const { unwrapped } = this.unwrapJsonCodeFence(content);
|
|
116
|
+
const obj = JSON.parse(unwrapped);
|
|
117
|
+
const val = obj[field];
|
|
118
|
+
if (typeof val === 'number' && Number.isInteger(val) && val >= min && val <= max) {
|
|
119
|
+
return { canFastPath: true, violated: false };
|
|
120
|
+
}
|
|
121
|
+
return { canFastPath: true, violated: true, reason: `${field}=${val} (expected integer ${min}-${max})` };
|
|
122
|
+
} catch {
|
|
123
|
+
return { canFastPath: false };
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Fast-path: Check if a field value is one of allowed enum values
|
|
129
|
+
*/
|
|
130
|
+
fastPathEnum(content, field, allowedValues) {
|
|
131
|
+
try {
|
|
132
|
+
const { unwrapped } = this.unwrapJsonCodeFence(content);
|
|
133
|
+
const obj = JSON.parse(unwrapped);
|
|
134
|
+
if (allowedValues.includes(obj[field])) {
|
|
135
|
+
return { canFastPath: true, violated: false };
|
|
136
|
+
}
|
|
137
|
+
return { canFastPath: true, violated: true, reason: `${field}="${obj[field]}" not in [${allowedValues.join(',')}]` };
|
|
138
|
+
} catch {
|
|
139
|
+
return { canFastPath: false };
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Fast-path: Check enum values inside nested objects within arrays
|
|
145
|
+
*/
|
|
146
|
+
fastPathEnumDeep(content, field, allowedValues, searchArrays) {
|
|
147
|
+
try {
|
|
148
|
+
const { unwrapped } = this.unwrapJsonCodeFence(content);
|
|
149
|
+
const obj = JSON.parse(unwrapped);
|
|
150
|
+
const invalid = [];
|
|
151
|
+
for (const arrName of searchArrays) {
|
|
152
|
+
if (Array.isArray(obj[arrName])) {
|
|
153
|
+
for (const item of obj[arrName]) {
|
|
154
|
+
if (item[field] && !allowedValues.includes(item[field])) {
|
|
155
|
+
invalid.push(`${arrName}.${field}="${item[field]}"`);
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
if (invalid.length === 0) return { canFastPath: true, violated: false };
|
|
161
|
+
return { canFastPath: true, violated: true, reason: invalid.join(', ') };
|
|
162
|
+
} catch {
|
|
163
|
+
return { canFastPath: false };
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* Fast-path: Check if specified fields are arrays (not strings/objects/null)
|
|
169
|
+
*/
|
|
170
|
+
fastPathArrayFields(content, arrayFields) {
|
|
171
|
+
try {
|
|
172
|
+
const { unwrapped } = this.unwrapJsonCodeFence(content);
|
|
173
|
+
const obj = JSON.parse(unwrapped);
|
|
174
|
+
const nonArrays = arrayFields.filter(f => f in obj && !Array.isArray(obj[f]));
|
|
175
|
+
if (nonArrays.length === 0) {
|
|
176
|
+
return { canFastPath: true, violated: false };
|
|
177
|
+
}
|
|
178
|
+
return { canFastPath: true, violated: true, reason: `non-array fields: ${nonArrays.join(', ')}` };
|
|
179
|
+
} catch {
|
|
180
|
+
return { canFastPath: false };
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
110
184
|
/**
|
|
111
185
|
* Execute fast-path optimization if configured
|
|
112
186
|
* @param {string} content - Content to check
|
|
@@ -130,6 +204,19 @@ export class LLMVerifier {
|
|
|
130
204
|
const fields = rule.fastPath.requiredFields || [];
|
|
131
205
|
return this.fastPathRequiredFields(content, fields);
|
|
132
206
|
|
|
207
|
+
case 'json-arrays':
|
|
208
|
+
// Array fields fast-path — check that specified fields are arrays
|
|
209
|
+
return this.fastPathArrayFields(content, rule.fastPath.arrayFields || []);
|
|
210
|
+
|
|
211
|
+
case 'json-score-range':
|
|
212
|
+
return this.fastPathScoreRange(content, rule.fastPath.field, rule.fastPath.min, rule.fastPath.max);
|
|
213
|
+
|
|
214
|
+
case 'json-enum':
|
|
215
|
+
return this.fastPathEnum(content, rule.fastPath.field, rule.fastPath.allowedValues || []);
|
|
216
|
+
|
|
217
|
+
case 'json-enum-deep':
|
|
218
|
+
return this.fastPathEnumDeep(content, rule.fastPath.field, rule.fastPath.allowedValues || [], rule.fastPath.searchArrays || []);
|
|
219
|
+
|
|
133
220
|
case 'none':
|
|
134
221
|
default:
|
|
135
222
|
// No fast-path, use LLM
|
|
@@ -228,7 +315,7 @@ export class LLMVerifier {
|
|
|
228
315
|
this.tracker.startRuleFix(content.length);
|
|
229
316
|
}
|
|
230
317
|
|
|
231
|
-
// Fast-path fix
|
|
318
|
+
// Fast-path fix: unwrap JSON code fences
|
|
232
319
|
if (rule.fastPath?.enabled && rule.fastPath.type === 'json-parse') {
|
|
233
320
|
const { isWrapped, unwrapped } = this.unwrapJsonCodeFence(content);
|
|
234
321
|
if (isWrapped) {
|
|
@@ -240,6 +327,127 @@ export class LLMVerifier {
|
|
|
240
327
|
}
|
|
241
328
|
}
|
|
242
329
|
|
|
330
|
+
// Fast-path fix: clamp score to valid range
|
|
331
|
+
if (rule.fastPath?.enabled && rule.fastPath.type === 'json-score-range') {
|
|
332
|
+
try {
|
|
333
|
+
const { unwrapped } = this.unwrapJsonCodeFence(content);
|
|
334
|
+
const obj = JSON.parse(unwrapped);
|
|
335
|
+
const { field, min, max } = rule.fastPath;
|
|
336
|
+
let val = obj[field];
|
|
337
|
+
if (typeof val !== 'number' || isNaN(val)) val = 50;
|
|
338
|
+
obj[field] = Math.round(Math.min(max, Math.max(min, val)));
|
|
339
|
+
const result = JSON.stringify(obj, null, 2);
|
|
340
|
+
console.log(`[DEBUG] Fast-path fix for ${rule.id}: clamped ${field} to ${obj[field]} (no LLM call)`);
|
|
341
|
+
if (this.tracker) this.tracker.endRuleFix(result.length);
|
|
342
|
+
return result;
|
|
343
|
+
} catch { /* fall through */ }
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
// Fast-path fix: correct enum value based on score
|
|
347
|
+
if (rule.fastPath?.enabled && rule.fastPath.type === 'json-enum' && rule.fastPath.field === 'validationStatus') {
|
|
348
|
+
try {
|
|
349
|
+
const { unwrapped } = this.unwrapJsonCodeFence(content);
|
|
350
|
+
const obj = JSON.parse(unwrapped);
|
|
351
|
+
const score = obj.overallScore;
|
|
352
|
+
if (typeof score === 'number') {
|
|
353
|
+
obj.validationStatus = score >= 90 ? 'excellent' : score >= 75 ? 'acceptable' : 'needs-improvement';
|
|
354
|
+
} else {
|
|
355
|
+
obj.validationStatus = 'needs-improvement';
|
|
356
|
+
}
|
|
357
|
+
const result = JSON.stringify(obj, null, 2);
|
|
358
|
+
console.log(`[DEBUG] Fast-path fix for ${rule.id}: set validationStatus="${obj.validationStatus}" (no LLM call)`);
|
|
359
|
+
if (this.tracker) this.tracker.endRuleFix(result.length);
|
|
360
|
+
return result;
|
|
361
|
+
} catch { /* fall through */ }
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
// Fast-path fix: correct severity enum values in nested arrays
|
|
365
|
+
if (rule.fastPath?.enabled && rule.fastPath.type === 'json-enum-deep') {
|
|
366
|
+
try {
|
|
367
|
+
const { unwrapped } = this.unwrapJsonCodeFence(content);
|
|
368
|
+
const obj = JSON.parse(unwrapped);
|
|
369
|
+
const { field, allowedValues, searchArrays } = rule.fastPath;
|
|
370
|
+
const severityMap = { high: 'critical', medium: 'major', low: 'minor' };
|
|
371
|
+
for (const arrName of searchArrays) {
|
|
372
|
+
if (Array.isArray(obj[arrName])) {
|
|
373
|
+
for (const item of obj[arrName]) {
|
|
374
|
+
if (item[field] && !allowedValues.includes(item[field])) {
|
|
375
|
+
item[field] = severityMap[item[field]] || 'major';
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
const result = JSON.stringify(obj, null, 2);
|
|
381
|
+
console.log(`[DEBUG] Fast-path fix for ${rule.id}: corrected ${field} values (no LLM call)`);
|
|
382
|
+
if (this.tracker) this.tracker.endRuleFix(result.length);
|
|
383
|
+
return result;
|
|
384
|
+
} catch { /* fall through */ }
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
// Fast-path fix: convert non-array fields to arrays
|
|
388
|
+
if (rule.fastPath?.enabled && rule.fastPath.type === 'json-arrays') {
|
|
389
|
+
try {
|
|
390
|
+
const { unwrapped } = this.unwrapJsonCodeFence(content);
|
|
391
|
+
const obj = JSON.parse(unwrapped);
|
|
392
|
+
const arrayFields = rule.fastPath.arrayFields || [];
|
|
393
|
+
let fixed = false;
|
|
394
|
+
for (const field of arrayFields) {
|
|
395
|
+
if (field in obj && !Array.isArray(obj[field])) {
|
|
396
|
+
const val = obj[field];
|
|
397
|
+
if (val === null || val === undefined) {
|
|
398
|
+
obj[field] = [];
|
|
399
|
+
} else if (typeof val === 'string') {
|
|
400
|
+
obj[field] = val ? [val] : [];
|
|
401
|
+
} else if (typeof val === 'object') {
|
|
402
|
+
obj[field] = [val];
|
|
403
|
+
} else {
|
|
404
|
+
obj[field] = [];
|
|
405
|
+
}
|
|
406
|
+
fixed = true;
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
if (fixed) {
|
|
410
|
+
const result = JSON.stringify(obj, null, 2);
|
|
411
|
+
console.log(`[DEBUG] Fast-path fix for ${rule.id}: converted non-array fields to arrays (no LLM call)`);
|
|
412
|
+
if (this.tracker) {
|
|
413
|
+
this.tracker.endRuleFix(result.length);
|
|
414
|
+
}
|
|
415
|
+
return result;
|
|
416
|
+
}
|
|
417
|
+
} catch { /* fall through to LLM */ }
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
// Fast-path fix: add missing required fields with defaults from fix prompt
|
|
421
|
+
if (rule.fastPath?.enabled && rule.fastPath.type === 'json-fields') {
|
|
422
|
+
try {
|
|
423
|
+
const { unwrapped } = this.unwrapJsonCodeFence(content);
|
|
424
|
+
const obj = JSON.parse(unwrapped);
|
|
425
|
+
const missing = (rule.fastPath.requiredFields || []).filter(f => !(f in obj));
|
|
426
|
+
if (missing.length > 0) {
|
|
427
|
+
// Parse defaults from fix prompt — use sensible fallbacks
|
|
428
|
+
const defaults = {
|
|
429
|
+
validationStatus: 'needs-improvement',
|
|
430
|
+
overallScore: 50,
|
|
431
|
+
structuralIssues: [],
|
|
432
|
+
contentIssues: [],
|
|
433
|
+
applicationFlowGaps: [],
|
|
434
|
+
strengths: ['Document structure present'],
|
|
435
|
+
improvementPriorities: [],
|
|
436
|
+
readyForPublication: false,
|
|
437
|
+
};
|
|
438
|
+
for (const field of missing) {
|
|
439
|
+
obj[field] = defaults[field] !== undefined ? defaults[field] : null;
|
|
440
|
+
}
|
|
441
|
+
const fixed = JSON.stringify(obj, null, 2);
|
|
442
|
+
console.log(`[DEBUG] Fast-path fix for ${rule.id}: added missing fields [${missing.join(', ')}] (no LLM call)`);
|
|
443
|
+
if (this.tracker) {
|
|
444
|
+
this.tracker.endRuleFix(fixed.length);
|
|
445
|
+
}
|
|
446
|
+
return fixed;
|
|
447
|
+
}
|
|
448
|
+
} catch { /* JSON parse failed — fall through to LLM */ }
|
|
449
|
+
}
|
|
450
|
+
|
|
243
451
|
// Fallback to LLM fix
|
|
244
452
|
const prompt = rule.fix.prompt.replace('{content}', content);
|
|
245
453
|
const maxTokens = rule.fix.maxTokens || 4096;
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* llm-xiaomi.js — Xiaomi MiMo LLM provider.
|
|
3
|
+
*
|
|
4
|
+
* Uses the OpenAI-compatible API at https://api.xiaomimimo.com/v1.
|
|
5
|
+
* Supports: mimo-v2-flash, mimo-v2-pro, mimo-v2-omni.
|
|
6
|
+
*
|
|
7
|
+
* Requires XIAOMI_API_KEY environment variable.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import OpenAI from 'openai';
|
|
11
|
+
import { LLMProvider } from './llm-provider.js';
|
|
12
|
+
|
|
13
|
+
const XIAOMI_BASE_URL = 'https://api.xiaomimimo.com/v1';
|
|
14
|
+
|
|
15
|
+
function cleanResponse(text) {
|
|
16
|
+
if (!text) return '';
|
|
17
|
+
// Strip markdown code fences
|
|
18
|
+
return text.replace(/^```(?:json)?\s*\n?/i, '').replace(/\n?```\s*$/i, '').trim();
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function extractJSON(text) {
|
|
22
|
+
// Try to find JSON object or array in the response
|
|
23
|
+
const jsonMatch = text.match(/(\{[\s\S]*\}|\[[\s\S]*\])/);
|
|
24
|
+
return jsonMatch ? jsonMatch[1] : text;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export class XiaomiProvider extends LLMProvider {
|
|
28
|
+
constructor(model) {
|
|
29
|
+
super('xiaomi', model || 'mimo-v2-flash');
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
_createClient() {
|
|
33
|
+
const apiKey = process.env.XIAOMI_API_KEY;
|
|
34
|
+
if (!apiKey) {
|
|
35
|
+
throw new Error('XIAOMI_API_KEY not set. Add it to your .env file. Get a key at https://platform.xiaomimimo.com/#/console/api-keys');
|
|
36
|
+
}
|
|
37
|
+
return new OpenAI({
|
|
38
|
+
apiKey,
|
|
39
|
+
baseURL: XIAOMI_BASE_URL,
|
|
40
|
+
timeout: 10 * 60_000, // 10 min
|
|
41
|
+
maxRetries: 0,
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
async _callProvider(prompt, maxTokens, systemInstructions) {
|
|
46
|
+
if (!this._client) this._client = this._createClient();
|
|
47
|
+
|
|
48
|
+
const messages = [];
|
|
49
|
+
if (systemInstructions) {
|
|
50
|
+
messages.push({ role: 'system', content: systemInstructions });
|
|
51
|
+
}
|
|
52
|
+
messages.push({ role: 'user', content: prompt });
|
|
53
|
+
|
|
54
|
+
const response = await this._client.chat.completions.create({
|
|
55
|
+
model: this.model,
|
|
56
|
+
messages,
|
|
57
|
+
max_tokens: maxTokens,
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
const usage = response.usage;
|
|
61
|
+
this._trackTokens(usage, { prompt, agentInstructions: systemInstructions, response: response.choices?.[0]?.message?.content });
|
|
62
|
+
|
|
63
|
+
return response.choices?.[0]?.message?.content || '';
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
async generateJSON(prompt, agentInstructions = null, cachedContext = null) {
|
|
67
|
+
if (!this._client) this._client = this._createClient();
|
|
68
|
+
|
|
69
|
+
const JSON_SYSTEM = 'You are a helpful assistant that always returns valid JSON. Your response must be a valid JSON object or array, nothing else. Do not include any thinking, reasoning, or explanation — only the JSON.';
|
|
70
|
+
const systemParts = [JSON_SYSTEM];
|
|
71
|
+
if (agentInstructions) systemParts.push(agentInstructions);
|
|
72
|
+
if (cachedContext) systemParts.push(`---\n\n${cachedContext}`);
|
|
73
|
+
|
|
74
|
+
const messages = [
|
|
75
|
+
{ role: 'system', content: systemParts.join('\n\n') },
|
|
76
|
+
{ role: 'user', content: prompt },
|
|
77
|
+
];
|
|
78
|
+
|
|
79
|
+
const _t0 = Date.now();
|
|
80
|
+
const response = await this._withRetry(
|
|
81
|
+
() => this._client.chat.completions.create({
|
|
82
|
+
model: this.model,
|
|
83
|
+
messages,
|
|
84
|
+
response_format: { type: 'json_object' },
|
|
85
|
+
}),
|
|
86
|
+
'JSON generation (Xiaomi)'
|
|
87
|
+
);
|
|
88
|
+
|
|
89
|
+
const content = response.choices?.[0]?.message?.content || '';
|
|
90
|
+
this._trackTokens(response.usage, {
|
|
91
|
+
prompt,
|
|
92
|
+
agentInstructions: agentInstructions ?? null,
|
|
93
|
+
response: content,
|
|
94
|
+
elapsed: Date.now() - _t0,
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
const jsonStr = extractJSON(cleanResponse(content));
|
|
98
|
+
try {
|
|
99
|
+
return JSON.parse(jsonStr);
|
|
100
|
+
} catch (firstError) {
|
|
101
|
+
// Try jsonrepair as fallback
|
|
102
|
+
try {
|
|
103
|
+
const { jsonrepair } = await import('jsonrepair');
|
|
104
|
+
return JSON.parse(jsonrepair(jsonStr));
|
|
105
|
+
} catch {
|
|
106
|
+
throw new Error(`Failed to parse JSON from Xiaomi (${this.model}): ${firstError.message}\n\nResponse:\n${content.slice(0, 500)}`);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
async generateText(prompt, agentInstructions = null, cachedContext = null) {
|
|
112
|
+
if (!this._client) this._client = this._createClient();
|
|
113
|
+
|
|
114
|
+
const systemParts = [];
|
|
115
|
+
if (agentInstructions) systemParts.push(agentInstructions);
|
|
116
|
+
if (cachedContext) systemParts.push(cachedContext);
|
|
117
|
+
|
|
118
|
+
const messages = [];
|
|
119
|
+
if (systemParts.length > 0) {
|
|
120
|
+
messages.push({ role: 'system', content: systemParts.join('\n\n') });
|
|
121
|
+
}
|
|
122
|
+
messages.push({ role: 'user', content: prompt });
|
|
123
|
+
|
|
124
|
+
const _t0 = Date.now();
|
|
125
|
+
const response = await this._withRetry(
|
|
126
|
+
() => this._client.chat.completions.create({
|
|
127
|
+
model: this.model,
|
|
128
|
+
messages,
|
|
129
|
+
}),
|
|
130
|
+
'Text generation (Xiaomi)'
|
|
131
|
+
);
|
|
132
|
+
|
|
133
|
+
const content = response.choices?.[0]?.message?.content || '';
|
|
134
|
+
this._trackTokens(response.usage, {
|
|
135
|
+
prompt,
|
|
136
|
+
agentInstructions: agentInstructions ?? null,
|
|
137
|
+
response: content,
|
|
138
|
+
elapsed: Date.now() - _t0,
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
return content;
|
|
142
|
+
}
|
|
143
|
+
}
|
package/cli/message-constants.js
CHANGED
|
@@ -21,18 +21,9 @@ export const MESSAGES = {
|
|
|
21
21
|
* Ceremony headers with titles and documentation URLs
|
|
22
22
|
*/
|
|
23
23
|
CEREMONY_HEADERS: {
|
|
24
|
-
'sponsor-call':
|
|
25
|
-
|
|
26
|
-
|
|
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
|
-
}
|
|
24
|
+
'sponsor-call': { title: 'Sponsor Call Ceremony' },
|
|
25
|
+
'sprint-planning': { title: 'Sprint Planning Ceremony' },
|
|
26
|
+
'seed': { title: 'Seed Ceremony' },
|
|
36
27
|
}
|
|
37
28
|
};
|
|
38
29
|
|
package/cli/messaging-api.js
CHANGED
|
@@ -67,22 +67,16 @@ export function cancelCommand() {
|
|
|
67
67
|
* Send a ceremony header (title + documentation URL)
|
|
68
68
|
* Automatically prevents duplicate headers within same command execution
|
|
69
69
|
*
|
|
70
|
-
* @param {string} title - Ceremony title
|
|
71
|
-
* @param {string} url - Documentation URL
|
|
72
|
-
*
|
|
73
|
-
* @example
|
|
74
|
-
* sendCeremonyHeader('Sponsor Call Ceremony', 'https://agilevibecoding.org/ceremonies/sponsor-call');
|
|
70
|
+
* @param {string} title - Ceremony title
|
|
75
71
|
*/
|
|
76
|
-
export function sendCeremonyHeader(title
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
// Prevent duplicate ceremony headers
|
|
80
|
-
if (ceremonySent.has(key)) {
|
|
72
|
+
export function sendCeremonyHeader(title) {
|
|
73
|
+
// Prevent duplicate ceremony headers within the same command execution
|
|
74
|
+
if (ceremonySent.has(title)) {
|
|
81
75
|
return;
|
|
82
76
|
}
|
|
83
77
|
|
|
84
|
-
ceremonySent.add(
|
|
85
|
-
const content =
|
|
78
|
+
ceremonySent.add(title);
|
|
79
|
+
const content = boldCyan(title);
|
|
86
80
|
messageManager.send(MessageType.CEREMONY_HEADER, content);
|
|
87
81
|
}
|
|
88
82
|
|