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.
Files changed (147) hide show
  1. package/README.md +18 -13
  2. package/bin/chati.js +12 -4
  3. package/framework/agents/build/dev.md +5 -1
  4. package/framework/agents/discover/greenfield-wu.md +16 -1
  5. package/framework/agents/plan/tasks.md +31 -0
  6. package/framework/agents/plan/ux-brand-architect.md +21 -2
  7. package/framework/agents/plan/ux-component-engineer.md +10 -0
  8. package/framework/agents/quality/qa-implementation.md +1 -1
  9. package/framework/agents/quality/qa-planning.md +1 -1
  10. package/framework/agents/quality/qa-visual.md +24 -21
  11. package/framework/config.yaml +5 -4
  12. package/framework/constitution.md +58 -11
  13. package/framework/context/governance.md +12 -10
  14. package/framework/context/root.md +2 -2
  15. package/framework/data/entity-registry.yaml +12 -4
  16. package/framework/data/qa-rubrics.yaml +335 -0
  17. package/framework/domains/constitution.yaml +3 -3
  18. package/framework/executors/json-validate.js +80 -0
  19. package/framework/executors/npm-script.js +72 -0
  20. package/framework/executors/path-check.js +67 -0
  21. package/framework/executors/registry.yaml +36 -0
  22. package/framework/hooks/advance-trigger.js +47 -3
  23. package/framework/hooks/constitution-guard.js +9 -4
  24. package/framework/hooks/git-push-authority.js +113 -0
  25. package/framework/hooks/license-guard.js +59 -9
  26. package/framework/hooks/model-governance.js +40 -17
  27. package/framework/hooks/prism-engine.js +78 -11
  28. package/framework/hooks/session-digest.js +12 -3
  29. package/framework/hooks/settings.json +7 -3
  30. package/framework/hooks/style-guard.js +52 -3
  31. package/framework/hooks/team-quality-gate.js +43 -5
  32. package/framework/i18n/en.yaml +3 -3
  33. package/framework/i18n/es.yaml +3 -3
  34. package/framework/i18n/fr.yaml +3 -3
  35. package/framework/i18n/pt.yaml +3 -3
  36. package/framework/intelligence/confidence.yaml +85 -6
  37. package/framework/intelligence/context-engine.md +9 -5
  38. package/framework/intelligence/decision-engine.md +1 -1
  39. package/framework/orchestrator/chati-router.js +1 -1
  40. package/framework/orchestrator/chati-update.md +19 -3
  41. package/framework/orchestrator/chati.md +19 -1
  42. package/framework/quality-gates/planning-gate.md +2 -0
  43. package/framework/scaffold/motion-premium/scaffold.yaml +2 -1
  44. package/framework/scaffold/motion-premium-3d/components/webgl/WebGLContext.tsx.template +1 -1
  45. package/framework/scaffold/saas-dashboard/README.md +58 -0
  46. package/framework/scaffold/saas-dashboard/app/dashboard/page.tsx.template +546 -0
  47. package/framework/scaffold/saas-dashboard/app/globals.css.template +746 -0
  48. package/framework/scaffold/saas-dashboard/app/layout.tsx.template +192 -0
  49. package/framework/scaffold/saas-dashboard/app/shell.css.template +1070 -0
  50. package/framework/scaffold/saas-dashboard/components/data/Chart.tsx.template +725 -0
  51. package/framework/scaffold/saas-dashboard/components/data/DataTable.tsx.template +623 -0
  52. package/framework/scaffold/saas-dashboard/components/data/DataTable.types.ts.template +104 -0
  53. package/framework/scaffold/saas-dashboard/components/data/DataTablePagination.tsx.template +234 -0
  54. package/framework/scaffold/saas-dashboard/components/data/SkeletonBlock.tsx.template +111 -0
  55. package/framework/scaffold/saas-dashboard/components/data/StatCard.tsx.template +393 -0
  56. package/framework/scaffold/saas-dashboard/components/data/StatCardGrid.tsx.template +121 -0
  57. package/framework/scaffold/saas-dashboard/components/feedback/EmptyState.tsx.template +281 -0
  58. package/framework/scaffold/saas-dashboard/components/interaction/CommandPalette.tsx.template +767 -0
  59. package/framework/scaffold/saas-dashboard/components/interaction/Field.tsx.template +351 -0
  60. package/framework/scaffold/saas-dashboard/components/interaction/Form.tsx.template +424 -0
  61. package/framework/scaffold/saas-dashboard/components/primitives/Badge.tsx.template +193 -0
  62. package/framework/scaffold/saas-dashboard/components/primitives/Button.tsx.template +261 -0
  63. package/framework/scaffold/saas-dashboard/components/primitives/Card.tsx.template +287 -0
  64. package/framework/scaffold/saas-dashboard/components/primitives/Input.tsx.template +310 -0
  65. package/framework/scaffold/saas-dashboard/components/primitives/Spinner.tsx.template +135 -0
  66. package/framework/scaffold/saas-dashboard/components/primitives/index.ts.template +60 -0
  67. package/framework/scaffold/saas-dashboard/components/shell/AppShell.tsx.template +174 -0
  68. package/framework/scaffold/saas-dashboard/components/shell/Breadcrumb.tsx.template +186 -0
  69. package/framework/scaffold/saas-dashboard/components/shell/Container.tsx.template +86 -0
  70. package/framework/scaffold/saas-dashboard/components/shell/NavItem.tsx.template +147 -0
  71. package/framework/scaffold/saas-dashboard/components/shell/Sidebar.tsx.template +378 -0
  72. package/framework/scaffold/saas-dashboard/components/shell/SidebarContext.tsx.template +169 -0
  73. package/framework/scaffold/saas-dashboard/components/shell/ThemeToggle.tsx.template +128 -0
  74. package/framework/scaffold/saas-dashboard/components/shell/Topbar.tsx.template +301 -0
  75. package/framework/scaffold/saas-dashboard/lib/brand.ts.template +324 -0
  76. package/framework/scaffold/saas-dashboard/scaffold.yaml +225 -0
  77. package/framework/schemas/session.schema.json +5 -0
  78. package/framework/schemas/task.schema.json +9 -0
  79. package/framework/scripts/visual-qa.js +87 -1
  80. package/framework/tasks/orchestrator-health.md +4 -4
  81. package/framework/tasks/qa-impl-verdict.md +11 -5
  82. package/framework/templates/qa-gate-tmpl.yaml +24 -11
  83. package/package.json +3 -3
  84. package/src/config/context-file-generator.js +0 -6
  85. package/src/dashboard/renderer.js +0 -36
  86. package/src/executors/runner.js +204 -0
  87. package/src/installer/core.js +2 -1
  88. package/src/installer/templates.js +5 -6
  89. package/src/intelligence/registry-manager.js +1 -1
  90. package/src/license/client.js +9 -3
  91. package/src/license/commands.js +12 -2
  92. package/src/license/machine-id.js +42 -1
  93. package/src/memory/gotchas.js +58 -40
  94. package/src/memory/magic-docs.js +1 -1
  95. package/src/memory/session-digest.js +9 -4
  96. package/src/orchestrator/cli.js +174 -14
  97. package/src/orchestrator/doctor.js +98 -16
  98. package/src/orchestrator/pipeline-manager.js +44 -22
  99. package/src/orchestrator/session-manager.js +56 -8
  100. package/src/telemetry/sender.js +7 -19
  101. package/src/terminal/run-team.js +3 -3
  102. package/src/upgrade/tracked-files-detector.js +34 -16
  103. package/src/utils/feature-flags.js +1 -1
  104. package/src/utils/flatten-entities.js +4 -40
  105. package/src/utils/schema-validator.js +0 -14
  106. package/src/wizard/i18n.js +3 -3
  107. package/src/api/index.js +0 -120
  108. package/src/autonomy/autonomous-gate.js +0 -294
  109. package/src/autonomy/build-loop.js +0 -281
  110. package/src/autonomy/build-state.js +0 -286
  111. package/src/autonomy/cause-analyzer.js +0 -177
  112. package/src/autonomy/escalation.js +0 -214
  113. package/src/autonomy/index.js +0 -51
  114. package/src/autonomy/mode-manager.js +0 -225
  115. package/src/autonomy/mode-suggester.js +0 -283
  116. package/src/autonomy/progress-reporter.js +0 -275
  117. package/src/autonomy/safety-net.js +0 -370
  118. package/src/config/agent-customizer.js +0 -231
  119. package/src/decision/analyzer.js +0 -291
  120. package/src/decision/engine.js +0 -250
  121. package/src/decision/index.js +0 -38
  122. package/src/decision/registry-healer.js +0 -468
  123. package/src/decision/registry-updater.js +0 -339
  124. package/src/extensions/loader.js +0 -145
  125. package/src/extensions/registry.js +0 -134
  126. package/src/gates/circuit-breaker.js +0 -151
  127. package/src/gates/g1-planning-complete.js +0 -154
  128. package/src/gates/g2-qa-planning.js +0 -156
  129. package/src/gates/g3-implementation.js +0 -215
  130. package/src/gates/g4-qa-implementation.js +0 -240
  131. package/src/gates/g5-deploy-ready.js +0 -181
  132. package/src/gates/gate-base.js +0 -185
  133. package/src/gates/index.js +0 -46
  134. package/src/health/auto-fix.js +0 -216
  135. package/src/health/engine.js +0 -246
  136. package/src/merger/semantic-merger.js +0 -292
  137. package/src/preview/detector.js +0 -238
  138. package/src/preview/index.js +0 -20
  139. package/src/preview/launcher.js +0 -235
  140. package/src/preview/log-buffer.js +0 -103
  141. package/src/quality/metrics-collector.js +0 -281
  142. package/src/quality/test-runner.js +0 -366
  143. package/src/tasks/executor.js +0 -195
  144. package/src/tasks/index.js +0 -4
  145. package/src/tasks/loader.js +0 -210
  146. package/src/tasks/router.js +0 -182
  147. 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
- }