midas-mcp 2.0.0 → 2.3.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 (46) hide show
  1. package/dist/cli.d.ts +1 -0
  2. package/dist/cli.d.ts.map +1 -1
  3. package/dist/cli.js +60 -7
  4. package/dist/cli.js.map +1 -1
  5. package/dist/config.js +1 -1
  6. package/dist/config.js.map +1 -1
  7. package/dist/docs/CURSORRULES_TEMPLATE.md +61 -46
  8. package/dist/docs/USER_RULES.md +62 -81
  9. package/dist/metrics.d.ts +36 -0
  10. package/dist/metrics.d.ts.map +1 -0
  11. package/dist/metrics.js +144 -0
  12. package/dist/metrics.js.map +1 -0
  13. package/dist/tests/analyze.test.d.ts +2 -0
  14. package/dist/tests/analyze.test.d.ts.map +1 -0
  15. package/dist/tests/analyze.test.js +120 -0
  16. package/dist/tests/analyze.test.js.map +1 -0
  17. package/dist/tests/edge-cases.test.d.ts +2 -0
  18. package/dist/tests/edge-cases.test.d.ts.map +1 -0
  19. package/dist/tests/edge-cases.test.js +232 -0
  20. package/dist/tests/edge-cases.test.js.map +1 -0
  21. package/dist/tests/journal.test.d.ts +2 -0
  22. package/dist/tests/journal.test.d.ts.map +1 -0
  23. package/dist/tests/journal.test.js +181 -0
  24. package/dist/tests/journal.test.js.map +1 -0
  25. package/dist/tests/metrics.test.d.ts +2 -0
  26. package/dist/tests/metrics.test.d.ts.map +1 -0
  27. package/dist/tests/metrics.test.js +178 -0
  28. package/dist/tests/metrics.test.js.map +1 -0
  29. package/dist/tests/prompts.test.d.ts +2 -0
  30. package/dist/tests/prompts.test.d.ts.map +1 -0
  31. package/dist/tests/prompts.test.js +157 -0
  32. package/dist/tests/prompts.test.js.map +1 -0
  33. package/dist/tests/server.test.d.ts +2 -0
  34. package/dist/tests/server.test.d.ts.map +1 -0
  35. package/dist/tests/server.test.js +93 -0
  36. package/dist/tests/server.test.js.map +1 -0
  37. package/dist/tests/tracker.test.d.ts +2 -0
  38. package/dist/tests/tracker.test.d.ts.map +1 -0
  39. package/dist/tests/tracker.test.js +197 -0
  40. package/dist/tests/tracker.test.js.map +1 -0
  41. package/dist/tui.d.ts.map +1 -1
  42. package/dist/tui.js +66 -57
  43. package/dist/tui.js.map +1 -1
  44. package/docs/CURSORRULES_TEMPLATE.md +61 -46
  45. package/docs/USER_RULES.md +62 -81
  46. package/package.json +1 -1
@@ -0,0 +1,93 @@
1
+ import { describe, it } from 'node:test';
2
+ import assert from 'node:assert';
3
+ import { createServer } from '../server.js';
4
+ describe('MCP Server', () => {
5
+ describe('createServer', () => {
6
+ it('creates a server instance', () => {
7
+ const server = createServer();
8
+ assert.notStrictEqual(server, null);
9
+ assert.notStrictEqual(server, undefined);
10
+ });
11
+ it('server has name and version', () => {
12
+ const server = createServer();
13
+ // The server object is created - basic smoke test
14
+ assert.strictEqual(typeof server, 'object');
15
+ });
16
+ });
17
+ describe('Tool Registration', () => {
18
+ it('registers all expected tools', () => {
19
+ // This is a smoke test - server creation registers tools
20
+ // If any tool registration fails, createServer will throw
21
+ assert.doesNotThrow(() => {
22
+ createServer();
23
+ });
24
+ });
25
+ });
26
+ describe('Prompt Registration', () => {
27
+ it('registers all prompts without error', () => {
28
+ assert.doesNotThrow(() => {
29
+ createServer();
30
+ });
31
+ });
32
+ });
33
+ describe('Resource Registration', () => {
34
+ it('registers all resources without error', () => {
35
+ assert.doesNotThrow(() => {
36
+ createServer();
37
+ });
38
+ });
39
+ });
40
+ });
41
+ describe('Tool Schemas', () => {
42
+ // Import all schemas to verify they're valid Zod schemas
43
+ it('all tool schemas are valid', async () => {
44
+ const { startProjectSchema, getPhaseSchema, setPhaseSchema, auditSchema, checkDocsSchema, oneshotSchema, tornadoSchema, horizonSchema, analyzeSchema, suggestPromptSchema, advancePhaseSchema, saveJournalSchema, getJournalSchema, searchJournalSchema, } = await import('../tools/index.js');
45
+ // All schemas should have a parse method (Zod schemas)
46
+ assert.strictEqual(typeof startProjectSchema.parse, 'function');
47
+ assert.strictEqual(typeof getPhaseSchema.parse, 'function');
48
+ assert.strictEqual(typeof setPhaseSchema.parse, 'function');
49
+ assert.strictEqual(typeof auditSchema.parse, 'function');
50
+ assert.strictEqual(typeof checkDocsSchema.parse, 'function');
51
+ assert.strictEqual(typeof oneshotSchema.parse, 'function');
52
+ assert.strictEqual(typeof tornadoSchema.parse, 'function');
53
+ assert.strictEqual(typeof horizonSchema.parse, 'function');
54
+ assert.strictEqual(typeof analyzeSchema.parse, 'function');
55
+ assert.strictEqual(typeof suggestPromptSchema.parse, 'function');
56
+ assert.strictEqual(typeof advancePhaseSchema.parse, 'function');
57
+ assert.strictEqual(typeof saveJournalSchema.parse, 'function');
58
+ assert.strictEqual(typeof getJournalSchema.parse, 'function');
59
+ assert.strictEqual(typeof searchJournalSchema.parse, 'function');
60
+ });
61
+ it('schemas validate correct input', async () => {
62
+ const { startProjectSchema, oneshotSchema, tornadoSchema } = await import('../tools/index.js');
63
+ // startProjectSchema
64
+ const startInput = { projectName: 'test-app' };
65
+ assert.doesNotThrow(() => startProjectSchema.parse(startInput));
66
+ // oneshotSchema
67
+ const oneshotInput = { originalPrompt: 'test', error: 'error' };
68
+ assert.doesNotThrow(() => oneshotSchema.parse(oneshotInput));
69
+ // tornadoSchema
70
+ const tornadoInput = { problem: 'test problem' };
71
+ assert.doesNotThrow(() => tornadoSchema.parse(tornadoInput));
72
+ });
73
+ it('schemas reject invalid input', async () => {
74
+ const { oneshotSchema, tornadoSchema, saveJournalSchema } = await import('../tools/index.js');
75
+ // oneshotSchema requires originalPrompt and error
76
+ assert.throws(() => oneshotSchema.parse({}));
77
+ // tornadoSchema requires problem
78
+ assert.throws(() => tornadoSchema.parse({}));
79
+ // saveJournalSchema requires title and conversation
80
+ assert.throws(() => saveJournalSchema.parse({}));
81
+ });
82
+ });
83
+ describe('Logger', () => {
84
+ it('logger exports expected methods', async () => {
85
+ const { logger } = await import('../logger.js');
86
+ assert.strictEqual(typeof logger.info, 'function');
87
+ assert.strictEqual(typeof logger.debug, 'function');
88
+ assert.strictEqual(typeof logger.warn, 'function');
89
+ assert.strictEqual(typeof logger.error, 'function');
90
+ assert.strictEqual(typeof logger.tool, 'function');
91
+ });
92
+ });
93
+ //# sourceMappingURL=server.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"server.test.js","sourceRoot":"","sources":["../../src/tests/server.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=tracker.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tracker.test.d.ts","sourceRoot":"","sources":["../../src/tests/tracker.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,197 @@
1
+ import { describe, it, beforeEach, afterEach } from 'node:test';
2
+ import assert from 'node:assert';
3
+ import { mkdirSync, rmSync, writeFileSync, existsSync } from 'fs';
4
+ import { join } from 'path';
5
+ import { tmpdir } from 'os';
6
+ import { execSync } from 'child_process';
7
+ import { loadTracker, saveTracker, trackToolCall, scanRecentFiles, getGitActivity, checkCompletionSignals, updateTracker, } from '../tracker.js';
8
+ describe('Tracker Module', () => {
9
+ const testDir = join(tmpdir(), 'midas-tracker-test-' + Date.now());
10
+ beforeEach(() => {
11
+ mkdirSync(testDir, { recursive: true });
12
+ });
13
+ afterEach(() => {
14
+ rmSync(testDir, { recursive: true, force: true });
15
+ });
16
+ describe('loadTracker', () => {
17
+ it('returns default tracker for new project', () => {
18
+ const tracker = loadTracker(testDir);
19
+ assert.strictEqual(tracker.recentFiles.length, 0);
20
+ assert.strictEqual(tracker.recentToolCalls.length, 0);
21
+ assert.strictEqual(tracker.gitActivity, null);
22
+ assert.deepStrictEqual(tracker.inferredPhase, { phase: 'IDLE' });
23
+ });
24
+ it('loads saved tracker state', () => {
25
+ const state = {
26
+ lastUpdated: new Date().toISOString(),
27
+ recentFiles: [{ path: 'test.ts', lastModified: Date.now() }],
28
+ recentToolCalls: [],
29
+ gitActivity: null,
30
+ completionSignals: { testsExist: true, docsComplete: false },
31
+ inferredPhase: { phase: 'BUILD', step: 'IMPLEMENT' },
32
+ confidence: 70,
33
+ };
34
+ mkdirSync(join(testDir, '.midas'), { recursive: true });
35
+ writeFileSync(join(testDir, '.midas', 'tracker.json'), JSON.stringify(state));
36
+ const tracker = loadTracker(testDir);
37
+ assert.strictEqual(tracker.recentFiles.length, 1);
38
+ assert.strictEqual(tracker.inferredPhase.phase, 'BUILD');
39
+ });
40
+ });
41
+ describe('saveTracker', () => {
42
+ it('creates .midas directory', () => {
43
+ const tracker = loadTracker(testDir);
44
+ saveTracker(testDir, tracker);
45
+ assert.strictEqual(existsSync(join(testDir, '.midas')), true);
46
+ });
47
+ it('persists tracker state', () => {
48
+ const tracker = loadTracker(testDir);
49
+ tracker.recentToolCalls.push({ tool: 'test_tool', timestamp: Date.now() });
50
+ saveTracker(testDir, tracker);
51
+ const loaded = loadTracker(testDir);
52
+ assert.strictEqual(loaded.recentToolCalls.length, 1);
53
+ assert.strictEqual(loaded.recentToolCalls[0].tool, 'test_tool');
54
+ });
55
+ it('updates lastUpdated timestamp', () => {
56
+ const tracker = loadTracker(testDir);
57
+ const before = tracker.lastUpdated;
58
+ // Wait a tiny bit
59
+ saveTracker(testDir, tracker);
60
+ const loaded = loadTracker(testDir);
61
+ assert.strictEqual(loaded.lastUpdated >= before, true);
62
+ });
63
+ });
64
+ describe('trackToolCall', () => {
65
+ it('adds tool call to tracker', () => {
66
+ trackToolCall(testDir, 'midas_audit', { projectPath: testDir });
67
+ const tracker = loadTracker(testDir);
68
+ assert.strictEqual(tracker.recentToolCalls.length, 1);
69
+ assert.strictEqual(tracker.recentToolCalls[0].tool, 'midas_audit');
70
+ });
71
+ it('stores tool arguments', () => {
72
+ const args = { projectPath: testDir, updatePhase: true };
73
+ trackToolCall(testDir, 'midas_analyze', args);
74
+ const tracker = loadTracker(testDir);
75
+ assert.deepStrictEqual(tracker.recentToolCalls[0].args, args);
76
+ });
77
+ it('keeps most recent calls first', () => {
78
+ trackToolCall(testDir, 'first_tool', {});
79
+ trackToolCall(testDir, 'second_tool', {});
80
+ trackToolCall(testDir, 'third_tool', {});
81
+ const tracker = loadTracker(testDir);
82
+ assert.strictEqual(tracker.recentToolCalls[0].tool, 'third_tool');
83
+ assert.strictEqual(tracker.recentToolCalls[2].tool, 'first_tool');
84
+ });
85
+ it('limits stored tool calls to 50', () => {
86
+ for (let i = 0; i < 60; i++) {
87
+ trackToolCall(testDir, `tool_${i}`, {});
88
+ }
89
+ const tracker = loadTracker(testDir);
90
+ assert.strictEqual(tracker.recentToolCalls.length, 50);
91
+ });
92
+ it('infers phase from specific tool calls', () => {
93
+ trackToolCall(testDir, 'midas_tornado', { problem: 'test' });
94
+ const tracker = loadTracker(testDir);
95
+ assert.strictEqual(tracker.inferredPhase.phase, 'BUILD');
96
+ assert.strictEqual(tracker.inferredPhase.step, 'DEBUG');
97
+ });
98
+ });
99
+ describe('scanRecentFiles', () => {
100
+ it('returns empty for empty directory', () => {
101
+ const files = scanRecentFiles(testDir);
102
+ assert.strictEqual(files.length, 0);
103
+ });
104
+ it('finds recently modified files', () => {
105
+ writeFileSync(join(testDir, 'test.ts'), 'content');
106
+ const files = scanRecentFiles(testDir);
107
+ assert.strictEqual(files.length, 1);
108
+ assert.strictEqual(files[0].path, 'test.ts');
109
+ });
110
+ it('ignores node_modules', () => {
111
+ mkdirSync(join(testDir, 'node_modules'), { recursive: true });
112
+ writeFileSync(join(testDir, 'node_modules', 'package.json'), '{}');
113
+ writeFileSync(join(testDir, 'src.ts'), 'code');
114
+ const files = scanRecentFiles(testDir);
115
+ assert.strictEqual(files.length, 1);
116
+ assert.strictEqual(files[0].path, 'src.ts');
117
+ });
118
+ it('ignores .git directory', () => {
119
+ mkdirSync(join(testDir, '.git'), { recursive: true });
120
+ writeFileSync(join(testDir, '.git', 'config'), 'gitconfig');
121
+ writeFileSync(join(testDir, 'app.ts'), 'code');
122
+ const files = scanRecentFiles(testDir);
123
+ assert.strictEqual(files.every(f => !f.path.includes('.git')), true);
124
+ });
125
+ it('sorts by most recent first', () => {
126
+ writeFileSync(join(testDir, 'old.ts'), 'old');
127
+ // Slight delay to ensure different timestamps
128
+ writeFileSync(join(testDir, 'new.ts'), 'new');
129
+ const files = scanRecentFiles(testDir);
130
+ if (files.length >= 2) {
131
+ assert.strictEqual(files[0].lastModified >= files[1].lastModified, true);
132
+ }
133
+ });
134
+ });
135
+ describe('getGitActivity', () => {
136
+ it('returns null for non-git directory', () => {
137
+ const activity = getGitActivity(testDir);
138
+ assert.strictEqual(activity, null);
139
+ });
140
+ it('detects git repository', () => {
141
+ // Initialize a git repo
142
+ execSync('git init', { cwd: testDir, stdio: 'ignore' });
143
+ execSync('git config user.email "test@test.com"', { cwd: testDir, stdio: 'ignore' });
144
+ execSync('git config user.name "Test"', { cwd: testDir, stdio: 'ignore' });
145
+ const activity = getGitActivity(testDir);
146
+ assert.notStrictEqual(activity, null);
147
+ assert.strictEqual(typeof activity?.branch, 'string');
148
+ });
149
+ it('counts uncommitted changes', () => {
150
+ execSync('git init', { cwd: testDir, stdio: 'ignore' });
151
+ execSync('git config user.email "test@test.com"', { cwd: testDir, stdio: 'ignore' });
152
+ execSync('git config user.name "Test"', { cwd: testDir, stdio: 'ignore' });
153
+ writeFileSync(join(testDir, 'file.txt'), 'content');
154
+ const activity = getGitActivity(testDir);
155
+ assert.strictEqual(activity?.uncommittedChanges, 1);
156
+ });
157
+ });
158
+ describe('checkCompletionSignals', () => {
159
+ it('detects test files', () => {
160
+ writeFileSync(join(testDir, 'app.test.ts'), 'test');
161
+ const signals = checkCompletionSignals(testDir);
162
+ assert.strictEqual(signals.testsExist, true);
163
+ });
164
+ it('detects spec files', () => {
165
+ writeFileSync(join(testDir, 'app.spec.ts'), 'spec');
166
+ const signals = checkCompletionSignals(testDir);
167
+ assert.strictEqual(signals.testsExist, true);
168
+ });
169
+ it('detects docs completeness', () => {
170
+ mkdirSync(join(testDir, 'docs'), { recursive: true });
171
+ writeFileSync(join(testDir, 'docs', 'brainlift.md'), '# Brainlift');
172
+ writeFileSync(join(testDir, 'docs', 'prd.md'), '# PRD');
173
+ writeFileSync(join(testDir, 'docs', 'gameplan.md'), '# Gameplan');
174
+ const signals = checkCompletionSignals(testDir);
175
+ assert.strictEqual(signals.docsComplete, true);
176
+ });
177
+ it('returns false when docs missing', () => {
178
+ const signals = checkCompletionSignals(testDir);
179
+ assert.strictEqual(signals.docsComplete, false);
180
+ });
181
+ });
182
+ describe('updateTracker', () => {
183
+ it('updates all tracker fields', () => {
184
+ writeFileSync(join(testDir, 'test.ts'), 'code');
185
+ const tracker = updateTracker(testDir);
186
+ assert.strictEqual(tracker.recentFiles.length >= 1, true);
187
+ assert.strictEqual(typeof tracker.lastUpdated, 'string');
188
+ });
189
+ it('persists updated tracker', () => {
190
+ writeFileSync(join(testDir, 'src.ts'), 'code');
191
+ updateTracker(testDir);
192
+ const loaded = loadTracker(testDir);
193
+ assert.strictEqual(loaded.recentFiles.some(f => f.path === 'src.ts'), true);
194
+ });
195
+ });
196
+ });
197
+ //# sourceMappingURL=tracker.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tracker.test.js","sourceRoot":"","sources":["../../src/tests/tracker.test.ts"],"names":[],"mappings":"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"}
package/dist/tui.d.ts.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"tui.d.ts","sourceRoot":"","sources":["../src/tui.ts"],"names":[],"mappings":"AAmTA,wBAAsB,cAAc,IAAI,OAAO,CAAC,IAAI,CAAC,CA0KpD"}
1
+ {"version":3,"file":"tui.d.ts","sourceRoot":"","sources":["../src/tui.ts"],"names":[],"mappings":"AAuTA,wBAAsB,cAAc,IAAI,OAAO,CAAC,IAAI,CAAC,CAoLpD"}
package/dist/tui.js CHANGED
@@ -1,12 +1,11 @@
1
1
  import { exec } from 'child_process';
2
- import { existsSync, readFileSync, writeFileSync, appendFileSync } from 'fs';
3
- import { join } from 'path';
4
2
  import { loadState, saveState, PHASE_INFO } from './state/phase.js';
5
3
  import { hasApiKey, ensureApiKey } from './config.js';
6
4
  import { logEvent, watchEvents } from './events.js';
7
5
  import { analyzeProject } from './analyzer.js';
8
6
  import { getActivitySummary, loadTracker } from './tracker.js';
9
7
  import { getJournalEntries } from './tools/journal.js';
8
+ import { startSession, endSession, recordPromptCopied, loadMetrics } from './metrics.js';
10
9
  // ANSI codes
11
10
  const ESC = '\x1b';
12
11
  const reset = `${ESC}[0m`;
@@ -26,19 +25,36 @@ const PHASE_COLORS = {
26
25
  SHIP: green,
27
26
  GROW: magenta,
28
27
  };
29
- // The Midas rules to add to .cursorrules
30
- const MIDAS_CURSORRULES = `
31
- # Golden Code Methodology (via Midas MCP)
28
+ // The Midas User Rules content (for Cursor Settings → Rules for AI)
29
+ const MIDAS_USER_RULES = `# Golden Code Methodology (via Midas MCP)
32
30
 
33
- ## Workflow Rules
34
- 1. **Before any code changes**: Call \`midas_analyze\` to understand current phase
35
- 2. **After important discussions**: Call \`midas_journal_save\` to preserve context
36
- 3. **When stuck**: Call \`midas_tornado\` for Research + Logs + Tests cycle
37
- 4. **When output doesn't fit**: Call \`midas_horizon\` to expand context
38
- 5. **When retrying failed prompts**: Call \`midas_oneshot\` to construct better retry
31
+ ## AUTOMATIC BEHAVIOR - Do these without being asked:
39
32
 
40
- ## Session Start
41
- At the start of each new chat session, call \`midas_journal_list\` to load context from previous sessions.
33
+ ### On EVERY new chat session:
34
+ 1. Call \`midas_journal_list\` to load context from previous sessions
35
+ 2. Call \`midas_analyze\` to understand current project phase
36
+ 3. Follow the suggested approach from the analysis
37
+
38
+ ### After EVERY significant code change:
39
+ 1. Git commit with descriptive message before AND after major changes
40
+ 2. Call \`midas_journal_save\` with title describing what was done
41
+ 3. Include the full conversation in the journal entry
42
+
43
+ ### Before EVERY implementation:
44
+ 1. Call \`midas_suggest_prompt\` to get phase-appropriate guidance
45
+ 2. Follow the Golden Code methodology for current phase
46
+
47
+ ## When Things Go Wrong:
48
+ - Stuck/confused: Call \`midas_tornado\` for Research + Logs + Tests cycle
49
+ - Output doesn't fit: Call \`midas_horizon\` to expand context
50
+ - Retry after error: Call \`midas_oneshot\` to construct better retry
51
+
52
+ ## Git Discipline:
53
+ - Commit BEFORE starting any significant change (checkpoint)
54
+ - Commit AFTER completing each logical unit of work
55
+
56
+ ## The 7-Step BUILD Cycle:
57
+ RULES → INDEX → READ → RESEARCH → IMPLEMENT → TEST → DEBUG
42
58
  `;
43
59
  // Session starter prompt
44
60
  function getSessionStarterPrompt(projectPath) {
@@ -49,23 +65,9 @@ function getSessionStarterPrompt(projectPath) {
49
65
  }
50
66
  return `Before we begin, please call midas_analyze to understand where we are in the project and what to do next.`;
51
67
  }
52
- // Check if .cursorrules has Midas content
53
- function hasMidasRules(projectPath) {
54
- const rulesPath = join(projectPath, '.cursorrules');
55
- if (!existsSync(rulesPath))
56
- return false;
57
- const content = readFileSync(rulesPath, 'utf-8');
58
- return content.includes('midas_analyze') || content.includes('Golden Code');
59
- }
60
- // Add Midas rules to .cursorrules
61
- function addMidasRules(projectPath) {
62
- const rulesPath = join(projectPath, '.cursorrules');
63
- if (existsSync(rulesPath)) {
64
- appendFileSync(rulesPath, '\n' + MIDAS_CURSORRULES);
65
- }
66
- else {
67
- writeFileSync(rulesPath, MIDAS_CURSORRULES.trim());
68
- }
68
+ // Copy User Rules to clipboard for pasting into Cursor Settings
69
+ function copyUserRules() {
70
+ return copyToClipboard(MIDAS_USER_RULES);
69
71
  }
70
72
  function copyToClipboard(text) {
71
73
  return new Promise((resolve, reject) => {
@@ -134,13 +136,14 @@ function drawUI(state, _projectPath) {
134
136
  const lines = [];
135
137
  // Header
136
138
  lines.push(`${cyan}╔${hLine}╗${reset}`);
137
- const statusIcons = `${state.hasApiKey ? `${green}●${reset}AI` : `${dim}○${reset}--`} ${state.hasMidasRules ? `${green}●${reset}Rules` : `${yellow}○${reset}Rules`}`;
138
- lines.push(row(`${bold}${white}MIDAS${reset} ${dim}- Golden Code Coach${reset} ${statusIcons}`, I));
139
+ const streakStr = state.sessionStreak > 0 ? `${yellow}${state.sessionStreak}d streak${reset}` : '';
140
+ const statusIcons = `${streakStr} ${state.hasApiKey ? `${green}[ok]${reset}AI` : `${dim}[--]${reset}`}`;
141
+ lines.push(row(`${bold}${white}MIDAS${reset} ${dim}- Golden Code Coach${reset} ${statusIcons}`, I));
139
142
  lines.push(`${cyan}╠${hLine}╣${reset}`);
140
143
  // Show session starter prompt first
141
144
  if (state.showingSessionStart) {
142
145
  lines.push(emptyRow());
143
- lines.push(row(`${bold}${yellow}NEW SESSION${reset}`, 14));
146
+ lines.push(row(`${bold}${yellow}NEW SESSION${reset}`, 12));
144
147
  lines.push(emptyRow());
145
148
  lines.push(row(`${dim}Paste this in your new Cursor chat:${reset}`, 36));
146
149
  lines.push(`${cyan}║${reset} ${dim}┌${hLineLight}┐${reset}${cyan}║${reset}`);
@@ -150,19 +153,17 @@ function drawUI(state, _projectPath) {
150
153
  }
151
154
  lines.push(`${cyan}║${reset} ${dim}└${hLineLight}┘${reset}${cyan}║${reset}`);
152
155
  lines.push(emptyRow());
153
- if (!state.hasMidasRules) {
154
- lines.push(row(`${yellow}!${reset} No Midas rules in .cursorrules`, 32));
155
- lines.push(row(`${dim}Press ${bold}[a]${reset}${dim} to add Golden Code rules${reset}`, 35));
156
- lines.push(emptyRow());
157
- }
156
+ lines.push(row(`${dim}TIP: Add User Rules in Cursor Settings for auto-behavior${reset}`, 57));
157
+ lines.push(row(`${dim}Press ${bold}[u]${reset}${dim} to copy User Rules to clipboard${reset}`, 42));
158
+ lines.push(emptyRow());
158
159
  lines.push(`${cyan}╠${hLine}╣${reset}`);
159
- lines.push(row(`${dim}[c]${reset} Copy starter ${dim}[s]${reset} Skip ${dim}[a]${reset} Add rules ${dim}[q]${reset} Quit`, 50));
160
+ lines.push(row(`${dim}[c]${reset} Copy starter ${dim}[u]${reset} Copy User Rules ${dim}[s]${reset} Skip ${dim}[q]${reset} Quit`, 54));
160
161
  lines.push(`${cyan}╚${hLine}╝${reset}`);
161
162
  return lines.join('\n');
162
163
  }
163
164
  if (state.isAnalyzing) {
164
165
  lines.push(emptyRow());
165
- lines.push(row(`${magenta}⟳${reset} ${bold}Analyzing project...${reset}`, 23));
166
+ lines.push(row(`${magenta}...${reset} ${bold}Analyzing project${reset}`, 22));
166
167
  lines.push(row(`${dim}Reading codebase, chat history, docs...${reset}`, 40));
167
168
  lines.push(emptyRow());
168
169
  lines.push(`${cyan}╚${hLine}╝${reset}`);
@@ -199,13 +200,13 @@ function drawUI(state, _projectPath) {
199
200
  const color = PHASE_COLORS[p];
200
201
  const sep = i < phases.length - 1 ? ' ' : '';
201
202
  if (i < currentPhaseIdx) {
202
- phaseBarText += `${green}✓${reset} ${dim}${info.name}${reset}${sep}`;
203
+ phaseBarText += `${green}[x]${reset} ${dim}${info.name}${reset}${sep}`;
203
204
  }
204
205
  else if (i === currentPhaseIdx) {
205
- phaseBarText += `${color}●${reset} ${bold}${info.name}${reset}${sep}`;
206
+ phaseBarText += `${color}[>]${reset} ${bold}${info.name}${reset}${sep}`;
206
207
  }
207
208
  else {
208
- phaseBarText += `${dim} ${info.name}${reset}${sep}`;
209
+ phaseBarText += `${dim}[ ] ${info.name}${reset}${sep}`;
209
210
  }
210
211
  phaseBarLen += 2 + info.name.length + sep.length;
211
212
  }
@@ -221,7 +222,7 @@ function drawUI(state, _projectPath) {
221
222
  lines.push(row(`${dim}Completed:${reset}`, 10));
222
223
  for (const done of a.whatsDone.slice(0, 4)) {
223
224
  const t = done.length > I - 4 ? done.slice(0, I - 7) + '...' : done;
224
- lines.push(row(`${green}✓${reset} ${dim}${t}${reset}`, 2 + t.length));
225
+ lines.push(row(`${green}[x]${reset} ${dim}${t}${reset}`, 4 + t.length));
225
226
  }
226
227
  lines.push(emptyRow());
227
228
  }
@@ -250,7 +251,7 @@ function drawUI(state, _projectPath) {
250
251
  lines.push(emptyRow());
251
252
  lines.push(row(`${dim}Recent MCP Activity:${reset}`, 20));
252
253
  for (const evt of state.recentEvents.slice(-3)) {
253
- const icon = evt.type === 'tool_called' ? `${green}⚡${reset}` : `${dim}○${reset}`;
254
+ const icon = evt.type === 'tool_called' ? `${green}>${reset}` : `${dim}-${reset}`;
254
255
  const label = evt.tool || evt.type;
255
256
  const time = new Date(evt.timestamp).toLocaleTimeString().slice(0, 5);
256
257
  lines.push(row(`${icon} ${label} ${dim}(${time})${reset}`, 3 + label.length + 2 + time.length + 2));
@@ -272,6 +273,10 @@ export async function runInteractive() {
272
273
  }
273
274
  process.stdin.resume();
274
275
  process.stdin.setEncoding('utf8');
276
+ // Start a new session for metrics tracking
277
+ const currentPhase = loadState(projectPath).current;
278
+ const sessionId = startSession(projectPath, currentPhase);
279
+ const metrics = loadMetrics(projectPath);
275
280
  const tuiState = {
276
281
  analysis: null,
277
282
  isAnalyzing: false,
@@ -280,9 +285,10 @@ export async function runInteractive() {
280
285
  recentEvents: [],
281
286
  message: '',
282
287
  hasApiKey: hasApiKey(),
283
- hasMidasRules: hasMidasRules(projectPath),
284
288
  showingSessionStart: true, // Start with session starter prompt
285
289
  sessionStarterPrompt: getSessionStarterPrompt(projectPath),
290
+ sessionId,
291
+ sessionStreak: metrics.currentStreak,
286
292
  };
287
293
  const render = () => {
288
294
  console.log(clearScreen);
@@ -340,7 +346,7 @@ export async function runInteractive() {
340
346
  const toolEvents = newEvents.filter(e => e.type === 'tool_called');
341
347
  if (toolEvents.length > 0) {
342
348
  const lastTool = toolEvents[toolEvents.length - 1];
343
- tuiState.message = `${green}⚡${reset} Cursor called ${bold}${lastTool.tool}${reset}`;
349
+ tuiState.message = `${green}>${reset} Cursor called ${bold}${lastTool.tool}${reset}`;
344
350
  // Auto-refresh analysis after certain tools
345
351
  const refreshTools = ['midas_journal_save', 'midas_set_phase', 'midas_advance_phase'];
346
352
  if (refreshTools.includes(lastTool.tool || '')) {
@@ -353,8 +359,11 @@ export async function runInteractive() {
353
359
  });
354
360
  process.stdin.on('data', async (key) => {
355
361
  if (key === 'q' || key === '\u0003') {
362
+ // End session and save metrics
363
+ const endPhase = tuiState.analysis?.currentPhase || { phase: 'IDLE' };
364
+ endSession(projectPath, tuiState.sessionId, endPhase);
356
365
  console.log(clearScreen);
357
- console.log(`\n ${cyan}Midas${reset} signing off. Happy vibecoding!\n`);
366
+ console.log(`\n ${cyan}Midas${reset} signing off. Session saved. Happy vibecoding!\n`);
358
367
  clearInterval(activityInterval);
359
368
  stopWatchingEvents();
360
369
  process.exit(0);
@@ -364,7 +373,7 @@ export async function runInteractive() {
364
373
  if (key === 'c') {
365
374
  try {
366
375
  await copyToClipboard(tuiState.sessionStarterPrompt);
367
- tuiState.message = `${green}✓${reset} Session starter copied! Paste it in your new Cursor chat.`;
376
+ tuiState.message = `${green}OK${reset} Session starter copied! Paste it in your new Cursor chat.`;
368
377
  }
369
378
  catch {
370
379
  tuiState.message = `${yellow}!${reset} Could not copy.`;
@@ -380,14 +389,13 @@ export async function runInteractive() {
380
389
  }
381
390
  return;
382
391
  }
383
- if (key === 'a') {
384
- if (!tuiState.hasMidasRules) {
385
- addMidasRules(projectPath);
386
- tuiState.hasMidasRules = true;
387
- tuiState.message = `${green}✓${reset} Added Golden Code rules to .cursorrules`;
392
+ if (key === 'u') {
393
+ try {
394
+ await copyUserRules();
395
+ tuiState.message = `${green}OK${reset} User Rules copied! Paste in Cursor Settings -> Rules for AI`;
388
396
  }
389
- else {
390
- tuiState.message = `${dim}Already has Midas rules${reset}`;
397
+ catch {
398
+ tuiState.message = `${yellow}!${reset} Could not copy.`;
391
399
  }
392
400
  render();
393
401
  return;
@@ -398,8 +406,9 @@ export async function runInteractive() {
398
406
  if (tuiState.analysis?.suggestedPrompt) {
399
407
  try {
400
408
  await copyToClipboard(tuiState.analysis.suggestedPrompt);
401
- tuiState.message = `${green}✓${reset} Prompt copied to clipboard!`;
409
+ tuiState.message = `${green}OK${reset} Prompt copied to clipboard!`;
402
410
  logEvent(projectPath, { type: 'prompt_copied', message: tuiState.analysis.suggestedPrompt.slice(0, 100) });
411
+ recordPromptCopied(projectPath, tuiState.sessionId);
403
412
  }
404
413
  catch {
405
414
  tuiState.message = `${yellow}!${reset} Could not copy.`;