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.
- package/README.md +18 -13
- package/bin/chati.js +32 -4
- package/framework/agents/build/dev.md +9 -5
- package/framework/agents/discover/brief.md +2 -0
- package/framework/agents/discover/brownfield-wu.md +2 -0
- package/framework/agents/discover/greenfield-wu.md +18 -1
- package/framework/agents/plan/detail.md +2 -0
- 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 +33 -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 +59 -5
- package/framework/orchestrator/chati-update.md +19 -3
- package/framework/orchestrator/chati.md +20 -1
- package/framework/quality-gates/planning-gate.md +2 -0
- package/framework/rules/human-writing-style.md +47 -0
- package/framework/scaffold/motion-premium/README.md +12 -2
- package/framework/scaffold/motion-premium/lib/animations/tokens.ts.template +1 -1
- package/framework/scaffold/motion-premium/scaffold.yaml +6 -1
- package/framework/scaffold/motion-premium-3d/README.md +10 -0
- package/framework/scaffold/motion-premium-3d/components/webgl/WebGLContext.tsx.template +1 -1
- package/framework/scaffold/motion-premium-3d/scaffold.yaml +3 -0
- 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 +101 -3
- package/framework/tasks/orchestrator-health.md +4 -4
- package/framework/tasks/qa-impl-verdict.md +11 -5
- package/framework/templates/brandbook-html-tmpl.md +1 -1
- package/framework/templates/qa-gate-tmpl.yaml +24 -11
- package/package.json +4 -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 +13 -3
- 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 +186 -15
- package/src/orchestrator/doctor.js +98 -16
- package/src/orchestrator/pipeline-manager.js +44 -22
- package/src/orchestrator/session-manager.js +64 -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,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
|
-
}
|