@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
|
@@ -0,0 +1,538 @@
|
|
|
1
|
+
import { readFileSync, existsSync } from 'fs';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import { fileURLToPath } from 'url';
|
|
4
|
+
import { runTier1Check, runTier2Check, runTier1Batch, runTier2Batch } from './micro-check-runner.js';
|
|
5
|
+
import { scoreChecks } from './micro-check-scorer.js';
|
|
6
|
+
import { fixFailedChecks } from './micro-check-fixer.js';
|
|
7
|
+
|
|
8
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
9
|
+
const __dirname = path.dirname(__filename);
|
|
10
|
+
|
|
11
|
+
// ---------------------------------------------------------------------------
|
|
12
|
+
// Debug helper — matches existing patterns in epic-story-validator.js
|
|
13
|
+
// ---------------------------------------------------------------------------
|
|
14
|
+
|
|
15
|
+
// ---------------------------------------------------------------------------
|
|
16
|
+
// Utility
|
|
17
|
+
// ---------------------------------------------------------------------------
|
|
18
|
+
|
|
19
|
+
function chunkArray(array, size) {
|
|
20
|
+
const chunks = [];
|
|
21
|
+
for (let i = 0; i < array.length; i += size) {
|
|
22
|
+
chunks.push(array.slice(i, i + size));
|
|
23
|
+
}
|
|
24
|
+
return chunks;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function debug(message, data = null) {
|
|
28
|
+
const timestamp = new Date().toISOString();
|
|
29
|
+
if (data) {
|
|
30
|
+
console.log(`[DEBUG][${timestamp}] ${message}`, JSON.stringify(data, null, 2));
|
|
31
|
+
} else {
|
|
32
|
+
console.log(`[DEBUG][${timestamp}] ${message}`);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// ---------------------------------------------------------------------------
|
|
37
|
+
// Check-definition loader
|
|
38
|
+
// ---------------------------------------------------------------------------
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Load Tier 1 and Tier 2 check definitions for the requested perspectives.
|
|
42
|
+
*
|
|
43
|
+
* Resolution order per perspective:
|
|
44
|
+
* 1. Project override .avc/customized-agents/checks/{workItemType}/{perspective}.json
|
|
45
|
+
* 2. Built-in src/cli/checks/{workItemType}/{perspective}.json
|
|
46
|
+
*
|
|
47
|
+
* @param {string} workItemType - "epic" or "story"
|
|
48
|
+
* @param {string[]} perspectives - List of perspective names
|
|
49
|
+
* @param {string} [projectRoot] - Project root for override loading
|
|
50
|
+
* @returns {{ tier1: Object[], tier2: Object[] }}
|
|
51
|
+
*/
|
|
52
|
+
function loadCheckDefinitions(workItemType, perspectives, projectRoot) {
|
|
53
|
+
const checks = { tier1: [], tier2: [] };
|
|
54
|
+
const checksDir = path.join(__dirname, 'checks');
|
|
55
|
+
|
|
56
|
+
debug('Loading check definitions', { workItemType, perspectives, checksDir });
|
|
57
|
+
|
|
58
|
+
for (const perspective of perspectives) {
|
|
59
|
+
// Resolve Tier 1 file — project override takes precedence
|
|
60
|
+
const builtinPath = path.join(checksDir, workItemType, `${perspective}.json`);
|
|
61
|
+
const overridePath = projectRoot
|
|
62
|
+
? path.join(projectRoot, '.avc', 'customized-agents', 'checks', workItemType, `${perspective}.json`)
|
|
63
|
+
: null;
|
|
64
|
+
const filePath = (overridePath && existsSync(overridePath)) ? overridePath : builtinPath;
|
|
65
|
+
|
|
66
|
+
if (existsSync(filePath)) {
|
|
67
|
+
try {
|
|
68
|
+
const raw = JSON.parse(readFileSync(filePath, 'utf8'));
|
|
69
|
+
// Support both formats: plain array or { checks: [...] } wrapper
|
|
70
|
+
const perspectiveChecks = Array.isArray(raw) ? raw : (Array.isArray(raw.checks) ? raw.checks : []);
|
|
71
|
+
debug(`Loaded ${perspectiveChecks.length} Tier 1 checks from ${filePath}`);
|
|
72
|
+
checks.tier1.push(...perspectiveChecks);
|
|
73
|
+
} catch (err) {
|
|
74
|
+
console.error(`[micro-check-validator] Failed to parse ${filePath}: ${err.message}`);
|
|
75
|
+
}
|
|
76
|
+
} else {
|
|
77
|
+
debug(`No Tier 1 check file found for perspective "${perspective}" (looked at ${builtinPath})`);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// Load Tier 2 cross-reference checks
|
|
82
|
+
const crossRefBuiltin = path.join(checksDir, 'cross-refs', `${workItemType}.json`);
|
|
83
|
+
const crossRefOverride = projectRoot
|
|
84
|
+
? path.join(projectRoot, '.avc', 'customized-agents', 'checks', 'cross-refs', `${workItemType}.json`)
|
|
85
|
+
: null;
|
|
86
|
+
const crossRefPath = (crossRefOverride && existsSync(crossRefOverride)) ? crossRefOverride : crossRefBuiltin;
|
|
87
|
+
|
|
88
|
+
if (existsSync(crossRefPath)) {
|
|
89
|
+
try {
|
|
90
|
+
const raw = JSON.parse(readFileSync(crossRefPath, 'utf8'));
|
|
91
|
+
const crossRefChecks = Array.isArray(raw) ? raw : (Array.isArray(raw.checks) ? raw.checks : []);
|
|
92
|
+
// Only include cross-refs whose perspectives overlap with selected perspectives
|
|
93
|
+
const applicable = crossRefChecks.filter(c =>
|
|
94
|
+
c.perspectives && c.perspectives.some(p => perspectives.includes(p))
|
|
95
|
+
);
|
|
96
|
+
debug(`Loaded ${applicable.length}/${crossRefChecks.length} Tier 2 cross-ref checks from ${crossRefPath}`);
|
|
97
|
+
checks.tier2.push(...applicable);
|
|
98
|
+
} catch (err) {
|
|
99
|
+
console.error(`[micro-check-validator] Failed to parse ${crossRefPath}: ${err.message}`);
|
|
100
|
+
}
|
|
101
|
+
} else {
|
|
102
|
+
debug(`No Tier 2 cross-ref file found (looked at ${crossRefBuiltin})`);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
debug('Check definitions loaded', {
|
|
106
|
+
tier1Count: checks.tier1.length,
|
|
107
|
+
tier2Count: checks.tier2.length
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
return checks;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// ---------------------------------------------------------------------------
|
|
114
|
+
// Concurrency-limited parallel runner
|
|
115
|
+
// ---------------------------------------------------------------------------
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Run an array of async task factories with a concurrency cap.
|
|
119
|
+
*
|
|
120
|
+
* @param {Array<() => Promise>} tasks - Factory functions returning promises
|
|
121
|
+
* @param {number} concurrency - Max parallel tasks
|
|
122
|
+
* @returns {Promise<any[]>}
|
|
123
|
+
*/
|
|
124
|
+
async function runWithConcurrency(tasks, concurrency) {
|
|
125
|
+
const results = [];
|
|
126
|
+
const executing = new Set();
|
|
127
|
+
|
|
128
|
+
for (const task of tasks) {
|
|
129
|
+
const p = task().then(result => {
|
|
130
|
+
executing.delete(p);
|
|
131
|
+
return result;
|
|
132
|
+
});
|
|
133
|
+
executing.add(p);
|
|
134
|
+
results.push(p);
|
|
135
|
+
|
|
136
|
+
if (executing.size >= concurrency) {
|
|
137
|
+
await Promise.race(executing);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
return Promise.all(results);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// ---------------------------------------------------------------------------
|
|
145
|
+
// Format helpers
|
|
146
|
+
// ---------------------------------------------------------------------------
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* Convert a micro-check result into the issue format used by the existing
|
|
150
|
+
* aggregateValidationResults() shape.
|
|
151
|
+
*
|
|
152
|
+
* @param {Object} check - A single check result object
|
|
153
|
+
* @returns {Object}
|
|
154
|
+
*/
|
|
155
|
+
function toIssueFormat(check) {
|
|
156
|
+
return {
|
|
157
|
+
severity: check.severity,
|
|
158
|
+
category: check.category,
|
|
159
|
+
description: check.failDescription || `Failed: ${check.id}`,
|
|
160
|
+
suggestion: check.failSuggestion || '',
|
|
161
|
+
example: ''
|
|
162
|
+
};
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* Compute a per-perspective score by filtering allResults down to checks
|
|
167
|
+
* belonging to a single perspective and scoring them independently.
|
|
168
|
+
*
|
|
169
|
+
* @param {string} perspective - Perspective name
|
|
170
|
+
* @param {Object[]} allResults - Combined Tier 1 + Tier 2 results
|
|
171
|
+
* @returns {Object} scoreChecks() output for this perspective slice
|
|
172
|
+
*/
|
|
173
|
+
function perspectiveSlice(perspective, allResults) {
|
|
174
|
+
const slice = allResults.filter(r =>
|
|
175
|
+
r.perspective === perspective ||
|
|
176
|
+
(Array.isArray(r.perspectives) && r.perspectives.includes(perspective))
|
|
177
|
+
);
|
|
178
|
+
if (slice.length === 0) {
|
|
179
|
+
return { score: 100, status: 'excellent', applicableCount: 0, skippedCount: 0 };
|
|
180
|
+
}
|
|
181
|
+
return scoreChecks(slice);
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
/**
|
|
185
|
+
* Derive issues for a single perspective from allResults.
|
|
186
|
+
*
|
|
187
|
+
* @param {string} perspective - Perspective name
|
|
188
|
+
* @param {Object[]} allResults - Combined Tier 1 + Tier 2 results
|
|
189
|
+
* @returns {Object[]}
|
|
190
|
+
*/
|
|
191
|
+
function perspectiveIssues(perspective, allResults) {
|
|
192
|
+
return allResults
|
|
193
|
+
.filter(r =>
|
|
194
|
+
(r.perspective === perspective ||
|
|
195
|
+
(Array.isArray(r.perspectives) && r.perspectives.includes(perspective))) &&
|
|
196
|
+
r.passed === false // excludes null (errored) and true (passed)
|
|
197
|
+
)
|
|
198
|
+
.map(toIssueFormat);
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
// ---------------------------------------------------------------------------
|
|
202
|
+
// Main entry point
|
|
203
|
+
// ---------------------------------------------------------------------------
|
|
204
|
+
|
|
205
|
+
/**
|
|
206
|
+
* Validate a work item using the 3-tier micro-check system.
|
|
207
|
+
* Single flow for ALL models — decomposed checks with programmatic scoring.
|
|
208
|
+
*
|
|
209
|
+
* @param {Object} workItem - Epic or story work.json object
|
|
210
|
+
* @param {string} workItemText - Work item context markdown
|
|
211
|
+
* @param {string} workItemType - "epic" or "story"
|
|
212
|
+
* @param {string[]} perspectives - Perspective names to validate
|
|
213
|
+
* @param {Object} llmProvider - LLM provider instance
|
|
214
|
+
* @param {Object} [options] - Configuration options
|
|
215
|
+
* @param {number} [options.concurrency=5] - Max parallel LLM calls
|
|
216
|
+
* @param {number} [options.maxFixAttempts=3] - Max fix iterations
|
|
217
|
+
* @param {Function} [options.generateContextFn] - Regenerate context markdown from work item
|
|
218
|
+
* @param {Function} [options.progressCallback] - Progress reporting callback
|
|
219
|
+
* @param {string} [options.projectRoot] - Project root for check override loading
|
|
220
|
+
* @returns {Promise<Object>} Result compatible with existing validateEpic()/validateStory() return shape
|
|
221
|
+
*/
|
|
222
|
+
export async function validateWithMicroChecks(
|
|
223
|
+
workItem,
|
|
224
|
+
workItemText,
|
|
225
|
+
workItemType,
|
|
226
|
+
perspectives,
|
|
227
|
+
llmProvider,
|
|
228
|
+
options = {}
|
|
229
|
+
) {
|
|
230
|
+
const {
|
|
231
|
+
concurrency = 5,
|
|
232
|
+
batchSize = 8,
|
|
233
|
+
maxFixAttempts = 3,
|
|
234
|
+
generateContextFn = null,
|
|
235
|
+
progressCallback = null,
|
|
236
|
+
projectRoot = null
|
|
237
|
+
} = options;
|
|
238
|
+
|
|
239
|
+
const startTime = Date.now();
|
|
240
|
+
debug('validateWithMicroChecks START', {
|
|
241
|
+
workItemType,
|
|
242
|
+
perspectives,
|
|
243
|
+
concurrency,
|
|
244
|
+
maxFixAttempts,
|
|
245
|
+
hasGenerateContextFn: !!generateContextFn
|
|
246
|
+
});
|
|
247
|
+
|
|
248
|
+
const progress = (msg) => {
|
|
249
|
+
debug(msg);
|
|
250
|
+
if (progressCallback) {
|
|
251
|
+
progressCallback(msg);
|
|
252
|
+
}
|
|
253
|
+
};
|
|
254
|
+
|
|
255
|
+
// -----------------------------------------------------------------
|
|
256
|
+
// Step 1 — Load check definitions
|
|
257
|
+
// -----------------------------------------------------------------
|
|
258
|
+
const checks = loadCheckDefinitions(workItemType, perspectives, projectRoot);
|
|
259
|
+
progress(`Loaded ${checks.tier1.length} Tier 1 + ${checks.tier2.length} Tier 2 checks for ${perspectives.length} perspective(s)`);
|
|
260
|
+
|
|
261
|
+
// -----------------------------------------------------------------
|
|
262
|
+
// Step 2 — Phase 1: Run Tier 1 checks in parallel
|
|
263
|
+
// -----------------------------------------------------------------
|
|
264
|
+
progress('Phase 1: Running Tier 1 checks...');
|
|
265
|
+
const tier1Start = Date.now();
|
|
266
|
+
|
|
267
|
+
const tier1Batches = chunkArray(checks.tier1, batchSize);
|
|
268
|
+
debug(`Tier 1: ${checks.tier1.length} checks in ${tier1Batches.length} batch(es) of up to ${batchSize}`);
|
|
269
|
+
|
|
270
|
+
const tier1Tasks = tier1Batches.map(batch =>
|
|
271
|
+
() => runTier1Batch(batch, workItemText, llmProvider, workItemType).catch(err => {
|
|
272
|
+
debug(`Tier 1 batch error (${batch.length} checks): ${err.message}`);
|
|
273
|
+
// Fallback: run each check individually
|
|
274
|
+
return Promise.all(batch.map(check =>
|
|
275
|
+
runTier1Check(check, workItemText, llmProvider, workItemType).catch(indivErr => {
|
|
276
|
+
debug(`Tier 1 check ${check.id} individual fallback error: ${indivErr.message}`);
|
|
277
|
+
return {
|
|
278
|
+
id: check.id, tier: 1, severity: check.severity, category: check.category,
|
|
279
|
+
perspective: check.perspective, universal: check.universal,
|
|
280
|
+
applicable: true, passed: null, evidence: `Error: ${indivErr.message}`,
|
|
281
|
+
failDescription: check.failDescription, failSuggestion: check.failSuggestion,
|
|
282
|
+
};
|
|
283
|
+
})
|
|
284
|
+
));
|
|
285
|
+
})
|
|
286
|
+
);
|
|
287
|
+
|
|
288
|
+
const tier1BatchResults = await runWithConcurrency(tier1Tasks, concurrency);
|
|
289
|
+
const tier1ResultsArray = tier1BatchResults.flat();
|
|
290
|
+
|
|
291
|
+
// Build Map: checkId → result
|
|
292
|
+
const tier1Results = new Map();
|
|
293
|
+
let tier1Passed = 0;
|
|
294
|
+
let tier1Failed = 0;
|
|
295
|
+
let tier1Skipped = 0;
|
|
296
|
+
let tier1Errored = 0;
|
|
297
|
+
|
|
298
|
+
for (const result of tier1ResultsArray) {
|
|
299
|
+
tier1Results.set(result.id, result);
|
|
300
|
+
if (result.applicable === false) {
|
|
301
|
+
tier1Skipped++;
|
|
302
|
+
} else if (result.passed === null) {
|
|
303
|
+
tier1Errored++;
|
|
304
|
+
} else if (result.passed) {
|
|
305
|
+
tier1Passed++;
|
|
306
|
+
} else {
|
|
307
|
+
tier1Failed++;
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
const tier1Elapsed = Date.now() - tier1Start;
|
|
312
|
+
const errorSuffix = tier1Errored > 0 ? `, ${tier1Errored} errored` : '';
|
|
313
|
+
progress(
|
|
314
|
+
`Tier 1: ${tier1ResultsArray.length}/${checks.tier1.length} checks in ${tier1Batches.length} batch(es) ` +
|
|
315
|
+
`(${tier1Passed} passed, ${tier1Failed} failed, ${tier1Skipped} skipped${errorSuffix}) — ${tier1Elapsed}ms`
|
|
316
|
+
);
|
|
317
|
+
|
|
318
|
+
// -----------------------------------------------------------------
|
|
319
|
+
// Step 3 — Phase 2: Run Tier 2 cross-reference checks in parallel
|
|
320
|
+
// -----------------------------------------------------------------
|
|
321
|
+
progress('Phase 2: Running Tier 2 cross-reference checks...');
|
|
322
|
+
const tier2Start = Date.now();
|
|
323
|
+
|
|
324
|
+
// Only run Tier 2 checks whose dependsOn checks are ALL present in tier1Results
|
|
325
|
+
const applicableTier2 = checks.tier2.filter(check => {
|
|
326
|
+
if (!Array.isArray(check.dependsOn) || check.dependsOn.length === 0) {
|
|
327
|
+
return true;
|
|
328
|
+
}
|
|
329
|
+
return check.dependsOn.every(depId => tier1Results.has(depId));
|
|
330
|
+
});
|
|
331
|
+
|
|
332
|
+
debug(`Tier 2: ${applicableTier2.length}/${checks.tier2.length} checks have satisfied dependencies`);
|
|
333
|
+
|
|
334
|
+
const tier2Batches = chunkArray(applicableTier2, batchSize);
|
|
335
|
+
debug(`Tier 2: ${applicableTier2.length} checks in ${tier2Batches.length} batch(es) of up to ${batchSize}`);
|
|
336
|
+
|
|
337
|
+
const tier2Tasks = tier2Batches.map(batch =>
|
|
338
|
+
() => runTier2Batch(batch, workItemText, tier1Results, llmProvider, workItemType).catch(err => {
|
|
339
|
+
debug(`Tier 2 batch error (${batch.length} checks): ${err.message}`);
|
|
340
|
+
// Fallback: run each check individually
|
|
341
|
+
return Promise.all(batch.map(check =>
|
|
342
|
+
runTier2Check(check, workItemText, tier1Results, llmProvider).catch(indivErr => {
|
|
343
|
+
debug(`Tier 2 check ${check.id} individual fallback error: ${indivErr.message}`);
|
|
344
|
+
return {
|
|
345
|
+
id: check.id, tier: 2, severity: check.severity, category: check.category,
|
|
346
|
+
perspectives: check.perspectives,
|
|
347
|
+
applicable: true, passed: null, evidence: `Error: ${indivErr.message}`,
|
|
348
|
+
failDescription: check.failDescription, failSuggestion: check.failSuggestion,
|
|
349
|
+
};
|
|
350
|
+
})
|
|
351
|
+
));
|
|
352
|
+
})
|
|
353
|
+
);
|
|
354
|
+
|
|
355
|
+
const tier2BatchResults = await runWithConcurrency(tier2Tasks, concurrency);
|
|
356
|
+
const tier2ResultsArray = tier2BatchResults.flat();
|
|
357
|
+
|
|
358
|
+
let tier2Passed = 0;
|
|
359
|
+
let tier2Failed = 0;
|
|
360
|
+
let tier2Skipped = 0;
|
|
361
|
+
let tier2Errored = 0;
|
|
362
|
+
|
|
363
|
+
for (const result of tier2ResultsArray) {
|
|
364
|
+
if (result.applicable === false) {
|
|
365
|
+
tier2Skipped++;
|
|
366
|
+
} else if (result.passed === null) {
|
|
367
|
+
tier2Errored++;
|
|
368
|
+
} else if (result.passed) {
|
|
369
|
+
tier2Passed++;
|
|
370
|
+
} else {
|
|
371
|
+
tier2Failed++;
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
const tier2Elapsed = Date.now() - tier2Start;
|
|
376
|
+
const tier2ErrorSuffix = tier2Errored > 0 ? `, ${tier2Errored} errored` : '';
|
|
377
|
+
progress(
|
|
378
|
+
`Tier 2: ${tier2ResultsArray.length}/${applicableTier2.length} checks in ${tier2Batches.length} batch(es) ` +
|
|
379
|
+
`(${tier2Passed} passed, ${tier2Failed} failed, ${tier2Skipped} skipped${tier2ErrorSuffix}) — ${tier2Elapsed}ms`
|
|
380
|
+
);
|
|
381
|
+
|
|
382
|
+
// -----------------------------------------------------------------
|
|
383
|
+
// Step 4 — Phase 3: Programmatic scoring (Tier 3 patterns included)
|
|
384
|
+
// -----------------------------------------------------------------
|
|
385
|
+
progress('Phase 3: Scoring...');
|
|
386
|
+
|
|
387
|
+
let allResults = [...tier1ResultsArray, ...tier2ResultsArray];
|
|
388
|
+
let scored = scoreChecks(allResults);
|
|
389
|
+
|
|
390
|
+
const criticalFails = allResults.filter(r => r.passed === false && r.severity === 'critical').length;
|
|
391
|
+
const majorFails = allResults.filter(r => r.passed === false && r.severity === 'major').length;
|
|
392
|
+
const minorFails = allResults.filter(r => r.passed === false && r.severity === 'minor').length;
|
|
393
|
+
const totalErrored = allResults.filter(r => r.passed === null && r.applicable !== false).length;
|
|
394
|
+
|
|
395
|
+
progress(
|
|
396
|
+
`Score: ${scored.score}/100 (${scored.status}) — ` +
|
|
397
|
+
`${criticalFails} critical, ${majorFails} major, ${minorFails} minor` +
|
|
398
|
+
(totalErrored > 0 ? ` (${totalErrored} checks excluded due to LLM errors)` : '')
|
|
399
|
+
);
|
|
400
|
+
|
|
401
|
+
// Log individual failed checks for diagnostics
|
|
402
|
+
const failedDetails = allResults.filter(r => r.passed === false);
|
|
403
|
+
if (failedDetails.length > 0) {
|
|
404
|
+
debug('Failed checks detail', failedDetails.map(r => ({
|
|
405
|
+
id: r.id,
|
|
406
|
+
severity: r.severity,
|
|
407
|
+
perspective: r.perspective || r.perspectives,
|
|
408
|
+
evidence: (r.evidence || '').slice(0, 200),
|
|
409
|
+
})));
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
// -----------------------------------------------------------------
|
|
413
|
+
// Step 5 — Phase 4: Fix failed checks (if critical or major)
|
|
414
|
+
// -----------------------------------------------------------------
|
|
415
|
+
let fixResults = null;
|
|
416
|
+
|
|
417
|
+
if (criticalFails > 0 || majorFails > 0) {
|
|
418
|
+
progress('Phase 4: Attempting fixes for critical/major failures...');
|
|
419
|
+
const fixStart = Date.now();
|
|
420
|
+
|
|
421
|
+
// Collect failed checks sorted by severity (critical first, then major)
|
|
422
|
+
const severityOrder = { critical: 0, major: 1, minor: 2 };
|
|
423
|
+
const failedChecks = allResults
|
|
424
|
+
.filter(r => r.passed === false && (r.severity === 'critical' || r.severity === 'major'))
|
|
425
|
+
.sort((a, b) => (severityOrder[a.severity] || 99) - (severityOrder[b.severity] || 99));
|
|
426
|
+
|
|
427
|
+
debug(`Attempting fixes for ${failedChecks.length} failed checks`, {
|
|
428
|
+
ids: failedChecks.map(c => c.id)
|
|
429
|
+
});
|
|
430
|
+
|
|
431
|
+
try {
|
|
432
|
+
fixResults = await fixFailedChecks({
|
|
433
|
+
failedChecks,
|
|
434
|
+
allCheckDefinitions: [...checks.tier1, ...checks.tier2],
|
|
435
|
+
allCheckResults: allResults,
|
|
436
|
+
workItem,
|
|
437
|
+
workItemText,
|
|
438
|
+
workItemType,
|
|
439
|
+
llmProvider,
|
|
440
|
+
maxFixAttempts,
|
|
441
|
+
generateContextFn,
|
|
442
|
+
progressCallback: progress,
|
|
443
|
+
concurrency,
|
|
444
|
+
tier1Results,
|
|
445
|
+
});
|
|
446
|
+
|
|
447
|
+
const fixElapsed = Date.now() - fixStart;
|
|
448
|
+
debug(`Fix phase completed in ${fixElapsed}ms`, {
|
|
449
|
+
fixesAttempted: fixResults?.fixResults?.length || 0,
|
|
450
|
+
regressionDetected: fixResults?.regressionDetected || false
|
|
451
|
+
});
|
|
452
|
+
|
|
453
|
+
// If fixes were applied, rescore using the rerun results
|
|
454
|
+
if (fixResults && fixResults.rerunResults && fixResults.rerunResults.length > 0) {
|
|
455
|
+
progress('Rescoring after fixes...');
|
|
456
|
+
allResults = fixResults.rerunResults;
|
|
457
|
+
scored = scoreChecks(allResults);
|
|
458
|
+
|
|
459
|
+
const postFixCritical = allResults.filter(r => r.passed === false && r.severity === 'critical').length;
|
|
460
|
+
const postFixMajor = allResults.filter(r => r.passed === false && r.severity === 'major').length;
|
|
461
|
+
const postFixMinor = allResults.filter(r => r.passed === false && r.severity === 'minor').length;
|
|
462
|
+
|
|
463
|
+
progress(
|
|
464
|
+
`Post-fix score: ${scored.score}/100 (${scored.status}) — ` +
|
|
465
|
+
`${postFixCritical} critical, ${postFixMajor} major, ${postFixMinor} minor`
|
|
466
|
+
);
|
|
467
|
+
}
|
|
468
|
+
} catch (fixErr) {
|
|
469
|
+
console.error(`[micro-check-validator] Fix phase error: ${fixErr.message}`);
|
|
470
|
+
debug('Fix phase failed', { error: fixErr.message, stack: fixErr.stack });
|
|
471
|
+
}
|
|
472
|
+
} else {
|
|
473
|
+
debug('Phase 4 skipped — no critical or major failures');
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
// -----------------------------------------------------------------
|
|
477
|
+
// Step 6 — Phase 5: Build result in existing format
|
|
478
|
+
// -----------------------------------------------------------------
|
|
479
|
+
progress('Phase 5: Building result...');
|
|
480
|
+
|
|
481
|
+
const finalScore = scored;
|
|
482
|
+
|
|
483
|
+
// Collect final failed checks across all severities (exclude errored/skipped)
|
|
484
|
+
const finalFailedChecks = allResults.filter(r => r.passed === false);
|
|
485
|
+
|
|
486
|
+
// Build per-perspective validator results
|
|
487
|
+
const validatorResults = perspectives.map(p => {
|
|
488
|
+
const pScore = perspectiveSlice(p, allResults);
|
|
489
|
+
const pIssues = perspectiveIssues(p, allResults);
|
|
490
|
+
return {
|
|
491
|
+
validator: `micro-check-${workItemType}-${p}`,
|
|
492
|
+
score: pScore.score,
|
|
493
|
+
status: pScore.status,
|
|
494
|
+
issues: pIssues
|
|
495
|
+
};
|
|
496
|
+
});
|
|
497
|
+
|
|
498
|
+
const result = {
|
|
499
|
+
averageScore: finalScore.score,
|
|
500
|
+
overallStatus: finalScore.status,
|
|
501
|
+
readyToPublish: finalScore.status !== 'needs-improvement',
|
|
502
|
+
readyForImplementation: finalScore.status !== 'needs-improvement',
|
|
503
|
+
criticalIssues: finalFailedChecks
|
|
504
|
+
.filter(c => c.severity === 'critical')
|
|
505
|
+
.map(toIssueFormat),
|
|
506
|
+
majorIssues: finalFailedChecks
|
|
507
|
+
.filter(c => c.severity === 'major')
|
|
508
|
+
.map(toIssueFormat),
|
|
509
|
+
minorIssues: finalFailedChecks
|
|
510
|
+
.filter(c => c.severity === 'minor')
|
|
511
|
+
.map(toIssueFormat),
|
|
512
|
+
validatorResults,
|
|
513
|
+
validatedAt: new Date().toISOString(),
|
|
514
|
+
microCheckDetails: {
|
|
515
|
+
tier1Count: tier1Results.size,
|
|
516
|
+
tier2Count: tier2ResultsArray.length,
|
|
517
|
+
applicableCount: finalScore.applicableCount,
|
|
518
|
+
skippedCount: finalScore.skippedCount,
|
|
519
|
+
splitRecommendation: finalScore.splitRecommendation,
|
|
520
|
+
splitReason: finalScore.splitReason,
|
|
521
|
+
clusterWarnings: finalScore.clusterWarnings,
|
|
522
|
+
fixesAttempted: fixResults?.fixResults?.length || 0,
|
|
523
|
+
regressionDetected: fixResults?.regressionDetected || false
|
|
524
|
+
}
|
|
525
|
+
};
|
|
526
|
+
|
|
527
|
+
const totalElapsed = Date.now() - startTime;
|
|
528
|
+
debug('validateWithMicroChecks COMPLETE', {
|
|
529
|
+
score: result.averageScore,
|
|
530
|
+
status: result.overallStatus,
|
|
531
|
+
criticalIssues: result.criticalIssues.length,
|
|
532
|
+
majorIssues: result.majorIssues.length,
|
|
533
|
+
minorIssues: result.minorIssues.length,
|
|
534
|
+
totalElapsedMs: totalElapsed
|
|
535
|
+
});
|
|
536
|
+
|
|
537
|
+
return result;
|
|
538
|
+
}
|
package/cli/model-pricing.js
CHANGED
|
@@ -96,6 +96,29 @@ export const MODEL_PRICING = {
|
|
|
96
96
|
unit: 1000000,
|
|
97
97
|
displayName: 'GPT-4o Mini',
|
|
98
98
|
provider: 'openai'
|
|
99
|
+
},
|
|
100
|
+
|
|
101
|
+
// Xiaomi MiMo models
|
|
102
|
+
'mimo-v2-flash': {
|
|
103
|
+
input: 0.09,
|
|
104
|
+
output: 0.29,
|
|
105
|
+
unit: 1000000,
|
|
106
|
+
displayName: 'MiMo V2 Flash',
|
|
107
|
+
provider: 'xiaomi'
|
|
108
|
+
},
|
|
109
|
+
'mimo-v2-pro': {
|
|
110
|
+
input: 1.0,
|
|
111
|
+
output: 3.0,
|
|
112
|
+
unit: 1000000,
|
|
113
|
+
displayName: 'MiMo V2 Pro',
|
|
114
|
+
provider: 'xiaomi'
|
|
115
|
+
},
|
|
116
|
+
'mimo-v2-omni': {
|
|
117
|
+
input: 0.40,
|
|
118
|
+
output: 2.0,
|
|
119
|
+
unit: 1000000,
|
|
120
|
+
displayName: 'MiMo V2 Omni',
|
|
121
|
+
provider: 'xiaomi'
|
|
99
122
|
}
|
|
100
123
|
};
|
|
101
124
|
|
package/cli/model-selector.js
CHANGED
|
@@ -46,8 +46,9 @@ function displayEnvironmentCheck() {
|
|
|
46
46
|
|
|
47
47
|
const keys = {
|
|
48
48
|
'ANTHROPIC_API_KEY': !!process.env.ANTHROPIC_API_KEY,
|
|
49
|
-
'OPENAI_API_KEY':
|
|
50
|
-
'GEMINI_API_KEY':
|
|
49
|
+
'OPENAI_API_KEY': !!(process.env.OPENAI_API_KEY || process.env.OPENAI_OAUTH_TOKEN),
|
|
50
|
+
'GEMINI_API_KEY': !!process.env.GEMINI_API_KEY,
|
|
51
|
+
'XIAOMI_API_KEY': !!process.env.XIAOMI_API_KEY,
|
|
51
52
|
};
|
|
52
53
|
|
|
53
54
|
for (const [key, available] of Object.entries(keys)) {
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
|
|
4
|
+
export class PromptLogger {
|
|
5
|
+
/**
|
|
6
|
+
* @param {string} projectRoot - absolute path to .avc parent
|
|
7
|
+
* @param {string} ceremony - e.g. 'sprint-planning', 'sponsor-call'
|
|
8
|
+
*/
|
|
9
|
+
constructor(projectRoot, ceremony) {
|
|
10
|
+
this.ceremony = ceremony;
|
|
11
|
+
this.callCount = 0;
|
|
12
|
+
this.runDir = null;
|
|
13
|
+
|
|
14
|
+
const avcDir = path.join(projectRoot, '.avc');
|
|
15
|
+
if (!fs.existsSync(avcDir)) return; // not an AVC project — silently skip
|
|
16
|
+
|
|
17
|
+
const runTs = new Date().toISOString().replace(/:/g, '-').replace(/\..+/, '');
|
|
18
|
+
this.runDir = path.join(avcDir, 'logs', 'prompts', `${ceremony}-${runTs}`);
|
|
19
|
+
fs.mkdirSync(this.runDir, { recursive: true });
|
|
20
|
+
|
|
21
|
+
this._pruneOldRuns(path.join(avcDir, 'logs', 'prompts'), ceremony, 5);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Write one call record. Called by _trackTokens in LLMProvider.
|
|
26
|
+
* @param {object} payload
|
|
27
|
+
*/
|
|
28
|
+
write(payload) {
|
|
29
|
+
if (!this.runDir) return;
|
|
30
|
+
this.callCount += 1;
|
|
31
|
+
const seq = String(this.callCount).padStart(3, '0');
|
|
32
|
+
const stage = (payload.stage || 'unknown').replace(/\s+/g, '-');
|
|
33
|
+
const timeStr = new Date().toISOString().substring(11, 19).replace(/:/g, '-');
|
|
34
|
+
const filename = `${seq}-${stage}-${timeStr}.json`;
|
|
35
|
+
try {
|
|
36
|
+
fs.writeFileSync(
|
|
37
|
+
path.join(this.runDir, filename),
|
|
38
|
+
JSON.stringify(payload, null, 2),
|
|
39
|
+
'utf8'
|
|
40
|
+
);
|
|
41
|
+
} catch { /* non-fatal */ }
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
_pruneOldRuns(promptsDir, ceremony, keep) {
|
|
45
|
+
if (!fs.existsSync(promptsDir)) return;
|
|
46
|
+
const dirs = fs.readdirSync(promptsDir)
|
|
47
|
+
.filter(d => d.startsWith(`${ceremony}-`))
|
|
48
|
+
.map(d => ({ name: d, full: path.join(promptsDir, d) }))
|
|
49
|
+
.sort((a, b) => a.name.localeCompare(b.name)); // oldest first
|
|
50
|
+
while (dirs.length >= keep) {
|
|
51
|
+
const oldest = dirs.shift();
|
|
52
|
+
try {
|
|
53
|
+
fs.rmSync(oldest.full, { recursive: true, force: true });
|
|
54
|
+
} catch { /* non-fatal */ }
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|