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