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,275 +0,0 @@
1
- /**
2
- * @fileoverview Progress reporting for autonomous execution
3
- * Reports progress to user without blocking the pipeline
4
- */
5
-
6
- /**
7
- * Build a progress report for the user.
8
- * @param {object} pipelineState
9
- * @param {object} [options] - { verbose: boolean }
10
- * @returns {{ summary: string, details: object, progress: number }}
11
- */
12
- export function buildProgressReport(pipelineState, options = {}) {
13
- const { verbose = false } = options;
14
-
15
- const progress = calculateCompletion(pipelineState);
16
- const summary = verbose
17
- ? formatDetailedReport(pipelineState)
18
- : formatProgressLine(pipelineState);
19
-
20
- const details = {
21
- currentAgent: pipelineState.current_agent,
22
- currentStage: pipelineState.current_stage,
23
- agentsCompleted: countCompletedAgents(pipelineState),
24
- agentsTotal: countTotalAgents(pipelineState),
25
- lastScore: pipelineState.last_score,
26
- warnings: pipelineState.warnings || [],
27
- };
28
-
29
- return {
30
- summary,
31
- details,
32
- progress,
33
- };
34
- }
35
-
36
- /**
37
- * Format progress as a compact one-line status.
38
- * Example: "[DISCOVER 2/3] brief ✅ → detail 🔄 (score: 92%)" or "[PLAN 3/6] phases 🔄 (score: 92%)"
39
- * @param {object} pipelineState
40
- * @returns {string}
41
- */
42
- export function formatProgressLine(pipelineState) {
43
- const stage = pipelineState.current_stage || 'INIT';
44
- const currentAgent = pipelineState.current_agent || 'none';
45
- const agentStatus = pipelineState.agent_status || {};
46
-
47
- // Count completed vs total in current stage
48
- const stageAgents = getStageAgents(stage);
49
- const completed = stageAgents.filter(a => agentStatus[a] === 'completed').length;
50
- const total = stageAgents.length;
51
-
52
- // Build agent chain
53
- const agentChain = buildAgentChain(stageAgents, agentStatus, currentAgent);
54
-
55
- // Add score if available
56
- const scoreInfo = pipelineState.last_score
57
- ? ` (score: ${pipelineState.last_score}%)`
58
- : '';
59
-
60
- return `[${stage} ${completed}/${total}] ${agentChain}${scoreInfo}`;
61
- }
62
-
63
- /**
64
- * Build a detailed multi-line report.
65
- * @param {object} pipelineState
66
- * @returns {string}
67
- */
68
- export function formatDetailedReport(pipelineState) {
69
- const lines = [];
70
- const progress = calculateCompletion(pipelineState);
71
- const stage = pipelineState.current_stage || 'INIT';
72
- const agentStatus = pipelineState.agent_status || {};
73
-
74
- lines.push(`Progress: ${progress}%`);
75
- lines.push(`Stage: ${stage}`);
76
- lines.push('');
77
-
78
- // DISCOVER agents
79
- lines.push('DISCOVER:');
80
- const discoverAgents = getStageAgents('DISCOVER');
81
- discoverAgents.forEach(agent => {
82
- const status = agentStatus[agent] || 'pending';
83
- const icon = getStatusIcon(status);
84
- const isActive = agent === pipelineState.current_agent;
85
- const prefix = isActive ? '→ ' : ' ';
86
- lines.push(`${prefix}${icon} ${agent}`);
87
- });
88
-
89
- lines.push('');
90
-
91
- // PLAN agents
92
- lines.push('PLAN:');
93
- const planAgents = getStageAgents('PLAN');
94
- planAgents.forEach(agent => {
95
- const status = agentStatus[agent] || 'pending';
96
- const icon = getStatusIcon(status);
97
- const isActive = agent === pipelineState.current_agent;
98
- const prefix = isActive ? '→ ' : ' ';
99
- lines.push(`${prefix}${icon} ${agent}`);
100
- });
101
-
102
- lines.push('');
103
-
104
- // BUILD agents
105
- lines.push('BUILD:');
106
- const buildAgents = getStageAgents('BUILD');
107
- buildAgents.forEach(agent => {
108
- const status = agentStatus[agent] || 'pending';
109
- const icon = getStatusIcon(status);
110
- const isActive = agent === pipelineState.current_agent;
111
- const prefix = isActive ? '→ ' : ' ';
112
- lines.push(`${prefix}${icon} ${agent}`);
113
- });
114
-
115
- lines.push('');
116
-
117
- // DEPLOY agents
118
- lines.push('DEPLOY:');
119
- const deployAgents = getStageAgents('DEPLOY');
120
- deployAgents.forEach(agent => {
121
- const status = agentStatus[agent] || 'pending';
122
- const icon = getStatusIcon(status);
123
- const isActive = agent === pipelineState.current_agent;
124
- const prefix = isActive ? '→ ' : ' ';
125
- lines.push(`${prefix}${icon} ${agent}`);
126
- });
127
-
128
- // Add warnings if any
129
- if (pipelineState.warnings && pipelineState.warnings.length > 0) {
130
- lines.push('');
131
- lines.push('Warnings:');
132
- pipelineState.warnings.forEach(warning => {
133
- lines.push(` ⚠ ${warning}`);
134
- });
135
- }
136
-
137
- return lines.join('\n');
138
- }
139
-
140
- /**
141
- * Calculate overall pipeline completion percentage.
142
- * @param {object} pipelineState
143
- * @returns {number} 0-100
144
- */
145
- export function calculateCompletion(pipelineState) {
146
- const agentStatus = pipelineState.agent_status || {};
147
- const allAgents = getAllAgents();
148
-
149
- const completed = Object.values(agentStatus).filter(s => s === 'completed').length;
150
- const total = allAgents.length;
151
-
152
- return total > 0 ? Math.round((completed / total) * 100) : 0;
153
- }
154
-
155
- /**
156
- * Get agents for a specific stage.
157
- * @param {string} stage
158
- * @returns {string[]}
159
- */
160
- function getStageAgents(stage) {
161
- const stages = {
162
- DISCOVER: ['greenfield-wu', 'brownfield-wu', 'brief'],
163
- PLAN: ['detail', 'architect', 'ux', 'phases', 'tasks'], // qa-planning runs inside Planning Team; score synced via team dissolution
164
- BUILD: ['dev', 'qa-implementation'],
165
- DEPLOY: ['devops'],
166
- };
167
-
168
- return stages[stage] || [];
169
- }
170
-
171
- /**
172
- * Get all agents in order.
173
- * @returns {string[]}
174
- */
175
- function getAllAgents() {
176
- return [
177
- ...getStageAgents('DISCOVER'),
178
- ...getStageAgents('PLAN'),
179
- ...getStageAgents('BUILD'),
180
- ...getStageAgents('DEPLOY'),
181
- ];
182
- }
183
-
184
- /**
185
- * Build agent chain visualization.
186
- * @param {string[]} agents
187
- * @param {object} agentStatus
188
- * @param {string} currentAgent
189
- * @returns {string}
190
- */
191
- function buildAgentChain(agents, agentStatus, currentAgent) {
192
- const parts = [];
193
-
194
- for (let i = 0; i < agents.length; i++) {
195
- const agent = agents[i];
196
- const status = agentStatus[agent] || 'pending';
197
- const isActive = agent === currentAgent;
198
-
199
- if (status === 'completed') {
200
- parts.push(`${agent} ✅`);
201
- } else if (isActive) {
202
- parts.push(`${agent} 🔄`);
203
- } else if (status === 'failed') {
204
- parts.push(`${agent} ❌`);
205
- } else {
206
- // Don't show pending agents in compact view
207
- break;
208
- }
209
-
210
- if (i < agents.length - 1 && (status === 'completed' || isActive)) {
211
- parts.push('→');
212
- }
213
- }
214
-
215
- return parts.join(' ');
216
- }
217
-
218
- /**
219
- * Get status icon for agent.
220
- * @param {string} status
221
- * @returns {string}
222
- */
223
- function getStatusIcon(status) {
224
- const icons = {
225
- completed: '✅',
226
- in_progress: '🔄',
227
- failed: '❌',
228
- pending: '⏸',
229
- skipped: '⏭',
230
- preview: '👁',
231
- };
232
-
233
- return icons[status] || '⏸';
234
- }
235
-
236
- /**
237
- * Count completed agents.
238
- * @param {object} pipelineState
239
- * @returns {number}
240
- */
241
- function countCompletedAgents(pipelineState) {
242
- const agentStatus = pipelineState.agent_status || {};
243
- return Object.values(agentStatus).filter(s => s === 'completed').length;
244
- }
245
-
246
- /**
247
- * Count total agents.
248
- * @param {object} _pipelineState
249
- * @returns {number}
250
- */
251
- function countTotalAgents(_pipelineState) {
252
- return getAllAgents().length;
253
- }
254
-
255
- /**
256
- * Get progress summary by stage.
257
- * @param {object} pipelineState
258
- * @returns {{ discover: number, plan: number, build: number, deploy: number }}
259
- */
260
- export function getStageProgress(pipelineState) {
261
- const agentStatus = pipelineState.agent_status || {};
262
-
263
- const getProgress = (stage) => {
264
- const agents = getStageAgents(stage);
265
- const completed = agents.filter(a => agentStatus[a] === 'completed').length;
266
- return agents.length > 0 ? Math.round((completed / agents.length) * 100) : 0;
267
- };
268
-
269
- return {
270
- discover: getProgress('DISCOVER'),
271
- plan: getProgress('PLAN'),
272
- build: getProgress('BUILD'),
273
- deploy: getProgress('DEPLOY'),
274
- };
275
- }
@@ -1,370 +0,0 @@
1
- /**
2
- * @fileoverview Safety net for autonomous execution
3
- * Auto-pauses pipeline on dangerous conditions
4
- */
5
-
6
- /**
7
- * Safety conditions that trigger automatic pause.
8
- */
9
- export const SAFETY_TRIGGERS = {
10
- CONSECUTIVE_FAILURES: 'consecutive_failures', // 3+ failures in a row
11
- LOW_SCORE: 'low_score', // Score < 70%
12
- CRITICAL_RISK: 'critical_risk', // Security/data risk detected
13
- TIMEOUT: 'timeout', // Agent exceeded time limit
14
- GOTCHA_SPIKE: 'gotcha_spike', // 5+ new gotchas in current session
15
- EDIT_LOOP: 'edit_loop', // Same file edited 3+ times in current task (Article XX)
16
- };
17
-
18
- // Critical risk keywords
19
- const CRITICAL_RISK_KEYWORDS = [
20
- 'security',
21
- 'vulnerability',
22
- 'exploit',
23
- 'injection',
24
- 'authentication',
25
- 'authorization',
26
- 'data loss',
27
- 'data corruption',
28
- 'production',
29
- 'database drop',
30
- 'rm -rf',
31
- 'delete all',
32
- 'credential',
33
- 'password',
34
- 'secret',
35
- 'token',
36
- 'privilege escalation',
37
- 'RCE',
38
- 'XSS',
39
- 'CSRF',
40
- 'SQL injection',
41
- 'command injection',
42
- 'backdoor',
43
- ];
44
-
45
- /**
46
- * Check all safety conditions against current state.
47
- * @param {object} state
48
- * @param {number} state.consecutiveFailures - Number of consecutive gate failures
49
- * @param {number} state.lastScore - Most recent gate score
50
- * @param {string[]} state.riskFlags - Active risk flags
51
- * @param {number} state.sessionGotchas - New gotchas this session
52
- * @param {number} [state.agentDurationMs] - Time current agent has been running
53
- * @param {number} [state.timeoutMs] - Maximum allowed duration
54
- * @returns {{ safe: boolean, triggers: object[], recommendation: string }}
55
- */
56
- export function checkSafety(state) {
57
- const triggers = [];
58
-
59
- // Check each trigger
60
- Object.values(SAFETY_TRIGGERS).forEach(trigger => {
61
- const result = evaluateTrigger(trigger, state);
62
- if (result.triggered) {
63
- triggers.push(result);
64
- }
65
- });
66
-
67
- const safe = triggers.length === 0;
68
- const recommendation = safe
69
- ? 'All safety checks passed'
70
- : getRecommendedAction(triggers).reason;
71
-
72
- return {
73
- safe,
74
- triggers,
75
- recommendation,
76
- };
77
- }
78
-
79
- /**
80
- * Evaluate a single safety condition.
81
- * @param {string} trigger - SAFETY_TRIGGERS value
82
- * @param {object} state
83
- * @returns {{ triggered: boolean, severity: 'warning'|'critical', details: string }}
84
- */
85
- export function evaluateTrigger(trigger, state) {
86
- switch (trigger) {
87
- case SAFETY_TRIGGERS.CONSECUTIVE_FAILURES:
88
- return evaluateConsecutiveFailures(state);
89
-
90
- case SAFETY_TRIGGERS.LOW_SCORE:
91
- return evaluateLowScore(state);
92
-
93
- case SAFETY_TRIGGERS.CRITICAL_RISK:
94
- return evaluateCriticalRisk(state);
95
-
96
- case SAFETY_TRIGGERS.TIMEOUT:
97
- return evaluateTimeout(state);
98
-
99
- case SAFETY_TRIGGERS.GOTCHA_SPIKE:
100
- return evaluateGotchaSpike(state);
101
-
102
- case SAFETY_TRIGGERS.EDIT_LOOP:
103
- return evaluateEditLoop(state);
104
-
105
- default:
106
- return {
107
- triggered: false,
108
- severity: 'warning',
109
- details: 'Unknown trigger',
110
- };
111
- }
112
- }
113
-
114
- /**
115
- * Get recommended action when safety net triggers.
116
- * @param {object[]} triggers - Triggered safety conditions
117
- * @returns {{ action: 'pause'|'warn'|'abort', reason: string, resumable: boolean }}
118
- */
119
- export function getRecommendedAction(triggers) {
120
- if (triggers.length === 0) {
121
- return {
122
- action: 'warn',
123
- reason: 'No safety triggers detected',
124
- resumable: true,
125
- };
126
- }
127
-
128
- // Check for critical severity
129
- const criticalTriggers = triggers.filter(t => t.severity === 'critical');
130
-
131
- if (criticalTriggers.length >= 2) {
132
- return {
133
- action: 'abort',
134
- reason: `Multiple critical safety conditions: ${criticalTriggers.map(t => t.details).join('; ')}`,
135
- resumable: false,
136
- };
137
- }
138
-
139
- if (criticalTriggers.length === 1) {
140
- return {
141
- action: 'pause',
142
- reason: `Critical safety condition: ${criticalTriggers[0].details}`,
143
- resumable: true,
144
- };
145
- }
146
-
147
- // Only warnings
148
- return {
149
- action: 'warn',
150
- reason: `Safety warnings: ${triggers.map(t => t.details).join('; ')}`,
151
- resumable: true,
152
- };
153
- }
154
-
155
- /**
156
- * Evaluate consecutive failures trigger.
157
- * @param {object} state
158
- * @returns {{ triggered: boolean, severity: string, details: string }}
159
- */
160
- function evaluateConsecutiveFailures(state) {
161
- const threshold = 3;
162
- const failures = state.consecutiveFailures || 0;
163
-
164
- if (failures >= threshold) {
165
- return {
166
- triggered: true,
167
- severity: 'critical',
168
- details: `${failures} consecutive gate failures (threshold: ${threshold})`,
169
- };
170
- }
171
-
172
- return {
173
- triggered: false,
174
- severity: 'warning',
175
- details: 'No consecutive failures',
176
- };
177
- }
178
-
179
- /**
180
- * Evaluate low score trigger.
181
- * @param {object} state
182
- * @returns {{ triggered: boolean, severity: string, details: string }}
183
- */
184
- function evaluateLowScore(state) {
185
- const threshold = 70;
186
- const score = state.lastScore;
187
-
188
- if (typeof score !== 'number') {
189
- return {
190
- triggered: false,
191
- severity: 'warning',
192
- details: 'No score available',
193
- };
194
- }
195
-
196
- if (score < threshold) {
197
- return {
198
- triggered: true,
199
- severity: 'warning',
200
- details: `Low quality score: ${score}% (threshold: ${threshold}%)`,
201
- };
202
- }
203
-
204
- return {
205
- triggered: false,
206
- severity: 'warning',
207
- details: 'Score within acceptable range',
208
- };
209
- }
210
-
211
- /**
212
- * Evaluate critical risk trigger.
213
- * @param {object} state
214
- * @returns {{ triggered: boolean, severity: string, details: string }}
215
- */
216
- function evaluateCriticalRisk(state) {
217
- const riskFlags = state.riskFlags || [];
218
-
219
- // Check for critical risk keywords
220
- const criticalFlags = riskFlags.filter(flag =>
221
- CRITICAL_RISK_KEYWORDS.some(keyword =>
222
- flag.toLowerCase().includes(keyword.toLowerCase())
223
- )
224
- );
225
-
226
- if (criticalFlags.length > 0) {
227
- return {
228
- triggered: true,
229
- severity: 'critical',
230
- details: `Critical risks detected: ${criticalFlags.join(', ')}`,
231
- };
232
- }
233
-
234
- return {
235
- triggered: false,
236
- severity: 'warning',
237
- details: 'No critical risks detected',
238
- };
239
- }
240
-
241
- /**
242
- * Evaluate timeout trigger.
243
- * @param {object} state
244
- * @returns {{ triggered: boolean, severity: string, details: string }}
245
- */
246
- function evaluateTimeout(state) {
247
- const { agentDurationMs, timeoutMs } = state;
248
-
249
- if (typeof agentDurationMs !== 'number' || typeof timeoutMs !== 'number') {
250
- return {
251
- triggered: false,
252
- severity: 'warning',
253
- details: 'No timeout configured',
254
- };
255
- }
256
-
257
- if (agentDurationMs >= timeoutMs) {
258
- const minutes = Math.round(agentDurationMs / 60000);
259
- const maxMinutes = Math.round(timeoutMs / 60000);
260
- return {
261
- triggered: true,
262
- severity: 'critical',
263
- details: `Agent timeout: ${minutes}min (max: ${maxMinutes}min)`,
264
- };
265
- }
266
-
267
- return {
268
- triggered: false,
269
- severity: 'warning',
270
- details: 'Agent within time limits',
271
- };
272
- }
273
-
274
- /**
275
- * Evaluate gotcha spike trigger.
276
- * @param {object} state
277
- * @returns {{ triggered: boolean, severity: string, details: string }}
278
- */
279
- function evaluateGotchaSpike(state) {
280
- const threshold = 5;
281
- const gotchas = state.sessionGotchas || 0;
282
-
283
- if (gotchas >= threshold) {
284
- return {
285
- triggered: true,
286
- severity: 'warning',
287
- details: `High gotcha count: ${gotchas} (threshold: ${threshold})`,
288
- };
289
- }
290
-
291
- return {
292
- triggered: false,
293
- severity: 'warning',
294
- details: 'Gotcha count within limits',
295
- };
296
- }
297
-
298
- /**
299
- * Article XX — Detect edit loops (same file edited 3+ times in current task).
300
- * state.fileEdits should be an object: { 'path/to/file.tsx': 3, ... }
301
- */
302
- function evaluateEditLoop(state) {
303
- const threshold = 3;
304
- const fileEdits = state.fileEdits || {};
305
- const loopedFiles = Object.entries(fileEdits)
306
- .filter(([, count]) => count >= threshold);
307
-
308
- if (loopedFiles.length > 0) {
309
- const details = loopedFiles
310
- .map(([path, count]) => `${path} (${count} edits)`)
311
- .join(', ');
312
- return {
313
- triggered: true,
314
- severity: 'critical',
315
- details: `Edit loop detected (Article XX): ${details}. Stop and diagnose root cause before continuing.`,
316
- };
317
- }
318
-
319
- return {
320
- triggered: false,
321
- severity: 'warning',
322
- details: 'No edit loops detected',
323
- };
324
- }
325
-
326
- /**
327
- * Get critical risk keywords list.
328
- * @returns {string[]}
329
- */
330
- export function getCriticalRiskKeywords() {
331
- return [...CRITICAL_RISK_KEYWORDS];
332
- }
333
-
334
- /**
335
- * Check if a risk flag contains critical keywords.
336
- * @param {string} flag
337
- * @returns {boolean}
338
- */
339
- export function isCriticalRisk(flag) {
340
- return CRITICAL_RISK_KEYWORDS.some(keyword =>
341
- flag.toLowerCase().includes(keyword.toLowerCase())
342
- );
343
- }
344
-
345
- /**
346
- * Build safety report for user.
347
- * @param {object} state
348
- * @returns {{ status: string, message: string, triggers: object[] }}
349
- */
350
- export function buildSafetyReport(state) {
351
- const result = checkSafety(state);
352
-
353
- let status;
354
- let message;
355
-
356
- if (result.safe) {
357
- status = 'safe';
358
- message = 'All safety checks passed. Pipeline can continue.';
359
- } else {
360
- const action = getRecommendedAction(result.triggers);
361
- status = (action.action === 'pause' || action.action === 'abort') ? 'unsafe' : 'warning';
362
- message = action.reason;
363
- }
364
-
365
- return {
366
- status,
367
- message,
368
- triggers: result.triggers,
369
- };
370
- }