chati-dev 2.1.0 → 3.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (51) hide show
  1. package/README.md +165 -176
  2. package/framework/agents/build/dev.md +1 -0
  3. package/framework/agents/deploy/devops.md +1 -0
  4. package/framework/agents/discover/brief.md +1 -0
  5. package/framework/agents/discover/brownfield-wu.md +1 -0
  6. package/framework/agents/discover/greenfield-wu.md +1 -0
  7. package/framework/agents/plan/architect.md +1 -0
  8. package/framework/agents/plan/detail.md +1 -0
  9. package/framework/agents/plan/phases.md +1 -0
  10. package/framework/agents/plan/tasks.md +1 -0
  11. package/framework/agents/plan/ux.md +1 -0
  12. package/framework/agents/quality/qa-implementation.md +1 -0
  13. package/framework/agents/quality/qa-planning.md +1 -0
  14. package/framework/config.yaml +21 -2
  15. package/framework/constitution.md +66 -1
  16. package/framework/domains/agents/brownfield-wu.yaml +4 -0
  17. package/framework/domains/agents/dev.yaml +4 -0
  18. package/framework/domains/agents/orchestrator.yaml +8 -0
  19. package/framework/domains/constitution.yaml +28 -0
  20. package/framework/domains/global.yaml +20 -0
  21. package/framework/hooks/model-governance.js +17 -15
  22. package/framework/intelligence/context-engine.md +29 -0
  23. package/framework/intelligence/memory-layer.md +17 -0
  24. package/framework/orchestrator/chati.md +94 -14
  25. package/framework/schemas/config.schema.json +44 -0
  26. package/framework/schemas/session.schema.json +27 -0
  27. package/package.json +6 -2
  28. package/src/autonomy/build-loop.js +194 -0
  29. package/src/autonomy/build-state.js +269 -0
  30. package/src/autonomy/execution-profile.js +151 -0
  31. package/src/config/context-file-generator.js +209 -0
  32. package/src/gates/g2-qa-planning.js +4 -2
  33. package/src/gates/g4-qa-implementation.js +5 -2
  34. package/src/gates/gate-base.js +33 -1
  35. package/src/health/engine.js +250 -0
  36. package/src/intelligence/file-tracker.js +117 -0
  37. package/src/intelligence/timeline.js +144 -0
  38. package/src/memory/gotchas-auto-capture.js +253 -0
  39. package/src/terminal/adapters/claude-adapter.js +43 -0
  40. package/src/terminal/adapters/codex-adapter.js +41 -0
  41. package/src/terminal/adapters/copilot-adapter.js +38 -0
  42. package/src/terminal/adapters/gemini-adapter.js +42 -0
  43. package/src/terminal/adapters/index.js +8 -0
  44. package/src/terminal/cli-registry.js +218 -0
  45. package/src/terminal/prompt-builder.js +18 -15
  46. package/src/terminal/spawner.js +19 -9
  47. package/src/terminal/wave-analyzer.js +143 -0
  48. package/framework/manifest.json +0 -5
  49. package/framework/manifest.sig +0 -1
  50. /package/assets/{logo - co/314/201pia.png" → logo - c/303/263pia.png"} +0 -0
  51. /package/assets/{logo - co/314/201pia.svg" → logo - c/303/263pia.svg"} +0 -0
@@ -0,0 +1,194 @@
1
+ /**
2
+ * @fileoverview Autonomous build loop (Ralph Wiggum v2).
3
+ *
4
+ * Executes tasks autonomously with checkpoint-based state management,
5
+ * retry logic, and quality gate integration.
6
+ *
7
+ * Named "Ralph Wiggum" internally — the autonomous execution mode
8
+ * that iterates until all tasks are complete or escalation is needed.
9
+ *
10
+ * Constitution Article XVII — Execution Mode Governance.
11
+ */
12
+
13
+ import {
14
+ createBuildState,
15
+ loadBuildState,
16
+ saveBuildState,
17
+ startBuild,
18
+ completeBuild,
19
+ failBuild,
20
+ updateCheckpoint,
21
+ getNextPendingTask,
22
+ isTaskExhausted,
23
+ isTimedOut,
24
+ getProgress,
25
+ CheckpointStatus,
26
+ BuildStatus,
27
+ } from './build-state.js';
28
+
29
+ // ---------------------------------------------------------------------------
30
+ // Build Loop
31
+ // ---------------------------------------------------------------------------
32
+
33
+ /**
34
+ * @typedef {object} BuildLoopConfig
35
+ * @property {string} projectDir - Project root directory
36
+ * @property {string[]} taskIds - Task IDs to execute
37
+ * @property {function(string): Promise<{success: boolean, output: string}>} executor - Task execution function
38
+ * @property {function(object): void} [onProgress] - Progress callback
39
+ * @property {boolean} [resume=false] - Whether to resume from existing state
40
+ */
41
+
42
+ /**
43
+ * @typedef {object} BuildLoopResult
44
+ * @property {string} status - Final build status
45
+ * @property {number} completed - Tasks completed
46
+ * @property {number} failed - Tasks failed
47
+ * @property {number} totalAttempts - Total execution attempts
48
+ * @property {string} duration - Human-readable duration
49
+ */
50
+
51
+ /**
52
+ * Run the autonomous build loop.
53
+ *
54
+ * Loop logic:
55
+ * 1. Load or create build state
56
+ * 2. Get next pending task
57
+ * 3. Execute task
58
+ * 4. Save checkpoint
59
+ * 5. If task failed and not exhausted, retry
60
+ * 6. If task exhausted, mark as failed and continue
61
+ * 7. Repeat until all tasks complete or global timeout
62
+ *
63
+ * @param {BuildLoopConfig} config
64
+ * @returns {Promise<BuildLoopResult>}
65
+ */
66
+ export async function runBuildLoop(config) {
67
+ const { projectDir, taskIds, executor, onProgress, resume = false } = config;
68
+
69
+ // Load or create state
70
+ let state = resume ? loadBuildState(projectDir) : null;
71
+
72
+ if (!state || state.status === BuildStatus.COMPLETED || state.status === BuildStatus.ABANDONED) {
73
+ state = createBuildState(taskIds);
74
+ }
75
+
76
+ state = startBuild(state);
77
+ saveBuildState(projectDir, state);
78
+
79
+ const startTime = Date.now();
80
+
81
+ // Main loop
82
+ while (true) {
83
+ // Check global timeout
84
+ if (isTimedOut(state)) {
85
+ state = failBuild(state, 'Global timeout exceeded');
86
+ saveBuildState(projectDir, state);
87
+ break;
88
+ }
89
+
90
+ // Get next task
91
+ const checkpoint = getNextPendingTask(state);
92
+ if (!checkpoint) {
93
+ // All tasks processed
94
+ const hasFailures = state.checkpoints.some((c) => c.status === CheckpointStatus.FAILED);
95
+ if (hasFailures) {
96
+ state = failBuild(state, 'Some tasks failed');
97
+ } else {
98
+ state = completeBuild(state);
99
+ }
100
+ saveBuildState(projectDir, state);
101
+ break;
102
+ }
103
+
104
+ // Check if task is exhausted
105
+ if (isTaskExhausted(checkpoint)) {
106
+ state = updateCheckpoint(state, checkpoint.taskId, {
107
+ status: CheckpointStatus.FAILED,
108
+ error: `Exceeded max iterations (${checkpoint.attempts})`,
109
+ });
110
+ saveBuildState(projectDir, state);
111
+
112
+ if (onProgress) {
113
+ onProgress({ type: 'task_exhausted', taskId: checkpoint.taskId, attempts: checkpoint.attempts });
114
+ }
115
+ continue;
116
+ }
117
+
118
+ // Mark task as in progress
119
+ state = updateCheckpoint(state, checkpoint.taskId, {
120
+ status: CheckpointStatus.IN_PROGRESS,
121
+ attempts: checkpoint.attempts + 1,
122
+ lastAttempt: new Date().toISOString(),
123
+ });
124
+ saveBuildState(projectDir, state);
125
+
126
+ if (onProgress) {
127
+ const progress = getProgress(state);
128
+ onProgress({ type: 'task_started', taskId: checkpoint.taskId, attempt: checkpoint.attempts + 1, progress });
129
+ }
130
+
131
+ // Execute task
132
+ try {
133
+ const result = await executor(checkpoint.taskId);
134
+
135
+ if (result.success) {
136
+ state = updateCheckpoint(state, checkpoint.taskId, {
137
+ status: CheckpointStatus.COMPLETED,
138
+ output: result.output?.slice(0, 1000) || 'Completed',
139
+ error: null,
140
+ });
141
+
142
+ if (onProgress) {
143
+ onProgress({ type: 'task_completed', taskId: checkpoint.taskId });
144
+ }
145
+ } else {
146
+ state = updateCheckpoint(state, checkpoint.taskId, {
147
+ status: CheckpointStatus.IN_PROGRESS, // Will retry
148
+ error: result.output?.slice(0, 500) || 'Task failed',
149
+ });
150
+
151
+ if (onProgress) {
152
+ onProgress({ type: 'task_failed', taskId: checkpoint.taskId, attempt: checkpoint.attempts + 1, error: result.output });
153
+ }
154
+ }
155
+ } catch (err) {
156
+ state = updateCheckpoint(state, checkpoint.taskId, {
157
+ status: CheckpointStatus.IN_PROGRESS, // Will retry
158
+ error: err.message?.slice(0, 500) || 'Execution error',
159
+ });
160
+ }
161
+
162
+ saveBuildState(projectDir, state);
163
+ }
164
+
165
+ const duration = Date.now() - startTime;
166
+ const progress = getProgress(state);
167
+
168
+ return {
169
+ status: state.status,
170
+ completed: progress.completed,
171
+ failed: progress.failed,
172
+ totalAttempts: state.totalAttempts,
173
+ duration: `${Math.round(duration / 1000)}s`,
174
+ };
175
+ }
176
+
177
+ /**
178
+ * Get current build loop status without executing.
179
+ *
180
+ * @param {string} projectDir
181
+ * @returns {object|null}
182
+ */
183
+ export function getBuildStatus(projectDir) {
184
+ const state = loadBuildState(projectDir);
185
+ if (!state) return null;
186
+
187
+ return {
188
+ sessionId: state.sessionId,
189
+ status: state.status,
190
+ progress: getProgress(state),
191
+ startedAt: state.startedAt,
192
+ lastCheckpoint: state.lastCheckpoint,
193
+ };
194
+ }
@@ -0,0 +1,269 @@
1
+ /**
2
+ * @fileoverview Build state manager with checkpoint-based persistence.
3
+ *
4
+ * Manages the state of autonomous build execution, including
5
+ * checkpoints saved after each task completion, abandoned build
6
+ * detection, and attempt logging.
7
+ *
8
+ * Constitution Article XVII — Execution Mode Governance.
9
+ */
10
+
11
+ import { existsSync, readFileSync, writeFileSync, mkdirSync } from 'fs';
12
+ import { join, dirname } from 'path';
13
+
14
+ // ---------------------------------------------------------------------------
15
+ // Constants
16
+ // ---------------------------------------------------------------------------
17
+
18
+ /** Maximum iterations per individual task before giving up */
19
+ export const MAX_ITERATIONS_PER_TASK = 10;
20
+
21
+ /** Global timeout for an autonomous build session (30 minutes) */
22
+ export const GLOBAL_TIMEOUT_MS = 30 * 60 * 1000;
23
+
24
+ /** Time after which a build is considered abandoned (1 hour) */
25
+ export const ABANDONED_THRESHOLD_MS = 60 * 60 * 1000;
26
+
27
+ /**
28
+ * Build status enum.
29
+ * @enum {string}
30
+ */
31
+ export const BuildStatus = {
32
+ PENDING: 'pending',
33
+ IN_PROGRESS: 'in_progress',
34
+ PAUSED: 'paused',
35
+ ABANDONED: 'abandoned',
36
+ FAILED: 'failed',
37
+ COMPLETED: 'completed',
38
+ };
39
+
40
+ /**
41
+ * Task checkpoint status.
42
+ * @enum {string}
43
+ */
44
+ export const CheckpointStatus = {
45
+ PENDING: 'pending',
46
+ IN_PROGRESS: 'in_progress',
47
+ COMPLETED: 'completed',
48
+ FAILED: 'failed',
49
+ SKIPPED: 'skipped',
50
+ };
51
+
52
+ // ---------------------------------------------------------------------------
53
+ // State Schema
54
+ // ---------------------------------------------------------------------------
55
+
56
+ /**
57
+ * @typedef {object} TaskCheckpoint
58
+ * @property {string} taskId
59
+ * @property {string} status - CheckpointStatus
60
+ * @property {number} attempts - Number of execution attempts
61
+ * @property {string|null} lastAttempt - ISO timestamp of last attempt
62
+ * @property {string|null} output - Last output summary
63
+ * @property {string|null} error - Last error message (if failed)
64
+ */
65
+
66
+ /**
67
+ * @typedef {object} BuildState
68
+ * @property {string} sessionId - Build session identifier
69
+ * @property {string} status - BuildStatus
70
+ * @property {TaskCheckpoint[]} checkpoints - Per-task checkpoints
71
+ * @property {string} startedAt - ISO timestamp
72
+ * @property {string|null} lastCheckpoint - ISO timestamp of last checkpoint
73
+ * @property {string|null} completedAt - ISO timestamp of completion
74
+ * @property {number} totalAttempts - Total execution attempts across all tasks
75
+ */
76
+
77
+ const BUILD_STATE_FILE = '.chati/build-state.json';
78
+
79
+ // ---------------------------------------------------------------------------
80
+ // State Management
81
+ // ---------------------------------------------------------------------------
82
+
83
+ /**
84
+ * Initialize a new build state.
85
+ *
86
+ * @param {string[]} taskIds - Array of task IDs to execute
87
+ * @returns {BuildState}
88
+ */
89
+ export function createBuildState(taskIds) {
90
+ return {
91
+ sessionId: `build-${Date.now()}`,
92
+ status: BuildStatus.PENDING,
93
+ checkpoints: taskIds.map((taskId) => ({
94
+ taskId,
95
+ status: CheckpointStatus.PENDING,
96
+ attempts: 0,
97
+ lastAttempt: null,
98
+ output: null,
99
+ error: null,
100
+ })),
101
+ startedAt: new Date().toISOString(),
102
+ lastCheckpoint: null,
103
+ completedAt: null,
104
+ totalAttempts: 0,
105
+ };
106
+ }
107
+
108
+ /**
109
+ * Load build state from disk.
110
+ *
111
+ * @param {string} projectDir
112
+ * @returns {BuildState|null}
113
+ */
114
+ export function loadBuildState(projectDir) {
115
+ const statePath = join(projectDir, BUILD_STATE_FILE);
116
+ if (!existsSync(statePath)) return null;
117
+ try {
118
+ const state = JSON.parse(readFileSync(statePath, 'utf-8'));
119
+
120
+ // Check for abandoned builds
121
+ if (state.status === BuildStatus.IN_PROGRESS && state.lastCheckpoint) {
122
+ const elapsed = Date.now() - new Date(state.lastCheckpoint).getTime();
123
+ if (elapsed > ABANDONED_THRESHOLD_MS) {
124
+ state.status = BuildStatus.ABANDONED;
125
+ }
126
+ }
127
+
128
+ return state;
129
+ } catch {
130
+ return null;
131
+ }
132
+ }
133
+
134
+ /**
135
+ * Save build state to disk.
136
+ *
137
+ * @param {string} projectDir
138
+ * @param {BuildState} state
139
+ */
140
+ export function saveBuildState(projectDir, state) {
141
+ const statePath = join(projectDir, BUILD_STATE_FILE);
142
+ mkdirSync(dirname(statePath), { recursive: true });
143
+ writeFileSync(statePath, JSON.stringify(state, null, 2));
144
+ }
145
+
146
+ /**
147
+ * Update a task checkpoint.
148
+ *
149
+ * @param {BuildState} state
150
+ * @param {string} taskId
151
+ * @param {Partial<TaskCheckpoint>} update
152
+ * @returns {BuildState}
153
+ */
154
+ export function updateCheckpoint(state, taskId, update) {
155
+ const checkpoint = state.checkpoints.find((c) => c.taskId === taskId);
156
+ if (!checkpoint) {
157
+ throw new Error(`Task "${taskId}" not found in build state`);
158
+ }
159
+
160
+ Object.assign(checkpoint, update);
161
+ state.lastCheckpoint = new Date().toISOString();
162
+
163
+ if (update.attempts !== undefined) {
164
+ state.totalAttempts = state.checkpoints.reduce((sum, c) => sum + c.attempts, 0);
165
+ }
166
+
167
+ return state;
168
+ }
169
+
170
+ /**
171
+ * Mark the build as started.
172
+ *
173
+ * @param {BuildState} state
174
+ * @returns {BuildState}
175
+ */
176
+ export function startBuild(state) {
177
+ state.status = BuildStatus.IN_PROGRESS;
178
+ state.startedAt = new Date().toISOString();
179
+ return state;
180
+ }
181
+
182
+ /**
183
+ * Mark the build as completed.
184
+ *
185
+ * @param {BuildState} state
186
+ * @returns {BuildState}
187
+ */
188
+ export function completeBuild(state) {
189
+ state.status = BuildStatus.COMPLETED;
190
+ state.completedAt = new Date().toISOString();
191
+ return state;
192
+ }
193
+
194
+ /**
195
+ * Mark the build as failed.
196
+ *
197
+ * @param {BuildState} state
198
+ * @param {string} [reason]
199
+ * @returns {BuildState}
200
+ */
201
+ export function failBuild(state, reason) {
202
+ state.status = BuildStatus.FAILED;
203
+ state.completedAt = new Date().toISOString();
204
+ return state;
205
+ }
206
+
207
+ /**
208
+ * Get the next pending task checkpoint.
209
+ *
210
+ * @param {BuildState} state
211
+ * @returns {TaskCheckpoint|null}
212
+ */
213
+ export function getNextPendingTask(state) {
214
+ return state.checkpoints.find(
215
+ (c) => c.status === CheckpointStatus.PENDING || c.status === CheckpointStatus.IN_PROGRESS,
216
+ ) || null;
217
+ }
218
+
219
+ /**
220
+ * Check if a task has exceeded max iterations.
221
+ *
222
+ * @param {TaskCheckpoint} checkpoint
223
+ * @returns {boolean}
224
+ */
225
+ export function isTaskExhausted(checkpoint) {
226
+ return checkpoint.attempts >= MAX_ITERATIONS_PER_TASK;
227
+ }
228
+
229
+ /**
230
+ * Check if the global timeout has been exceeded.
231
+ *
232
+ * @param {BuildState} state
233
+ * @returns {boolean}
234
+ */
235
+ export function isTimedOut(state) {
236
+ const elapsed = Date.now() - new Date(state.startedAt).getTime();
237
+ return elapsed > GLOBAL_TIMEOUT_MS;
238
+ }
239
+
240
+ /**
241
+ * Get build progress summary.
242
+ *
243
+ * @param {BuildState} state
244
+ * @returns {{ total: number, completed: number, failed: number, pending: number, progress: number }}
245
+ */
246
+ export function getProgress(state) {
247
+ const total = state.checkpoints.length;
248
+ const completed = state.checkpoints.filter((c) => c.status === CheckpointStatus.COMPLETED).length;
249
+ const failed = state.checkpoints.filter((c) => c.status === CheckpointStatus.FAILED).length;
250
+ const pending = state.checkpoints.filter(
251
+ (c) => c.status === CheckpointStatus.PENDING || c.status === CheckpointStatus.IN_PROGRESS,
252
+ ).length;
253
+ const progress = total > 0 ? Math.round((completed / total) * 100) : 0;
254
+
255
+ return { total, completed, failed, pending, progress };
256
+ }
257
+
258
+ /**
259
+ * Clear build state (after completion or manual reset).
260
+ *
261
+ * @param {string} projectDir
262
+ */
263
+ export function clearBuildState(projectDir) {
264
+ const statePath = join(projectDir, BUILD_STATE_FILE);
265
+ if (existsSync(statePath)) {
266
+ const { unlinkSync } = require('fs');
267
+ unlinkSync(statePath);
268
+ }
269
+ }
@@ -0,0 +1,151 @@
1
+ /**
2
+ * @fileoverview Execution profile manager.
3
+ *
4
+ * Manages 3 execution profiles that govern confirmation requirements:
5
+ * - explore: read-only, no writes
6
+ * - guided: confirm before writes (default)
7
+ * - autonomous: full autonomy with quality gates
8
+ *
9
+ * Profiles are orthogonal to mode governance (Article XI).
10
+ * Modes control WHERE; profiles control WHETHER.
11
+ *
12
+ * Constitution Article XVIII — Execution Profile Governance.
13
+ */
14
+
15
+ import { existsSync, readFileSync, writeFileSync } from 'fs';
16
+ import { join } from 'path';
17
+
18
+ // ---------------------------------------------------------------------------
19
+ // Profile Definitions
20
+ // ---------------------------------------------------------------------------
21
+
22
+ /**
23
+ * Execution profile enum.
24
+ * @enum {string}
25
+ */
26
+ export const Profile = {
27
+ /** Read-only. Agents analyze and suggest but perform no writes. */
28
+ EXPLORE: 'explore',
29
+ /** Default. Agents propose actions and wait for user confirmation. */
30
+ GUIDED: 'guided',
31
+ /** Full autonomy. Agents execute without confirmation when gates pass. */
32
+ AUTONOMOUS: 'autonomous',
33
+ };
34
+
35
+ /** Minimum gate score required to activate autonomous profile */
36
+ export const AUTONOMOUS_GATE_THRESHOLD = 90;
37
+
38
+ /**
39
+ * Operations that ALWAYS require confirmation regardless of profile.
40
+ * Constitution Article XVIII, point 4.
41
+ */
42
+ export const ALWAYS_CONFIRM_OPERATIONS = [
43
+ 'file_delete',
44
+ 'database_drop',
45
+ 'force_push',
46
+ 'deploy_production',
47
+ 'deviation_activate',
48
+ 'backward_transition',
49
+ ];
50
+
51
+ // ---------------------------------------------------------------------------
52
+ // Profile Management
53
+ // ---------------------------------------------------------------------------
54
+
55
+ /**
56
+ * Get the current execution profile from session state.
57
+ *
58
+ * @param {string} projectDir
59
+ * @returns {string} Current profile (defaults to 'guided')
60
+ */
61
+ export function getCurrentProfile(projectDir) {
62
+ const sessionPath = join(projectDir, '.chati', 'session.yaml');
63
+ if (!existsSync(sessionPath)) return Profile.GUIDED;
64
+
65
+ const raw = readFileSync(sessionPath, 'utf-8');
66
+ const match = raw.match(/^\s*execution_profile:\s*(.+)$/m);
67
+ const profile = match ? match[1].trim().replace(/^["']|["']$/g, '') : Profile.GUIDED;
68
+
69
+ return Object.values(Profile).includes(profile) ? profile : Profile.GUIDED;
70
+ }
71
+
72
+ /**
73
+ * Check if a write operation is allowed under the current profile.
74
+ *
75
+ * @param {string} profile - Current execution profile
76
+ * @param {string} operation - Operation type (e.g., 'file_write', 'file_delete')
77
+ * @returns {{ allowed: boolean, reason: string }}
78
+ */
79
+ export function isWriteAllowed(profile, operation) {
80
+ // Explore profile blocks ALL writes
81
+ if (profile === Profile.EXPLORE) {
82
+ return { allowed: false, reason: '[Article XVIII] Explore profile: read-only mode. No writes permitted.' };
83
+ }
84
+
85
+ // Always-confirm operations need user approval regardless of profile
86
+ if (ALWAYS_CONFIRM_OPERATIONS.includes(operation)) {
87
+ return { allowed: false, reason: `[Article XVIII] Operation "${operation}" always requires user confirmation.` };
88
+ }
89
+
90
+ // Guided profile: allowed but requires confirmation (caller handles confirmation)
91
+ if (profile === Profile.GUIDED) {
92
+ return { allowed: true, reason: 'Guided profile: confirmation required.' };
93
+ }
94
+
95
+ // Autonomous profile: allowed without confirmation
96
+ if (profile === Profile.AUTONOMOUS) {
97
+ return { allowed: true, reason: 'Autonomous profile: proceeding without confirmation.' };
98
+ }
99
+
100
+ return { allowed: true, reason: 'Default: allowed.' };
101
+ }
102
+
103
+ /**
104
+ * Validate whether autonomous profile can be activated.
105
+ *
106
+ * @param {string} projectDir
107
+ * @returns {{ canActivate: boolean, reason: string, currentScore: number }}
108
+ */
109
+ export function canActivateAutonomous(projectDir) {
110
+ const sessionPath = join(projectDir, '.chati', 'session.yaml');
111
+ if (!existsSync(sessionPath)) {
112
+ return { canActivate: false, reason: 'No active session', currentScore: 0 };
113
+ }
114
+
115
+ const raw = readFileSync(sessionPath, 'utf-8');
116
+
117
+ // Look for most recent QA gate score
118
+ const scoreMatches = raw.match(/score:\s*([\d.]+)/g);
119
+ if (!scoreMatches || scoreMatches.length === 0) {
120
+ return { canActivate: false, reason: 'No gate scores found. Run QA gates first.', currentScore: 0 };
121
+ }
122
+
123
+ const scores = scoreMatches.map((m) => parseFloat(m.replace('score:', '').trim()));
124
+ const latestScore = scores[scores.length - 1];
125
+
126
+ if (latestScore < AUTONOMOUS_GATE_THRESHOLD) {
127
+ return {
128
+ canActivate: false,
129
+ reason: `Gate score ${latestScore}% < required ${AUTONOMOUS_GATE_THRESHOLD}%`,
130
+ currentScore: latestScore,
131
+ };
132
+ }
133
+
134
+ return { canActivate: true, reason: 'Gate scores meet threshold', currentScore: latestScore };
135
+ }
136
+
137
+ /**
138
+ * Check if autonomous profile should be downgraded due to quality drop.
139
+ *
140
+ * @param {number} currentScore - Current quality score
141
+ * @returns {{ shouldDowngrade: boolean, reason: string }}
142
+ */
143
+ export function shouldDowngradeAutonomous(currentScore) {
144
+ if (currentScore < AUTONOMOUS_GATE_THRESHOLD) {
145
+ return {
146
+ shouldDowngrade: true,
147
+ reason: `Quality dropped to ${currentScore}% (< ${AUTONOMOUS_GATE_THRESHOLD}%). Auto-downgrading to guided profile.`,
148
+ };
149
+ }
150
+ return { shouldDowngrade: false, reason: '' };
151
+ }