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,151 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @fileoverview Circuit Breaker pattern for quality gate evaluation.
|
|
3
|
-
*
|
|
4
|
-
* Prevents repeated failures from consuming resources by tracking
|
|
5
|
-
* consecutive failures and temporarily stopping execution.
|
|
6
|
-
*
|
|
7
|
-
* States:
|
|
8
|
-
* CLOSED — Normal operation, requests flow through
|
|
9
|
-
* OPEN — Failures exceeded threshold, requests rejected immediately
|
|
10
|
-
* HALF_OPEN — After reset timeout, allows a single test request
|
|
11
|
-
*/
|
|
12
|
-
|
|
13
|
-
import { track as telemetryTrack } from '../telemetry/collector.js';
|
|
14
|
-
|
|
15
|
-
export const CIRCUIT_STATES = {
|
|
16
|
-
CLOSED: 'CLOSED',
|
|
17
|
-
OPEN: 'OPEN',
|
|
18
|
-
HALF_OPEN: 'HALF_OPEN',
|
|
19
|
-
};
|
|
20
|
-
|
|
21
|
-
export class CircuitBreaker {
|
|
22
|
-
/**
|
|
23
|
-
* @param {object} [options]
|
|
24
|
-
* @param {number} [options.failureThreshold=3] - Consecutive failures before opening
|
|
25
|
-
* @param {number} [options.resetTimeout=60000] - Milliseconds before trying recovery
|
|
26
|
-
*/
|
|
27
|
-
constructor(options = {}) {
|
|
28
|
-
this.failureThreshold = options.failureThreshold ?? 3;
|
|
29
|
-
this.resetTimeout = options.resetTimeout ?? 60000;
|
|
30
|
-
|
|
31
|
-
this._state = CIRCUIT_STATES.CLOSED;
|
|
32
|
-
this._failures = 0;
|
|
33
|
-
this._successes = 0;
|
|
34
|
-
this._lastFailure = null;
|
|
35
|
-
this._lastSuccess = null;
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
/**
|
|
39
|
-
* Execute a function through the circuit breaker.
|
|
40
|
-
*
|
|
41
|
-
* @param {Function} fn - Function to execute (may be async)
|
|
42
|
-
* @returns {*} Result from fn
|
|
43
|
-
* @throws {Error} If circuit is OPEN and timeout has not elapsed
|
|
44
|
-
*/
|
|
45
|
-
execute(fn) {
|
|
46
|
-
if (this._state === CIRCUIT_STATES.OPEN) {
|
|
47
|
-
// Check if reset timeout has elapsed
|
|
48
|
-
const elapsed = Date.now() - (this._lastFailure || 0);
|
|
49
|
-
if (elapsed >= this.resetTimeout) {
|
|
50
|
-
this._state = CIRCUIT_STATES.HALF_OPEN;
|
|
51
|
-
} else {
|
|
52
|
-
throw new Error('Circuit breaker is OPEN. Request rejected.');
|
|
53
|
-
}
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
try {
|
|
57
|
-
const result = fn();
|
|
58
|
-
this._onSuccess();
|
|
59
|
-
return result;
|
|
60
|
-
} catch (error) {
|
|
61
|
-
this._onFailure();
|
|
62
|
-
throw error;
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
/**
|
|
67
|
-
* Execute an async function through the circuit breaker.
|
|
68
|
-
* @param {Function} fn - Async function to execute
|
|
69
|
-
* @returns {Promise<*>} Result from fn
|
|
70
|
-
*/
|
|
71
|
-
async executeAsync(fn) {
|
|
72
|
-
if (this._state === CIRCUIT_STATES.OPEN) {
|
|
73
|
-
const elapsed = Date.now() - (this._lastFailure || 0);
|
|
74
|
-
if (elapsed >= this.resetTimeout) {
|
|
75
|
-
this._state = CIRCUIT_STATES.HALF_OPEN;
|
|
76
|
-
} else {
|
|
77
|
-
throw new Error('Circuit breaker is OPEN. Request rejected.');
|
|
78
|
-
}
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
try {
|
|
82
|
-
const result = await fn();
|
|
83
|
-
this._onSuccess();
|
|
84
|
-
return result;
|
|
85
|
-
} catch (error) {
|
|
86
|
-
this._onFailure();
|
|
87
|
-
throw error;
|
|
88
|
-
}
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
/** @private */
|
|
92
|
-
_onSuccess() {
|
|
93
|
-
this._successes++;
|
|
94
|
-
this._lastSuccess = Date.now();
|
|
95
|
-
|
|
96
|
-
if (this._state === CIRCUIT_STATES.HALF_OPEN) {
|
|
97
|
-
// Recovery confirmed — close the circuit
|
|
98
|
-
this._state = CIRCUIT_STATES.CLOSED;
|
|
99
|
-
this._failures = 0;
|
|
100
|
-
}
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
/** @private */
|
|
104
|
-
_onFailure() {
|
|
105
|
-
this._failures++;
|
|
106
|
-
this._lastFailure = Date.now();
|
|
107
|
-
|
|
108
|
-
if (this._state === CIRCUIT_STATES.HALF_OPEN) {
|
|
109
|
-
// Recovery failed — reopen
|
|
110
|
-
this._state = CIRCUIT_STATES.OPEN;
|
|
111
|
-
} else if (this._failures >= this.failureThreshold) {
|
|
112
|
-
this._state = CIRCUIT_STATES.OPEN;
|
|
113
|
-
telemetryTrack('circuit_breaker_triggered', {
|
|
114
|
-
trigger: 'failure_threshold',
|
|
115
|
-
agent: 'unknown',
|
|
116
|
-
provider: 'unknown',
|
|
117
|
-
});
|
|
118
|
-
}
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
/**
|
|
122
|
-
* Get current circuit state.
|
|
123
|
-
* @returns {'CLOSED' | 'OPEN' | 'HALF_OPEN'}
|
|
124
|
-
*/
|
|
125
|
-
getState() {
|
|
126
|
-
return this._state;
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
/**
|
|
130
|
-
* Get circuit statistics.
|
|
131
|
-
* @returns {{ state: string, failures: number, successes: number, lastFailure: number|null, lastSuccess: number|null }}
|
|
132
|
-
*/
|
|
133
|
-
getStats() {
|
|
134
|
-
return {
|
|
135
|
-
state: this._state,
|
|
136
|
-
failures: this._failures,
|
|
137
|
-
successes: this._successes,
|
|
138
|
-
lastFailure: this._lastFailure,
|
|
139
|
-
lastSuccess: this._lastSuccess,
|
|
140
|
-
};
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
/**
|
|
144
|
-
* Manually reset the circuit breaker to CLOSED state.
|
|
145
|
-
*/
|
|
146
|
-
reset() {
|
|
147
|
-
this._state = CIRCUIT_STATES.CLOSED;
|
|
148
|
-
this._failures = 0;
|
|
149
|
-
this._lastFailure = null;
|
|
150
|
-
}
|
|
151
|
-
}
|
|
@@ -1,154 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @fileoverview G1 — Plan Complete Gate
|
|
3
|
-
*
|
|
4
|
-
* Pre-BUILD gate that validates the PLAN phase is fully completed.
|
|
5
|
-
* Checks that all required artifacts exist and QA-Planning has approved.
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
import { existsSync, readdirSync } from 'node:fs';
|
|
9
|
-
import { join } from 'node:path';
|
|
10
|
-
import { resolveFrameworkDir } from '../utils/framework-dir.js';
|
|
11
|
-
import { GateBase } from './gate-base.js';
|
|
12
|
-
import { loadSession } from '../orchestrator/session-manager.js';
|
|
13
|
-
import { loadHandoff } from '../tasks/handoff.js';
|
|
14
|
-
|
|
15
|
-
const REQUIRED_ARTIFACTS = [
|
|
16
|
-
{ key: 'brief', dir: '1-Brief', label: 'Brief artifact' },
|
|
17
|
-
{ key: 'prd', dir: '2-PRD', label: 'PRD artifact' },
|
|
18
|
-
{ key: 'architecture', dir: '3-Architecture', label: 'Architecture artifact' },
|
|
19
|
-
{ key: 'ux', dir: '4-UX', label: 'UX artifact' },
|
|
20
|
-
{ key: 'phases', dir: '5-Phases', label: 'Phases artifact' },
|
|
21
|
-
{ key: 'tasks', dir: '6-Tasks', label: 'Tasks artifact' },
|
|
22
|
-
];
|
|
23
|
-
|
|
24
|
-
export class PlanningCompleteGate extends GateBase {
|
|
25
|
-
constructor() {
|
|
26
|
-
super({
|
|
27
|
-
id: 'g1-planning-complete',
|
|
28
|
-
name: 'Planning Complete',
|
|
29
|
-
pipelinePoint: 'pre-build',
|
|
30
|
-
agent: 'qa-planning',
|
|
31
|
-
});
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
/**
|
|
35
|
-
* Collect evidence by checking filesystem for PLAN artifacts.
|
|
36
|
-
*
|
|
37
|
-
* @param {string} projectDir
|
|
38
|
-
* @returns {object} Evidence about artifact presence, session state, and handoff
|
|
39
|
-
*/
|
|
40
|
-
_collectEvidence(projectDir) {
|
|
41
|
-
const artifactsBase = join(projectDir, resolveFrameworkDir(projectDir), 'artifacts');
|
|
42
|
-
const evidence = {
|
|
43
|
-
artifacts: {},
|
|
44
|
-
sessionState: null,
|
|
45
|
-
qaPlanningHandoff: null,
|
|
46
|
-
};
|
|
47
|
-
|
|
48
|
-
// Check each required artifact directory
|
|
49
|
-
for (const artifact of REQUIRED_ARTIFACTS) {
|
|
50
|
-
const dirPath = join(artifactsBase, artifact.dir);
|
|
51
|
-
const exists = existsSync(dirPath);
|
|
52
|
-
let hasContent = false;
|
|
53
|
-
|
|
54
|
-
if (exists) {
|
|
55
|
-
try {
|
|
56
|
-
const entries = readdirSync(dirPath);
|
|
57
|
-
hasContent = entries.length > 0;
|
|
58
|
-
} catch { /* expected: directory may not exist */
|
|
59
|
-
hasContent = false;
|
|
60
|
-
}
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
evidence.artifacts[artifact.key] = {
|
|
64
|
-
exists,
|
|
65
|
-
hasContent,
|
|
66
|
-
label: artifact.label,
|
|
67
|
-
};
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
// Check session state
|
|
71
|
-
const sessionResult = loadSession(projectDir);
|
|
72
|
-
if (sessionResult.loaded && sessionResult.session) {
|
|
73
|
-
evidence.sessionState = {
|
|
74
|
-
mode: sessionResult.session.mode,
|
|
75
|
-
completedAgents: sessionResult.session.completed_agents || [],
|
|
76
|
-
agents: sessionResult.session.agents || {},
|
|
77
|
-
};
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
// Check QA-Planning handoff
|
|
81
|
-
const handoffResult = loadHandoff(projectDir, 'qa-planning');
|
|
82
|
-
if (handoffResult.loaded && handoffResult.handoff) {
|
|
83
|
-
evidence.qaPlanningHandoff = {
|
|
84
|
-
score: handoffResult.handoff.score,
|
|
85
|
-
status: handoffResult.handoff.status,
|
|
86
|
-
blockers: handoffResult.handoff.blockers || [],
|
|
87
|
-
};
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
return evidence;
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
/**
|
|
94
|
-
* Validate evidence and produce a score.
|
|
95
|
-
*
|
|
96
|
-
* @param {object} evidence
|
|
97
|
-
* @returns {{ score: number, criteriaResults: string[], allCriteria: string[], warnings: string[] }}
|
|
98
|
-
*/
|
|
99
|
-
_validateEvidence(evidence) {
|
|
100
|
-
const allCriteria = [
|
|
101
|
-
...REQUIRED_ARTIFACTS.map(a => `${a.label} exists`),
|
|
102
|
-
'QA-Planning handoff with passing score',
|
|
103
|
-
'Session shows plan completed',
|
|
104
|
-
];
|
|
105
|
-
|
|
106
|
-
const criteriaResults = [];
|
|
107
|
-
const warnings = [];
|
|
108
|
-
|
|
109
|
-
// Check artifacts
|
|
110
|
-
for (const artifact of REQUIRED_ARTIFACTS) {
|
|
111
|
-
const info = evidence.artifacts[artifact.key];
|
|
112
|
-
if (info && info.exists && info.hasContent) {
|
|
113
|
-
criteriaResults.push(`${artifact.label} exists`);
|
|
114
|
-
} else if (info && info.exists) {
|
|
115
|
-
warnings.push(`${artifact.label} directory exists but is empty`);
|
|
116
|
-
}
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
// Check QA-Planning handoff score
|
|
120
|
-
if (evidence.qaPlanningHandoff) {
|
|
121
|
-
if (evidence.qaPlanningHandoff.score >= 95) {
|
|
122
|
-
criteriaResults.push('QA-Planning handoff with passing score');
|
|
123
|
-
} else {
|
|
124
|
-
warnings.push(`QA-Planning score ${evidence.qaPlanningHandoff.score} below 95 threshold`);
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
if (evidence.qaPlanningHandoff.blockers.length > 0) {
|
|
128
|
-
warnings.push(`${evidence.qaPlanningHandoff.blockers.length} blocker(s) from QA-Planning`);
|
|
129
|
-
}
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
// Check session state
|
|
133
|
-
if (evidence.sessionState) {
|
|
134
|
-
const planAgents = ['brief', 'detail', 'architect', 'ux', 'phases', 'tasks', 'qa-planning'];
|
|
135
|
-
const completed = planAgents.filter(a =>
|
|
136
|
-
evidence.sessionState.completedAgents.includes(a)
|
|
137
|
-
);
|
|
138
|
-
|
|
139
|
-
if (completed.length === planAgents.length) {
|
|
140
|
-
criteriaResults.push('Session shows plan completed');
|
|
141
|
-
} else {
|
|
142
|
-
const missing = planAgents.filter(a => !completed.includes(a));
|
|
143
|
-
warnings.push(`Incomplete plan agents: ${missing.join(', ')}`);
|
|
144
|
-
}
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
// Calculate score
|
|
148
|
-
const score = allCriteria.length > 0
|
|
149
|
-
? Math.round((criteriaResults.length / allCriteria.length) * 100)
|
|
150
|
-
: 0;
|
|
151
|
-
|
|
152
|
-
return { score, criteriaResults, allCriteria, warnings };
|
|
153
|
-
}
|
|
154
|
-
}
|
|
@@ -1,156 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @fileoverview G2 — QA Planning Gate
|
|
3
|
-
*
|
|
4
|
-
* Post-QA-Planning gate that validates the test strategy and
|
|
5
|
-
* quality plan are properly defined before BUILD begins.
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
import { existsSync, readdirSync } from 'node:fs';
|
|
9
|
-
import { join } from 'node:path';
|
|
10
|
-
import { resolveFrameworkDir } from '../utils/framework-dir.js';
|
|
11
|
-
import { GateBase, determineVerdict } from './gate-base.js';
|
|
12
|
-
import { loadSession } from '../orchestrator/session-manager.js';
|
|
13
|
-
import { loadHandoff } from '../tasks/handoff.js';
|
|
14
|
-
|
|
15
|
-
export class QAPlanningGate extends GateBase {
|
|
16
|
-
constructor() {
|
|
17
|
-
super({
|
|
18
|
-
id: 'g2-qa-planning',
|
|
19
|
-
name: 'QA Planning',
|
|
20
|
-
pipelinePoint: 'post-qa-planning',
|
|
21
|
-
agent: 'qa-planning',
|
|
22
|
-
});
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
/**
|
|
26
|
-
* Collect evidence about QA planning artifacts.
|
|
27
|
-
*
|
|
28
|
-
* @param {string} projectDir
|
|
29
|
-
* @returns {object} Evidence about test strategy, coverage plan, risk assessment
|
|
30
|
-
*/
|
|
31
|
-
_collectEvidence(projectDir) {
|
|
32
|
-
const artifactsBase = join(projectDir, resolveFrameworkDir(projectDir), 'artifacts');
|
|
33
|
-
const evidence = {
|
|
34
|
-
testStrategy: false,
|
|
35
|
-
coveragePlan: false,
|
|
36
|
-
riskAssessment: false,
|
|
37
|
-
qaPlanningHandoff: null,
|
|
38
|
-
sessionState: null,
|
|
39
|
-
};
|
|
40
|
-
|
|
41
|
-
// Check for QA-Planning artifacts (may be in a qa-planning folder or 7-QA-Planning)
|
|
42
|
-
const qaDirs = ['7-QA-Planning', 'qa-planning'];
|
|
43
|
-
for (const dir of qaDirs) {
|
|
44
|
-
const qaPath = join(artifactsBase, dir);
|
|
45
|
-
if (existsSync(qaPath)) {
|
|
46
|
-
try {
|
|
47
|
-
const entries = readdirSync(qaPath);
|
|
48
|
-
for (const entry of entries) {
|
|
49
|
-
const lower = entry.toLowerCase();
|
|
50
|
-
if (lower.includes('strategy') || lower.includes('test-plan')) {
|
|
51
|
-
evidence.testStrategy = true;
|
|
52
|
-
}
|
|
53
|
-
if (lower.includes('coverage')) {
|
|
54
|
-
evidence.coveragePlan = true;
|
|
55
|
-
}
|
|
56
|
-
if (lower.includes('risk')) {
|
|
57
|
-
evidence.riskAssessment = true;
|
|
58
|
-
}
|
|
59
|
-
}
|
|
60
|
-
} catch { /* expected: operation may fail gracefully */
|
|
61
|
-
// Directory read failed, leave as false
|
|
62
|
-
}
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
// Load QA-Planning handoff
|
|
67
|
-
const handoffResult = loadHandoff(projectDir, 'qa-planning');
|
|
68
|
-
if (handoffResult.loaded && handoffResult.handoff) {
|
|
69
|
-
evidence.qaPlanningHandoff = {
|
|
70
|
-
score: handoffResult.handoff.score,
|
|
71
|
-
status: handoffResult.handoff.status,
|
|
72
|
-
outputs: handoffResult.handoff.outputs || [],
|
|
73
|
-
criteriaUnmet: handoffResult.handoff.criteria_unmet || [],
|
|
74
|
-
};
|
|
75
|
-
|
|
76
|
-
// Handoff outputs may indicate strategy/coverage/risk exist
|
|
77
|
-
for (const output of evidence.qaPlanningHandoff.outputs) {
|
|
78
|
-
const lower = (typeof output === 'string' ? output : '').toLowerCase();
|
|
79
|
-
if (lower.includes('strategy') || lower.includes('test-plan')) {
|
|
80
|
-
evidence.testStrategy = true;
|
|
81
|
-
}
|
|
82
|
-
if (lower.includes('coverage')) {
|
|
83
|
-
evidence.coveragePlan = true;
|
|
84
|
-
}
|
|
85
|
-
if (lower.includes('risk')) {
|
|
86
|
-
evidence.riskAssessment = true;
|
|
87
|
-
}
|
|
88
|
-
}
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
// Load session state
|
|
92
|
-
const sessionResult = loadSession(projectDir);
|
|
93
|
-
if (sessionResult.loaded && sessionResult.session) {
|
|
94
|
-
evidence.sessionState = {
|
|
95
|
-
qaPlanningAgent: sessionResult.session.agents?.['qa-planning'] || null,
|
|
96
|
-
completedAgents: sessionResult.session.completed_agents || [],
|
|
97
|
-
};
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
return evidence;
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
/**
|
|
104
|
-
* Validate QA planning evidence.
|
|
105
|
-
*
|
|
106
|
-
* @param {object} evidence
|
|
107
|
-
* @returns {{ score: number, criteriaResults: string[], allCriteria: string[], warnings: string[] }}
|
|
108
|
-
*/
|
|
109
|
-
_validateEvidence(evidence) {
|
|
110
|
-
const allCriteria = [
|
|
111
|
-
'Test strategy document exists',
|
|
112
|
-
'Coverage plan exists',
|
|
113
|
-
'Risk assessment documented',
|
|
114
|
-
'QA-Planning handoff with score >= 95',
|
|
115
|
-
'All QA-Planning criteria met',
|
|
116
|
-
];
|
|
117
|
-
|
|
118
|
-
const criteriaResults = [];
|
|
119
|
-
const warnings = [];
|
|
120
|
-
|
|
121
|
-
if (evidence.testStrategy) {
|
|
122
|
-
criteriaResults.push('Test strategy document exists');
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
if (evidence.coveragePlan) {
|
|
126
|
-
criteriaResults.push('Coverage plan exists');
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
if (evidence.riskAssessment) {
|
|
130
|
-
criteriaResults.push('Risk assessment documented');
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
// Check handoff score
|
|
134
|
-
if (evidence.qaPlanningHandoff) {
|
|
135
|
-
if (evidence.qaPlanningHandoff.score >= 95) {
|
|
136
|
-
criteriaResults.push('QA-Planning handoff with score >= 95');
|
|
137
|
-
} else {
|
|
138
|
-
warnings.push(`QA-Planning score: ${evidence.qaPlanningHandoff.score} (need >= 95)`);
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
if (evidence.qaPlanningHandoff.criteriaUnmet.length === 0) {
|
|
142
|
-
criteriaResults.push('All QA-Planning criteria met');
|
|
143
|
-
} else {
|
|
144
|
-
warnings.push(`${evidence.qaPlanningHandoff.criteriaUnmet.length} unmet criteria`);
|
|
145
|
-
}
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
const score = allCriteria.length > 0
|
|
149
|
-
? Math.round((criteriaResults.length / allCriteria.length) * 100)
|
|
150
|
-
: 0;
|
|
151
|
-
|
|
152
|
-
const verdict = determineVerdict(score, 95);
|
|
153
|
-
|
|
154
|
-
return { score, criteriaResults, allCriteria, warnings, verdict };
|
|
155
|
-
}
|
|
156
|
-
}
|
|
@@ -1,215 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @fileoverview G3 — Implementation Gate
|
|
3
|
-
*
|
|
4
|
-
* Post-Dev gate that validates implementation quality.
|
|
5
|
-
* Checks that all dev tasks are completed, tests exist alongside
|
|
6
|
-
* source files, lint passes, and no security issues are flagged.
|
|
7
|
-
*/
|
|
8
|
-
|
|
9
|
-
import { existsSync, readdirSync, readFileSync } from 'node:fs';
|
|
10
|
-
import { join } from 'node:path';
|
|
11
|
-
import { resolveFrameworkDir } from '../utils/framework-dir.js';
|
|
12
|
-
import { GateBase } from './gate-base.js';
|
|
13
|
-
import { loadSession } from '../orchestrator/session-manager.js';
|
|
14
|
-
import { loadHandoff } from '../tasks/handoff.js';
|
|
15
|
-
|
|
16
|
-
export class ImplementationGate extends GateBase {
|
|
17
|
-
constructor() {
|
|
18
|
-
super({
|
|
19
|
-
id: 'g3-implementation',
|
|
20
|
-
name: 'Implementation Quality',
|
|
21
|
-
pipelinePoint: 'post-dev',
|
|
22
|
-
agent: 'dev',
|
|
23
|
-
});
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
/**
|
|
27
|
-
* Collect evidence about implementation completeness and quality.
|
|
28
|
-
*
|
|
29
|
-
* @param {string} projectDir
|
|
30
|
-
* @returns {object} Evidence about dev tasks, files, tests, lint, and security
|
|
31
|
-
*/
|
|
32
|
-
_collectEvidence(projectDir) {
|
|
33
|
-
const evidence = {
|
|
34
|
-
devTasksCompleted: false,
|
|
35
|
-
sourceFilesCreated: false,
|
|
36
|
-
testFilesCreated: false,
|
|
37
|
-
lintPasses: null, // null = unknown, true/false = checked
|
|
38
|
-
securityClean: null,
|
|
39
|
-
devHandoff: null,
|
|
40
|
-
sessionState: null,
|
|
41
|
-
};
|
|
42
|
-
|
|
43
|
-
// Load session state for dev agent status
|
|
44
|
-
const sessionResult = loadSession(projectDir);
|
|
45
|
-
if (sessionResult.loaded && sessionResult.session) {
|
|
46
|
-
const devAgent = sessionResult.session.agents?.dev;
|
|
47
|
-
evidence.devTasksCompleted = devAgent?.status === 'completed';
|
|
48
|
-
|
|
49
|
-
evidence.sessionState = {
|
|
50
|
-
devStatus: devAgent?.status || 'pending',
|
|
51
|
-
devScore: devAgent?.score || 0,
|
|
52
|
-
completedAgents: sessionResult.session.completed_agents || [],
|
|
53
|
-
};
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
// Load dev handoff
|
|
57
|
-
const handoffResult = loadHandoff(projectDir, 'dev');
|
|
58
|
-
if (handoffResult.loaded && handoffResult.handoff) {
|
|
59
|
-
evidence.devHandoff = {
|
|
60
|
-
score: handoffResult.handoff.score,
|
|
61
|
-
status: handoffResult.handoff.status,
|
|
62
|
-
outputs: handoffResult.handoff.outputs || [],
|
|
63
|
-
blockers: handoffResult.handoff.blockers || [],
|
|
64
|
-
criteriaUnmet: handoffResult.handoff.criteria_unmet || [],
|
|
65
|
-
};
|
|
66
|
-
|
|
67
|
-
// Check if outputs indicate source and test files
|
|
68
|
-
const outputs = evidence.devHandoff.outputs;
|
|
69
|
-
evidence.sourceFilesCreated = outputs.some(o => {
|
|
70
|
-
const lower = (typeof o === 'string' ? o : '').toLowerCase();
|
|
71
|
-
return lower.includes('src/') || lower.includes('.js') || lower.includes('.ts');
|
|
72
|
-
});
|
|
73
|
-
evidence.testFilesCreated = outputs.some(o => {
|
|
74
|
-
const lower = (typeof o === 'string' ? o : '').toLowerCase();
|
|
75
|
-
return lower.includes('test') || lower.includes('.test.') || lower.includes('.spec.');
|
|
76
|
-
});
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
// Check for lint results file — parse content for real findings
|
|
80
|
-
const lintResultPath = join(projectDir, '.lint-result');
|
|
81
|
-
if (existsSync(lintResultPath)) {
|
|
82
|
-
try {
|
|
83
|
-
const content = readFileSync(lintResultPath, 'utf-8');
|
|
84
|
-
const hasErrors = /error|failed|failure|\d+\s+error/i.test(content);
|
|
85
|
-
const hasPassIndicator = /pass|clean|ok|no\s*(errors|issues|warnings)|0\s+errors|success/i.test(content);
|
|
86
|
-
if (hasErrors && !hasPassIndicator) {
|
|
87
|
-
evidence.lintPasses = false;
|
|
88
|
-
} else if (hasPassIndicator) {
|
|
89
|
-
evidence.lintPasses = true;
|
|
90
|
-
} else {
|
|
91
|
-
evidence.lintPasses = null; // Ambiguous — file exists but no clear verdict
|
|
92
|
-
}
|
|
93
|
-
} catch { /* expected: file may not exist */
|
|
94
|
-
evidence.lintPasses = null;
|
|
95
|
-
}
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
// Check for security scan results
|
|
99
|
-
const securityPaths = [
|
|
100
|
-
join(projectDir, '.security-scan'),
|
|
101
|
-
join(projectDir, resolveFrameworkDir(projectDir), 'artifacts', 'security-report.md'),
|
|
102
|
-
];
|
|
103
|
-
for (const secPath of securityPaths) {
|
|
104
|
-
if (existsSync(secPath)) {
|
|
105
|
-
try {
|
|
106
|
-
const content = readFileSync(secPath, 'utf-8');
|
|
107
|
-
const hasBlockers = /blocker|critical|high\s*severity|vulnerability found|failed/i.test(content);
|
|
108
|
-
const hasPassIndicator = /pass|clean|no\s*(issues|vulnerabilities|findings)|score:\s*(9[5-9]|100)/i.test(content);
|
|
109
|
-
if (hasBlockers) {
|
|
110
|
-
evidence.securityClean = false;
|
|
111
|
-
} else if (hasPassIndicator) {
|
|
112
|
-
evidence.securityClean = true;
|
|
113
|
-
} else {
|
|
114
|
-
evidence.securityClean = null;
|
|
115
|
-
}
|
|
116
|
-
} catch { /* expected: file may not exist */
|
|
117
|
-
evidence.securityClean = null;
|
|
118
|
-
}
|
|
119
|
-
break;
|
|
120
|
-
}
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
// If no explicit source/test file indicators, check project src/ and test/ dirs
|
|
124
|
-
if (!evidence.sourceFilesCreated) {
|
|
125
|
-
const srcDir = join(projectDir, 'src');
|
|
126
|
-
if (existsSync(srcDir)) {
|
|
127
|
-
try {
|
|
128
|
-
const entries = readdirSync(srcDir, { recursive: false });
|
|
129
|
-
evidence.sourceFilesCreated = entries.length > 0;
|
|
130
|
-
} catch { /* expected: directory may not exist */
|
|
131
|
-
// Ignore
|
|
132
|
-
}
|
|
133
|
-
}
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
if (!evidence.testFilesCreated) {
|
|
137
|
-
const testDir = join(projectDir, 'test');
|
|
138
|
-
if (existsSync(testDir)) {
|
|
139
|
-
try {
|
|
140
|
-
const entries = readdirSync(testDir, { recursive: false });
|
|
141
|
-
evidence.testFilesCreated = entries.length > 0;
|
|
142
|
-
} catch { /* expected: directory may not exist */
|
|
143
|
-
// Ignore
|
|
144
|
-
}
|
|
145
|
-
}
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
return evidence;
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
/**
|
|
152
|
-
* Validate implementation evidence.
|
|
153
|
-
*
|
|
154
|
-
* @param {object} evidence
|
|
155
|
-
* @returns {{ score: number, criteriaResults: string[], allCriteria: string[], warnings: string[] }}
|
|
156
|
-
*/
|
|
157
|
-
_validateEvidence(evidence) {
|
|
158
|
-
const allCriteria = [
|
|
159
|
-
'All dev tasks completed',
|
|
160
|
-
'Source files created',
|
|
161
|
-
'Test files created alongside source',
|
|
162
|
-
'Lint passes',
|
|
163
|
-
'No security issues flagged',
|
|
164
|
-
'Dev handoff score >= 90',
|
|
165
|
-
];
|
|
166
|
-
|
|
167
|
-
const criteriaResults = [];
|
|
168
|
-
const warnings = [];
|
|
169
|
-
|
|
170
|
-
if (evidence.devTasksCompleted) {
|
|
171
|
-
criteriaResults.push('All dev tasks completed');
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
if (evidence.sourceFilesCreated) {
|
|
175
|
-
criteriaResults.push('Source files created');
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
if (evidence.testFilesCreated) {
|
|
179
|
-
criteriaResults.push('Test files created alongside source');
|
|
180
|
-
} else {
|
|
181
|
-
warnings.push('No test files detected');
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
if (evidence.lintPasses === true) {
|
|
185
|
-
criteriaResults.push('Lint passes');
|
|
186
|
-
} else if (evidence.lintPasses === null) {
|
|
187
|
-
warnings.push('Lint status unknown — no .lint-result found');
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
if (evidence.securityClean === true) {
|
|
191
|
-
criteriaResults.push('No security issues flagged');
|
|
192
|
-
} else if (evidence.securityClean === null) {
|
|
193
|
-
warnings.push('Security scan not found — consider running a scan');
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
// Check dev handoff score
|
|
197
|
-
if (evidence.devHandoff) {
|
|
198
|
-
if (evidence.devHandoff.score >= 90) {
|
|
199
|
-
criteriaResults.push('Dev handoff score >= 90');
|
|
200
|
-
} else {
|
|
201
|
-
warnings.push(`Dev handoff score: ${evidence.devHandoff.score} (need >= 90)`);
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
if (evidence.devHandoff.blockers.length > 0) {
|
|
205
|
-
warnings.push(`${evidence.devHandoff.blockers.length} blocker(s) from dev`);
|
|
206
|
-
}
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
const score = allCriteria.length > 0
|
|
210
|
-
? Math.round((criteriaResults.length / allCriteria.length) * 100)
|
|
211
|
-
: 0;
|
|
212
|
-
|
|
213
|
-
return { score, criteriaResults, allCriteria, warnings };
|
|
214
|
-
}
|
|
215
|
-
}
|