chati-dev 1.4.0 → 2.0.2
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 +40 -24
- package/framework/agents/build/dev.md +343 -0
- package/framework/agents/clarity/architect.md +112 -0
- package/framework/agents/clarity/brief.md +182 -0
- package/framework/agents/clarity/brownfield-wu.md +181 -0
- package/framework/agents/clarity/detail.md +110 -0
- package/framework/agents/clarity/greenfield-wu.md +153 -0
- package/framework/agents/clarity/ux.md +112 -0
- package/framework/config.yaml +3 -3
- package/framework/constitution.md +31 -1
- package/framework/context/governance.md +37 -0
- package/framework/context/protocols.md +34 -0
- package/framework/context/quality.md +27 -0
- package/framework/context/root.md +24 -0
- package/framework/data/entity-registry.yaml +1 -1
- package/framework/domains/agents/architect.yaml +51 -0
- package/framework/domains/agents/brief.yaml +47 -0
- package/framework/domains/agents/brownfield-wu.yaml +49 -0
- package/framework/domains/agents/detail.yaml +47 -0
- package/framework/domains/agents/dev.yaml +49 -0
- package/framework/domains/agents/devops.yaml +43 -0
- package/framework/domains/agents/greenfield-wu.yaml +47 -0
- package/framework/domains/agents/orchestrator.yaml +49 -0
- package/framework/domains/agents/phases.yaml +47 -0
- package/framework/domains/agents/qa-implementation.yaml +43 -0
- package/framework/domains/agents/qa-planning.yaml +44 -0
- package/framework/domains/agents/tasks.yaml +48 -0
- package/framework/domains/agents/ux.yaml +50 -0
- package/framework/domains/constitution.yaml +77 -0
- package/framework/domains/global.yaml +64 -0
- package/framework/domains/workflows/brownfield-discovery.yaml +16 -0
- package/framework/domains/workflows/brownfield-fullstack.yaml +26 -0
- package/framework/domains/workflows/brownfield-service.yaml +22 -0
- package/framework/domains/workflows/brownfield-ui.yaml +22 -0
- package/framework/domains/workflows/greenfield-fullstack.yaml +26 -0
- package/framework/hooks/constitution-guard.js +101 -0
- package/framework/hooks/mode-governance.js +92 -0
- package/framework/hooks/model-governance.js +76 -0
- package/framework/hooks/prism-engine.js +89 -0
- package/framework/hooks/session-digest.js +60 -0
- package/framework/hooks/settings.json +44 -0
- 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/decision-engine.md +1 -1
- package/framework/migrations/v1.4-to-v2.0.yaml +167 -0
- package/framework/migrations/v2.0-to-v2.0.1.yaml +132 -0
- package/framework/orchestrator/chati.md +284 -6
- package/framework/tasks/architect-api-design.md +63 -0
- package/framework/tasks/architect-consolidate.md +47 -0
- package/framework/tasks/architect-db-design.md +73 -0
- package/framework/tasks/architect-design.md +95 -0
- package/framework/tasks/architect-security-review.md +62 -0
- package/framework/tasks/architect-stack-selection.md +53 -0
- package/framework/tasks/brief-consolidate.md +249 -0
- package/framework/tasks/brief-constraint-identify.md +277 -0
- package/framework/tasks/brief-extract-requirements.md +339 -0
- package/framework/tasks/brief-stakeholder-map.md +176 -0
- package/framework/tasks/brief-validate-completeness.md +121 -0
- package/framework/tasks/brownfield-wu-architecture-map.md +394 -0
- package/framework/tasks/brownfield-wu-deep-discovery.md +312 -0
- package/framework/tasks/brownfield-wu-dependency-scan.md +359 -0
- package/framework/tasks/brownfield-wu-migration-plan.md +483 -0
- package/framework/tasks/brownfield-wu-report.md +325 -0
- package/framework/tasks/brownfield-wu-risk-assess.md +424 -0
- package/framework/tasks/detail-acceptance-criteria.md +372 -0
- package/framework/tasks/detail-consolidate.md +138 -0
- package/framework/tasks/detail-edge-case-analysis.md +300 -0
- package/framework/tasks/detail-expand-prd.md +389 -0
- package/framework/tasks/detail-nfr-extraction.md +223 -0
- package/framework/tasks/dev-code-review.md +404 -0
- package/framework/tasks/dev-consolidate.md +543 -0
- package/framework/tasks/dev-debug.md +322 -0
- package/framework/tasks/dev-implement.md +252 -0
- package/framework/tasks/dev-iterate.md +411 -0
- package/framework/tasks/dev-pr-prepare.md +497 -0
- package/framework/tasks/dev-refactor.md +342 -0
- package/framework/tasks/dev-test-write.md +306 -0
- package/framework/tasks/devops-ci-setup.md +412 -0
- package/framework/tasks/devops-consolidate.md +712 -0
- package/framework/tasks/devops-deploy-config.md +598 -0
- package/framework/tasks/devops-monitoring-setup.md +658 -0
- package/framework/tasks/devops-release-prepare.md +673 -0
- package/framework/tasks/greenfield-wu-analyze-empty.md +169 -0
- package/framework/tasks/greenfield-wu-report.md +266 -0
- package/framework/tasks/greenfield-wu-scaffold-detection.md +203 -0
- package/framework/tasks/greenfield-wu-tech-stack-assess.md +255 -0
- package/framework/tasks/orchestrator-deviation.md +260 -0
- package/framework/tasks/orchestrator-escalate.md +276 -0
- package/framework/tasks/orchestrator-handoff.md +243 -0
- package/framework/tasks/orchestrator-health.md +372 -0
- package/framework/tasks/orchestrator-mode-switch.md +262 -0
- package/framework/tasks/orchestrator-resume.md +189 -0
- package/framework/tasks/orchestrator-route.md +169 -0
- package/framework/tasks/orchestrator-spawn-terminal.md +358 -0
- package/framework/tasks/orchestrator-status.md +260 -0
- package/framework/tasks/orchestrator-suggest-mode.md +372 -0
- package/framework/tasks/phases-breakdown.md +91 -0
- package/framework/tasks/phases-dependency-mapping.md +67 -0
- package/framework/tasks/phases-mvp-scoping.md +94 -0
- package/framework/tasks/qa-impl-consolidate.md +522 -0
- package/framework/tasks/qa-impl-performance-test.md +487 -0
- package/framework/tasks/qa-impl-regression-check.md +413 -0
- package/framework/tasks/qa-impl-sast-scan.md +402 -0
- package/framework/tasks/qa-impl-test-execute.md +344 -0
- package/framework/tasks/qa-impl-verdict.md +339 -0
- package/framework/tasks/qa-planning-consolidate.md +309 -0
- package/framework/tasks/qa-planning-coverage-plan.md +338 -0
- package/framework/tasks/qa-planning-gate-define.md +339 -0
- package/framework/tasks/qa-planning-risk-matrix.md +631 -0
- package/framework/tasks/qa-planning-test-strategy.md +217 -0
- package/framework/tasks/tasks-acceptance-write.md +75 -0
- package/framework/tasks/tasks-consolidate.md +57 -0
- package/framework/tasks/tasks-decompose.md +80 -0
- package/framework/tasks/tasks-estimate.md +66 -0
- package/framework/tasks/ux-a11y-check.md +49 -0
- package/framework/tasks/ux-component-map.md +55 -0
- package/framework/tasks/ux-consolidate.md +46 -0
- package/framework/tasks/ux-user-flow.md +46 -0
- package/framework/tasks/ux-wireframe.md +76 -0
- package/package.json +2 -2
- package/scripts/bundle-framework.js +2 -0
- package/scripts/changelog-generator.js +222 -0
- package/scripts/codebase-mapper.js +728 -0
- package/scripts/commit-message-generator.js +167 -0
- package/scripts/coverage-analyzer.js +260 -0
- package/scripts/dependency-analyzer.js +280 -0
- package/scripts/framework-analyzer.js +308 -0
- package/scripts/generate-constitution-domain.js +253 -0
- package/scripts/health-check.js +481 -0
- package/scripts/ide-sync.js +327 -0
- package/scripts/performance-analyzer.js +325 -0
- package/scripts/plan-tracker.js +278 -0
- package/scripts/populate-entity-registry.js +481 -0
- package/scripts/pr-review.js +317 -0
- package/scripts/rollback-manager.js +310 -0
- package/scripts/stuck-detector.js +343 -0
- package/scripts/test-quality-assessment.js +257 -0
- package/scripts/validate-agents.js +367 -0
- package/scripts/validate-tasks.js +465 -0
- package/src/autonomy/autonomous-gate.js +293 -0
- package/src/autonomy/index.js +51 -0
- package/src/autonomy/mode-manager.js +225 -0
- package/src/autonomy/mode-suggester.js +283 -0
- package/src/autonomy/progress-reporter.js +268 -0
- package/src/autonomy/safety-net.js +320 -0
- package/src/context/bracket-tracker.js +79 -0
- package/src/context/domain-loader.js +107 -0
- package/src/context/engine.js +144 -0
- package/src/context/formatter.js +184 -0
- package/src/context/index.js +4 -0
- package/src/context/layers/l0-constitution.js +28 -0
- package/src/context/layers/l1-global.js +37 -0
- package/src/context/layers/l2-agent.js +39 -0
- package/src/context/layers/l3-workflow.js +42 -0
- package/src/context/layers/l4-task.js +24 -0
- package/src/decision/analyzer.js +167 -0
- package/src/decision/engine.js +270 -0
- package/src/decision/index.js +38 -0
- package/src/decision/registry-healer.js +450 -0
- package/src/decision/registry-updater.js +330 -0
- package/src/gates/circuit-breaker.js +119 -0
- package/src/gates/g1-planning-complete.js +153 -0
- package/src/gates/g2-qa-planning.js +153 -0
- package/src/gates/g3-implementation.js +188 -0
- package/src/gates/g4-qa-implementation.js +207 -0
- package/src/gates/g5-deploy-ready.js +180 -0
- package/src/gates/gate-base.js +144 -0
- package/src/gates/index.js +46 -0
- package/src/installer/brownfield-upgrader.js +249 -0
- package/src/installer/core.js +82 -11
- package/src/installer/file-hasher.js +51 -0
- package/src/installer/manifest.js +117 -0
- package/src/installer/templates.js +17 -15
- package/src/installer/transaction.js +229 -0
- package/src/installer/validator.js +18 -1
- package/src/intelligence/registry-manager.js +2 -2
- package/src/memory/agent-memory.js +255 -0
- package/src/memory/gotchas-injector.js +72 -0
- package/src/memory/gotchas.js +361 -0
- package/src/memory/index.js +35 -0
- package/src/memory/search.js +233 -0
- package/src/memory/session-digest.js +239 -0
- package/src/merger/env-merger.js +112 -0
- package/src/merger/index.js +56 -0
- package/src/merger/replace-merger.js +51 -0
- package/src/merger/yaml-merger.js +127 -0
- package/src/orchestrator/agent-selector.js +285 -0
- package/src/orchestrator/deviation-handler.js +350 -0
- package/src/orchestrator/handoff-engine.js +271 -0
- package/src/orchestrator/index.js +67 -0
- package/src/orchestrator/intent-classifier.js +264 -0
- package/src/orchestrator/pipeline-manager.js +492 -0
- package/src/orchestrator/pipeline-state.js +223 -0
- package/src/orchestrator/session-manager.js +409 -0
- package/src/tasks/executor.js +195 -0
- package/src/tasks/handoff.js +226 -0
- package/src/tasks/index.js +4 -0
- package/src/tasks/loader.js +210 -0
- package/src/tasks/router.js +182 -0
- package/src/terminal/collector.js +216 -0
- package/src/terminal/index.js +30 -0
- package/src/terminal/isolation.js +129 -0
- package/src/terminal/monitor.js +277 -0
- package/src/terminal/spawner.js +269 -0
- package/src/upgrade/checker.js +1 -1
- package/src/wizard/i18n.js +3 -3
|
@@ -0,0 +1,320 @@
|
|
|
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
|
+
};
|
|
16
|
+
|
|
17
|
+
// Critical risk keywords
|
|
18
|
+
const CRITICAL_RISK_KEYWORDS = [
|
|
19
|
+
'security',
|
|
20
|
+
'vulnerability',
|
|
21
|
+
'exploit',
|
|
22
|
+
'injection',
|
|
23
|
+
'authentication',
|
|
24
|
+
'authorization',
|
|
25
|
+
'data loss',
|
|
26
|
+
'data corruption',
|
|
27
|
+
'production',
|
|
28
|
+
'database drop',
|
|
29
|
+
'rm -rf',
|
|
30
|
+
'delete all',
|
|
31
|
+
];
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Check all safety conditions against current state.
|
|
35
|
+
* @param {object} state
|
|
36
|
+
* @param {number} state.consecutiveFailures - Number of consecutive gate failures
|
|
37
|
+
* @param {number} state.lastScore - Most recent gate score
|
|
38
|
+
* @param {string[]} state.riskFlags - Active risk flags
|
|
39
|
+
* @param {number} state.sessionGotchas - New gotchas this session
|
|
40
|
+
* @param {number} [state.agentDurationMs] - Time current agent has been running
|
|
41
|
+
* @param {number} [state.timeoutMs] - Maximum allowed duration
|
|
42
|
+
* @returns {{ safe: boolean, triggers: object[], recommendation: string }}
|
|
43
|
+
*/
|
|
44
|
+
export function checkSafety(state) {
|
|
45
|
+
const triggers = [];
|
|
46
|
+
|
|
47
|
+
// Check each trigger
|
|
48
|
+
Object.values(SAFETY_TRIGGERS).forEach(trigger => {
|
|
49
|
+
const result = evaluateTrigger(trigger, state);
|
|
50
|
+
if (result.triggered) {
|
|
51
|
+
triggers.push(result);
|
|
52
|
+
}
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
const safe = triggers.length === 0;
|
|
56
|
+
const recommendation = safe
|
|
57
|
+
? 'All safety checks passed'
|
|
58
|
+
: getRecommendedAction(triggers).reason;
|
|
59
|
+
|
|
60
|
+
return {
|
|
61
|
+
safe,
|
|
62
|
+
triggers,
|
|
63
|
+
recommendation,
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Evaluate a single safety condition.
|
|
69
|
+
* @param {string} trigger - SAFETY_TRIGGERS value
|
|
70
|
+
* @param {object} state
|
|
71
|
+
* @returns {{ triggered: boolean, severity: 'warning'|'critical', details: string }}
|
|
72
|
+
*/
|
|
73
|
+
export function evaluateTrigger(trigger, state) {
|
|
74
|
+
switch (trigger) {
|
|
75
|
+
case SAFETY_TRIGGERS.CONSECUTIVE_FAILURES:
|
|
76
|
+
return evaluateConsecutiveFailures(state);
|
|
77
|
+
|
|
78
|
+
case SAFETY_TRIGGERS.LOW_SCORE:
|
|
79
|
+
return evaluateLowScore(state);
|
|
80
|
+
|
|
81
|
+
case SAFETY_TRIGGERS.CRITICAL_RISK:
|
|
82
|
+
return evaluateCriticalRisk(state);
|
|
83
|
+
|
|
84
|
+
case SAFETY_TRIGGERS.TIMEOUT:
|
|
85
|
+
return evaluateTimeout(state);
|
|
86
|
+
|
|
87
|
+
case SAFETY_TRIGGERS.GOTCHA_SPIKE:
|
|
88
|
+
return evaluateGotchaSpike(state);
|
|
89
|
+
|
|
90
|
+
default:
|
|
91
|
+
return {
|
|
92
|
+
triggered: false,
|
|
93
|
+
severity: 'warning',
|
|
94
|
+
details: 'Unknown trigger',
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Get recommended action when safety net triggers.
|
|
101
|
+
* @param {object[]} triggers - Triggered safety conditions
|
|
102
|
+
* @returns {{ action: 'pause'|'warn'|'abort', reason: string, resumable: boolean }}
|
|
103
|
+
*/
|
|
104
|
+
export function getRecommendedAction(triggers) {
|
|
105
|
+
if (triggers.length === 0) {
|
|
106
|
+
return {
|
|
107
|
+
action: 'warn',
|
|
108
|
+
reason: 'No safety triggers detected',
|
|
109
|
+
resumable: true,
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// Check for critical severity
|
|
114
|
+
const hasCritical = triggers.some(t => t.severity === 'critical');
|
|
115
|
+
|
|
116
|
+
if (hasCritical) {
|
|
117
|
+
const criticalTriggers = triggers.filter(t => t.severity === 'critical');
|
|
118
|
+
return {
|
|
119
|
+
action: 'pause',
|
|
120
|
+
reason: `Critical safety conditions: ${criticalTriggers.map(t => t.details).join('; ')}`,
|
|
121
|
+
resumable: true,
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// Only warnings
|
|
126
|
+
return {
|
|
127
|
+
action: 'warn',
|
|
128
|
+
reason: `Safety warnings: ${triggers.map(t => t.details).join('; ')}`,
|
|
129
|
+
resumable: true,
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Evaluate consecutive failures trigger.
|
|
135
|
+
* @param {object} state
|
|
136
|
+
* @returns {{ triggered: boolean, severity: string, details: string }}
|
|
137
|
+
*/
|
|
138
|
+
function evaluateConsecutiveFailures(state) {
|
|
139
|
+
const threshold = 3;
|
|
140
|
+
const failures = state.consecutiveFailures || 0;
|
|
141
|
+
|
|
142
|
+
if (failures >= threshold) {
|
|
143
|
+
return {
|
|
144
|
+
triggered: true,
|
|
145
|
+
severity: 'critical',
|
|
146
|
+
details: `${failures} consecutive gate failures (threshold: ${threshold})`,
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
return {
|
|
151
|
+
triggered: false,
|
|
152
|
+
severity: 'warning',
|
|
153
|
+
details: 'No consecutive failures',
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* Evaluate low score trigger.
|
|
159
|
+
* @param {object} state
|
|
160
|
+
* @returns {{ triggered: boolean, severity: string, details: string }}
|
|
161
|
+
*/
|
|
162
|
+
function evaluateLowScore(state) {
|
|
163
|
+
const threshold = 70;
|
|
164
|
+
const score = state.lastScore;
|
|
165
|
+
|
|
166
|
+
if (typeof score !== 'number') {
|
|
167
|
+
return {
|
|
168
|
+
triggered: false,
|
|
169
|
+
severity: 'warning',
|
|
170
|
+
details: 'No score available',
|
|
171
|
+
};
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
if (score < threshold) {
|
|
175
|
+
return {
|
|
176
|
+
triggered: true,
|
|
177
|
+
severity: 'warning',
|
|
178
|
+
details: `Low quality score: ${score}% (threshold: ${threshold}%)`,
|
|
179
|
+
};
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
return {
|
|
183
|
+
triggered: false,
|
|
184
|
+
severity: 'warning',
|
|
185
|
+
details: 'Score within acceptable range',
|
|
186
|
+
};
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
/**
|
|
190
|
+
* Evaluate critical risk trigger.
|
|
191
|
+
* @param {object} state
|
|
192
|
+
* @returns {{ triggered: boolean, severity: string, details: string }}
|
|
193
|
+
*/
|
|
194
|
+
function evaluateCriticalRisk(state) {
|
|
195
|
+
const riskFlags = state.riskFlags || [];
|
|
196
|
+
|
|
197
|
+
// Check for critical risk keywords
|
|
198
|
+
const criticalFlags = riskFlags.filter(flag =>
|
|
199
|
+
CRITICAL_RISK_KEYWORDS.some(keyword =>
|
|
200
|
+
flag.toLowerCase().includes(keyword.toLowerCase())
|
|
201
|
+
)
|
|
202
|
+
);
|
|
203
|
+
|
|
204
|
+
if (criticalFlags.length > 0) {
|
|
205
|
+
return {
|
|
206
|
+
triggered: true,
|
|
207
|
+
severity: 'critical',
|
|
208
|
+
details: `Critical risks detected: ${criticalFlags.join(', ')}`,
|
|
209
|
+
};
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
return {
|
|
213
|
+
triggered: false,
|
|
214
|
+
severity: 'warning',
|
|
215
|
+
details: 'No critical risks detected',
|
|
216
|
+
};
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
/**
|
|
220
|
+
* Evaluate timeout trigger.
|
|
221
|
+
* @param {object} state
|
|
222
|
+
* @returns {{ triggered: boolean, severity: string, details: string }}
|
|
223
|
+
*/
|
|
224
|
+
function evaluateTimeout(state) {
|
|
225
|
+
const { agentDurationMs, timeoutMs } = state;
|
|
226
|
+
|
|
227
|
+
if (typeof agentDurationMs !== 'number' || typeof timeoutMs !== 'number') {
|
|
228
|
+
return {
|
|
229
|
+
triggered: false,
|
|
230
|
+
severity: 'warning',
|
|
231
|
+
details: 'No timeout configured',
|
|
232
|
+
};
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
if (agentDurationMs >= timeoutMs) {
|
|
236
|
+
const minutes = Math.round(agentDurationMs / 60000);
|
|
237
|
+
const maxMinutes = Math.round(timeoutMs / 60000);
|
|
238
|
+
return {
|
|
239
|
+
triggered: true,
|
|
240
|
+
severity: 'critical',
|
|
241
|
+
details: `Agent timeout: ${minutes}min (max: ${maxMinutes}min)`,
|
|
242
|
+
};
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
return {
|
|
246
|
+
triggered: false,
|
|
247
|
+
severity: 'warning',
|
|
248
|
+
details: 'Agent within time limits',
|
|
249
|
+
};
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
/**
|
|
253
|
+
* Evaluate gotcha spike trigger.
|
|
254
|
+
* @param {object} state
|
|
255
|
+
* @returns {{ triggered: boolean, severity: string, details: string }}
|
|
256
|
+
*/
|
|
257
|
+
function evaluateGotchaSpike(state) {
|
|
258
|
+
const threshold = 5;
|
|
259
|
+
const gotchas = state.sessionGotchas || 0;
|
|
260
|
+
|
|
261
|
+
if (gotchas >= threshold) {
|
|
262
|
+
return {
|
|
263
|
+
triggered: true,
|
|
264
|
+
severity: 'warning',
|
|
265
|
+
details: `High gotcha count: ${gotchas} (threshold: ${threshold})`,
|
|
266
|
+
};
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
return {
|
|
270
|
+
triggered: false,
|
|
271
|
+
severity: 'warning',
|
|
272
|
+
details: 'Gotcha count within limits',
|
|
273
|
+
};
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
/**
|
|
277
|
+
* Get critical risk keywords list.
|
|
278
|
+
* @returns {string[]}
|
|
279
|
+
*/
|
|
280
|
+
export function getCriticalRiskKeywords() {
|
|
281
|
+
return [...CRITICAL_RISK_KEYWORDS];
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
/**
|
|
285
|
+
* Check if a risk flag contains critical keywords.
|
|
286
|
+
* @param {string} flag
|
|
287
|
+
* @returns {boolean}
|
|
288
|
+
*/
|
|
289
|
+
export function isCriticalRisk(flag) {
|
|
290
|
+
return CRITICAL_RISK_KEYWORDS.some(keyword =>
|
|
291
|
+
flag.toLowerCase().includes(keyword.toLowerCase())
|
|
292
|
+
);
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
/**
|
|
296
|
+
* Build safety report for user.
|
|
297
|
+
* @param {object} state
|
|
298
|
+
* @returns {{ status: string, message: string, triggers: object[] }}
|
|
299
|
+
*/
|
|
300
|
+
export function buildSafetyReport(state) {
|
|
301
|
+
const result = checkSafety(state);
|
|
302
|
+
|
|
303
|
+
let status;
|
|
304
|
+
let message;
|
|
305
|
+
|
|
306
|
+
if (result.safe) {
|
|
307
|
+
status = 'safe';
|
|
308
|
+
message = 'All safety checks passed. Pipeline can continue.';
|
|
309
|
+
} else {
|
|
310
|
+
const action = getRecommendedAction(result.triggers);
|
|
311
|
+
status = action.action === 'pause' ? 'unsafe' : 'warning';
|
|
312
|
+
message = action.reason;
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
return {
|
|
316
|
+
status,
|
|
317
|
+
message,
|
|
318
|
+
triggers: result.triggers,
|
|
319
|
+
};
|
|
320
|
+
}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Bracket Tracker — Pure arithmetic for context window management.
|
|
3
|
+
*
|
|
4
|
+
* Brackets determine how much context to inject:
|
|
5
|
+
* FRESH (60-100%) → All 5 layers active
|
|
6
|
+
* MODERATE (40-60%) → L0 + L1 + L2 + L3 (skip L4 task detail)
|
|
7
|
+
* DEPLETED (25-40%) → L0 + L1 + L2 only
|
|
8
|
+
* CRITICAL (<25%) → L0 + L1 only (handoff mandatory)
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
const BRACKETS = {
|
|
12
|
+
FRESH: { min: 60, max: 100, layers: ['L0', 'L1', 'L2', 'L3', 'L4'], tokenBudget: 8000 },
|
|
13
|
+
MODERATE: { min: 40, max: 60, layers: ['L0', 'L1', 'L2', 'L3'], tokenBudget: 5000 },
|
|
14
|
+
DEPLETED: { min: 25, max: 40, layers: ['L0', 'L1', 'L2'], tokenBudget: 3000 },
|
|
15
|
+
CRITICAL: { min: 0, max: 25, layers: ['L0', 'L1'], tokenBudget: 1500 },
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
const MEMORY_LEVELS = {
|
|
19
|
+
FRESH: 'full',
|
|
20
|
+
MODERATE: 'chunks',
|
|
21
|
+
DEPLETED: 'metadata',
|
|
22
|
+
CRITICAL: 'none',
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Calculate bracket from remaining context percentage.
|
|
27
|
+
* @param {number} remainingPercent - 0 to 100
|
|
28
|
+
* @returns {{ bracket: string, activeLayers: string[], tokenBudget: number, memoryLevel: string, handoffRequired: boolean }}
|
|
29
|
+
*/
|
|
30
|
+
export function calculateBracket(remainingPercent) {
|
|
31
|
+
const pct = Math.max(0, Math.min(100, remainingPercent));
|
|
32
|
+
|
|
33
|
+
let name = 'CRITICAL';
|
|
34
|
+
if (pct >= 60) name = 'FRESH';
|
|
35
|
+
else if (pct >= 40) name = 'MODERATE';
|
|
36
|
+
else if (pct >= 25) name = 'DEPLETED';
|
|
37
|
+
|
|
38
|
+
const def = BRACKETS[name];
|
|
39
|
+
return {
|
|
40
|
+
bracket: name,
|
|
41
|
+
activeLayers: [...def.layers],
|
|
42
|
+
tokenBudget: def.tokenBudget,
|
|
43
|
+
memoryLevel: MEMORY_LEVELS[name],
|
|
44
|
+
handoffRequired: pct < 15,
|
|
45
|
+
remainingPercent: pct,
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Estimate remaining context percentage from prompt/turn count.
|
|
51
|
+
* Heuristic: each turn consumes ~2-4% of context window.
|
|
52
|
+
* @param {number} turnCount - Number of conversation turns so far
|
|
53
|
+
* @param {number} [maxTurns=40] - Estimated max turns before context exhaustion
|
|
54
|
+
* @returns {number} Estimated remaining percentage (0-100)
|
|
55
|
+
*/
|
|
56
|
+
export function estimateRemaining(turnCount, maxTurns = 40) {
|
|
57
|
+
const used = Math.min(turnCount, maxTurns) / maxTurns;
|
|
58
|
+
return Math.round((1 - used) * 100);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Check if a specific layer is active for a given bracket.
|
|
63
|
+
* @param {string} bracket - FRESH, MODERATE, DEPLETED, or CRITICAL
|
|
64
|
+
* @param {string} layer - L0, L1, L2, L3, or L4
|
|
65
|
+
* @returns {boolean}
|
|
66
|
+
*/
|
|
67
|
+
export function isLayerActive(bracket, layer) {
|
|
68
|
+
const def = BRACKETS[bracket];
|
|
69
|
+
if (!def) return false;
|
|
70
|
+
return def.layers.includes(layer);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Get all bracket definitions (for display/dashboard).
|
|
75
|
+
* @returns {Record<string, { min: number, max: number, layers: string[], tokenBudget: number }>}
|
|
76
|
+
*/
|
|
77
|
+
export function getBracketDefinitions() {
|
|
78
|
+
return { ...BRACKETS };
|
|
79
|
+
}
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Domain Loader — Parses YAML domain files for context injection.
|
|
3
|
+
*
|
|
4
|
+
* Domain files live in chati.dev/domains/ and contain rules/context
|
|
5
|
+
* that PRISM injects into agent prompts based on the active layer.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { existsSync, readFileSync, readdirSync } from 'fs';
|
|
9
|
+
import { join, basename } from 'path';
|
|
10
|
+
import yaml from 'js-yaml';
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Load a single domain YAML file.
|
|
14
|
+
* @param {string} filePath - Absolute path to YAML file
|
|
15
|
+
* @returns {{ loaded: boolean, data: object|null, error: string|null }}
|
|
16
|
+
*/
|
|
17
|
+
export function loadDomainFile(filePath) {
|
|
18
|
+
if (!existsSync(filePath)) {
|
|
19
|
+
return { loaded: false, data: null, error: `File not found: ${filePath}` };
|
|
20
|
+
}
|
|
21
|
+
try {
|
|
22
|
+
const raw = readFileSync(filePath, 'utf-8');
|
|
23
|
+
const data = yaml.load(raw) || {};
|
|
24
|
+
return { loaded: true, data, error: null };
|
|
25
|
+
} catch (err) {
|
|
26
|
+
return { loaded: false, data: null, error: `Parse error in ${filePath}: ${err.message}` };
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Load all agent domain files from chati.dev/domains/agents/.
|
|
32
|
+
* @param {string} domainsDir - Path to chati.dev/domains/
|
|
33
|
+
* @returns {Map<string, object>} Map of agentName → domain data
|
|
34
|
+
*/
|
|
35
|
+
export function loadAgentDomains(domainsDir) {
|
|
36
|
+
const agentsDir = join(domainsDir, 'agents');
|
|
37
|
+
const domains = new Map();
|
|
38
|
+
|
|
39
|
+
if (!existsSync(agentsDir)) return domains;
|
|
40
|
+
|
|
41
|
+
const files = readdirSync(agentsDir).filter(f => f.endsWith('.yaml'));
|
|
42
|
+
for (const file of files) {
|
|
43
|
+
const name = basename(file, '.yaml');
|
|
44
|
+
const result = loadDomainFile(join(agentsDir, file));
|
|
45
|
+
if (result.loaded) {
|
|
46
|
+
domains.set(name, result.data);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
return domains;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Load all workflow domain files from chati.dev/domains/workflows/.
|
|
54
|
+
* @param {string} domainsDir - Path to chati.dev/domains/
|
|
55
|
+
* @returns {Map<string, object>} Map of workflowName → domain data
|
|
56
|
+
*/
|
|
57
|
+
export function loadWorkflowDomains(domainsDir) {
|
|
58
|
+
const workflowsDir = join(domainsDir, 'workflows');
|
|
59
|
+
const domains = new Map();
|
|
60
|
+
|
|
61
|
+
if (!existsSync(workflowsDir)) return domains;
|
|
62
|
+
|
|
63
|
+
const files = readdirSync(workflowsDir).filter(f => f.endsWith('.yaml'));
|
|
64
|
+
for (const file of files) {
|
|
65
|
+
const name = basename(file, '.yaml');
|
|
66
|
+
const result = loadDomainFile(join(workflowsDir, file));
|
|
67
|
+
if (result.loaded) {
|
|
68
|
+
domains.set(name, result.data);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
return domains;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Load the constitution domain (extracted rules from constitution.md).
|
|
76
|
+
* @param {string} domainsDir - Path to chati.dev/domains/
|
|
77
|
+
* @returns {object|null}
|
|
78
|
+
*/
|
|
79
|
+
export function loadConstitutionDomain(domainsDir) {
|
|
80
|
+
const result = loadDomainFile(join(domainsDir, 'constitution.yaml'));
|
|
81
|
+
return result.loaded ? result.data : null;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Load the global domain (coding standards, bracket rules).
|
|
86
|
+
* @param {string} domainsDir - Path to chati.dev/domains/
|
|
87
|
+
* @returns {object|null}
|
|
88
|
+
*/
|
|
89
|
+
export function loadGlobalDomain(domainsDir) {
|
|
90
|
+
const result = loadDomainFile(join(domainsDir, 'global.yaml'));
|
|
91
|
+
return result.loaded ? result.data : null;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Extract rules array from a domain object.
|
|
96
|
+
* Domain files have a `rules` key with an array of rule objects.
|
|
97
|
+
* @param {object} domain - Parsed domain object
|
|
98
|
+
* @returns {Array<{ id: string, text: string, priority: string }>}
|
|
99
|
+
*/
|
|
100
|
+
export function extractRules(domain) {
|
|
101
|
+
if (!domain || !Array.isArray(domain.rules)) return [];
|
|
102
|
+
return domain.rules.map(r => ({
|
|
103
|
+
id: r.id || 'unknown',
|
|
104
|
+
text: r.text || r.rule || '',
|
|
105
|
+
priority: r.priority || 'normal',
|
|
106
|
+
}));
|
|
107
|
+
}
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PRISM Context Engine — 5-layer context injection pipeline.
|
|
3
|
+
*
|
|
4
|
+
* Orchestrates L0-L4 layers, respects bracket constraints,
|
|
5
|
+
* and produces formatted XML context for agent prompts.
|
|
6
|
+
*
|
|
7
|
+
* Pipeline: bracket calculation → layer processing → formatting → output
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { calculateBracket, isLayerActive } from './bracket-tracker.js';
|
|
11
|
+
import { processL0 } from './layers/l0-constitution.js';
|
|
12
|
+
import { processL1 } from './layers/l1-global.js';
|
|
13
|
+
import { processL2 } from './layers/l2-agent.js';
|
|
14
|
+
import { processL3 } from './layers/l3-workflow.js';
|
|
15
|
+
import { processL4 } from './layers/l4-task.js';
|
|
16
|
+
import { formatContext } from './formatter.js';
|
|
17
|
+
|
|
18
|
+
const LAYER_TIMEOUT_MS = 100;
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Run the PRISM context engine pipeline.
|
|
22
|
+
*
|
|
23
|
+
* @param {object} input
|
|
24
|
+
* @param {string} input.domainsDir - Path to chati.dev/domains/
|
|
25
|
+
* @param {number} input.remainingPercent - Context window remaining (0-100)
|
|
26
|
+
* @param {string} [input.mode] - Current mode (clarity, build, deploy)
|
|
27
|
+
* @param {string} [input.agent] - Active agent name
|
|
28
|
+
* @param {string} [input.workflow] - Active workflow name
|
|
29
|
+
* @param {string} [input.pipelinePosition] - Current pipeline step
|
|
30
|
+
* @param {string} [input.taskId] - Active task ID
|
|
31
|
+
* @param {object} [input.handoff] - Handoff data from previous agent
|
|
32
|
+
* @param {string[]} [input.artifacts] - Relevant artifact paths
|
|
33
|
+
* @param {string[]} [input.taskCriteria] - Active task criteria
|
|
34
|
+
* @returns {{ xml: string, bracket: object, layers: object[], errors: string[] }}
|
|
35
|
+
*/
|
|
36
|
+
export function runPrism(input) {
|
|
37
|
+
const errors = [];
|
|
38
|
+
const layers = [];
|
|
39
|
+
|
|
40
|
+
// 1. Calculate bracket
|
|
41
|
+
const bracket = calculateBracket(input.remainingPercent);
|
|
42
|
+
|
|
43
|
+
// 2. Build context for layer processors
|
|
44
|
+
const ctx = {
|
|
45
|
+
domainsDir: input.domainsDir,
|
|
46
|
+
mode: input.mode || 'clarity',
|
|
47
|
+
bracket: bracket.bracket,
|
|
48
|
+
agent: input.agent || null,
|
|
49
|
+
workflow: input.workflow || null,
|
|
50
|
+
pipelinePosition: input.pipelinePosition || null,
|
|
51
|
+
taskId: input.taskId || null,
|
|
52
|
+
handoff: input.handoff || {},
|
|
53
|
+
artifacts: input.artifacts || [],
|
|
54
|
+
taskCriteria: input.taskCriteria || [],
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
// 3. Process each active layer with timeout protection
|
|
58
|
+
const layerResults = {};
|
|
59
|
+
|
|
60
|
+
// L0 — Always active
|
|
61
|
+
const l0 = safeProcess('L0', () => processL0(ctx), errors);
|
|
62
|
+
if (l0) { layers.push(l0); layerResults.l0 = l0; }
|
|
63
|
+
|
|
64
|
+
// L1 — Always active
|
|
65
|
+
if (isLayerActive(bracket.bracket, 'L1')) {
|
|
66
|
+
const l1 = safeProcess('L1', () => processL1(ctx), errors);
|
|
67
|
+
if (l1) { layers.push(l1); layerResults.l1 = l1; }
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// L2 — Agent layer
|
|
71
|
+
if (isLayerActive(bracket.bracket, 'L2') && ctx.agent) {
|
|
72
|
+
const l2 = safeProcess('L2', () => processL2(ctx), errors);
|
|
73
|
+
if (l2) { layers.push(l2); layerResults.l2 = l2; }
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// L3 — Workflow/Pipeline layer
|
|
77
|
+
if (isLayerActive(bracket.bracket, 'L3') && ctx.workflow) {
|
|
78
|
+
const l3 = safeProcess('L3', () => processL3(ctx), errors);
|
|
79
|
+
if (l3) { layers.push(l3); layerResults.l3 = l3; }
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// L4 — Task layer
|
|
83
|
+
if (isLayerActive(bracket.bracket, 'L4') && ctx.taskId) {
|
|
84
|
+
const l4 = safeProcess('L4', () => processL4(ctx), errors);
|
|
85
|
+
if (l4) { layers.push(l4); layerResults.l4 = l4; }
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// 4. Format output
|
|
89
|
+
const xml = formatContext({
|
|
90
|
+
bracket: bracket.bracket,
|
|
91
|
+
tokenBudget: bracket.tokenBudget,
|
|
92
|
+
...layerResults,
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
return {
|
|
96
|
+
xml,
|
|
97
|
+
bracket,
|
|
98
|
+
layers,
|
|
99
|
+
errors,
|
|
100
|
+
layerCount: layers.length,
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Run a layer processor with error protection.
|
|
106
|
+
* If the processor throws, capture the error and return null (graceful degradation).
|
|
107
|
+
*/
|
|
108
|
+
function safeProcess(layerName, processFn, errors) {
|
|
109
|
+
try {
|
|
110
|
+
const start = Date.now();
|
|
111
|
+
const result = processFn();
|
|
112
|
+
const elapsed = Date.now() - start;
|
|
113
|
+
|
|
114
|
+
if (elapsed > LAYER_TIMEOUT_MS) {
|
|
115
|
+
errors.push(`${layerName} exceeded timeout (${elapsed}ms > ${LAYER_TIMEOUT_MS}ms)`);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
return result;
|
|
119
|
+
} catch (err) {
|
|
120
|
+
errors.push(`${layerName} failed: ${err.message}`);
|
|
121
|
+
return null;
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Get a summary of PRISM capabilities for display.
|
|
127
|
+
* @returns {{ layers: number, brackets: string[], features: string[] }}
|
|
128
|
+
*/
|
|
129
|
+
export function getPrismInfo() {
|
|
130
|
+
return {
|
|
131
|
+
name: 'PRISM',
|
|
132
|
+
version: '1.0.0',
|
|
133
|
+
layers: 5,
|
|
134
|
+
layerNames: ['L0 Constitution', 'L1 Global', 'L2 Agent', 'L3 Workflow', 'L4 Task'],
|
|
135
|
+
brackets: ['FRESH', 'MODERATE', 'DEPLETED', 'CRITICAL'],
|
|
136
|
+
features: [
|
|
137
|
+
'Priority-based context truncation',
|
|
138
|
+
'Per-layer timeout protection',
|
|
139
|
+
'Graceful degradation on layer failure',
|
|
140
|
+
'XML structured output',
|
|
141
|
+
'Bracket-aware layer activation',
|
|
142
|
+
],
|
|
143
|
+
};
|
|
144
|
+
}
|