@equilateral_ai/mindmeld 3.2.0 → 3.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 (68) hide show
  1. package/README.md +4 -4
  2. package/hooks/README.md +46 -4
  3. package/hooks/pre-compact.js +87 -1
  4. package/hooks/session-end.js +292 -0
  5. package/hooks/session-start.js +292 -23
  6. package/package.json +4 -2
  7. package/scripts/auth-login.js +53 -0
  8. package/scripts/init-project.js +69 -375
  9. package/src/core/AuthManager.js +498 -0
  10. package/src/core/CrossReferenceEngine.js +624 -0
  11. package/src/core/DeprecationScheduler.js +183 -0
  12. package/src/core/LLMPatternDetector.js +218 -0
  13. package/src/core/RapportOrchestrator.js +186 -0
  14. package/src/core/RelevanceDetector.js +32 -2
  15. package/src/core/StandardLifecycle.js +244 -0
  16. package/src/core/StandardsIngestion.js +341 -28
  17. package/src/core/parsers/adrParser.js +479 -0
  18. package/src/core/parsers/cursorRulesParser.js +564 -0
  19. package/src/core/parsers/eslintParser.js +439 -0
  20. package/src/handlers/alerts/alertsAcknowledge.js +4 -3
  21. package/src/handlers/analytics/activitySummaryGet.js +235 -0
  22. package/src/handlers/analytics/coachingGet.js +361 -0
  23. package/src/handlers/analytics/developerScoreGet.js +207 -0
  24. package/src/handlers/collaborators/collaboratorAdd.js +4 -5
  25. package/src/handlers/collaborators/collaboratorInvite.js +6 -5
  26. package/src/handlers/collaborators/collaboratorList.js +3 -3
  27. package/src/handlers/collaborators/collaboratorRemove.js +5 -4
  28. package/src/handlers/correlations/correlationsDeveloperGet.js +12 -11
  29. package/src/handlers/correlations/correlationsGet.js +1 -1
  30. package/src/handlers/correlations/correlationsProjectGet.js +7 -6
  31. package/src/handlers/enterprise/enterpriseAuditGet.js +108 -0
  32. package/src/handlers/enterprise/enterpriseContributorsGet.js +85 -0
  33. package/src/handlers/enterprise/enterpriseKnowledgeCategoriesGet.js +53 -0
  34. package/src/handlers/enterprise/enterpriseKnowledgeCreate.js +77 -0
  35. package/src/handlers/enterprise/enterpriseKnowledgeDelete.js +71 -0
  36. package/src/handlers/enterprise/enterpriseKnowledgeGet.js +87 -0
  37. package/src/handlers/enterprise/enterpriseKnowledgeUpdate.js +122 -0
  38. package/src/handlers/enterprise/enterpriseOnboardingComplete.js +77 -0
  39. package/src/handlers/enterprise/enterpriseOnboardingInvite.js +138 -0
  40. package/src/handlers/enterprise/enterpriseOnboardingSetup.js +89 -0
  41. package/src/handlers/enterprise/enterpriseOnboardingStatus.js +90 -0
  42. package/src/handlers/github/githubConnectionStatus.js +1 -1
  43. package/src/handlers/github/githubDiscoverPatterns.js +264 -5
  44. package/src/handlers/github/githubOAuthCallback.js +14 -2
  45. package/src/handlers/github/githubOAuthStart.js +1 -1
  46. package/src/handlers/github/githubPatternsReview.js +1 -1
  47. package/src/handlers/github/githubReposList.js +1 -1
  48. package/src/handlers/helpers/auditLogger.js +201 -0
  49. package/src/handlers/helpers/index.js +19 -1
  50. package/src/handlers/helpers/lambdaWrapper.js +1 -1
  51. package/src/handlers/notifications/sendNotification.js +1 -1
  52. package/src/handlers/projects/projectCreate.js +28 -1
  53. package/src/handlers/projects/projectDelete.js +3 -3
  54. package/src/handlers/projects/projectUpdate.js +4 -5
  55. package/src/handlers/scheduled/analyzeCorrelations.js +3 -3
  56. package/src/handlers/scheduled/generateAlerts.js +1 -1
  57. package/src/handlers/standards/catalogGet.js +185 -0
  58. package/src/handlers/standards/catalogSync.js +120 -0
  59. package/src/handlers/standards/projectStandardsGet.js +135 -0
  60. package/src/handlers/standards/projectStandardsPut.js +131 -0
  61. package/src/handlers/standards/standardsAuditGet.js +65 -0
  62. package/src/handlers/standards/standardsParseUpload.js +153 -0
  63. package/src/handlers/standards/standardsRelevantPost.js +213 -0
  64. package/src/handlers/standards/standardsTransition.js +64 -0
  65. package/src/handlers/user/userSplashAck.js +91 -0
  66. package/src/handlers/user/userSplashGet.js +194 -0
  67. package/src/handlers/users/userProfilePut.js +77 -0
  68. package/src/index.js +37 -29
package/README.md CHANGED
@@ -58,8 +58,8 @@ open https://mindmeld.dev/login
58
58
  - < 500ms hook execution target
59
59
 
60
60
  ### 2. Standards Ingestion (Phase 1)
61
- - Ingests 100+ patterns from .equilateral-standards/
62
- - Extracts enforcement rules, anti-patterns, examples, cost impacts
61
+ - Ingests 100+ patterns from .equilateral-standards/ (YAML format)
62
+ - Parses structured rules, anti-patterns, examples, cost impacts
63
63
  - Stores in `rapport.standards_patterns` database table
64
64
  - Enables intelligent context injection (only relevant standards)
65
65
 
@@ -239,8 +239,8 @@ NODE_ENV, APP_URL
239
239
  Rapport integrates with `.equilateral-standards/` to provide context-aware standards injection:
240
240
 
241
241
  **What it does**:
242
- - Ingests 100+ patterns from .equilateral-standards/ markdown files
243
- - Extracts enforcement rules, anti-patterns, examples, cost impacts
242
+ - Ingests 100+ patterns from .equilateral-standards/ YAML files
243
+ - Parses structured rules (ALWAYS/NEVER/USE/PREFER/AVOID), anti-patterns, examples, cost impacts
244
244
  - Stores in `rapport.standards_patterns` database table
245
245
  - Enables intelligent context injection (only relevant standards)
246
246
 
package/hooks/README.md CHANGED
@@ -74,6 +74,31 @@ npm run test:pre-compact
74
74
  }
75
75
  ```
76
76
 
77
+ ### 3. `session-end.js` - Session Completion
78
+
79
+ **Purpose**: Record session completion and outcomes when Claude Code exits
80
+
81
+ **Execution**: Called when Claude Code session terminates (exit command, logout, clear)
82
+
83
+ **Input** (stdin JSON from Claude Code):
84
+ - `session_id` - Session identifier
85
+ - `transcript_path` - Path to conversation JSONL
86
+ - `cwd` - Working directory
87
+ - `reason` - Exit reason (clear, logout, prompt_input_exit, other)
88
+
89
+ **Output**: API call to POST /api/sessions/end with session metadata
90
+
91
+ **Configuration**:
92
+ ```json
93
+ {
94
+ "claudeCode": {
95
+ "hooks": {
96
+ "sessionEnd": "hooks/session-end.js"
97
+ }
98
+ }
99
+ }
100
+ ```
101
+
77
102
  ---
78
103
 
79
104
  ## How Hooks Work
@@ -110,6 +135,20 @@ npm run test:pre-compact
110
135
  5. Return summary to Claude Code
111
136
  ```
112
137
 
138
+ ### SessionEnd Flow
139
+
140
+ ```
141
+ 1. Claude Code sends session metadata via stdin
142
+ 2. Hook checks if Rapport configured
143
+ 3. If not configured → exit immediately
144
+ 4. If configured:
145
+ a. Load auth token
146
+ b. Get project ID, git branch, session duration
147
+ c. POST /api/sessions/end (fire-and-forget, 3s timeout)
148
+ d. API records session completion and calculates compliance
149
+ 5. Exit (never blocks session termination)
150
+ ```
151
+
113
152
  ---
114
153
 
115
154
  ## Performance Characteristics
@@ -392,10 +431,13 @@ hooks/
392
431
  │ ├── checkRapportConfiguration()
393
432
  │ └── formatContextInjection()
394
433
 
395
- └── pre-compact.js
396
- ├── harvestPatterns()
397
- ├── validatePatterns()
398
- └── checkPromotionCandidates()
434
+ ├── pre-compact.js
435
+ ├── harvestPatterns()
436
+ ├── validatePatterns()
437
+ └── checkPromotionCandidates()
438
+
439
+ └── session-end.js
440
+ └── recordSessionEnd()
399
441
 
400
442
  ↓ Uses ↓
401
443
 
@@ -27,6 +27,86 @@ try {
27
27
  // LLM module not available - will use regex fallback
28
28
  }
29
29
 
30
+ /**
31
+ * Load auth token for API calls
32
+ * Priority: env var → project credentials.json → global ~/.mindmeld/auth.json
33
+ * @returns {Promise<string|null>} Auth token or null
34
+ */
35
+ async function loadAuthToken() {
36
+ // 1. Env var (highest priority)
37
+ if (process.env.MINDMELD_AUTH_TOKEN) {
38
+ return process.env.MINDMELD_AUTH_TOKEN;
39
+ }
40
+
41
+ // 2. Project-level credentials.json
42
+ try {
43
+ const credPath = path.join(process.cwd(), '.mindmeld', 'credentials.json');
44
+ const content = await fs.readFile(credPath, 'utf-8');
45
+ const creds = JSON.parse(content);
46
+ if (creds.auth_token || creds.token) {
47
+ return creds.auth_token || creds.token;
48
+ }
49
+ } catch (error) {
50
+ // No project-level credentials
51
+ }
52
+
53
+ // 3. Global ~/.mindmeld/auth.json (from browser login / mindmeld CLI)
54
+ try {
55
+ const { AuthManager } = require('../src/core/AuthManager');
56
+ const cognitoConfig = await loadCognitoConfig();
57
+ const authManager = new AuthManager(cognitoConfig);
58
+ const token = await authManager.getValidToken();
59
+ if (token) {
60
+ return token;
61
+ }
62
+ return null;
63
+ } catch (error) {
64
+ return null;
65
+ }
66
+ }
67
+
68
+ /**
69
+ * Load Cognito config from .myworld.json
70
+ * @returns {Promise<Object>} Cognito constructor options or empty object
71
+ */
72
+ async function loadCognitoConfig() {
73
+ try {
74
+ const configPath = path.join(process.cwd(), '.myworld.json');
75
+ const content = await fs.readFile(configPath, 'utf-8');
76
+ const config = JSON.parse(content);
77
+ const auth = config.deployments?.backend?.auth;
78
+ if (auth?.domain && auth?.client_id) {
79
+ return {
80
+ cognitoDomain: `${auth.domain}.auth.us-east-2.amazoncognito.com`,
81
+ cognitoClientId: auth.client_id
82
+ };
83
+ }
84
+ } catch (error) {
85
+ // No .myworld.json — use production defaults
86
+ }
87
+ return {};
88
+ }
89
+
90
+ /**
91
+ * Load API configuration from .myworld.json
92
+ * @returns {Promise<{apiUrl: string}>}
93
+ */
94
+ async function loadApiConfig() {
95
+ try {
96
+ const configPath = path.join(process.cwd(), '.myworld.json');
97
+ const content = await fs.readFile(configPath, 'utf-8');
98
+ const config = JSON.parse(content);
99
+ const backend = config.deployments?.backend;
100
+ return {
101
+ apiUrl: backend?.api?.base_url || 'https://api.mindmeld.dev'
102
+ };
103
+ } catch (error) {
104
+ return {
105
+ apiUrl: process.env.MINDMELD_API_URL || 'https://api.mindmeld.dev'
106
+ };
107
+ }
108
+ }
109
+
30
110
  /**
31
111
  * Main hook execution
32
112
  * @param {Object} sessionTranscript - Claude Code session data
@@ -41,11 +121,17 @@ async function harvestPatterns(sessionTranscript) {
41
121
  return { skipped: true, reason: 'No MindMeld configuration' };
42
122
  }
43
123
 
124
+ // Load auth token and API config for authenticated API calls
125
+ const authToken = await loadAuthToken();
126
+ const apiConfig = await loadApiConfig();
127
+
44
128
  // Load MindmeldClient with graceful degradation
45
129
  const { MindmeldClient } = require('../src/index');
46
130
 
47
131
  const mindmeld = new MindmeldClient({
48
- projectPath: process.cwd()
132
+ projectPath: process.cwd(),
133
+ authToken: authToken,
134
+ apiUrl: apiConfig.apiUrl
49
135
  });
50
136
 
51
137
  // Extract session metadata
@@ -0,0 +1,292 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * MindMeld - Claude Code Session End Hook
4
+ *
5
+ * Records session completion and outcomes when a Claude Code session ends.
6
+ * Calls POST /api/sessions/end with session metadata.
7
+ *
8
+ * Input (stdin JSON from Claude Code):
9
+ * { session_id, transcript_path, cwd, reason, hook_event_name }
10
+ *
11
+ * @equilateral_ai/mindmeld v3.3.0
12
+ */
13
+
14
+ const path = require('path');
15
+ const fs = require('fs').promises;
16
+ const { execSync } = require('child_process');
17
+
18
+ /**
19
+ * Load auth token for API calls
20
+ * Priority: env var → project credentials.json → global ~/.mindmeld/auth.json
21
+ * @returns {Promise<string|null>} Auth token or null
22
+ */
23
+ async function loadAuthToken() {
24
+ // 1. Env var (highest priority)
25
+ if (process.env.MINDMELD_AUTH_TOKEN) {
26
+ return process.env.MINDMELD_AUTH_TOKEN;
27
+ }
28
+
29
+ // 2. Project-level credentials.json
30
+ try {
31
+ const credPath = path.join(process.cwd(), '.mindmeld', 'credentials.json');
32
+ const content = await fs.readFile(credPath, 'utf-8');
33
+ const creds = JSON.parse(content);
34
+ if (creds.auth_token || creds.token) {
35
+ return creds.auth_token || creds.token;
36
+ }
37
+ } catch (error) {
38
+ // No project-level credentials
39
+ }
40
+
41
+ // 3. Global ~/.mindmeld/auth.json (from browser login / mindmeld CLI)
42
+ try {
43
+ const { AuthManager } = require('../src/core/AuthManager');
44
+ const cognitoConfig = await loadCognitoConfig();
45
+ const authManager = new AuthManager(cognitoConfig);
46
+ const token = await authManager.getValidToken();
47
+ if (token) {
48
+ return token;
49
+ }
50
+ return null;
51
+ } catch (error) {
52
+ return null;
53
+ }
54
+ }
55
+
56
+ /**
57
+ * Load Cognito config from .myworld.json
58
+ * @returns {Promise<Object>} Cognito constructor options or empty object
59
+ */
60
+ async function loadCognitoConfig() {
61
+ try {
62
+ const configPath = path.join(process.cwd(), '.myworld.json');
63
+ const content = await fs.readFile(configPath, 'utf-8');
64
+ const config = JSON.parse(content);
65
+ const auth = config.deployments?.backend?.auth;
66
+ if (auth?.domain && auth?.client_id) {
67
+ return {
68
+ cognitoDomain: `${auth.domain}.auth.us-east-2.amazoncognito.com`,
69
+ cognitoClientId: auth.client_id
70
+ };
71
+ }
72
+ } catch (error) {
73
+ // No .myworld.json — use production defaults
74
+ }
75
+ return {};
76
+ }
77
+
78
+ /**
79
+ * Load API configuration from .myworld.json
80
+ * @returns {Promise<{apiUrl: string}>}
81
+ */
82
+ async function loadApiConfig() {
83
+ try {
84
+ const configPath = path.join(process.cwd(), '.myworld.json');
85
+ const content = await fs.readFile(configPath, 'utf-8');
86
+ const config = JSON.parse(content);
87
+ const backend = config.deployments?.backend;
88
+ return {
89
+ apiUrl: backend?.api?.base_url || 'https://api.mindmeld.dev'
90
+ };
91
+ } catch (error) {
92
+ return {
93
+ apiUrl: process.env.MINDMELD_API_URL || 'https://api.mindmeld.dev'
94
+ };
95
+ }
96
+ }
97
+
98
+ /**
99
+ * Get current git branch
100
+ * @param {string} cwd - Working directory
101
+ * @returns {string|null} Branch name or null
102
+ */
103
+ function getGitBranch(cwd) {
104
+ try {
105
+ return execSync('git rev-parse --abbrev-ref HEAD', {
106
+ cwd,
107
+ encoding: 'utf-8',
108
+ timeout: 2000
109
+ }).trim();
110
+ } catch (error) {
111
+ return null;
112
+ }
113
+ }
114
+
115
+ /**
116
+ * Estimate session duration from transcript file creation time
117
+ * @param {string} transcriptPath - Path to session transcript
118
+ * @returns {Promise<number|null>} Duration in seconds or null
119
+ */
120
+ async function estimateSessionDuration(transcriptPath) {
121
+ if (!transcriptPath) return null;
122
+ try {
123
+ const stat = await fs.stat(transcriptPath);
124
+ const createdAt = stat.birthtime || stat.ctime;
125
+ return Math.round((Date.now() - createdAt.getTime()) / 1000);
126
+ } catch (error) {
127
+ return null;
128
+ }
129
+ }
130
+
131
+ /**
132
+ * Load project ID from .mindmeld/config.json
133
+ * @returns {Promise<string|null>} Project ID or null
134
+ */
135
+ async function loadProjectId() {
136
+ try {
137
+ const configPath = path.join(process.cwd(), '.mindmeld', 'config.json');
138
+ const content = await fs.readFile(configPath, 'utf-8');
139
+ const config = JSON.parse(content);
140
+ return config.projectId || config.project_id || null;
141
+ } catch (error) {
142
+ return null;
143
+ }
144
+ }
145
+
146
+ /**
147
+ * Read hook input from stdin
148
+ * @returns {Promise<Object>} Parsed JSON input
149
+ */
150
+ function readStdin() {
151
+ return new Promise((resolve) => {
152
+ let data = '';
153
+ process.stdin.setEncoding('utf-8');
154
+ process.stdin.on('data', (chunk) => { data += chunk; });
155
+ process.stdin.on('end', () => {
156
+ try {
157
+ resolve(JSON.parse(data));
158
+ } catch (error) {
159
+ resolve({});
160
+ }
161
+ });
162
+ // If stdin is not piped, resolve immediately
163
+ if (process.stdin.isTTY) {
164
+ resolve({});
165
+ }
166
+ });
167
+ }
168
+
169
+ /**
170
+ * Main hook execution
171
+ * Records session end via API call (fire-and-forget)
172
+ */
173
+ async function recordSessionEnd() {
174
+ const startTime = Date.now();
175
+
176
+ try {
177
+ // Fast bail if MindMeld not configured
178
+ try {
179
+ await fs.access(path.join(process.cwd(), '.mindmeld', 'config.json'));
180
+ } catch (error) {
181
+ return { skipped: true, reason: 'No MindMeld configuration' };
182
+ }
183
+
184
+ // Read hook input from stdin
185
+ const input = await readStdin();
186
+ const sessionId = input.session_id;
187
+ const transcriptPath = input.transcript_path;
188
+ const cwd = input.cwd || process.cwd();
189
+ const reason = input.reason;
190
+
191
+ if (!sessionId) {
192
+ console.error('[MindMeld] session-end: No session_id provided');
193
+ return { skipped: true, reason: 'No session_id' };
194
+ }
195
+
196
+ // Load auth and config in parallel
197
+ const [authToken, apiConfig, projectId, duration, gitBranch] = await Promise.all([
198
+ loadAuthToken(),
199
+ loadApiConfig(),
200
+ loadProjectId(),
201
+ estimateSessionDuration(transcriptPath),
202
+ Promise.resolve(getGitBranch(cwd))
203
+ ]);
204
+
205
+ if (!authToken) {
206
+ console.error('[MindMeld] session-end: No auth token available, skipping');
207
+ return { skipped: true, reason: 'No auth token' };
208
+ }
209
+
210
+ // Build request payload
211
+ const payload = {
212
+ session_id: sessionId,
213
+ project_id: projectId,
214
+ user_id: process.env.USER || 'unknown',
215
+ duration_seconds: duration,
216
+ working_directory: cwd,
217
+ git_branch: gitBranch,
218
+ session_data: {
219
+ end_reason: reason,
220
+ hook_version: '3.3.0'
221
+ }
222
+ };
223
+
224
+ // Fire-and-forget API call with timeout
225
+ const url = `${apiConfig.apiUrl}/api/sessions/end`;
226
+ const http = require(url.startsWith('https') ? 'https' : 'http');
227
+ const urlObj = new URL(url);
228
+
229
+ const options = {
230
+ hostname: urlObj.hostname,
231
+ port: urlObj.port || (urlObj.protocol === 'https:' ? 443 : 80),
232
+ path: urlObj.pathname,
233
+ method: 'POST',
234
+ headers: {
235
+ 'Content-Type': 'application/json',
236
+ 'Authorization': `Bearer ${authToken}`
237
+ },
238
+ timeout: 3000
239
+ };
240
+
241
+ await new Promise((resolve) => {
242
+ const req = http.request(options, (res) => {
243
+ let body = '';
244
+ res.on('data', (chunk) => { body += chunk; });
245
+ res.on('end', () => {
246
+ const elapsed = Date.now() - startTime;
247
+ if (res.statusCode >= 200 && res.statusCode < 300) {
248
+ console.error(`[MindMeld] Session end recorded in ${elapsed}ms (session: ${sessionId}, reason: ${reason})`);
249
+ } else {
250
+ console.error(`[MindMeld] Session end API returned ${res.statusCode}: ${body}`);
251
+ }
252
+ resolve();
253
+ });
254
+ });
255
+
256
+ req.on('error', (err) => {
257
+ console.error(`[MindMeld] Session end API error (non-fatal): ${err.message}`);
258
+ resolve(); // Never block exit
259
+ });
260
+
261
+ req.on('timeout', () => {
262
+ req.destroy();
263
+ console.error('[MindMeld] Session end API timeout (non-fatal)');
264
+ resolve(); // Never block exit
265
+ });
266
+
267
+ req.write(JSON.stringify(payload));
268
+ req.end();
269
+ });
270
+
271
+ return {
272
+ sessionId,
273
+ reason,
274
+ duration,
275
+ elapsed: Date.now() - startTime
276
+ };
277
+
278
+ } catch (error) {
279
+ // Graceful degradation - never block session exit
280
+ console.error('[MindMeld] session-end hook error (non-fatal):', error.message);
281
+ return { error: error.message };
282
+ }
283
+ }
284
+
285
+ // Execute if called directly
286
+ if (require.main === module) {
287
+ recordSessionEnd()
288
+ .then(() => process.exit(0))
289
+ .catch(() => process.exit(0)); // Never block exit
290
+ }
291
+
292
+ module.exports = { recordSessionEnd };