@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,449 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* micro-check-runner.js
|
|
3
|
+
*
|
|
4
|
+
* Runs individual micro-checks (Tier 1 and Tier 2) against an LLM provider
|
|
5
|
+
* to evaluate work item quality.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
const SYSTEM_INSTRUCTIONS =
|
|
9
|
+
"You are a work item analyzer. Answer the applicability question about whether this check is relevant to the given work item. Return JSON with 'applicable' (boolean) and 'reason' (string).";
|
|
10
|
+
|
|
11
|
+
const QUALITY_CHECK_INSTRUCTIONS =
|
|
12
|
+
"You are a work item quality checker. Answer YES (passed: true) or NO (passed: false) to the quality question. Provide brief evidence from the work item text supporting your answer. Return JSON with 'passed' (boolean) and 'evidence' (string).\n\n" +
|
|
13
|
+
"IMPORTANT calibration rules:\n" +
|
|
14
|
+
"- Accept SEMANTIC EQUIVALENCE: if the concept is addressed through a described mechanism, even without using the exact terminology the question uses, answer YES. Example: 'SameSite=Strict cookies' addresses CSRF even without the word 'CSRF'.\n" +
|
|
15
|
+
"- Match strictness to the WORK ITEM LEVEL: Epics are high-level planning artifacts — they describe WHAT and WHY, not HOW. Do not fail an epic for lacking implementation details (specific HTTP methods, error code enumerations, layer-by-layer validation). Stories should be more specific.\n" +
|
|
16
|
+
"- When in doubt, answer YES if the spirit of the requirement is met, even if the exact wording differs.";
|
|
17
|
+
|
|
18
|
+
// ---------------------------------------------------------------------------
|
|
19
|
+
// JSON parse helpers with regex fallback
|
|
20
|
+
// ---------------------------------------------------------------------------
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Parse an applicability response from the LLM.
|
|
24
|
+
* Tries JSON.parse first, then regex fallback.
|
|
25
|
+
* Defaults to applicable = true on total failure (conservative).
|
|
26
|
+
* @param {string|Object} raw - Raw LLM response
|
|
27
|
+
* @returns {{ applicable: boolean, reason: string }}
|
|
28
|
+
*/
|
|
29
|
+
function parseApplicabilityResponse(raw) {
|
|
30
|
+
if (raw && typeof raw === 'object') {
|
|
31
|
+
return {
|
|
32
|
+
applicable: Boolean(raw.applicable),
|
|
33
|
+
reason: raw.reason || '',
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const text = String(raw);
|
|
38
|
+
|
|
39
|
+
try {
|
|
40
|
+
const parsed = JSON.parse(text);
|
|
41
|
+
return {
|
|
42
|
+
applicable: Boolean(parsed.applicable),
|
|
43
|
+
reason: parsed.reason || '',
|
|
44
|
+
};
|
|
45
|
+
} catch {
|
|
46
|
+
// Regex fallback
|
|
47
|
+
const applicableMatch = text.match(/"applicable"\s*:\s*(true|false)/i);
|
|
48
|
+
if (applicableMatch) {
|
|
49
|
+
return {
|
|
50
|
+
applicable: applicableMatch[1].toLowerCase() === 'true',
|
|
51
|
+
reason: '',
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// Total failure — default to not-applicable (skip) to avoid phantom failures
|
|
56
|
+
// from unparseable LLM responses inflating failure counts
|
|
57
|
+
return { applicable: false, reason: 'Failed to parse LLM response, defaulting to skip' };
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Parse a quality check response from the LLM.
|
|
63
|
+
* Tries JSON.parse first, then regex fallback.
|
|
64
|
+
* Defaults to passed = false on total failure (conservative).
|
|
65
|
+
* @param {string|Object} raw - Raw LLM response
|
|
66
|
+
* @returns {{ passed: boolean, evidence: string }}
|
|
67
|
+
*/
|
|
68
|
+
function parseCheckResponse(raw) {
|
|
69
|
+
if (raw && typeof raw === 'object') {
|
|
70
|
+
return {
|
|
71
|
+
passed: Boolean(raw.passed),
|
|
72
|
+
evidence: raw.evidence || '',
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
const text = String(raw);
|
|
77
|
+
|
|
78
|
+
try {
|
|
79
|
+
const parsed = JSON.parse(text);
|
|
80
|
+
return {
|
|
81
|
+
passed: Boolean(parsed.passed),
|
|
82
|
+
evidence: parsed.evidence || '',
|
|
83
|
+
};
|
|
84
|
+
} catch {
|
|
85
|
+
// Regex fallback
|
|
86
|
+
const passedMatch = text.match(/"passed"\s*:\s*(true|false)/i);
|
|
87
|
+
const evidenceMatch = text.match(/"evidence"\s*:\s*"([^"]*)"/);
|
|
88
|
+
|
|
89
|
+
if (passedMatch) {
|
|
90
|
+
return {
|
|
91
|
+
passed: passedMatch[1].toLowerCase() === 'true',
|
|
92
|
+
evidence: evidenceMatch ? evidenceMatch[1] : '',
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// Total failure — mark as indeterminate (null) so scorer can exclude it
|
|
97
|
+
return { passed: null, evidence: 'Failed to parse LLM response' };
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// ---------------------------------------------------------------------------
|
|
102
|
+
// Prompt builders
|
|
103
|
+
// ---------------------------------------------------------------------------
|
|
104
|
+
|
|
105
|
+
function buildApplicabilityPrompt(workItemText, applicabilityQuestion) {
|
|
106
|
+
return (
|
|
107
|
+
`Given the following work item:\n\n${workItemText}\n\n` +
|
|
108
|
+
`Question: ${applicabilityQuestion}\n\n` +
|
|
109
|
+
`Respond with JSON: {"applicable": true/false, "reason": "brief reason"}`
|
|
110
|
+
);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
function buildQualityCheckPrompt(workItemText, question, workItemType) {
|
|
114
|
+
const levelHint = workItemType === 'epic'
|
|
115
|
+
? 'This is an EPIC (high-level planning document). Evaluate at epic-level granularity — do not require implementation details.\n\n'
|
|
116
|
+
: 'This is a STORY (implementation-level specification). Evaluate at story-level granularity — expect specific details.\n\n';
|
|
117
|
+
return (
|
|
118
|
+
levelHint +
|
|
119
|
+
`Given the following work item:\n\n${workItemText}\n\n` +
|
|
120
|
+
`Question: ${question}\n\n` +
|
|
121
|
+
`Respond with JSON: {"passed": true/false, "evidence": "brief quote or explanation supporting your answer"}`
|
|
122
|
+
);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// ---------------------------------------------------------------------------
|
|
126
|
+
// Template variable resolution (Tier 2)
|
|
127
|
+
// ---------------------------------------------------------------------------
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Resolve template variables like {{checkId.evidence}} in a question string.
|
|
131
|
+
* @param {string} question - The question template
|
|
132
|
+
* @param {Map} tier1Results - Map of checkId -> tier1Result
|
|
133
|
+
* @returns {string} The resolved question
|
|
134
|
+
*/
|
|
135
|
+
function resolveTemplateVariables(question, tier1Results) {
|
|
136
|
+
return question.replace(/\{\{([\w-]+)\.evidence\}\}/g, (_match, checkId) => {
|
|
137
|
+
const result = tier1Results.get(checkId);
|
|
138
|
+
if (result && result.evidence) {
|
|
139
|
+
return result.evidence;
|
|
140
|
+
}
|
|
141
|
+
return '(not available)';
|
|
142
|
+
});
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// ---------------------------------------------------------------------------
|
|
146
|
+
// Exported check runners
|
|
147
|
+
// ---------------------------------------------------------------------------
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* Run a single Tier 1 check against a work item.
|
|
151
|
+
* @param {Object} check - Check definition from catalog JSON
|
|
152
|
+
* @param {string} workItemText - The full work item text (epic/story context markdown)
|
|
153
|
+
* @param {Object} llmProvider - LLM provider instance with generateJSON() method
|
|
154
|
+
* @param {string} [workItemType] - "epic" or "story" (falls back to check ID heuristic)
|
|
155
|
+
* @returns {Object} { id, tier, severity, category, perspective, universal, applicable, passed, evidence }
|
|
156
|
+
*/
|
|
157
|
+
export async function runTier1Check(check, workItemText, llmProvider, workItemType) {
|
|
158
|
+
const baseResult = {
|
|
159
|
+
id: check.id,
|
|
160
|
+
tier: 1,
|
|
161
|
+
severity: check.severity,
|
|
162
|
+
category: check.category,
|
|
163
|
+
perspective: check.perspective,
|
|
164
|
+
universal: check.universal,
|
|
165
|
+
};
|
|
166
|
+
|
|
167
|
+
// Step 1: Applicability gate
|
|
168
|
+
if (check.universal !== true) {
|
|
169
|
+
const applicabilityPrompt = buildApplicabilityPrompt(workItemText, check.applicabilityQuestion);
|
|
170
|
+
const applicabilityRaw = await llmProvider.generateJSON(applicabilityPrompt, SYSTEM_INSTRUCTIONS);
|
|
171
|
+
const applicability = parseApplicabilityResponse(applicabilityRaw);
|
|
172
|
+
|
|
173
|
+
if (!applicability.applicable) {
|
|
174
|
+
return {
|
|
175
|
+
...baseResult,
|
|
176
|
+
applicable: false,
|
|
177
|
+
passed: null,
|
|
178
|
+
evidence: null,
|
|
179
|
+
};
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
// Step 2: Quality check
|
|
184
|
+
const itemType = workItemType || (check.id.includes('-epic-') ? 'epic' : 'story');
|
|
185
|
+
const qualityPrompt = buildQualityCheckPrompt(workItemText, check.question, itemType);
|
|
186
|
+
const qualityRaw = await llmProvider.generateJSON(qualityPrompt, QUALITY_CHECK_INSTRUCTIONS);
|
|
187
|
+
const qualityResult = parseCheckResponse(qualityRaw);
|
|
188
|
+
|
|
189
|
+
return {
|
|
190
|
+
...baseResult,
|
|
191
|
+
applicable: true,
|
|
192
|
+
passed: qualityResult.passed,
|
|
193
|
+
evidence: qualityResult.evidence,
|
|
194
|
+
failDescription: check.failDescription,
|
|
195
|
+
failSuggestion: check.failSuggestion,
|
|
196
|
+
};
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
/**
|
|
200
|
+
* Run a single Tier 2 cross-reference check.
|
|
201
|
+
* @param {Object} check - Tier 2 check definition with dependsOn and template variables
|
|
202
|
+
* @param {string} workItemText - The full work item text
|
|
203
|
+
* @param {Map} tier1Results - Map of checkId -> tier1Result (for template resolution)
|
|
204
|
+
* @param {Object} llmProvider - LLM provider instance
|
|
205
|
+
* @returns {Object} { id, tier, severity, category, perspectives, applicable: true, passed, evidence }
|
|
206
|
+
*/
|
|
207
|
+
// ---------------------------------------------------------------------------
|
|
208
|
+
// Batch operations — run N checks in a single LLM call
|
|
209
|
+
// ---------------------------------------------------------------------------
|
|
210
|
+
|
|
211
|
+
const BATCH_SYSTEM_INSTRUCTIONS =
|
|
212
|
+
"You are a work item quality checker evaluating MULTIPLE checks in a single pass.\n\n" +
|
|
213
|
+
"IMPORTANT calibration rules:\n" +
|
|
214
|
+
"- Accept SEMANTIC EQUIVALENCE: if the concept is addressed through a described mechanism, even without using the exact terminology the question uses, answer YES. Example: 'SameSite=Strict cookies' addresses CSRF even without the word 'CSRF'.\n" +
|
|
215
|
+
"- Match strictness to the WORK ITEM LEVEL: Epics are high-level planning artifacts — they describe WHAT and WHY, not HOW. Do not fail an epic for lacking implementation details. Stories should be more specific.\n" +
|
|
216
|
+
"- When in doubt, answer YES if the spirit of the requirement is met, even if the exact wording differs.\n\n" +
|
|
217
|
+
"For each check, determine:\n" +
|
|
218
|
+
"1. If it has an applicability question: is this check relevant to the work item? If not, set applicable=false.\n" +
|
|
219
|
+
"2. If applicable (or universal): does the work item pass the quality question?\n\n" +
|
|
220
|
+
"Return a JSON object keyed by check ID. Each value must have: {\"applicable\": boolean, \"passed\": boolean|null, \"evidence\": string}.\n" +
|
|
221
|
+
"If not applicable, set passed=null and evidence=\"\".";
|
|
222
|
+
|
|
223
|
+
/**
|
|
224
|
+
* Build a single prompt that evaluates multiple checks against one work item.
|
|
225
|
+
* @param {Object[]} checks - Array of check definitions
|
|
226
|
+
* @param {string} workItemText - The full work item text
|
|
227
|
+
* @param {string} workItemType - "epic" or "story"
|
|
228
|
+
* @returns {string}
|
|
229
|
+
*/
|
|
230
|
+
function buildBatchPrompt(checks, workItemText, workItemType) {
|
|
231
|
+
const levelHint = workItemType === 'epic'
|
|
232
|
+
? 'This is an EPIC (high-level planning document). Evaluate at epic-level granularity — do not require implementation details.\n\n'
|
|
233
|
+
: 'This is a STORY (implementation-level specification). Evaluate at story-level granularity — expect specific details.\n\n';
|
|
234
|
+
|
|
235
|
+
let prompt = levelHint;
|
|
236
|
+
prompt += `Given the following work item:\n\n${workItemText}\n\n`;
|
|
237
|
+
prompt += `Evaluate the following ${checks.length} checks:\n\n`;
|
|
238
|
+
|
|
239
|
+
for (const check of checks) {
|
|
240
|
+
prompt += `--- CHECK "${check.id}" ---\n`;
|
|
241
|
+
if (check.universal !== true && check.applicabilityQuestion) {
|
|
242
|
+
prompt += `Type: CONDITIONAL (first determine applicability)\n`;
|
|
243
|
+
prompt += `Applicability question: ${check.applicabilityQuestion}\n`;
|
|
244
|
+
} else {
|
|
245
|
+
prompt += `Type: UNIVERSAL (always applicable)\n`;
|
|
246
|
+
}
|
|
247
|
+
prompt += `Quality question: ${check.question}\n\n`;
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
prompt += `Respond with a JSON object. Keys are check IDs, values are {\"applicable\": boolean, \"passed\": boolean|null, \"evidence\": \"...\"}.\n`;
|
|
251
|
+
prompt += `Example: {"check-id-1": {"applicable": true, "passed": true, "evidence": "Found in section X"}, "check-id-2": {"applicable": false, "passed": null, "evidence": ""}}`;
|
|
252
|
+
|
|
253
|
+
return prompt;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
/**
|
|
257
|
+
* Parse a batch LLM response into a Map of checkId → result or null.
|
|
258
|
+
* Tries JSON.parse first, then regex fallback per check ID.
|
|
259
|
+
* @param {string|Object} raw - Raw LLM response
|
|
260
|
+
* @param {Object[]} checks - The checks that were batched (for ID list)
|
|
261
|
+
* @returns {Map<string, Object|null>}
|
|
262
|
+
*/
|
|
263
|
+
function parseBatchResponse(raw, checks) {
|
|
264
|
+
const results = new Map();
|
|
265
|
+
const checkIds = checks.map(c => c.id);
|
|
266
|
+
|
|
267
|
+
// Try full JSON parse
|
|
268
|
+
let parsed = null;
|
|
269
|
+
if (raw && typeof raw === 'object') {
|
|
270
|
+
parsed = raw;
|
|
271
|
+
} else {
|
|
272
|
+
const text = String(raw);
|
|
273
|
+
try {
|
|
274
|
+
parsed = JSON.parse(text);
|
|
275
|
+
} catch {
|
|
276
|
+
// Fall through to regex
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
if (parsed && typeof parsed === 'object') {
|
|
281
|
+
for (const id of checkIds) {
|
|
282
|
+
if (parsed[id] && typeof parsed[id] === 'object') {
|
|
283
|
+
results.set(id, {
|
|
284
|
+
applicable: parsed[id].applicable !== false,
|
|
285
|
+
passed: parsed[id].applicable === false ? null : (parsed[id].passed === null ? null : Boolean(parsed[id].passed)),
|
|
286
|
+
evidence: parsed[id].applicable === false ? '' : (parsed[id].evidence || ''),
|
|
287
|
+
});
|
|
288
|
+
} else {
|
|
289
|
+
results.set(id, null); // Not found in response
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
return results;
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
// Regex fallback — try to extract per-check JSON blocks
|
|
296
|
+
const text = String(raw);
|
|
297
|
+
for (const id of checkIds) {
|
|
298
|
+
// Look for "check-id": { ... }
|
|
299
|
+
const escapedId = id.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
300
|
+
const pattern = new RegExp(`"${escapedId}"\\s*:\\s*\\{([^}]+)\\}`, 'i');
|
|
301
|
+
const match = text.match(pattern);
|
|
302
|
+
if (match) {
|
|
303
|
+
try {
|
|
304
|
+
const block = JSON.parse(`{${match[1]}}`);
|
|
305
|
+
results.set(id, {
|
|
306
|
+
applicable: block.applicable !== false,
|
|
307
|
+
passed: block.applicable === false ? null : (block.passed === null ? null : Boolean(block.passed)),
|
|
308
|
+
evidence: block.applicable === false ? '' : (block.evidence || ''),
|
|
309
|
+
});
|
|
310
|
+
} catch {
|
|
311
|
+
results.set(id, null);
|
|
312
|
+
}
|
|
313
|
+
} else {
|
|
314
|
+
results.set(id, null);
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
return results;
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
/**
|
|
322
|
+
* Run a batch of Tier 1 checks in a single LLM call.
|
|
323
|
+
* Falls back to individual runTier1Check() for any unparseable results.
|
|
324
|
+
* @param {Object[]} checks - Array of Tier 1 check definitions
|
|
325
|
+
* @param {string} workItemText - The full work item text
|
|
326
|
+
* @param {Object} llmProvider - LLM provider instance
|
|
327
|
+
* @param {string} workItemType - "epic" or "story"
|
|
328
|
+
* @returns {Promise<Object[]>} Array of results with same shape as runTier1Check()
|
|
329
|
+
*/
|
|
330
|
+
export async function runTier1Batch(checks, workItemText, llmProvider, workItemType) {
|
|
331
|
+
const batchPrompt = buildBatchPrompt(checks, workItemText, workItemType);
|
|
332
|
+
const raw = await llmProvider.generateJSON(batchPrompt, BATCH_SYSTEM_INSTRUCTIONS);
|
|
333
|
+
const parsed = parseBatchResponse(raw, checks);
|
|
334
|
+
|
|
335
|
+
const results = [];
|
|
336
|
+
const fallbacks = [];
|
|
337
|
+
|
|
338
|
+
for (const check of checks) {
|
|
339
|
+
const entry = parsed.get(check.id);
|
|
340
|
+
if (entry) {
|
|
341
|
+
results.push({
|
|
342
|
+
id: check.id,
|
|
343
|
+
tier: 1,
|
|
344
|
+
severity: check.severity,
|
|
345
|
+
category: check.category,
|
|
346
|
+
perspective: check.perspective,
|
|
347
|
+
universal: check.universal,
|
|
348
|
+
applicable: entry.applicable,
|
|
349
|
+
passed: entry.passed,
|
|
350
|
+
evidence: entry.evidence,
|
|
351
|
+
failDescription: check.failDescription,
|
|
352
|
+
failSuggestion: check.failSuggestion,
|
|
353
|
+
});
|
|
354
|
+
} else {
|
|
355
|
+
fallbacks.push(check);
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
// Individual fallback for unparseable entries
|
|
360
|
+
if (fallbacks.length > 0) {
|
|
361
|
+
const fallbackResults = await Promise.all(
|
|
362
|
+
fallbacks.map(check => runTier1Check(check, workItemText, llmProvider, workItemType))
|
|
363
|
+
);
|
|
364
|
+
results.push(...fallbackResults);
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
return results;
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
/**
|
|
371
|
+
* Run a batch of Tier 2 checks in a single LLM call.
|
|
372
|
+
* Resolves template variables before building the batch prompt.
|
|
373
|
+
* Falls back to individual runTier2Check() for unparseable results.
|
|
374
|
+
* @param {Object[]} checks - Array of Tier 2 check definitions
|
|
375
|
+
* @param {string} workItemText - The full work item text
|
|
376
|
+
* @param {Map} tier1Results - Map of checkId → tier1Result
|
|
377
|
+
* @param {Object} llmProvider - LLM provider instance
|
|
378
|
+
* @param {string} workItemType - "epic" or "story"
|
|
379
|
+
* @returns {Promise<Object[]>} Array of results with same shape as runTier2Check()
|
|
380
|
+
*/
|
|
381
|
+
export async function runTier2Batch(checks, workItemText, tier1Results, llmProvider, workItemType) {
|
|
382
|
+
// Resolve template variables in each check's question before batching
|
|
383
|
+
const resolvedChecks = checks.map(check => ({
|
|
384
|
+
...check,
|
|
385
|
+
question: resolveTemplateVariables(check.question, tier1Results),
|
|
386
|
+
universal: true, // Tier 2 checks skip applicability gate
|
|
387
|
+
}));
|
|
388
|
+
|
|
389
|
+
const batchPrompt = buildBatchPrompt(resolvedChecks, workItemText, workItemType);
|
|
390
|
+
const raw = await llmProvider.generateJSON(batchPrompt, BATCH_SYSTEM_INSTRUCTIONS);
|
|
391
|
+
const parsed = parseBatchResponse(raw, resolvedChecks);
|
|
392
|
+
|
|
393
|
+
const results = [];
|
|
394
|
+
const fallbacks = [];
|
|
395
|
+
|
|
396
|
+
for (const check of checks) {
|
|
397
|
+
const entry = parsed.get(check.id);
|
|
398
|
+
if (entry) {
|
|
399
|
+
results.push({
|
|
400
|
+
id: check.id,
|
|
401
|
+
tier: 2,
|
|
402
|
+
severity: check.severity,
|
|
403
|
+
category: check.category,
|
|
404
|
+
perspectives: check.perspectives,
|
|
405
|
+
applicable: true,
|
|
406
|
+
passed: entry.passed,
|
|
407
|
+
evidence: entry.evidence,
|
|
408
|
+
failDescription: check.failDescription,
|
|
409
|
+
failSuggestion: check.failSuggestion,
|
|
410
|
+
});
|
|
411
|
+
} else {
|
|
412
|
+
fallbacks.push(check);
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
// Individual fallback for unparseable entries
|
|
417
|
+
if (fallbacks.length > 0) {
|
|
418
|
+
const fallbackResults = await Promise.all(
|
|
419
|
+
fallbacks.map(check => runTier2Check(check, workItemText, tier1Results, llmProvider))
|
|
420
|
+
);
|
|
421
|
+
results.push(...fallbackResults);
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
return results;
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
export async function runTier2Check(check, workItemText, tier1Results, llmProvider) {
|
|
428
|
+
// Step 1: Resolve template variables in the question
|
|
429
|
+
const resolvedQuestion = resolveTemplateVariables(check.question, tier1Results);
|
|
430
|
+
|
|
431
|
+
// Step 2: Run quality check with resolved question
|
|
432
|
+
const itemType = check.id.includes('-epic') ? 'epic' : 'story';
|
|
433
|
+
const qualityPrompt = buildQualityCheckPrompt(workItemText, resolvedQuestion, itemType);
|
|
434
|
+
const qualityRaw = await llmProvider.generateJSON(qualityPrompt, QUALITY_CHECK_INSTRUCTIONS);
|
|
435
|
+
const qualityResult = parseCheckResponse(qualityRaw);
|
|
436
|
+
|
|
437
|
+
return {
|
|
438
|
+
id: check.id,
|
|
439
|
+
tier: 2,
|
|
440
|
+
severity: check.severity,
|
|
441
|
+
category: check.category,
|
|
442
|
+
perspectives: check.perspectives,
|
|
443
|
+
applicable: true,
|
|
444
|
+
passed: qualityResult.passed,
|
|
445
|
+
evidence: qualityResult.evidence,
|
|
446
|
+
failDescription: check.failDescription,
|
|
447
|
+
failSuggestion: check.failSuggestion,
|
|
448
|
+
};
|
|
449
|
+
}
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Score micro-check results programmatically.
|
|
3
|
+
* Pure JS module — no LLM involvement, no external dependencies.
|
|
4
|
+
*
|
|
5
|
+
* @param {Object[]} checkResults - Array of completed check results from runner.
|
|
6
|
+
* Each: { id, tier, severity, category, perspective, universal, applicable,
|
|
7
|
+
* passed, evidence?, failDescription?, failSuggestion? }
|
|
8
|
+
* @returns {Object} Scoring result with failure counts, status, and pattern detection.
|
|
9
|
+
*/
|
|
10
|
+
export function scoreChecks(checkResults) {
|
|
11
|
+
// 1. Partition into applicable vs skipped vs errored
|
|
12
|
+
const applicable = [];
|
|
13
|
+
let skippedCount = 0;
|
|
14
|
+
let erroredCount = 0;
|
|
15
|
+
|
|
16
|
+
for (const check of checkResults) {
|
|
17
|
+
if (check.applicable === false) {
|
|
18
|
+
skippedCount++;
|
|
19
|
+
} else if (check.passed === null) {
|
|
20
|
+
// LLM parse error or runtime error — exclude from scoring
|
|
21
|
+
erroredCount++;
|
|
22
|
+
} else {
|
|
23
|
+
applicable.push(check);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const applicableCount = applicable.length;
|
|
28
|
+
|
|
29
|
+
// 2. Count failures by severity (only actual failures, not errors)
|
|
30
|
+
const failures = applicable.filter((c) => c.passed === false);
|
|
31
|
+
|
|
32
|
+
let criticalFails = 0;
|
|
33
|
+
let majorFails = 0;
|
|
34
|
+
let minorFails = 0;
|
|
35
|
+
|
|
36
|
+
for (const f of failures) {
|
|
37
|
+
switch (f.severity) {
|
|
38
|
+
case 'critical':
|
|
39
|
+
criticalFails++;
|
|
40
|
+
break;
|
|
41
|
+
case 'major':
|
|
42
|
+
majorFails++;
|
|
43
|
+
break;
|
|
44
|
+
case 'minor':
|
|
45
|
+
minorFails++;
|
|
46
|
+
break;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// 3. Scoring formula (matches existing validators)
|
|
51
|
+
let score;
|
|
52
|
+
|
|
53
|
+
if (criticalFails > 0) {
|
|
54
|
+
score = Math.max(0, Math.min(69, 60 - (criticalFails - 1) * 10));
|
|
55
|
+
} else if (majorFails > 0) {
|
|
56
|
+
score = Math.max(70, Math.min(89, 88 - (majorFails - 1) * 5));
|
|
57
|
+
} else {
|
|
58
|
+
score = Math.max(95, Math.min(100, 98 - minorFails));
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// 4. Derive status
|
|
62
|
+
let status;
|
|
63
|
+
|
|
64
|
+
if (score >= 90) {
|
|
65
|
+
status = 'excellent';
|
|
66
|
+
} else if (score >= 70) {
|
|
67
|
+
status = 'acceptable';
|
|
68
|
+
} else {
|
|
69
|
+
status = 'needs-improvement';
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// 5. Collect detailed failure info
|
|
73
|
+
const failedChecks = failures.map((f) => ({
|
|
74
|
+
id: f.id,
|
|
75
|
+
tier: f.tier,
|
|
76
|
+
severity: f.severity,
|
|
77
|
+
category: f.category,
|
|
78
|
+
perspective: f.perspective,
|
|
79
|
+
failDescription: f.failDescription,
|
|
80
|
+
failSuggestion: f.failSuggestion,
|
|
81
|
+
evidence: f.evidence,
|
|
82
|
+
}));
|
|
83
|
+
|
|
84
|
+
// 6. Tier 3 pattern detection
|
|
85
|
+
|
|
86
|
+
const result = {
|
|
87
|
+
score,
|
|
88
|
+
status,
|
|
89
|
+
applicableCount,
|
|
90
|
+
skippedCount,
|
|
91
|
+
erroredCount,
|
|
92
|
+
criticalFails,
|
|
93
|
+
majorFails,
|
|
94
|
+
minorFails,
|
|
95
|
+
failedChecks,
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
// 6a. Split recommendation — look for concentrated major/critical failures
|
|
99
|
+
// in acceptance-criteria or implementation-clarity categories
|
|
100
|
+
const splitTargetCategories = new Set([
|
|
101
|
+
'acceptance-criteria',
|
|
102
|
+
'implementation-clarity',
|
|
103
|
+
]);
|
|
104
|
+
const severeCategoryBuckets = {};
|
|
105
|
+
|
|
106
|
+
for (const f of failures) {
|
|
107
|
+
if (
|
|
108
|
+
(f.severity === 'major' || f.severity === 'critical') &&
|
|
109
|
+
splitTargetCategories.has(f.category)
|
|
110
|
+
) {
|
|
111
|
+
severeCategoryBuckets[f.category] =
|
|
112
|
+
(severeCategoryBuckets[f.category] || 0) + 1;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
for (const [category, count] of Object.entries(severeCategoryBuckets)) {
|
|
117
|
+
if (count >= 3) {
|
|
118
|
+
result.splitRecommendation = true;
|
|
119
|
+
result.splitReason =
|
|
120
|
+
`${count} major/critical issues in ${category} suggest this work item combines too many concerns`;
|
|
121
|
+
break; // report the first qualifying category
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// 6b. Issue clustering — perspectives with 3+ failures
|
|
126
|
+
const perspectiveBuckets = {};
|
|
127
|
+
|
|
128
|
+
for (const f of failures) {
|
|
129
|
+
perspectiveBuckets[f.perspective] =
|
|
130
|
+
(perspectiveBuckets[f.perspective] || 0) + 1;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
const clusterWarnings = [];
|
|
134
|
+
|
|
135
|
+
for (const [perspective, count] of Object.entries(perspectiveBuckets)) {
|
|
136
|
+
if (count >= 3) {
|
|
137
|
+
clusterWarnings.push(
|
|
138
|
+
`${count} issues from ${perspective} perspective — may indicate fundamental gap in this domain`
|
|
139
|
+
);
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
if (clusterWarnings.length > 0) {
|
|
144
|
+
result.clusterWarnings = clusterWarnings;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
return result;
|
|
148
|
+
}
|