chati-dev 4.3.1 → 4.4.0
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 +18 -13
- package/bin/chati.js +12 -4
- package/framework/agents/build/dev.md +5 -1
- package/framework/agents/discover/greenfield-wu.md +16 -1
- package/framework/agents/plan/tasks.md +31 -0
- package/framework/agents/plan/ux-brand-architect.md +21 -2
- package/framework/agents/plan/ux-component-engineer.md +10 -0
- package/framework/agents/quality/qa-implementation.md +1 -1
- package/framework/agents/quality/qa-planning.md +1 -1
- package/framework/agents/quality/qa-visual.md +24 -21
- package/framework/config.yaml +5 -4
- package/framework/constitution.md +58 -11
- package/framework/context/governance.md +12 -10
- package/framework/context/root.md +2 -2
- package/framework/data/entity-registry.yaml +12 -4
- package/framework/data/qa-rubrics.yaml +335 -0
- package/framework/domains/constitution.yaml +3 -3
- package/framework/executors/json-validate.js +80 -0
- package/framework/executors/npm-script.js +72 -0
- package/framework/executors/path-check.js +67 -0
- package/framework/executors/registry.yaml +36 -0
- package/framework/hooks/advance-trigger.js +47 -3
- package/framework/hooks/constitution-guard.js +9 -4
- package/framework/hooks/git-push-authority.js +113 -0
- package/framework/hooks/license-guard.js +59 -9
- package/framework/hooks/model-governance.js +40 -17
- package/framework/hooks/prism-engine.js +78 -11
- package/framework/hooks/session-digest.js +12 -3
- package/framework/hooks/settings.json +7 -3
- package/framework/hooks/style-guard.js +52 -3
- package/framework/hooks/team-quality-gate.js +43 -5
- package/framework/i18n/en.yaml +3 -3
- package/framework/i18n/es.yaml +3 -3
- package/framework/i18n/fr.yaml +3 -3
- package/framework/i18n/pt.yaml +3 -3
- package/framework/intelligence/confidence.yaml +85 -6
- package/framework/intelligence/context-engine.md +9 -5
- package/framework/intelligence/decision-engine.md +1 -1
- package/framework/orchestrator/chati-router.js +1 -1
- package/framework/orchestrator/chati-update.md +19 -3
- package/framework/orchestrator/chati.md +19 -1
- package/framework/quality-gates/planning-gate.md +2 -0
- package/framework/scaffold/motion-premium/scaffold.yaml +2 -1
- package/framework/scaffold/motion-premium-3d/components/webgl/WebGLContext.tsx.template +1 -1
- package/framework/scaffold/saas-dashboard/README.md +58 -0
- package/framework/scaffold/saas-dashboard/app/dashboard/page.tsx.template +546 -0
- package/framework/scaffold/saas-dashboard/app/globals.css.template +746 -0
- package/framework/scaffold/saas-dashboard/app/layout.tsx.template +192 -0
- package/framework/scaffold/saas-dashboard/app/shell.css.template +1070 -0
- package/framework/scaffold/saas-dashboard/components/data/Chart.tsx.template +725 -0
- package/framework/scaffold/saas-dashboard/components/data/DataTable.tsx.template +623 -0
- package/framework/scaffold/saas-dashboard/components/data/DataTable.types.ts.template +104 -0
- package/framework/scaffold/saas-dashboard/components/data/DataTablePagination.tsx.template +234 -0
- package/framework/scaffold/saas-dashboard/components/data/SkeletonBlock.tsx.template +111 -0
- package/framework/scaffold/saas-dashboard/components/data/StatCard.tsx.template +393 -0
- package/framework/scaffold/saas-dashboard/components/data/StatCardGrid.tsx.template +121 -0
- package/framework/scaffold/saas-dashboard/components/feedback/EmptyState.tsx.template +281 -0
- package/framework/scaffold/saas-dashboard/components/interaction/CommandPalette.tsx.template +767 -0
- package/framework/scaffold/saas-dashboard/components/interaction/Field.tsx.template +351 -0
- package/framework/scaffold/saas-dashboard/components/interaction/Form.tsx.template +424 -0
- package/framework/scaffold/saas-dashboard/components/primitives/Badge.tsx.template +193 -0
- package/framework/scaffold/saas-dashboard/components/primitives/Button.tsx.template +261 -0
- package/framework/scaffold/saas-dashboard/components/primitives/Card.tsx.template +287 -0
- package/framework/scaffold/saas-dashboard/components/primitives/Input.tsx.template +310 -0
- package/framework/scaffold/saas-dashboard/components/primitives/Spinner.tsx.template +135 -0
- package/framework/scaffold/saas-dashboard/components/primitives/index.ts.template +60 -0
- package/framework/scaffold/saas-dashboard/components/shell/AppShell.tsx.template +174 -0
- package/framework/scaffold/saas-dashboard/components/shell/Breadcrumb.tsx.template +186 -0
- package/framework/scaffold/saas-dashboard/components/shell/Container.tsx.template +86 -0
- package/framework/scaffold/saas-dashboard/components/shell/NavItem.tsx.template +147 -0
- package/framework/scaffold/saas-dashboard/components/shell/Sidebar.tsx.template +378 -0
- package/framework/scaffold/saas-dashboard/components/shell/SidebarContext.tsx.template +169 -0
- package/framework/scaffold/saas-dashboard/components/shell/ThemeToggle.tsx.template +128 -0
- package/framework/scaffold/saas-dashboard/components/shell/Topbar.tsx.template +301 -0
- package/framework/scaffold/saas-dashboard/lib/brand.ts.template +324 -0
- package/framework/scaffold/saas-dashboard/scaffold.yaml +225 -0
- package/framework/schemas/session.schema.json +5 -0
- package/framework/schemas/task.schema.json +9 -0
- package/framework/scripts/visual-qa.js +87 -1
- package/framework/tasks/orchestrator-health.md +4 -4
- package/framework/tasks/qa-impl-verdict.md +11 -5
- package/framework/templates/qa-gate-tmpl.yaml +24 -11
- package/package.json +3 -3
- package/src/config/context-file-generator.js +0 -6
- package/src/dashboard/renderer.js +0 -36
- package/src/executors/runner.js +204 -0
- package/src/installer/core.js +2 -1
- package/src/installer/templates.js +5 -6
- package/src/intelligence/registry-manager.js +1 -1
- package/src/license/client.js +9 -3
- package/src/license/commands.js +12 -2
- package/src/license/machine-id.js +42 -1
- package/src/memory/gotchas.js +58 -40
- package/src/memory/magic-docs.js +1 -1
- package/src/memory/session-digest.js +9 -4
- package/src/orchestrator/cli.js +174 -14
- package/src/orchestrator/doctor.js +98 -16
- package/src/orchestrator/pipeline-manager.js +44 -22
- package/src/orchestrator/session-manager.js +56 -8
- package/src/telemetry/sender.js +7 -19
- package/src/terminal/run-team.js +3 -3
- package/src/upgrade/tracked-files-detector.js +34 -16
- package/src/utils/feature-flags.js +1 -1
- package/src/utils/flatten-entities.js +4 -40
- package/src/utils/schema-validator.js +0 -14
- package/src/wizard/i18n.js +3 -3
- package/src/api/index.js +0 -120
- package/src/autonomy/autonomous-gate.js +0 -294
- package/src/autonomy/build-loop.js +0 -281
- package/src/autonomy/build-state.js +0 -286
- package/src/autonomy/cause-analyzer.js +0 -177
- package/src/autonomy/escalation.js +0 -214
- package/src/autonomy/index.js +0 -51
- package/src/autonomy/mode-manager.js +0 -225
- package/src/autonomy/mode-suggester.js +0 -283
- package/src/autonomy/progress-reporter.js +0 -275
- package/src/autonomy/safety-net.js +0 -370
- package/src/config/agent-customizer.js +0 -231
- package/src/decision/analyzer.js +0 -291
- package/src/decision/engine.js +0 -250
- package/src/decision/index.js +0 -38
- package/src/decision/registry-healer.js +0 -468
- package/src/decision/registry-updater.js +0 -339
- package/src/extensions/loader.js +0 -145
- package/src/extensions/registry.js +0 -134
- package/src/gates/circuit-breaker.js +0 -151
- package/src/gates/g1-planning-complete.js +0 -154
- package/src/gates/g2-qa-planning.js +0 -156
- package/src/gates/g3-implementation.js +0 -215
- package/src/gates/g4-qa-implementation.js +0 -240
- package/src/gates/g5-deploy-ready.js +0 -181
- package/src/gates/gate-base.js +0 -185
- package/src/gates/index.js +0 -46
- package/src/health/auto-fix.js +0 -216
- package/src/health/engine.js +0 -246
- package/src/merger/semantic-merger.js +0 -292
- package/src/preview/detector.js +0 -238
- package/src/preview/index.js +0 -20
- package/src/preview/launcher.js +0 -235
- package/src/preview/log-buffer.js +0 -103
- package/src/quality/metrics-collector.js +0 -281
- package/src/quality/test-runner.js +0 -366
- package/src/tasks/executor.js +0 -195
- package/src/tasks/index.js +0 -4
- package/src/tasks/loader.js +0 -210
- package/src/tasks/router.js +0 -182
- package/src/utils/event-bus.js +0 -126
|
@@ -1,240 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @fileoverview G4 — QA Implementation Gate
|
|
3
|
-
*
|
|
4
|
-
* Post-QA-Implementation gate — the most critical gate in the pipeline.
|
|
5
|
-
* Validates that all tests pass, no critical bugs remain, performance
|
|
6
|
-
* benchmarks are met, and security scans are clean.
|
|
7
|
-
*
|
|
8
|
-
* Verdicts:
|
|
9
|
-
* PASS — All criteria met, score >= 95%
|
|
10
|
-
* CONCERNS — Score 90-95%, minor issues noted
|
|
11
|
-
* FAIL — Score < 90% or critical issues
|
|
12
|
-
* WAIVED — Human explicitly overrode
|
|
13
|
-
*/
|
|
14
|
-
|
|
15
|
-
import { existsSync, readFileSync } from 'node:fs';
|
|
16
|
-
import { join } from 'node:path';
|
|
17
|
-
import { resolveFrameworkDir } from '../utils/framework-dir.js';
|
|
18
|
-
import { GateBase, determineVerdict } from './gate-base.js';
|
|
19
|
-
import { loadSession } from '../orchestrator/session-manager.js';
|
|
20
|
-
import { loadHandoff } from '../tasks/handoff.js';
|
|
21
|
-
|
|
22
|
-
export const QA_IMPL_VERDICTS = {
|
|
23
|
-
PASS: 'pass',
|
|
24
|
-
CONCERNS: 'concerns',
|
|
25
|
-
FAIL: 'fail',
|
|
26
|
-
WAIVED: 'waived',
|
|
27
|
-
};
|
|
28
|
-
|
|
29
|
-
export class QAImplementationGate extends GateBase {
|
|
30
|
-
constructor() {
|
|
31
|
-
super({
|
|
32
|
-
id: 'g4-qa-implementation',
|
|
33
|
-
name: 'QA Implementation',
|
|
34
|
-
pipelinePoint: 'post-qa-impl',
|
|
35
|
-
agent: 'qa-implementation',
|
|
36
|
-
});
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
/**
|
|
40
|
-
* Collect evidence about QA implementation results.
|
|
41
|
-
*
|
|
42
|
-
* @param {string} projectDir
|
|
43
|
-
* @returns {object} Evidence about test results, bugs, performance, security
|
|
44
|
-
*/
|
|
45
|
-
_collectEvidence(projectDir) {
|
|
46
|
-
const evidence = {
|
|
47
|
-
qaImplHandoff: null,
|
|
48
|
-
allTestCriteriaMet: false,
|
|
49
|
-
noCriticalBugs: true,
|
|
50
|
-
performanceBenchmarksMet: null, // null = not defined
|
|
51
|
-
securityScanClean: null,
|
|
52
|
-
sessionState: null,
|
|
53
|
-
verdict: null,
|
|
54
|
-
};
|
|
55
|
-
|
|
56
|
-
// Load QA-Implementation handoff
|
|
57
|
-
const handoffResult = loadHandoff(projectDir, 'qa-implementation');
|
|
58
|
-
if (handoffResult.loaded && handoffResult.handoff) {
|
|
59
|
-
const handoff = handoffResult.handoff;
|
|
60
|
-
evidence.qaImplHandoff = {
|
|
61
|
-
score: handoff.score,
|
|
62
|
-
status: handoff.status,
|
|
63
|
-
outputs: handoff.outputs || [],
|
|
64
|
-
blockers: handoff.blockers || [],
|
|
65
|
-
criteriaUnmet: handoff.criteria_unmet || [],
|
|
66
|
-
criteriaMet: handoff.criteria_met || [],
|
|
67
|
-
};
|
|
68
|
-
|
|
69
|
-
evidence.allTestCriteriaMet = evidence.qaImplHandoff.criteriaUnmet.length === 0;
|
|
70
|
-
evidence.noCriticalBugs = evidence.qaImplHandoff.blockers.length === 0;
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
// Check for performance benchmark results — parse content for real findings
|
|
74
|
-
const perfPaths = [
|
|
75
|
-
join(projectDir, resolveFrameworkDir(projectDir), 'artifacts', 'performance-report.md'),
|
|
76
|
-
join(projectDir, '.performance-benchmark'),
|
|
77
|
-
];
|
|
78
|
-
for (const perfPath of perfPaths) {
|
|
79
|
-
if (existsSync(perfPath)) {
|
|
80
|
-
try {
|
|
81
|
-
const content = readFileSync(perfPath, 'utf-8');
|
|
82
|
-
const hasFailures = /fail|exceeded|timeout|regression|degradation|below\s*threshold/i.test(content);
|
|
83
|
-
const hasPassIndicator = /pass|met|within\s*budget|no\s*(regressions|issues)|acceptable/i.test(content);
|
|
84
|
-
if (hasFailures && !hasPassIndicator) {
|
|
85
|
-
evidence.performanceBenchmarksMet = false;
|
|
86
|
-
} else if (hasPassIndicator) {
|
|
87
|
-
evidence.performanceBenchmarksMet = true;
|
|
88
|
-
} else {
|
|
89
|
-
evidence.performanceBenchmarksMet = null; // Ambiguous
|
|
90
|
-
}
|
|
91
|
-
} catch { /* expected: file may not exist */
|
|
92
|
-
evidence.performanceBenchmarksMet = null;
|
|
93
|
-
}
|
|
94
|
-
break;
|
|
95
|
-
}
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
// Check for security scan — parse content for real findings
|
|
99
|
-
const secPaths = [
|
|
100
|
-
join(projectDir, resolveFrameworkDir(projectDir), 'artifacts', 'security-report.md'),
|
|
101
|
-
join(projectDir, '.security-scan'),
|
|
102
|
-
];
|
|
103
|
-
for (const secPath of secPaths) {
|
|
104
|
-
if (existsSync(secPath)) {
|
|
105
|
-
try {
|
|
106
|
-
const content = readFileSync(secPath, 'utf-8').toLowerCase();
|
|
107
|
-
// Look for indicators of security failures
|
|
108
|
-
const hasBlockers = /blocker|critical|high\s*severity|vulnerability found|failed/i.test(content);
|
|
109
|
-
const hasPassIndicator = /pass|clean|no\s*(issues|vulnerabilities|findings)|score:\s*(9[5-9]|100)/i.test(content);
|
|
110
|
-
if (hasBlockers) {
|
|
111
|
-
evidence.securityScanClean = false;
|
|
112
|
-
} else if (hasPassIndicator) {
|
|
113
|
-
evidence.securityScanClean = true;
|
|
114
|
-
} else {
|
|
115
|
-
// File exists but has no clear pass/fail indicators — ambiguous — not clean by default
|
|
116
|
-
evidence.securityScanClean = null;
|
|
117
|
-
}
|
|
118
|
-
} catch { /* expected: operation may fail gracefully */
|
|
119
|
-
// File exists but cannot be read — treat as not available
|
|
120
|
-
evidence.securityScanClean = null;
|
|
121
|
-
}
|
|
122
|
-
break;
|
|
123
|
-
}
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
// Load session state
|
|
127
|
-
const sessionResult = loadSession(projectDir);
|
|
128
|
-
if (sessionResult.loaded && sessionResult.session) {
|
|
129
|
-
const qaAgent = sessionResult.session.agents?.['qa-implementation'];
|
|
130
|
-
evidence.sessionState = {
|
|
131
|
-
qaImplStatus: qaAgent?.status || 'pending',
|
|
132
|
-
qaImplScore: qaAgent?.score || 0,
|
|
133
|
-
completedAgents: sessionResult.session.completed_agents || [],
|
|
134
|
-
};
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
// Determine verdict
|
|
138
|
-
evidence.verdict = this._determineVerdict(evidence);
|
|
139
|
-
|
|
140
|
-
return evidence;
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
/**
|
|
144
|
-
* Determine the QA verdict based on evidence.
|
|
145
|
-
*
|
|
146
|
-
* @param {object} evidence
|
|
147
|
-
* @returns {string} One of QA_IMPL_VERDICTS
|
|
148
|
-
*/
|
|
149
|
-
_determineVerdict(evidence) {
|
|
150
|
-
if (!evidence.qaImplHandoff) {
|
|
151
|
-
return QA_IMPL_VERDICTS.FAIL;
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
const score = evidence.qaImplHandoff.score ?? 0;
|
|
155
|
-
|
|
156
|
-
if (!evidence.noCriticalBugs) {
|
|
157
|
-
return QA_IMPL_VERDICTS.FAIL;
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
if (score >= 95 && evidence.allTestCriteriaMet) {
|
|
161
|
-
return QA_IMPL_VERDICTS.PASS;
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
if (score >= 90) {
|
|
165
|
-
return QA_IMPL_VERDICTS.CONCERNS;
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
return QA_IMPL_VERDICTS.FAIL;
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
/**
|
|
172
|
-
* Validate QA implementation evidence.
|
|
173
|
-
*
|
|
174
|
-
* @param {object} evidence
|
|
175
|
-
* @returns {{ score: number, criteriaResults: string[], allCriteria: string[], warnings: string[] }}
|
|
176
|
-
*/
|
|
177
|
-
_validateEvidence(evidence) {
|
|
178
|
-
const allCriteria = [
|
|
179
|
-
'QA-Implementation handoff exists',
|
|
180
|
-
'All test criteria met',
|
|
181
|
-
'No critical bugs open',
|
|
182
|
-
'Performance benchmarks met',
|
|
183
|
-
'Security scan clean',
|
|
184
|
-
'QA-Implementation score >= 95',
|
|
185
|
-
];
|
|
186
|
-
|
|
187
|
-
const criteriaResults = [];
|
|
188
|
-
const warnings = [];
|
|
189
|
-
|
|
190
|
-
// Check handoff exists
|
|
191
|
-
if (evidence.qaImplHandoff) {
|
|
192
|
-
criteriaResults.push('QA-Implementation handoff exists');
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
// Check test criteria
|
|
196
|
-
if (evidence.allTestCriteriaMet) {
|
|
197
|
-
criteriaResults.push('All test criteria met');
|
|
198
|
-
} else if (evidence.qaImplHandoff) {
|
|
199
|
-
warnings.push(`${evidence.qaImplHandoff.criteriaUnmet.length} test criteria unmet`);
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
// Check bugs
|
|
203
|
-
if (evidence.noCriticalBugs) {
|
|
204
|
-
criteriaResults.push('No critical bugs open');
|
|
205
|
-
} else {
|
|
206
|
-
warnings.push('Critical bugs remain open');
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
// Check performance
|
|
210
|
-
if (evidence.performanceBenchmarksMet === true) {
|
|
211
|
-
criteriaResults.push('Performance benchmarks met');
|
|
212
|
-
} else if (evidence.performanceBenchmarksMet === null) {
|
|
213
|
-
// Not defined is not a failure — just a warning
|
|
214
|
-
warnings.push('Performance benchmarks not defined');
|
|
215
|
-
}
|
|
216
|
-
|
|
217
|
-
// Check security
|
|
218
|
-
if (evidence.securityScanClean === true) {
|
|
219
|
-
criteriaResults.push('Security scan clean');
|
|
220
|
-
} else if (evidence.securityScanClean === null) {
|
|
221
|
-
warnings.push('Security scan not found');
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
// Check score
|
|
225
|
-
if (evidence.qaImplHandoff && evidence.qaImplHandoff.score >= 95) {
|
|
226
|
-
criteriaResults.push('QA-Implementation score >= 95');
|
|
227
|
-
} else if (evidence.qaImplHandoff) {
|
|
228
|
-
warnings.push(`QA-Impl score: ${evidence.qaImplHandoff.score} (need >= 95)`);
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
const score = allCriteria.length > 0
|
|
232
|
-
? Math.round((criteriaResults.length / allCriteria.length) * 100)
|
|
233
|
-
: 0;
|
|
234
|
-
|
|
235
|
-
const hasCriticalBlocker = !evidence.noCriticalBugs;
|
|
236
|
-
const verdict = determineVerdict(score, 95, hasCriticalBlocker);
|
|
237
|
-
|
|
238
|
-
return { score, criteriaResults, allCriteria, warnings, verdict };
|
|
239
|
-
}
|
|
240
|
-
}
|
|
@@ -1,181 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @fileoverview G5 — Deploy Ready Gate
|
|
3
|
-
*
|
|
4
|
-
* Pre-Deploy gate that validates everything is ready for deployment.
|
|
5
|
-
* Checks that QA-Implementation passed, documentation is updated,
|
|
6
|
-
* release notes exist, all tasks are complete, and no blockers remain.
|
|
7
|
-
*/
|
|
8
|
-
|
|
9
|
-
import { existsSync } from 'node:fs';
|
|
10
|
-
import { join } from 'node:path';
|
|
11
|
-
import { GateBase } from './gate-base.js';
|
|
12
|
-
import { loadSession } from '../orchestrator/session-manager.js';
|
|
13
|
-
import { loadHandoff } from '../tasks/handoff.js';
|
|
14
|
-
import { resolveFrameworkDir } from '../utils/framework-dir.js';
|
|
15
|
-
|
|
16
|
-
export class DeployReadyGate extends GateBase {
|
|
17
|
-
constructor() {
|
|
18
|
-
super({
|
|
19
|
-
id: 'g5-deploy-ready',
|
|
20
|
-
name: 'Deploy Ready',
|
|
21
|
-
pipelinePoint: 'pre-deploy',
|
|
22
|
-
agent: 'devops',
|
|
23
|
-
});
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
/**
|
|
27
|
-
* Collect evidence about deployment readiness.
|
|
28
|
-
*
|
|
29
|
-
* @param {string} projectDir
|
|
30
|
-
* @returns {object} Evidence about QA status, docs, release notes, blockers
|
|
31
|
-
*/
|
|
32
|
-
_collectEvidence(projectDir) {
|
|
33
|
-
const evidence = {
|
|
34
|
-
qaImplPassed: false,
|
|
35
|
-
readmeUpdated: false,
|
|
36
|
-
changelogUpdated: false,
|
|
37
|
-
releaseNotesPrepared: false,
|
|
38
|
-
allTasksCompleted: false,
|
|
39
|
-
noOpenBlockers: true,
|
|
40
|
-
sessionState: null,
|
|
41
|
-
};
|
|
42
|
-
|
|
43
|
-
// Check QA-Implementation status
|
|
44
|
-
const qaHandoff = loadHandoff(projectDir, 'qa-implementation');
|
|
45
|
-
if (qaHandoff.loaded && qaHandoff.handoff) {
|
|
46
|
-
evidence.qaImplPassed = qaHandoff.handoff.score >= 90 &&
|
|
47
|
-
qaHandoff.handoff.status === 'complete';
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
// Check README exists
|
|
51
|
-
const readmePaths = [
|
|
52
|
-
join(projectDir, 'README.md'),
|
|
53
|
-
join(projectDir, 'readme.md'),
|
|
54
|
-
];
|
|
55
|
-
for (const readmePath of readmePaths) {
|
|
56
|
-
if (existsSync(readmePath)) {
|
|
57
|
-
evidence.readmeUpdated = true;
|
|
58
|
-
break;
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
// Check CHANGELOG exists
|
|
63
|
-
const changelogPaths = [
|
|
64
|
-
join(projectDir, 'CHANGELOG.md'),
|
|
65
|
-
join(projectDir, 'docs', 'CHANGELOG.md'),
|
|
66
|
-
join(projectDir, 'changelog.md'),
|
|
67
|
-
join(projectDir, 'CHANGES.md'),
|
|
68
|
-
];
|
|
69
|
-
for (const clPath of changelogPaths) {
|
|
70
|
-
if (existsSync(clPath)) {
|
|
71
|
-
evidence.changelogUpdated = true;
|
|
72
|
-
break;
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
// Check release notes
|
|
77
|
-
const releaseNotePaths = [
|
|
78
|
-
join(projectDir, 'RELEASE.md'),
|
|
79
|
-
join(projectDir, resolveFrameworkDir(projectDir), 'artifacts', 'release-notes.md'),
|
|
80
|
-
];
|
|
81
|
-
for (const rnPath of releaseNotePaths) {
|
|
82
|
-
if (existsSync(rnPath)) {
|
|
83
|
-
evidence.releaseNotesPrepared = true;
|
|
84
|
-
break;
|
|
85
|
-
}
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
// Load session state
|
|
89
|
-
const sessionResult = loadSession(projectDir);
|
|
90
|
-
if (sessionResult.loaded && sessionResult.session) {
|
|
91
|
-
const agents = sessionResult.session.agents || {};
|
|
92
|
-
const completedAgents = sessionResult.session.completed_agents || [];
|
|
93
|
-
|
|
94
|
-
// Check all required agents are completed
|
|
95
|
-
const requiredAgents = Object.keys(agents).filter(
|
|
96
|
-
a => agents[a].status !== 'skipped'
|
|
97
|
-
);
|
|
98
|
-
const allComplete = requiredAgents.every(
|
|
99
|
-
a => completedAgents.includes(a) || agents[a].status === 'completed'
|
|
100
|
-
);
|
|
101
|
-
|
|
102
|
-
evidence.allTasksCompleted = allComplete;
|
|
103
|
-
|
|
104
|
-
// Check for open blockers in any handoff
|
|
105
|
-
for (const agentName of completedAgents) {
|
|
106
|
-
const agentHandoff = loadHandoff(projectDir, agentName);
|
|
107
|
-
if (agentHandoff.loaded && agentHandoff.handoff) {
|
|
108
|
-
const blockers = agentHandoff.handoff.blockers || [];
|
|
109
|
-
if (blockers.length > 0) {
|
|
110
|
-
evidence.noOpenBlockers = false;
|
|
111
|
-
break;
|
|
112
|
-
}
|
|
113
|
-
}
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
evidence.sessionState = {
|
|
117
|
-
mode: sessionResult.session.mode,
|
|
118
|
-
completedCount: completedAgents.length,
|
|
119
|
-
totalAgents: Object.keys(agents).length,
|
|
120
|
-
};
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
return evidence;
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
/**
|
|
127
|
-
* Validate deployment readiness evidence.
|
|
128
|
-
*
|
|
129
|
-
* @param {object} evidence
|
|
130
|
-
* @returns {{ score: number, criteriaResults: string[], allCriteria: string[], warnings: string[] }}
|
|
131
|
-
*/
|
|
132
|
-
_validateEvidence(evidence) {
|
|
133
|
-
const allCriteria = [
|
|
134
|
-
'QA-Implementation passed',
|
|
135
|
-
'README updated',
|
|
136
|
-
'CHANGELOG updated',
|
|
137
|
-
'Release notes prepared',
|
|
138
|
-
'All tasks completed',
|
|
139
|
-
'No open blockers',
|
|
140
|
-
];
|
|
141
|
-
|
|
142
|
-
const criteriaResults = [];
|
|
143
|
-
const warnings = [];
|
|
144
|
-
|
|
145
|
-
if (evidence.qaImplPassed) {
|
|
146
|
-
criteriaResults.push('QA-Implementation passed');
|
|
147
|
-
} else {
|
|
148
|
-
warnings.push('QA-Implementation has not passed');
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
if (evidence.readmeUpdated) {
|
|
152
|
-
criteriaResults.push('README updated');
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
if (evidence.changelogUpdated) {
|
|
156
|
-
criteriaResults.push('CHANGELOG updated');
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
if (evidence.releaseNotesPrepared) {
|
|
160
|
-
criteriaResults.push('Release notes prepared');
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
if (evidence.allTasksCompleted) {
|
|
164
|
-
criteriaResults.push('All tasks completed');
|
|
165
|
-
} else {
|
|
166
|
-
warnings.push('Not all tasks are completed');
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
if (evidence.noOpenBlockers) {
|
|
170
|
-
criteriaResults.push('No open blockers');
|
|
171
|
-
} else {
|
|
172
|
-
warnings.push('Open blockers remain from agent handoffs');
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
const score = allCriteria.length > 0
|
|
176
|
-
? Math.round((criteriaResults.length / allCriteria.length) * 100)
|
|
177
|
-
: 0;
|
|
178
|
-
|
|
179
|
-
return { score, criteriaResults, allCriteria, warnings };
|
|
180
|
-
}
|
|
181
|
-
}
|
package/src/gates/gate-base.js
DELETED
|
@@ -1,185 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @fileoverview GateBase — Template Method pattern base class for quality gates.
|
|
3
|
-
*
|
|
4
|
-
* Provides the abstract skeleton for all pipeline quality gates.
|
|
5
|
-
* Subclasses implement _collectEvidence() and _validateEvidence()
|
|
6
|
-
* while this base class handles evaluation orchestration.
|
|
7
|
-
*/
|
|
8
|
-
|
|
9
|
-
import { evaluateGate, getGateThreshold, resolveGateAction } from '../autonomy/autonomous-gate.js';
|
|
10
|
-
import { track as telemetryTrack } from '../telemetry/collector.js';
|
|
11
|
-
|
|
12
|
-
/**
|
|
13
|
-
* Gate verdict constants (v3.0.0).
|
|
14
|
-
* APPROVED: Score >= threshold — proceed to next pipeline stage.
|
|
15
|
-
* NEEDS_REVISION: Score within 5 points below threshold — return to agent for fixes.
|
|
16
|
-
* BLOCKED: Score significantly below threshold or critical blocker — halt pipeline.
|
|
17
|
-
*/
|
|
18
|
-
export const GateVerdict = {
|
|
19
|
-
APPROVED: 'approved',
|
|
20
|
-
NEEDS_REVISION: 'needs_revision',
|
|
21
|
-
BLOCKED: 'blocked',
|
|
22
|
-
};
|
|
23
|
-
|
|
24
|
-
/**
|
|
25
|
-
* Determine the gate verdict from a score and threshold.
|
|
26
|
-
*
|
|
27
|
-
* @param {number} score - Gate evaluation score (0-100)
|
|
28
|
-
* @param {number} threshold - Minimum score to pass
|
|
29
|
-
* @param {boolean} [hasCriticalBlocker=false] - Whether a critical blocker exists
|
|
30
|
-
* @returns {string} One of GateVerdict values
|
|
31
|
-
*/
|
|
32
|
-
export function determineVerdict(score, threshold, hasCriticalBlocker = false) {
|
|
33
|
-
if (hasCriticalBlocker) return GateVerdict.BLOCKED;
|
|
34
|
-
if (score >= threshold) return GateVerdict.APPROVED;
|
|
35
|
-
if (score >= threshold - 5) return GateVerdict.NEEDS_REVISION;
|
|
36
|
-
return GateVerdict.BLOCKED;
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
/**
|
|
40
|
-
* Abstract base class for quality gates.
|
|
41
|
-
*
|
|
42
|
-
* Uses the Template Method pattern: evaluate() is the template that
|
|
43
|
-
* calls _collectEvidence() and _validateEvidence() which subclasses implement.
|
|
44
|
-
*/
|
|
45
|
-
export class GateBase {
|
|
46
|
-
/**
|
|
47
|
-
* @param {object} config
|
|
48
|
-
* @param {string} config.id - Unique gate identifier (e.g. 'g1-planning-complete')
|
|
49
|
-
* @param {string} config.name - Human-readable gate name
|
|
50
|
-
* @param {string} config.pipelinePoint - Pipeline point this gate guards
|
|
51
|
-
* @param {string} config.agent - Agent whose output this gate evaluates
|
|
52
|
-
*/
|
|
53
|
-
constructor(config) {
|
|
54
|
-
if (new.target === GateBase) {
|
|
55
|
-
throw new Error('GateBase is abstract and cannot be instantiated directly.');
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
const { id, name, pipelinePoint, agent } = config;
|
|
59
|
-
|
|
60
|
-
if (!id || !name || !pipelinePoint || !agent) {
|
|
61
|
-
throw new Error('GateBase requires id, name, pipelinePoint, and agent in config.');
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
this.id = id;
|
|
65
|
-
this.name = name;
|
|
66
|
-
this.pipelinePoint = pipelinePoint;
|
|
67
|
-
this.agent = agent;
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
/**
|
|
71
|
-
* Evaluate the quality gate.
|
|
72
|
-
*
|
|
73
|
-
* Template Method: collects evidence, validates it, then either runs
|
|
74
|
-
* autonomous evaluation or returns recommendation for human review.
|
|
75
|
-
*
|
|
76
|
-
* @param {string} projectDir - Project root directory
|
|
77
|
-
* @param {string} [mode='autonomous'] - 'autonomous' or 'human-in-the-loop'
|
|
78
|
-
* @returns {object} GateResult: { result, score, evidence, recommendation, canProceed }
|
|
79
|
-
*/
|
|
80
|
-
evaluate(projectDir, mode = 'autonomous') {
|
|
81
|
-
// Step 1: Collect evidence (subclass implements)
|
|
82
|
-
const evidence = this._collectEvidence(projectDir);
|
|
83
|
-
|
|
84
|
-
// Step 2: Validate evidence (subclass implements)
|
|
85
|
-
const validation = this._validateEvidence(evidence);
|
|
86
|
-
|
|
87
|
-
const { score, criteriaResults, allCriteria, warnings = [] } = validation;
|
|
88
|
-
|
|
89
|
-
if (mode === 'autonomous') {
|
|
90
|
-
// Step 3a: Autonomous evaluation via autonomous-gate module
|
|
91
|
-
const gateResult = evaluateGate({
|
|
92
|
-
agent: this.agent,
|
|
93
|
-
score,
|
|
94
|
-
criteriaResults,
|
|
95
|
-
allCriteria,
|
|
96
|
-
warnings,
|
|
97
|
-
});
|
|
98
|
-
|
|
99
|
-
const action = resolveGateAction(gateResult.result, mode);
|
|
100
|
-
|
|
101
|
-
const threshold = getGateThreshold(this.agent);
|
|
102
|
-
const hasCriticalBlocker = warnings.some(w => w.toLowerCase().includes('critical'));
|
|
103
|
-
const verdict = determineVerdict(gateResult.score, threshold, hasCriticalBlocker);
|
|
104
|
-
|
|
105
|
-
// Track telemetry
|
|
106
|
-
telemetryTrack('gate_evaluated', {
|
|
107
|
-
gate: this.id,
|
|
108
|
-
result: verdict,
|
|
109
|
-
score: gateResult.score,
|
|
110
|
-
blockers: warnings.filter(w => w.toLowerCase().includes('critical')),
|
|
111
|
-
});
|
|
112
|
-
|
|
113
|
-
return {
|
|
114
|
-
gateId: this.id,
|
|
115
|
-
gateName: this.name,
|
|
116
|
-
result: gateResult.result,
|
|
117
|
-
verdict,
|
|
118
|
-
score: gateResult.score,
|
|
119
|
-
evidence,
|
|
120
|
-
recommendation: action.action,
|
|
121
|
-
canProceed: verdict === GateVerdict.APPROVED,
|
|
122
|
-
details: gateResult.details,
|
|
123
|
-
warnings,
|
|
124
|
-
};
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
// Step 3b: Human-in-the-loop — return evidence + recommendation
|
|
128
|
-
const threshold = getGateThreshold(this.agent);
|
|
129
|
-
const recommendation = score >= threshold
|
|
130
|
-
? 'Recommend approval — criteria met.'
|
|
131
|
-
: `Recommend review — score ${score} below threshold ${threshold}.`;
|
|
132
|
-
|
|
133
|
-
return {
|
|
134
|
-
gateId: this.id,
|
|
135
|
-
gateName: this.name,
|
|
136
|
-
result: 'review',
|
|
137
|
-
score,
|
|
138
|
-
evidence,
|
|
139
|
-
recommendation,
|
|
140
|
-
canProceed: false, // Human must explicitly approve
|
|
141
|
-
details: {
|
|
142
|
-
criteriaResults,
|
|
143
|
-
allCriteria,
|
|
144
|
-
threshold,
|
|
145
|
-
},
|
|
146
|
-
warnings,
|
|
147
|
-
};
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
/**
|
|
151
|
-
* Collect evidence from the project filesystem.
|
|
152
|
-
* ABSTRACT — subclass MUST implement.
|
|
153
|
-
*
|
|
154
|
-
* @param {string} _projectDir
|
|
155
|
-
* @returns {object} Evidence data
|
|
156
|
-
*/
|
|
157
|
-
_collectEvidence(_projectDir) {
|
|
158
|
-
throw new Error(`${this.constructor.name} must implement _collectEvidence()`);
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
/**
|
|
162
|
-
* Validate collected evidence and produce a score.
|
|
163
|
-
* ABSTRACT — subclass MUST implement.
|
|
164
|
-
*
|
|
165
|
-
* @param {object} _evidence
|
|
166
|
-
* @returns {{ score: number, criteriaResults: string[], allCriteria: string[], warnings: string[] }}
|
|
167
|
-
*/
|
|
168
|
-
_validateEvidence(_evidence) {
|
|
169
|
-
throw new Error(`${this.constructor.name} must implement _validateEvidence()`);
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
/**
|
|
173
|
-
* Get gate metadata.
|
|
174
|
-
*
|
|
175
|
-
* @returns {{ id: string, name: string, pipelinePoint: string, threshold: number }}
|
|
176
|
-
*/
|
|
177
|
-
getInfo() {
|
|
178
|
-
return {
|
|
179
|
-
id: this.id,
|
|
180
|
-
name: this.name,
|
|
181
|
-
pipelinePoint: this.pipelinePoint,
|
|
182
|
-
threshold: getGateThreshold(this.agent),
|
|
183
|
-
};
|
|
184
|
-
}
|
|
185
|
-
}
|
package/src/gates/index.js
DELETED
|
@@ -1,46 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @fileoverview Quality Gates barrel exports and registry.
|
|
3
|
-
*
|
|
4
|
-
* Provides all gate classes and a registry function to look up
|
|
5
|
-
* the appropriate gate for a given pipeline point.
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
export { GateBase } from './gate-base.js';
|
|
9
|
-
export { CircuitBreaker, CIRCUIT_STATES } from './circuit-breaker.js';
|
|
10
|
-
export { PlanningCompleteGate } from './g1-planning-complete.js';
|
|
11
|
-
export { QAPlanningGate } from './g2-qa-planning.js';
|
|
12
|
-
export { ImplementationGate } from './g3-implementation.js';
|
|
13
|
-
export { QAImplementationGate, QA_IMPL_VERDICTS } from './g4-qa-implementation.js';
|
|
14
|
-
export { DeployReadyGate } from './g5-deploy-ready.js';
|
|
15
|
-
|
|
16
|
-
import { PlanningCompleteGate as _G1 } from './g1-planning-complete.js';
|
|
17
|
-
import { QAPlanningGate as _G2 } from './g2-qa-planning.js';
|
|
18
|
-
import { ImplementationGate as _G3 } from './g3-implementation.js';
|
|
19
|
-
import { QAImplementationGate as _G4 } from './g4-qa-implementation.js';
|
|
20
|
-
import { DeployReadyGate as _G5 } from './g5-deploy-ready.js';
|
|
21
|
-
|
|
22
|
-
/**
|
|
23
|
-
* Pipeline point to gate class mapping.
|
|
24
|
-
*/
|
|
25
|
-
const PIPELINE_POINT_MAP = {
|
|
26
|
-
'pre-build': _G1,
|
|
27
|
-
'post-qa-planning': _G2,
|
|
28
|
-
'post-dev': _G3,
|
|
29
|
-
'post-qa-impl': _G4,
|
|
30
|
-
'pre-deploy': _G5,
|
|
31
|
-
};
|
|
32
|
-
|
|
33
|
-
/**
|
|
34
|
-
* Get the gate instance for a specific pipeline point.
|
|
35
|
-
*
|
|
36
|
-
* @param {string} point - Pipeline point identifier
|
|
37
|
-
* @returns {import('./gate-base.js').GateBase} Gate instance
|
|
38
|
-
* @throws {Error} If no gate is registered for the given point
|
|
39
|
-
*/
|
|
40
|
-
export function getGateForPipelinePoint(point) {
|
|
41
|
-
const GateClass = PIPELINE_POINT_MAP[point];
|
|
42
|
-
if (!GateClass) {
|
|
43
|
-
throw new Error(`No gate registered for pipeline point: ${point}`);
|
|
44
|
-
}
|
|
45
|
-
return new GateClass();
|
|
46
|
-
}
|