midas-mcp 2.3.0 → 2.6.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/dist/analyzer.d.ts +15 -0
- package/dist/analyzer.d.ts.map +1 -1
- package/dist/analyzer.js +183 -67
- package/dist/analyzer.js.map +1 -1
- package/dist/context.d.ts +55 -0
- package/dist/context.d.ts.map +1 -0
- package/dist/context.js +336 -0
- package/dist/context.js.map +1 -0
- package/dist/security.d.ts +29 -0
- package/dist/security.d.ts.map +1 -0
- package/dist/security.js +72 -0
- package/dist/security.js.map +1 -0
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +13 -2
- package/dist/server.js.map +1 -1
- package/dist/tests/edge-cases.test.js +4 -2
- package/dist/tests/edge-cases.test.js.map +1 -1
- package/dist/tests/journal.test.js +4 -1
- package/dist/tests/journal.test.js.map +1 -1
- package/dist/tests/security.test.d.ts +2 -0
- package/dist/tests/security.test.d.ts.map +1 -0
- package/dist/tests/security.test.js +105 -0
- package/dist/tests/security.test.js.map +1 -0
- package/dist/tools/index.d.ts +1 -0
- package/dist/tools/index.d.ts.map +1 -1
- package/dist/tools/index.js +2 -0
- package/dist/tools/index.js.map +1 -1
- package/dist/tools/journal.d.ts.map +1 -1
- package/dist/tools/journal.js +15 -10
- package/dist/tools/journal.js.map +1 -1
- package/dist/tools/phase.d.ts +1 -0
- package/dist/tools/phase.d.ts.map +1 -1
- package/dist/tools/phase.js +55 -16
- package/dist/tools/phase.js.map +1 -1
- package/dist/tools/tornado.d.ts +2 -2
- package/dist/tools/verify.d.ts +137 -0
- package/dist/tools/verify.d.ts.map +1 -0
- package/dist/tools/verify.js +151 -0
- package/dist/tools/verify.js.map +1 -0
- package/dist/tracker.d.ts +87 -0
- package/dist/tracker.d.ts.map +1 -1
- package/dist/tracker.js +608 -47
- package/dist/tracker.js.map +1 -1
- package/dist/tui.d.ts.map +1 -1
- package/dist/tui.js +184 -44
- package/dist/tui.js.map +1 -1
- package/package.json +2 -3
package/dist/tracker.js
CHANGED
|
@@ -1,8 +1,14 @@
|
|
|
1
1
|
import { existsSync, readFileSync, writeFileSync, mkdirSync, statSync, readdirSync } from 'fs';
|
|
2
2
|
import { join, relative } from 'path';
|
|
3
3
|
import { execSync } from 'child_process';
|
|
4
|
+
import { loadState, saveState, getNextPhase } from './state/phase.js';
|
|
5
|
+
import { sanitizePath, isShellSafe } from './security.js';
|
|
6
|
+
import { logger } from './logger.js';
|
|
4
7
|
const MIDAS_DIR = '.midas';
|
|
5
8
|
const TRACKER_FILE = 'tracker.json';
|
|
9
|
+
// ============================================================================
|
|
10
|
+
// PERSISTENCE
|
|
11
|
+
// ============================================================================
|
|
6
12
|
function getTrackerPath(projectPath) {
|
|
7
13
|
return join(projectPath, MIDAS_DIR, TRACKER_FILE);
|
|
8
14
|
}
|
|
@@ -13,12 +19,17 @@ function ensureDir(projectPath) {
|
|
|
13
19
|
}
|
|
14
20
|
}
|
|
15
21
|
export function loadTracker(projectPath) {
|
|
16
|
-
const
|
|
22
|
+
const safePath = sanitizePath(projectPath);
|
|
23
|
+
const path = getTrackerPath(safePath);
|
|
17
24
|
if (existsSync(path)) {
|
|
18
25
|
try {
|
|
19
|
-
|
|
26
|
+
const data = JSON.parse(readFileSync(path, 'utf-8'));
|
|
27
|
+
// Merge with defaults to handle schema evolution
|
|
28
|
+
return { ...getDefaultTracker(), ...data };
|
|
29
|
+
}
|
|
30
|
+
catch (error) {
|
|
31
|
+
logger.error('Failed to parse tracker state', error);
|
|
20
32
|
}
|
|
21
|
-
catch { }
|
|
22
33
|
}
|
|
23
34
|
return getDefaultTracker();
|
|
24
35
|
}
|
|
@@ -39,22 +50,578 @@ function getDefaultTracker() {
|
|
|
39
50
|
},
|
|
40
51
|
inferredPhase: { phase: 'IDLE' },
|
|
41
52
|
confidence: 0,
|
|
53
|
+
// NEW defaults
|
|
54
|
+
gates: {
|
|
55
|
+
compiles: null,
|
|
56
|
+
compiledAt: null,
|
|
57
|
+
testsPass: null,
|
|
58
|
+
testedAt: null,
|
|
59
|
+
lintsPass: null,
|
|
60
|
+
lintedAt: null,
|
|
61
|
+
},
|
|
62
|
+
errorMemory: [],
|
|
63
|
+
currentTask: null,
|
|
64
|
+
suggestionHistory: [],
|
|
65
|
+
fileSnapshot: [],
|
|
66
|
+
lastAnalysis: null,
|
|
42
67
|
};
|
|
43
68
|
}
|
|
44
|
-
//
|
|
69
|
+
// ============================================================================
|
|
70
|
+
// TOOL CALL TRACKING
|
|
71
|
+
// ============================================================================
|
|
45
72
|
export function trackToolCall(projectPath, tool, args) {
|
|
46
|
-
const
|
|
73
|
+
const safePath = sanitizePath(projectPath);
|
|
74
|
+
const tracker = loadTracker(safePath);
|
|
47
75
|
tracker.recentToolCalls = [
|
|
48
76
|
{ tool, timestamp: Date.now(), args },
|
|
49
|
-
...tracker.recentToolCalls.slice(0, 49),
|
|
77
|
+
...tracker.recentToolCalls.slice(0, 49),
|
|
50
78
|
];
|
|
51
|
-
// Update phase based on tool calls
|
|
52
79
|
updatePhaseFromToolCalls(tracker);
|
|
80
|
+
saveTracker(safePath, tracker);
|
|
81
|
+
}
|
|
82
|
+
// ============================================================================
|
|
83
|
+
// VERIFICATION GATES
|
|
84
|
+
// ============================================================================
|
|
85
|
+
export function runVerificationGates(projectPath) {
|
|
86
|
+
const safePath = sanitizePath(projectPath);
|
|
87
|
+
const gates = {
|
|
88
|
+
compiles: null,
|
|
89
|
+
compiledAt: null,
|
|
90
|
+
testsPass: null,
|
|
91
|
+
testedAt: null,
|
|
92
|
+
lintsPass: null,
|
|
93
|
+
lintedAt: null,
|
|
94
|
+
};
|
|
95
|
+
if (!isShellSafe(safePath)) {
|
|
96
|
+
logger.debug('Unsafe path for verification', { path: safePath });
|
|
97
|
+
return gates;
|
|
98
|
+
}
|
|
99
|
+
// Check if package.json exists
|
|
100
|
+
const pkgPath = join(safePath, 'package.json');
|
|
101
|
+
if (!existsSync(pkgPath)) {
|
|
102
|
+
return gates;
|
|
103
|
+
}
|
|
104
|
+
let pkg = {};
|
|
105
|
+
try {
|
|
106
|
+
pkg = JSON.parse(readFileSync(pkgPath, 'utf-8'));
|
|
107
|
+
}
|
|
108
|
+
catch {
|
|
109
|
+
return gates;
|
|
110
|
+
}
|
|
111
|
+
// Run build if script exists
|
|
112
|
+
if (pkg.scripts?.build) {
|
|
113
|
+
try {
|
|
114
|
+
execSync('npm run build 2>&1', { cwd: safePath, encoding: 'utf-8', timeout: 60000 });
|
|
115
|
+
gates.compiles = true;
|
|
116
|
+
gates.compiledAt = Date.now();
|
|
117
|
+
}
|
|
118
|
+
catch (error) {
|
|
119
|
+
gates.compiles = false;
|
|
120
|
+
gates.compiledAt = Date.now();
|
|
121
|
+
gates.compileError = error instanceof Error ? error.message.slice(0, 500) : String(error).slice(0, 500);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
// Run tests if script exists
|
|
125
|
+
if (pkg.scripts?.test) {
|
|
126
|
+
try {
|
|
127
|
+
execSync('npm test 2>&1', { cwd: safePath, encoding: 'utf-8', timeout: 120000 });
|
|
128
|
+
gates.testsPass = true;
|
|
129
|
+
gates.testedAt = Date.now();
|
|
130
|
+
}
|
|
131
|
+
catch (error) {
|
|
132
|
+
gates.testsPass = false;
|
|
133
|
+
gates.testedAt = Date.now();
|
|
134
|
+
const output = error instanceof Error ? error.message : String(error);
|
|
135
|
+
gates.testError = output.slice(0, 500);
|
|
136
|
+
// Try to extract failed test count
|
|
137
|
+
const failMatch = output.match(/(\d+) fail/i);
|
|
138
|
+
if (failMatch)
|
|
139
|
+
gates.failedTests = parseInt(failMatch[1]);
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
// Run lint if script exists
|
|
143
|
+
if (pkg.scripts?.lint) {
|
|
144
|
+
try {
|
|
145
|
+
execSync('npm run lint 2>&1', { cwd: safePath, encoding: 'utf-8', timeout: 30000 });
|
|
146
|
+
gates.lintsPass = true;
|
|
147
|
+
gates.lintedAt = Date.now();
|
|
148
|
+
}
|
|
149
|
+
catch (error) {
|
|
150
|
+
gates.lintsPass = false;
|
|
151
|
+
gates.lintedAt = Date.now();
|
|
152
|
+
const output = error instanceof Error ? error.message : String(error);
|
|
153
|
+
// Try to extract error count
|
|
154
|
+
const errorMatch = output.match(/(\d+) error/i);
|
|
155
|
+
if (errorMatch)
|
|
156
|
+
gates.lintErrors = parseInt(errorMatch[1]);
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
// Update tracker with gates
|
|
160
|
+
const tracker = loadTracker(safePath);
|
|
161
|
+
tracker.gates = gates;
|
|
162
|
+
saveTracker(safePath, tracker);
|
|
163
|
+
return gates;
|
|
164
|
+
}
|
|
165
|
+
export function getGatesStatus(projectPath) {
|
|
166
|
+
const tracker = loadTracker(projectPath);
|
|
167
|
+
const gates = tracker.gates;
|
|
168
|
+
const failing = [];
|
|
169
|
+
if (gates.compiles === false)
|
|
170
|
+
failing.push('build');
|
|
171
|
+
if (gates.testsPass === false)
|
|
172
|
+
failing.push('tests');
|
|
173
|
+
if (gates.lintsPass === false)
|
|
174
|
+
failing.push('lint');
|
|
175
|
+
// Consider gates stale if older than 10 minutes or if files changed since
|
|
176
|
+
const oldestGate = Math.min(gates.compiledAt || Infinity, gates.testedAt || Infinity, gates.lintedAt || Infinity);
|
|
177
|
+
const stale = oldestGate === Infinity || (Date.now() - oldestGate > 600000);
|
|
178
|
+
return {
|
|
179
|
+
allPass: failing.length === 0 && gates.compiles === true,
|
|
180
|
+
failing,
|
|
181
|
+
stale,
|
|
182
|
+
};
|
|
183
|
+
}
|
|
184
|
+
// ============================================================================
|
|
185
|
+
// ERROR MEMORY
|
|
186
|
+
// ============================================================================
|
|
187
|
+
export function recordError(projectPath, error, file, line) {
|
|
188
|
+
const tracker = loadTracker(projectPath);
|
|
189
|
+
// Check if we've seen this error before
|
|
190
|
+
const existing = tracker.errorMemory.find(e => e.error === error && e.file === file && !e.resolved);
|
|
191
|
+
if (existing) {
|
|
192
|
+
existing.lastSeen = Date.now();
|
|
193
|
+
saveTracker(projectPath, tracker);
|
|
194
|
+
return existing;
|
|
195
|
+
}
|
|
196
|
+
// New error
|
|
197
|
+
const newError = {
|
|
198
|
+
id: `err-${Date.now()}-${Math.random().toString(36).slice(2, 6)}`,
|
|
199
|
+
error,
|
|
200
|
+
file,
|
|
201
|
+
line,
|
|
202
|
+
firstSeen: Date.now(),
|
|
203
|
+
lastSeen: Date.now(),
|
|
204
|
+
fixAttempts: [],
|
|
205
|
+
resolved: false,
|
|
206
|
+
};
|
|
207
|
+
tracker.errorMemory = [newError, ...tracker.errorMemory.slice(0, 49)];
|
|
53
208
|
saveTracker(projectPath, tracker);
|
|
209
|
+
return newError;
|
|
54
210
|
}
|
|
55
|
-
|
|
211
|
+
export function recordFixAttempt(projectPath, errorId, approach, worked) {
|
|
212
|
+
const tracker = loadTracker(projectPath);
|
|
213
|
+
const error = tracker.errorMemory.find(e => e.id === errorId);
|
|
214
|
+
if (error) {
|
|
215
|
+
error.fixAttempts.push({
|
|
216
|
+
approach,
|
|
217
|
+
timestamp: Date.now(),
|
|
218
|
+
worked,
|
|
219
|
+
});
|
|
220
|
+
if (worked) {
|
|
221
|
+
error.resolved = true;
|
|
222
|
+
}
|
|
223
|
+
saveTracker(projectPath, tracker);
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
export function getUnresolvedErrors(projectPath) {
|
|
227
|
+
const tracker = loadTracker(projectPath);
|
|
228
|
+
return tracker.errorMemory.filter(e => !e.resolved);
|
|
229
|
+
}
|
|
230
|
+
export function getStuckErrors(projectPath) {
|
|
231
|
+
const tracker = loadTracker(projectPath);
|
|
232
|
+
return tracker.errorMemory.filter(e => !e.resolved && e.fixAttempts.length >= 2);
|
|
233
|
+
}
|
|
234
|
+
// ============================================================================
|
|
235
|
+
// TASK FOCUS
|
|
236
|
+
// ============================================================================
|
|
237
|
+
export function setTaskFocus(projectPath, description, relatedFiles = []) {
|
|
238
|
+
const tracker = loadTracker(projectPath);
|
|
239
|
+
const task = {
|
|
240
|
+
description,
|
|
241
|
+
startedAt: new Date().toISOString(),
|
|
242
|
+
relatedFiles,
|
|
243
|
+
phase: 'plan',
|
|
244
|
+
attempts: 0,
|
|
245
|
+
};
|
|
246
|
+
tracker.currentTask = task;
|
|
247
|
+
saveTracker(projectPath, tracker);
|
|
248
|
+
return task;
|
|
249
|
+
}
|
|
250
|
+
export function updateTaskPhase(projectPath, phase) {
|
|
251
|
+
const tracker = loadTracker(projectPath);
|
|
252
|
+
if (tracker.currentTask) {
|
|
253
|
+
tracker.currentTask.phase = phase;
|
|
254
|
+
if (phase === 'implement') {
|
|
255
|
+
tracker.currentTask.attempts++;
|
|
256
|
+
}
|
|
257
|
+
saveTracker(projectPath, tracker);
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
export function clearTaskFocus(projectPath) {
|
|
261
|
+
const tracker = loadTracker(projectPath);
|
|
262
|
+
tracker.currentTask = null;
|
|
263
|
+
saveTracker(projectPath, tracker);
|
|
264
|
+
}
|
|
265
|
+
// ============================================================================
|
|
266
|
+
// SUGGESTION TRACKING
|
|
267
|
+
// ============================================================================
|
|
268
|
+
export function recordSuggestion(projectPath, suggestion) {
|
|
269
|
+
const tracker = loadTracker(projectPath);
|
|
270
|
+
tracker.suggestionHistory = [
|
|
271
|
+
{
|
|
272
|
+
timestamp: Date.now(),
|
|
273
|
+
suggestion,
|
|
274
|
+
accepted: false, // Will be updated when we see what user sends
|
|
275
|
+
},
|
|
276
|
+
...tracker.suggestionHistory.slice(0, 19),
|
|
277
|
+
];
|
|
278
|
+
saveTracker(projectPath, tracker);
|
|
279
|
+
}
|
|
280
|
+
export function recordSuggestionOutcome(projectPath, accepted, userPrompt, rejectionReason) {
|
|
281
|
+
const tracker = loadTracker(projectPath);
|
|
282
|
+
if (tracker.suggestionHistory.length > 0) {
|
|
283
|
+
const latest = tracker.suggestionHistory[0];
|
|
284
|
+
latest.accepted = accepted;
|
|
285
|
+
if (userPrompt)
|
|
286
|
+
latest.userPrompt = userPrompt;
|
|
287
|
+
if (rejectionReason)
|
|
288
|
+
latest.rejectionReason = rejectionReason;
|
|
289
|
+
saveTracker(projectPath, tracker);
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
export function getSuggestionAcceptanceRate(projectPath) {
|
|
293
|
+
const tracker = loadTracker(projectPath);
|
|
294
|
+
const recent = tracker.suggestionHistory.slice(0, 10);
|
|
295
|
+
if (recent.length === 0)
|
|
296
|
+
return 0;
|
|
297
|
+
const accepted = recent.filter(s => s.accepted).length;
|
|
298
|
+
return Math.round((accepted / recent.length) * 100);
|
|
299
|
+
}
|
|
300
|
+
// ============================================================================
|
|
301
|
+
// FILE CHANGE DETECTION
|
|
302
|
+
// ============================================================================
|
|
303
|
+
export function takeFileSnapshot(projectPath) {
|
|
304
|
+
const files = scanRecentFiles(projectPath, 0);
|
|
305
|
+
return files.map(f => ({
|
|
306
|
+
path: f.path,
|
|
307
|
+
mtime: f.lastModified,
|
|
308
|
+
size: 0, // We don't need size for change detection
|
|
309
|
+
}));
|
|
310
|
+
}
|
|
311
|
+
export function detectFileChanges(projectPath) {
|
|
312
|
+
const tracker = loadTracker(projectPath);
|
|
313
|
+
const oldSnapshot = tracker.fileSnapshot;
|
|
314
|
+
const newSnapshot = takeFileSnapshot(projectPath);
|
|
315
|
+
const oldMap = new Map(oldSnapshot.map(f => [f.path, f]));
|
|
316
|
+
const newMap = new Map(newSnapshot.map(f => [f.path, f]));
|
|
317
|
+
const changed = [];
|
|
318
|
+
const added = [];
|
|
319
|
+
const deleted = [];
|
|
320
|
+
// Find changed and added files
|
|
321
|
+
for (const [path, file] of newMap) {
|
|
322
|
+
const old = oldMap.get(path);
|
|
323
|
+
if (!old) {
|
|
324
|
+
added.push(path);
|
|
325
|
+
}
|
|
326
|
+
else if (old.mtime !== file.mtime) {
|
|
327
|
+
changed.push(path);
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
// Find deleted files
|
|
331
|
+
for (const path of oldMap.keys()) {
|
|
332
|
+
if (!newMap.has(path)) {
|
|
333
|
+
deleted.push(path);
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
// Update snapshot
|
|
337
|
+
tracker.fileSnapshot = newSnapshot;
|
|
338
|
+
saveTracker(projectPath, tracker);
|
|
339
|
+
return { changed, added, deleted };
|
|
340
|
+
}
|
|
341
|
+
export function hasFilesChangedSinceAnalysis(projectPath) {
|
|
342
|
+
const tracker = loadTracker(projectPath);
|
|
343
|
+
if (!tracker.lastAnalysis)
|
|
344
|
+
return true;
|
|
345
|
+
const recentFiles = scanRecentFiles(projectPath, tracker.lastAnalysis);
|
|
346
|
+
return recentFiles.length > 0;
|
|
347
|
+
}
|
|
348
|
+
export function markAnalysisComplete(projectPath) {
|
|
349
|
+
const tracker = loadTracker(projectPath);
|
|
350
|
+
tracker.lastAnalysis = Date.now();
|
|
351
|
+
saveTracker(projectPath, tracker);
|
|
352
|
+
}
|
|
353
|
+
// ============================================================================
|
|
354
|
+
// SMART PROMPT SUGGESTION
|
|
355
|
+
// ============================================================================
|
|
356
|
+
// ============================================================================
|
|
357
|
+
// COACHING EXPLANATIONS
|
|
358
|
+
// ============================================================================
|
|
359
|
+
/**
|
|
360
|
+
* Educational explanations for each suggestion type
|
|
361
|
+
* These teach the user WHY this is the right next step
|
|
362
|
+
*/
|
|
363
|
+
const COACHING = {
|
|
364
|
+
buildFailing: {
|
|
365
|
+
short: 'Build is failing - must fix before continuing',
|
|
366
|
+
explain: `Your code won't compile. In Golden Code, we never proceed with broken builds because:
|
|
367
|
+
1. You can't run tests on code that doesn't compile
|
|
368
|
+
2. Errors compound - fixing later is harder
|
|
369
|
+
3. Every minute coding on a broken base is wasted
|
|
370
|
+
Fix compilation errors first, always.`,
|
|
371
|
+
},
|
|
372
|
+
testsFailing: {
|
|
373
|
+
short: 'Tests are failing - fix before new features',
|
|
374
|
+
explain: `Failing tests mean your safety net has holes. The Golden Code rule:
|
|
375
|
+
1. Never add features with failing tests
|
|
376
|
+
2. A test failure is a gift - it caught a bug before users did
|
|
377
|
+
3. Fix tests immediately while context is fresh
|
|
378
|
+
The longer you wait, the harder it gets to remember what broke.`,
|
|
379
|
+
},
|
|
380
|
+
stuckOnError: {
|
|
381
|
+
short: 'Same error multiple times - time for Tornado',
|
|
382
|
+
explain: `You've tried fixing this ${'{attempts}'} times without success. Random fixes won't work.
|
|
383
|
+
The Tornado cycle breaks the loop:
|
|
384
|
+
1. RESEARCH - Search docs, StackOverflow, GitHub issues for this exact error
|
|
385
|
+
2. LOGS - Add console.log/debugger around the problem to see actual values
|
|
386
|
+
3. TESTS - Write a minimal test case that reproduces the bug
|
|
387
|
+
This systematic approach works when guessing doesn't.`,
|
|
388
|
+
},
|
|
389
|
+
lintErrors: {
|
|
390
|
+
short: 'Linter errors present',
|
|
391
|
+
explain: `Linting catches bugs before they become runtime errors:
|
|
392
|
+
- Unused variables often indicate logic mistakes
|
|
393
|
+
- Type errors prevent crashes
|
|
394
|
+
- Style consistency makes code readable for future you
|
|
395
|
+
Fix these now - they take seconds but prevent hours of debugging later.`,
|
|
396
|
+
},
|
|
397
|
+
unresolvedError: {
|
|
398
|
+
short: 'Unresolved error from earlier session',
|
|
399
|
+
explain: `You left off with an error. Continuing without fixing it means:
|
|
400
|
+
- The bug is still there
|
|
401
|
+
- You'll hit it again (probably at a worse time)
|
|
402
|
+
- Context you had is fading
|
|
403
|
+
Address it now while it's still fresh in the codebase.`,
|
|
404
|
+
},
|
|
405
|
+
verifyChanges: {
|
|
406
|
+
short: 'No verification run recently',
|
|
407
|
+
explain: `You've made changes but haven't verified them. Golden Code principle:
|
|
408
|
+
- Verify early, verify often
|
|
409
|
+
- The longer between checks, the harder to find what broke
|
|
410
|
+
- A passing build gives confidence to continue
|
|
411
|
+
Run build + tests now to catch issues while changes are small.`,
|
|
412
|
+
},
|
|
413
|
+
allGatesPass: {
|
|
414
|
+
short: 'All gates pass - ready to advance',
|
|
415
|
+
explain: `Build passes, tests pass, lint passes. This is the green light.
|
|
416
|
+
- Your code is verified working
|
|
417
|
+
- It's safe to commit and move forward
|
|
418
|
+
- You've earned the right to add new features
|
|
419
|
+
Consider committing this checkpoint before starting the next task.`,
|
|
420
|
+
},
|
|
421
|
+
phaseDefault: (phase, step) => ({
|
|
422
|
+
short: `Continuing ${phase}:${step}`,
|
|
423
|
+
explain: `You're in the ${phase} phase, ${step} step.
|
|
424
|
+
${getPhaseExplanation(phase, step)}
|
|
425
|
+
Focus on completing this step before moving to the next.`,
|
|
426
|
+
}),
|
|
427
|
+
};
|
|
428
|
+
function getPhaseExplanation(phase, step) {
|
|
429
|
+
const explanations = {
|
|
430
|
+
EAGLE_SIGHT: {
|
|
431
|
+
IDEA: 'Define the core problem, who it affects, and why now is the right time to solve it.',
|
|
432
|
+
RESEARCH: 'Study what exists. What works? What fails? Where are the gaps?',
|
|
433
|
+
BRAINLIFT: 'Document your unique insights - what do you know that others don\'t?',
|
|
434
|
+
PRD: 'Write clear requirements. Vague requirements lead to vague implementations.',
|
|
435
|
+
GAMEPLAN: 'Break the build into ordered tasks. Each task should be completable in one session.',
|
|
436
|
+
},
|
|
437
|
+
BUILD: {
|
|
438
|
+
RULES: 'Read project constraints first. Building without knowing the rules wastes time.',
|
|
439
|
+
INDEX: 'Understand the codebase structure before diving in. Where does what live?',
|
|
440
|
+
READ: 'Read the specific files you\'ll touch. Understand before you modify.',
|
|
441
|
+
RESEARCH: 'Look up docs for APIs you\'ll use. Don\'t guess at library behavior.',
|
|
442
|
+
IMPLEMENT: 'Write code with tests. Test-first catches bugs before they compound.',
|
|
443
|
+
TEST: 'Run all tests. Green means safe. Red means stop and fix.',
|
|
444
|
+
DEBUG: 'Use the Tornado cycle: Research + Logs + Tests when stuck.',
|
|
445
|
+
},
|
|
446
|
+
SHIP: {
|
|
447
|
+
REVIEW: 'Review for security, performance, and edge cases before shipping.',
|
|
448
|
+
DEPLOY: 'Deploy with proper CI/CD. Manual deploys are error-prone.',
|
|
449
|
+
MONITOR: 'Set up logs and alerts. You can\'t fix what you can\'t see.',
|
|
450
|
+
},
|
|
451
|
+
GROW: {
|
|
452
|
+
FEEDBACK: 'Collect real user feedback. Your assumptions need validation.',
|
|
453
|
+
ANALYZE: 'Study the data. Where do users struggle? What do they love?',
|
|
454
|
+
ITERATE: 'Plan the next cycle based on evidence, not guesses. Back to Eagle Sight.',
|
|
455
|
+
},
|
|
456
|
+
};
|
|
457
|
+
return explanations[phase]?.[step] || 'Continue with the current step.';
|
|
458
|
+
}
|
|
459
|
+
export function getSmartPromptSuggestion(projectPath) {
|
|
460
|
+
const tracker = loadTracker(projectPath);
|
|
461
|
+
const gates = tracker.gates;
|
|
462
|
+
const stuckErrors = getStuckErrors(projectPath);
|
|
463
|
+
const unresolvedErrors = getUnresolvedErrors(projectPath);
|
|
464
|
+
// Priority 1: CRITICAL - Build is broken
|
|
465
|
+
if (gates.compiles === false) {
|
|
466
|
+
return {
|
|
467
|
+
prompt: `Fix the TypeScript compilation errors:\n${gates.compileError || 'Run npm run build to see errors'}`,
|
|
468
|
+
reason: COACHING.buildFailing.short,
|
|
469
|
+
explanation: COACHING.buildFailing.explain,
|
|
470
|
+
priority: 'critical',
|
|
471
|
+
context: gates.compileError,
|
|
472
|
+
};
|
|
473
|
+
}
|
|
474
|
+
// Priority 2: HIGH - Tests are failing
|
|
475
|
+
if (gates.testsPass === false) {
|
|
476
|
+
return {
|
|
477
|
+
prompt: `Fix the failing tests (${gates.failedTests || 'some'} failures):\n${gates.testError || 'Run npm test to see failures'}`,
|
|
478
|
+
reason: COACHING.testsFailing.short,
|
|
479
|
+
explanation: COACHING.testsFailing.explain,
|
|
480
|
+
priority: 'high',
|
|
481
|
+
context: gates.testError,
|
|
482
|
+
};
|
|
483
|
+
}
|
|
484
|
+
// Priority 3: HIGH - Stuck on same error
|
|
485
|
+
if (stuckErrors.length > 0) {
|
|
486
|
+
const stuck = stuckErrors[0];
|
|
487
|
+
const triedApproaches = stuck.fixAttempts.filter(a => !a.worked).map(a => a.approach);
|
|
488
|
+
return {
|
|
489
|
+
prompt: `Stuck on error (tried ${stuck.fixAttempts.length}x). Tornado time:\n1. Research: "${stuck.error.slice(0, 50)}"\n2. Add logging around the issue\n3. Write a minimal test case\n\nAlready tried: ${triedApproaches.join(', ')}`,
|
|
490
|
+
reason: COACHING.stuckOnError.short,
|
|
491
|
+
explanation: COACHING.stuckOnError.explain.replace('{attempts}', String(stuck.fixAttempts.length)),
|
|
492
|
+
priority: 'high',
|
|
493
|
+
context: stuck.error,
|
|
494
|
+
};
|
|
495
|
+
}
|
|
496
|
+
// Priority 4: NORMAL - Lint errors
|
|
497
|
+
if (gates.lintsPass === false) {
|
|
498
|
+
return {
|
|
499
|
+
prompt: `Fix ${gates.lintErrors || 'the'} linter errors, then run lint again.`,
|
|
500
|
+
reason: COACHING.lintErrors.short,
|
|
501
|
+
explanation: COACHING.lintErrors.explain,
|
|
502
|
+
priority: 'normal',
|
|
503
|
+
};
|
|
504
|
+
}
|
|
505
|
+
// Priority 5: NORMAL - Unresolved errors from recent session
|
|
506
|
+
if (unresolvedErrors.length > 0) {
|
|
507
|
+
const recent = unresolvedErrors[0];
|
|
508
|
+
return {
|
|
509
|
+
prompt: `Address this error${recent.file ? ` in ${recent.file}` : ''}:\n${recent.error}`,
|
|
510
|
+
reason: COACHING.unresolvedError.short,
|
|
511
|
+
explanation: COACHING.unresolvedError.explain,
|
|
512
|
+
priority: 'normal',
|
|
513
|
+
context: recent.error,
|
|
514
|
+
};
|
|
515
|
+
}
|
|
516
|
+
// Priority 6: Check if gates are stale
|
|
517
|
+
const gatesStatus = getGatesStatus(projectPath);
|
|
518
|
+
if (gatesStatus.stale && tracker.currentTask?.phase === 'implement') {
|
|
519
|
+
return {
|
|
520
|
+
prompt: 'Verify changes: run build and tests to check everything still works.',
|
|
521
|
+
reason: COACHING.verifyChanges.short,
|
|
522
|
+
explanation: COACHING.verifyChanges.explain,
|
|
523
|
+
priority: 'normal',
|
|
524
|
+
};
|
|
525
|
+
}
|
|
526
|
+
// Priority 7: All gates pass - suggest advancement
|
|
527
|
+
if (gatesStatus.allPass) {
|
|
528
|
+
return {
|
|
529
|
+
prompt: 'All gates pass. Ready to advance to the next step.',
|
|
530
|
+
reason: COACHING.allGatesPass.short,
|
|
531
|
+
explanation: COACHING.allGatesPass.explain,
|
|
532
|
+
priority: 'low',
|
|
533
|
+
};
|
|
534
|
+
}
|
|
535
|
+
// Default: Continue with current phase
|
|
536
|
+
const phase = tracker.inferredPhase;
|
|
537
|
+
const phaseStr = phase.phase;
|
|
538
|
+
const stepStr = 'step' in phase ? phase.step : 'IDLE';
|
|
539
|
+
const coaching = COACHING.phaseDefault(phaseStr, stepStr);
|
|
540
|
+
return {
|
|
541
|
+
prompt: getPhaseBasedPrompt(tracker.inferredPhase, tracker.currentTask),
|
|
542
|
+
reason: coaching.short,
|
|
543
|
+
explanation: coaching.explain,
|
|
544
|
+
priority: 'normal',
|
|
545
|
+
};
|
|
546
|
+
}
|
|
547
|
+
function getPhaseBasedPrompt(phase, task) {
|
|
548
|
+
if (phase.phase === 'IDLE') {
|
|
549
|
+
return 'Start a new project or set the phase with midas_set_phase.';
|
|
550
|
+
}
|
|
551
|
+
if (phase.phase === 'EAGLE_SIGHT') {
|
|
552
|
+
const stepPrompts = {
|
|
553
|
+
IDEA: 'Define the core idea: What problem? Who for? Why now?',
|
|
554
|
+
RESEARCH: 'Research the landscape: What exists? What works? What fails?',
|
|
555
|
+
BRAINLIFT: 'Document your unique insights in docs/brainlift.md',
|
|
556
|
+
PRD: 'Write requirements in docs/prd.md',
|
|
557
|
+
GAMEPLAN: 'Plan the build in docs/gameplan.md',
|
|
558
|
+
};
|
|
559
|
+
return stepPrompts[phase.step] || 'Continue planning.';
|
|
560
|
+
}
|
|
561
|
+
if (phase.phase === 'BUILD') {
|
|
562
|
+
const taskContext = task ? ` for: ${task.description}` : '';
|
|
563
|
+
const stepPrompts = {
|
|
564
|
+
RULES: `Load .cursorrules and understand project constraints${taskContext}`,
|
|
565
|
+
INDEX: `Index the codebase structure and architecture${taskContext}`,
|
|
566
|
+
READ: `Read the specific files needed${taskContext}`,
|
|
567
|
+
RESEARCH: `Research docs and APIs needed${taskContext}`,
|
|
568
|
+
IMPLEMENT: `Implement${taskContext} with tests`,
|
|
569
|
+
TEST: 'Run tests and fix any failures',
|
|
570
|
+
DEBUG: 'Debug using Tornado: Research + Logs + Tests',
|
|
571
|
+
};
|
|
572
|
+
return stepPrompts[phase.step] || 'Continue building.';
|
|
573
|
+
}
|
|
574
|
+
if (phase.phase === 'SHIP') {
|
|
575
|
+
const stepPrompts = {
|
|
576
|
+
REVIEW: 'Code review: Check security, performance, edge cases',
|
|
577
|
+
DEPLOY: 'Deploy to production: CI/CD, environment config',
|
|
578
|
+
MONITOR: 'Set up monitoring: logs, alerts, health checks',
|
|
579
|
+
};
|
|
580
|
+
return stepPrompts[phase.step] || 'Continue shipping.';
|
|
581
|
+
}
|
|
582
|
+
if (phase.phase === 'GROW') {
|
|
583
|
+
const stepPrompts = {
|
|
584
|
+
FEEDBACK: 'Collect user feedback: interviews, support tickets, reviews',
|
|
585
|
+
ANALYZE: 'Study the data: metrics, behavior patterns, retention',
|
|
586
|
+
ITERATE: 'Plan next cycle: prioritize and return to Eagle Sight',
|
|
587
|
+
};
|
|
588
|
+
return stepPrompts[phase.step] || 'Continue growing.';
|
|
589
|
+
}
|
|
590
|
+
return 'Continue with the current phase.';
|
|
591
|
+
}
|
|
592
|
+
// ============================================================================
|
|
593
|
+
// AUTO-ADVANCE PHASE
|
|
594
|
+
// ============================================================================
|
|
595
|
+
export function maybeAutoAdvance(projectPath) {
|
|
596
|
+
const tracker = loadTracker(projectPath);
|
|
597
|
+
const gatesStatus = getGatesStatus(projectPath);
|
|
598
|
+
const state = loadState(projectPath);
|
|
599
|
+
const currentPhase = state.current;
|
|
600
|
+
// Only auto-advance if all gates pass
|
|
601
|
+
if (!gatesStatus.allPass) {
|
|
602
|
+
return { advanced: false, from: currentPhase, to: currentPhase };
|
|
603
|
+
}
|
|
604
|
+
// Only auto-advance from BUILD:IMPLEMENT or BUILD:TEST
|
|
605
|
+
if (currentPhase.phase === 'BUILD') {
|
|
606
|
+
if (currentPhase.step === 'IMPLEMENT' || currentPhase.step === 'TEST') {
|
|
607
|
+
const nextPhase = getNextPhase(currentPhase);
|
|
608
|
+
// Update state
|
|
609
|
+
state.history.push(currentPhase);
|
|
610
|
+
state.current = nextPhase;
|
|
611
|
+
saveState(projectPath, state);
|
|
612
|
+
// Update tracker
|
|
613
|
+
tracker.inferredPhase = nextPhase;
|
|
614
|
+
saveTracker(projectPath, tracker);
|
|
615
|
+
return { advanced: true, from: currentPhase, to: nextPhase };
|
|
616
|
+
}
|
|
617
|
+
}
|
|
618
|
+
return { advanced: false, from: currentPhase, to: currentPhase };
|
|
619
|
+
}
|
|
620
|
+
// ============================================================================
|
|
621
|
+
// EXISTING FUNCTIONS (preserved with enhancements)
|
|
622
|
+
// ============================================================================
|
|
56
623
|
export function scanRecentFiles(projectPath, since) {
|
|
57
|
-
const cutoff = since || Date.now() - 3600000;
|
|
624
|
+
const cutoff = since || Date.now() - 3600000;
|
|
58
625
|
const files = [];
|
|
59
626
|
const ignore = ['node_modules', '.git', 'dist', 'build', '.next', '__pycache__', '.midas'];
|
|
60
627
|
function scan(dir, depth = 0) {
|
|
@@ -87,45 +654,47 @@ export function scanRecentFiles(projectPath, since) {
|
|
|
87
654
|
scan(projectPath);
|
|
88
655
|
return files.sort((a, b) => b.lastModified - a.lastModified);
|
|
89
656
|
}
|
|
90
|
-
// Get git activity
|
|
91
657
|
export function getGitActivity(projectPath) {
|
|
92
|
-
|
|
658
|
+
const safePath = sanitizePath(projectPath);
|
|
659
|
+
if (!existsSync(join(safePath, '.git')))
|
|
93
660
|
return null;
|
|
661
|
+
if (!isShellSafe(safePath)) {
|
|
662
|
+
logger.debug('Unsafe path for git commands', { path: safePath });
|
|
663
|
+
return null;
|
|
664
|
+
}
|
|
94
665
|
try {
|
|
95
|
-
const branch = execSync('git branch --show-current', { cwd:
|
|
666
|
+
const branch = execSync('git branch --show-current', { cwd: safePath, encoding: 'utf-8' }).trim();
|
|
96
667
|
let lastCommit;
|
|
97
668
|
let lastCommitMessage;
|
|
98
669
|
let lastCommitTime;
|
|
99
670
|
try {
|
|
100
|
-
lastCommit = execSync('git log -1 --format=%H', { cwd:
|
|
101
|
-
lastCommitMessage = execSync('git log -1 --format=%s', { cwd:
|
|
102
|
-
const timeStr = execSync('git log -1 --format=%ct', { cwd:
|
|
671
|
+
lastCommit = execSync('git log -1 --format=%H', { cwd: safePath, encoding: 'utf-8' }).trim();
|
|
672
|
+
lastCommitMessage = execSync('git log -1 --format=%s', { cwd: safePath, encoding: 'utf-8' }).trim();
|
|
673
|
+
const timeStr = execSync('git log -1 --format=%ct', { cwd: safePath, encoding: 'utf-8' }).trim();
|
|
103
674
|
lastCommitTime = parseInt(timeStr) * 1000;
|
|
104
675
|
}
|
|
105
676
|
catch { }
|
|
106
677
|
let uncommittedChanges = 0;
|
|
107
678
|
try {
|
|
108
|
-
const status = execSync('git status --porcelain', { cwd:
|
|
679
|
+
const status = execSync('git status --porcelain', { cwd: safePath, encoding: 'utf-8' });
|
|
109
680
|
uncommittedChanges = status.split('\n').filter(Boolean).length;
|
|
110
681
|
}
|
|
111
682
|
catch { }
|
|
112
683
|
return { branch, lastCommit, lastCommitMessage, lastCommitTime, uncommittedChanges };
|
|
113
684
|
}
|
|
114
|
-
catch {
|
|
685
|
+
catch (error) {
|
|
686
|
+
logger.error('Failed to get git activity', error);
|
|
115
687
|
return null;
|
|
116
688
|
}
|
|
117
689
|
}
|
|
118
|
-
// Check completion signals
|
|
119
690
|
export function checkCompletionSignals(projectPath) {
|
|
120
691
|
const signals = {
|
|
121
692
|
testsExist: false,
|
|
122
693
|
docsComplete: false,
|
|
123
694
|
};
|
|
124
|
-
// Check for tests
|
|
125
695
|
const testPatterns = ['.test.', '.spec.', '__tests__', 'tests/'];
|
|
126
|
-
const files = scanRecentFiles(projectPath, 0);
|
|
696
|
+
const files = scanRecentFiles(projectPath, 0);
|
|
127
697
|
signals.testsExist = files.some(f => testPatterns.some(p => f.path.includes(p)));
|
|
128
|
-
// Check docs
|
|
129
698
|
const docsPath = join(projectPath, 'docs');
|
|
130
699
|
if (existsSync(docsPath)) {
|
|
131
700
|
const brainlift = existsSync(join(docsPath, 'brainlift.md'));
|
|
@@ -135,13 +704,11 @@ export function checkCompletionSignals(projectPath) {
|
|
|
135
704
|
}
|
|
136
705
|
return signals;
|
|
137
706
|
}
|
|
138
|
-
// Infer phase from tool calls
|
|
139
707
|
function updatePhaseFromToolCalls(tracker) {
|
|
140
708
|
const recent = tracker.recentToolCalls.slice(0, 10);
|
|
141
709
|
if (recent.length === 0)
|
|
142
710
|
return;
|
|
143
711
|
const lastTool = recent[0].tool;
|
|
144
|
-
// Tool -> Phase mapping
|
|
145
712
|
const toolPhaseMap = {
|
|
146
713
|
'midas_start_project': { phase: 'EAGLE_SIGHT', step: 'IDEA' },
|
|
147
714
|
'midas_check_docs': { phase: 'EAGLE_SIGHT', step: 'BRAINLIFT' },
|
|
@@ -149,32 +716,17 @@ function updatePhaseFromToolCalls(tracker) {
|
|
|
149
716
|
'midas_oneshot': { phase: 'BUILD', step: 'DEBUG' },
|
|
150
717
|
'midas_horizon': { phase: 'BUILD', step: 'IMPLEMENT' },
|
|
151
718
|
'midas_audit': { phase: 'SHIP', step: 'REVIEW' },
|
|
719
|
+
'midas_verify': { phase: 'BUILD', step: 'TEST' },
|
|
152
720
|
};
|
|
153
721
|
if (toolPhaseMap[lastTool]) {
|
|
154
722
|
tracker.inferredPhase = toolPhaseMap[lastTool];
|
|
155
723
|
tracker.confidence = 80;
|
|
156
724
|
}
|
|
157
725
|
}
|
|
158
|
-
// Full tracker update - call this periodically or on-demand
|
|
159
|
-
export function updateTracker(projectPath) {
|
|
160
|
-
const tracker = loadTracker(projectPath);
|
|
161
|
-
// Update file activity
|
|
162
|
-
tracker.recentFiles = scanRecentFiles(projectPath);
|
|
163
|
-
// Update git activity
|
|
164
|
-
tracker.gitActivity = getGitActivity(projectPath);
|
|
165
|
-
// Update completion signals
|
|
166
|
-
tracker.completionSignals = checkCompletionSignals(projectPath);
|
|
167
|
-
// Infer phase from signals
|
|
168
|
-
inferPhaseFromSignals(tracker);
|
|
169
|
-
saveTracker(projectPath, tracker);
|
|
170
|
-
return tracker;
|
|
171
|
-
}
|
|
172
|
-
// Use multiple signals to infer phase
|
|
173
726
|
function inferPhaseFromSignals(tracker) {
|
|
174
727
|
const signals = tracker.completionSignals;
|
|
175
728
|
const git = tracker.gitActivity;
|
|
176
729
|
const recentTools = tracker.recentToolCalls.slice(0, 5).map(t => t.tool);
|
|
177
|
-
// If docs don't exist yet, we're in EAGLE_SIGHT
|
|
178
730
|
if (!signals.docsComplete) {
|
|
179
731
|
if (!existsSync(join(process.cwd(), 'docs'))) {
|
|
180
732
|
tracker.inferredPhase = { phase: 'IDLE' };
|
|
@@ -185,9 +737,7 @@ function inferPhaseFromSignals(tracker) {
|
|
|
185
737
|
tracker.confidence = 70;
|
|
186
738
|
return;
|
|
187
739
|
}
|
|
188
|
-
// If we have uncommitted changes and recent file edits, we're building
|
|
189
740
|
if (git && git.uncommittedChanges > 0 && tracker.recentFiles.length > 0) {
|
|
190
|
-
// Check what type of files changed
|
|
191
741
|
const recentPaths = tracker.recentFiles.map(f => f.path);
|
|
192
742
|
const hasTestChanges = recentPaths.some(p => p.includes('.test.') || p.includes('.spec.'));
|
|
193
743
|
const hasSrcChanges = recentPaths.some(p => p.includes('src/') || p.includes('lib/'));
|
|
@@ -203,28 +753,32 @@ function inferPhaseFromSignals(tracker) {
|
|
|
203
753
|
tracker.confidence = 60;
|
|
204
754
|
return;
|
|
205
755
|
}
|
|
206
|
-
// If audit was recently called, we're shipping
|
|
207
756
|
if (recentTools.includes('midas_audit')) {
|
|
208
757
|
tracker.inferredPhase = { phase: 'SHIP', step: 'REVIEW' };
|
|
209
758
|
tracker.confidence = 75;
|
|
210
759
|
return;
|
|
211
760
|
}
|
|
212
|
-
// Default to BUILD:IMPLEMENT if we have code
|
|
213
761
|
if (tracker.recentFiles.length > 0) {
|
|
214
762
|
tracker.inferredPhase = { phase: 'BUILD', step: 'IMPLEMENT' };
|
|
215
763
|
tracker.confidence = 40;
|
|
216
764
|
}
|
|
217
765
|
}
|
|
218
|
-
|
|
766
|
+
export function updateTracker(projectPath) {
|
|
767
|
+
const tracker = loadTracker(projectPath);
|
|
768
|
+
tracker.recentFiles = scanRecentFiles(projectPath);
|
|
769
|
+
tracker.gitActivity = getGitActivity(projectPath);
|
|
770
|
+
tracker.completionSignals = checkCompletionSignals(projectPath);
|
|
771
|
+
inferPhaseFromSignals(tracker);
|
|
772
|
+
saveTracker(projectPath, tracker);
|
|
773
|
+
return tracker;
|
|
774
|
+
}
|
|
219
775
|
export function getActivitySummary(projectPath) {
|
|
220
776
|
const tracker = updateTracker(projectPath);
|
|
221
777
|
const lines = [];
|
|
222
|
-
// Recent files
|
|
223
778
|
if (tracker.recentFiles.length > 0) {
|
|
224
779
|
const topFiles = tracker.recentFiles.slice(0, 3);
|
|
225
780
|
lines.push(`Files: ${topFiles.map(f => f.path.split('/').pop()).join(', ')}`);
|
|
226
781
|
}
|
|
227
|
-
// Git status
|
|
228
782
|
if (tracker.gitActivity) {
|
|
229
783
|
if (tracker.gitActivity.uncommittedChanges > 0) {
|
|
230
784
|
lines.push(`${tracker.gitActivity.uncommittedChanges} uncommitted changes`);
|
|
@@ -233,11 +787,18 @@ export function getActivitySummary(projectPath) {
|
|
|
233
787
|
lines.push(`Last: "${tracker.gitActivity.lastCommitMessage.slice(0, 30)}..."`);
|
|
234
788
|
}
|
|
235
789
|
}
|
|
236
|
-
// Recent tools
|
|
237
790
|
if (tracker.recentToolCalls.length > 0) {
|
|
238
791
|
const lastTool = tracker.recentToolCalls[0].tool.replace('midas_', '');
|
|
239
792
|
lines.push(`Tool: ${lastTool}`);
|
|
240
793
|
}
|
|
794
|
+
// Add gate status
|
|
795
|
+
const gatesStatus = getGatesStatus(projectPath);
|
|
796
|
+
if (gatesStatus.failing.length > 0) {
|
|
797
|
+
lines.push(`Failing: ${gatesStatus.failing.join(', ')}`);
|
|
798
|
+
}
|
|
799
|
+
else if (gatesStatus.allPass) {
|
|
800
|
+
lines.push('Gates: all pass');
|
|
801
|
+
}
|
|
241
802
|
return lines.join(' | ') || 'No recent activity';
|
|
242
803
|
}
|
|
243
804
|
//# sourceMappingURL=tracker.js.map
|