chati-dev 4.3.1 → 4.4.1
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 +28 -4
- package/framework/agents/build/dev.md +5 -1
- package/framework/agents/discover/greenfield-wu.md +16 -1
- package/framework/agents/plan/tasks.md +31 -0
- package/framework/agents/plan/ux-brand-architect.md +21 -2
- package/framework/agents/plan/ux-component-engineer.md +10 -0
- package/framework/agents/quality/qa-implementation.md +1 -1
- package/framework/agents/quality/qa-planning.md +1 -1
- package/framework/agents/quality/qa-visual.md +24 -21
- package/framework/config.yaml +5 -4
- package/framework/constitution.md +60 -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/manifest.json +1351 -0
- package/framework/manifest.sig +1 -0
- package/framework/orchestrator/chati-router.js +1 -1
- package/framework/orchestrator/chati-update.md +19 -3
- package/framework/orchestrator/chati.md +24 -3
- package/framework/quality-gates/planning-gate.md +2 -0
- package/framework/scaffold/motion-premium/scaffold.yaml +2 -1
- package/framework/scaffold/motion-premium-3d/components/webgl/WebGLContext.tsx.template +1 -1
- package/framework/scaffold/saas-dashboard/README.md +58 -0
- package/framework/scaffold/saas-dashboard/app/dashboard/page.tsx.template +546 -0
- package/framework/scaffold/saas-dashboard/app/globals.css.template +746 -0
- package/framework/scaffold/saas-dashboard/app/layout.tsx.template +192 -0
- package/framework/scaffold/saas-dashboard/app/shell.css.template +1070 -0
- package/framework/scaffold/saas-dashboard/components/data/Chart.tsx.template +725 -0
- package/framework/scaffold/saas-dashboard/components/data/DataTable.tsx.template +623 -0
- package/framework/scaffold/saas-dashboard/components/data/DataTable.types.ts.template +104 -0
- package/framework/scaffold/saas-dashboard/components/data/DataTablePagination.tsx.template +234 -0
- package/framework/scaffold/saas-dashboard/components/data/SkeletonBlock.tsx.template +111 -0
- package/framework/scaffold/saas-dashboard/components/data/StatCard.tsx.template +393 -0
- package/framework/scaffold/saas-dashboard/components/data/StatCardGrid.tsx.template +121 -0
- package/framework/scaffold/saas-dashboard/components/feedback/EmptyState.tsx.template +281 -0
- package/framework/scaffold/saas-dashboard/components/interaction/CommandPalette.tsx.template +767 -0
- package/framework/scaffold/saas-dashboard/components/interaction/Field.tsx.template +351 -0
- package/framework/scaffold/saas-dashboard/components/interaction/Form.tsx.template +424 -0
- package/framework/scaffold/saas-dashboard/components/primitives/Badge.tsx.template +193 -0
- package/framework/scaffold/saas-dashboard/components/primitives/Button.tsx.template +261 -0
- package/framework/scaffold/saas-dashboard/components/primitives/Card.tsx.template +287 -0
- package/framework/scaffold/saas-dashboard/components/primitives/Input.tsx.template +310 -0
- package/framework/scaffold/saas-dashboard/components/primitives/Spinner.tsx.template +135 -0
- package/framework/scaffold/saas-dashboard/components/primitives/index.ts.template +60 -0
- package/framework/scaffold/saas-dashboard/components/shell/AppShell.tsx.template +174 -0
- package/framework/scaffold/saas-dashboard/components/shell/Breadcrumb.tsx.template +186 -0
- package/framework/scaffold/saas-dashboard/components/shell/Container.tsx.template +86 -0
- package/framework/scaffold/saas-dashboard/components/shell/NavItem.tsx.template +147 -0
- package/framework/scaffold/saas-dashboard/components/shell/Sidebar.tsx.template +378 -0
- package/framework/scaffold/saas-dashboard/components/shell/SidebarContext.tsx.template +169 -0
- package/framework/scaffold/saas-dashboard/components/shell/ThemeToggle.tsx.template +128 -0
- package/framework/scaffold/saas-dashboard/components/shell/Topbar.tsx.template +301 -0
- package/framework/scaffold/saas-dashboard/lib/brand.ts.template +324 -0
- package/framework/scaffold/saas-dashboard/scaffold.yaml +225 -0
- package/framework/schemas/session.schema.json +5 -0
- package/framework/schemas/task.schema.json +9 -0
- package/framework/scripts/visual-qa.js +87 -1
- package/framework/tasks/orchestrator-health.md +4 -4
- package/framework/tasks/qa-impl-verdict.md +11 -5
- package/framework/templates/qa-gate-tmpl.yaml +24 -11
- package/package.json +4 -4
- package/src/config/claude-settings-generator.js +9 -6
- 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 +35 -4
- package/src/installer/manifest.js +30 -4
- package/src/installer/signing-public-key.pem +1 -1
- package/src/installer/templates.js +5 -6
- package/src/installer/validator.js +2 -1
- package/src/intelligence/registry-manager.js +3 -2
- 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 +7 -2
- package/src/memory/session-digest.js +9 -4
- package/src/orchestrator/cli.js +195 -23
- package/src/orchestrator/deviation-handler.js +5 -3
- package/src/orchestrator/doctor.js +98 -16
- package/src/orchestrator/pipeline-manager.js +44 -22
- package/src/orchestrator/session-manager.js +56 -8
- package/src/telemetry/sender.js +7 -19
- package/src/terminal/run-parallel.js +14 -14
- package/src/terminal/run-team.js +3 -3
- package/src/upgrade/backup.js +22 -44
- package/src/upgrade/migrator.js +51 -12
- package/src/upgrade/tracked-files-detector.js +34 -16
- package/src/utils/constitution-meta.js +12 -0
- package/src/utils/feature-flags.js +26 -2
- 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,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
|
-
}
|
|
@@ -1,231 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @fileoverview Agent customization via per-project override files.
|
|
3
|
-
*
|
|
4
|
-
* Loads `.customize.yaml` files from `chati.dev/customize/<agent>.yaml`
|
|
5
|
-
* and applies overrides to agent prompts.
|
|
6
|
-
*
|
|
7
|
-
* Constitution Article XVI — Model Governance.
|
|
8
|
-
*/
|
|
9
|
-
|
|
10
|
-
import { existsSync, readFileSync } from 'fs';
|
|
11
|
-
import { join } from 'path';
|
|
12
|
-
import yaml from 'js-yaml';
|
|
13
|
-
import { resolveFrameworkDir } from '../utils/framework-dir.js';
|
|
14
|
-
|
|
15
|
-
// ---------------------------------------------------------------------------
|
|
16
|
-
// Constants
|
|
17
|
-
// ---------------------------------------------------------------------------
|
|
18
|
-
|
|
19
|
-
/**
|
|
20
|
-
* Get the customize directory for a given project.
|
|
21
|
-
* Supports both .chati.dev/ and chati.dev/ structures.
|
|
22
|
-
* @param {string} projectDir
|
|
23
|
-
* @returns {string} Relative path from project root
|
|
24
|
-
*/
|
|
25
|
-
function getCustomizeDir(projectDir) {
|
|
26
|
-
return `${resolveFrameworkDir(projectDir)}/customize`;
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
/** Valid override fields that customizations can set. */
|
|
30
|
-
export const VALID_OVERRIDE_FIELDS = [
|
|
31
|
-
'model',
|
|
32
|
-
'extra_context',
|
|
33
|
-
'skip_sections',
|
|
34
|
-
'add_sections',
|
|
35
|
-
'provider',
|
|
36
|
-
'timeout',
|
|
37
|
-
'max_iterations',
|
|
38
|
-
];
|
|
39
|
-
|
|
40
|
-
// ---------------------------------------------------------------------------
|
|
41
|
-
// Public API
|
|
42
|
-
// ---------------------------------------------------------------------------
|
|
43
|
-
|
|
44
|
-
/**
|
|
45
|
-
* @typedef {object} Customization
|
|
46
|
-
* @property {boolean} found - Whether a customization file was found
|
|
47
|
-
* @property {string} [model] - Override model name
|
|
48
|
-
* @property {string} [extra_context] - Additional context to inject
|
|
49
|
-
* @property {string[]} [skip_sections] - Sections to omit from prompt
|
|
50
|
-
* @property {{ name: string, content: string }[]} [add_sections] - Sections to append
|
|
51
|
-
* @property {string} [provider] - Override provider name
|
|
52
|
-
* @property {number} [timeout] - Override timeout in ms
|
|
53
|
-
* @property {number} [max_iterations] - Override max iterations
|
|
54
|
-
*/
|
|
55
|
-
|
|
56
|
-
/**
|
|
57
|
-
* Load customization for a specific agent.
|
|
58
|
-
*
|
|
59
|
-
* @param {string} projectDir - Project root directory
|
|
60
|
-
* @param {string} agentName - Agent name (e.g., 'brief', 'dev')
|
|
61
|
-
* @returns {{ found: boolean, overrides: Customization }}
|
|
62
|
-
*/
|
|
63
|
-
export function loadCustomization(projectDir, agentName) {
|
|
64
|
-
if (!projectDir || !agentName) {
|
|
65
|
-
return { found: false, overrides: {} };
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
const customizePath = join(projectDir, getCustomizeDir(projectDir), `${agentName}.yaml`);
|
|
69
|
-
|
|
70
|
-
if (!existsSync(customizePath)) {
|
|
71
|
-
return { found: false, overrides: {} };
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
try {
|
|
75
|
-
const raw = readFileSync(customizePath, 'utf-8');
|
|
76
|
-
const parsed = yaml.load(raw);
|
|
77
|
-
|
|
78
|
-
if (!parsed || typeof parsed !== 'object') {
|
|
79
|
-
return { found: true, overrides: {} };
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
// Filter to valid override fields only
|
|
83
|
-
const overrides = {};
|
|
84
|
-
for (const field of VALID_OVERRIDE_FIELDS) {
|
|
85
|
-
if (parsed[field] !== undefined) {
|
|
86
|
-
overrides[field] = parsed[field];
|
|
87
|
-
}
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
return { found: true, overrides };
|
|
91
|
-
} catch { /* expected: content may be malformed */
|
|
92
|
-
return { found: false, overrides: {} };
|
|
93
|
-
}
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
/**
|
|
97
|
-
* Apply customization overrides to an agent prompt.
|
|
98
|
-
*
|
|
99
|
-
* @param {string} agentPrompt - Original agent prompt
|
|
100
|
-
* @param {Customization} customization - Customization overrides
|
|
101
|
-
* @returns {string} Modified prompt
|
|
102
|
-
*/
|
|
103
|
-
export function applyCustomization(agentPrompt, customization) {
|
|
104
|
-
if (!agentPrompt || !customization) {
|
|
105
|
-
return agentPrompt || '';
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
let prompt = agentPrompt;
|
|
109
|
-
|
|
110
|
-
// Skip sections
|
|
111
|
-
if (customization.skip_sections && Array.isArray(customization.skip_sections)) {
|
|
112
|
-
for (const sectionName of customization.skip_sections) {
|
|
113
|
-
// Match section heading + all content until the next heading of same or higher level, or end of text
|
|
114
|
-
// Two-pass approach: try lookahead for next heading, then fallback to end
|
|
115
|
-
const headingPattern = new RegExp(
|
|
116
|
-
`^(#{1,3})\\s+${escapeRegex(sectionName)}[^\\n]*\\n([\\s\\S]*?)(?=^#{1,3}\\s)`,
|
|
117
|
-
'gm'
|
|
118
|
-
);
|
|
119
|
-
let replaced = prompt.replace(headingPattern, '');
|
|
120
|
-
if (replaced === prompt) {
|
|
121
|
-
// Section is at the end — no next heading follows
|
|
122
|
-
const endPattern = new RegExp(
|
|
123
|
-
`^(#{1,3})\\s+${escapeRegex(sectionName)}[^\\n]*\\n[\\s\\S]*$`,
|
|
124
|
-
'gm'
|
|
125
|
-
);
|
|
126
|
-
replaced = prompt.replace(endPattern, '');
|
|
127
|
-
}
|
|
128
|
-
prompt = replaced;
|
|
129
|
-
}
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
// Add extra context at the end
|
|
133
|
-
if (customization.extra_context && typeof customization.extra_context === 'string') {
|
|
134
|
-
prompt += `\n\n---\n\n## Additional Context\n\n${customization.extra_context}\n`;
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
// Add new sections
|
|
138
|
-
if (customization.add_sections && Array.isArray(customization.add_sections)) {
|
|
139
|
-
for (const section of customization.add_sections) {
|
|
140
|
-
if (section.name && section.content) {
|
|
141
|
-
prompt += `\n\n## ${section.name}\n\n${section.content}\n`;
|
|
142
|
-
}
|
|
143
|
-
}
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
return prompt;
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
/**
|
|
150
|
-
* Validate a customization object.
|
|
151
|
-
*
|
|
152
|
-
* @param {object} customization
|
|
153
|
-
* @returns {{ valid: boolean, errors: string[] }}
|
|
154
|
-
*/
|
|
155
|
-
export function validateCustomization(customization) {
|
|
156
|
-
const errors = [];
|
|
157
|
-
|
|
158
|
-
if (!customization || typeof customization !== 'object') {
|
|
159
|
-
errors.push('Customization must be a non-null object');
|
|
160
|
-
return { valid: false, errors };
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
// Check for unknown fields
|
|
164
|
-
for (const key of Object.keys(customization)) {
|
|
165
|
-
if (!VALID_OVERRIDE_FIELDS.includes(key)) {
|
|
166
|
-
errors.push(`Unknown override field: "${key}"`);
|
|
167
|
-
}
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
// Validate model
|
|
171
|
-
if (customization.model !== undefined && typeof customization.model !== 'string') {
|
|
172
|
-
errors.push('model must be a string');
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
// Validate extra_context
|
|
176
|
-
if (customization.extra_context !== undefined && typeof customization.extra_context !== 'string') {
|
|
177
|
-
errors.push('extra_context must be a string');
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
// Validate skip_sections
|
|
181
|
-
if (customization.skip_sections !== undefined) {
|
|
182
|
-
if (!Array.isArray(customization.skip_sections)) {
|
|
183
|
-
errors.push('skip_sections must be an array');
|
|
184
|
-
} else if (customization.skip_sections.some(s => typeof s !== 'string')) {
|
|
185
|
-
errors.push('skip_sections must contain only strings');
|
|
186
|
-
}
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
// Validate add_sections
|
|
190
|
-
if (customization.add_sections !== undefined) {
|
|
191
|
-
if (!Array.isArray(customization.add_sections)) {
|
|
192
|
-
errors.push('add_sections must be an array');
|
|
193
|
-
} else {
|
|
194
|
-
for (const section of customization.add_sections) {
|
|
195
|
-
if (!section.name || !section.content) {
|
|
196
|
-
errors.push('Each add_section must have name and content');
|
|
197
|
-
}
|
|
198
|
-
}
|
|
199
|
-
}
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
// Validate timeout
|
|
203
|
-
if (customization.timeout !== undefined) {
|
|
204
|
-
if (typeof customization.timeout !== 'number' || customization.timeout <= 0) {
|
|
205
|
-
errors.push('timeout must be a positive number');
|
|
206
|
-
}
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
// Validate max_iterations
|
|
210
|
-
if (customization.max_iterations !== undefined) {
|
|
211
|
-
if (typeof customization.max_iterations !== 'number' || customization.max_iterations <= 0) {
|
|
212
|
-
errors.push('max_iterations must be a positive number');
|
|
213
|
-
}
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
return { valid: errors.length === 0, errors };
|
|
217
|
-
}
|
|
218
|
-
|
|
219
|
-
// ---------------------------------------------------------------------------
|
|
220
|
-
// Helpers
|
|
221
|
-
// ---------------------------------------------------------------------------
|
|
222
|
-
|
|
223
|
-
/**
|
|
224
|
-
* Escape special regex characters in a string.
|
|
225
|
-
*
|
|
226
|
-
* @param {string} str
|
|
227
|
-
* @returns {string}
|
|
228
|
-
*/
|
|
229
|
-
function escapeRegex(str) {
|
|
230
|
-
return str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
231
|
-
}
|