@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.
- package/README.md +4 -4
- package/hooks/README.md +46 -4
- package/hooks/pre-compact.js +87 -1
- package/hooks/session-end.js +292 -0
- package/hooks/session-start.js +292 -23
- package/package.json +4 -2
- package/scripts/auth-login.js +53 -0
- package/scripts/init-project.js +69 -375
- package/src/core/AuthManager.js +498 -0
- package/src/core/CrossReferenceEngine.js +624 -0
- package/src/core/DeprecationScheduler.js +183 -0
- package/src/core/LLMPatternDetector.js +218 -0
- package/src/core/RapportOrchestrator.js +186 -0
- package/src/core/RelevanceDetector.js +32 -2
- package/src/core/StandardLifecycle.js +244 -0
- package/src/core/StandardsIngestion.js +341 -28
- package/src/core/parsers/adrParser.js +479 -0
- package/src/core/parsers/cursorRulesParser.js +564 -0
- package/src/core/parsers/eslintParser.js +439 -0
- package/src/handlers/alerts/alertsAcknowledge.js +4 -3
- package/src/handlers/analytics/activitySummaryGet.js +235 -0
- package/src/handlers/analytics/coachingGet.js +361 -0
- package/src/handlers/analytics/developerScoreGet.js +207 -0
- package/src/handlers/collaborators/collaboratorAdd.js +4 -5
- package/src/handlers/collaborators/collaboratorInvite.js +6 -5
- package/src/handlers/collaborators/collaboratorList.js +3 -3
- package/src/handlers/collaborators/collaboratorRemove.js +5 -4
- package/src/handlers/correlations/correlationsDeveloperGet.js +12 -11
- package/src/handlers/correlations/correlationsGet.js +1 -1
- package/src/handlers/correlations/correlationsProjectGet.js +7 -6
- package/src/handlers/enterprise/enterpriseAuditGet.js +108 -0
- package/src/handlers/enterprise/enterpriseContributorsGet.js +85 -0
- package/src/handlers/enterprise/enterpriseKnowledgeCategoriesGet.js +53 -0
- package/src/handlers/enterprise/enterpriseKnowledgeCreate.js +77 -0
- package/src/handlers/enterprise/enterpriseKnowledgeDelete.js +71 -0
- package/src/handlers/enterprise/enterpriseKnowledgeGet.js +87 -0
- package/src/handlers/enterprise/enterpriseKnowledgeUpdate.js +122 -0
- package/src/handlers/enterprise/enterpriseOnboardingComplete.js +77 -0
- package/src/handlers/enterprise/enterpriseOnboardingInvite.js +138 -0
- package/src/handlers/enterprise/enterpriseOnboardingSetup.js +89 -0
- package/src/handlers/enterprise/enterpriseOnboardingStatus.js +90 -0
- package/src/handlers/github/githubConnectionStatus.js +1 -1
- package/src/handlers/github/githubDiscoverPatterns.js +264 -5
- package/src/handlers/github/githubOAuthCallback.js +14 -2
- package/src/handlers/github/githubOAuthStart.js +1 -1
- package/src/handlers/github/githubPatternsReview.js +1 -1
- package/src/handlers/github/githubReposList.js +1 -1
- package/src/handlers/helpers/auditLogger.js +201 -0
- package/src/handlers/helpers/index.js +19 -1
- package/src/handlers/helpers/lambdaWrapper.js +1 -1
- package/src/handlers/notifications/sendNotification.js +1 -1
- package/src/handlers/projects/projectCreate.js +28 -1
- package/src/handlers/projects/projectDelete.js +3 -3
- package/src/handlers/projects/projectUpdate.js +4 -5
- package/src/handlers/scheduled/analyzeCorrelations.js +3 -3
- package/src/handlers/scheduled/generateAlerts.js +1 -1
- package/src/handlers/standards/catalogGet.js +185 -0
- package/src/handlers/standards/catalogSync.js +120 -0
- package/src/handlers/standards/projectStandardsGet.js +135 -0
- package/src/handlers/standards/projectStandardsPut.js +131 -0
- package/src/handlers/standards/standardsAuditGet.js +65 -0
- package/src/handlers/standards/standardsParseUpload.js +153 -0
- package/src/handlers/standards/standardsRelevantPost.js +213 -0
- package/src/handlers/standards/standardsTransition.js +64 -0
- package/src/handlers/user/userSplashAck.js +91 -0
- package/src/handlers/user/userSplashGet.js +194 -0
- package/src/handlers/users/userProfilePut.js +77 -0
- 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
|
-
-
|
|
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/
|
|
243
|
-
-
|
|
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
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
434
|
+
├── pre-compact.js
|
|
435
|
+
│ ├── harvestPatterns()
|
|
436
|
+
│ ├── validatePatterns()
|
|
437
|
+
│ └── checkPromotionCandidates()
|
|
438
|
+
│
|
|
439
|
+
└── session-end.js
|
|
440
|
+
└── recordSessionEnd()
|
|
399
441
|
|
|
400
442
|
↓ Uses ↓
|
|
401
443
|
|
package/hooks/pre-compact.js
CHANGED
|
@@ -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 };
|