@equilateral_ai/mindmeld 3.5.2 → 4.0.1
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/hooks/session-end.js +25 -0
- package/hooks/session-start.js +363 -83
- package/hooks/session-watcher.js +585 -0
- package/package.json +19 -13
- package/scripts/init-project.js +9 -23
- package/src/client/dbShim.js +16 -0
- package/src/core/AuthManager.js +3 -2
- package/src/handlers/helpers/dbOperations.js +9 -46
- package/src/index.js +2 -217
- package/src/utils/piiMask.js +16 -0
- package/scripts/harvest.js +0 -601
- package/scripts/inject.js +0 -409
- package/scripts/mcp-bridge.js +0 -220
- package/scripts/repo-analyzer.js +0 -870
- package/src/collaboration/CollaborationPrompt.js +0 -460
- package/src/core/AlertEngine.js +0 -813
- package/src/core/AlertNotifier.js +0 -363
- package/src/core/CorrelationAnalyzer.js +0 -931
- package/src/core/CrossReferenceEngine.js +0 -624
- package/src/core/CurationEngine.js +0 -688
- package/src/core/DeprecationScheduler.js +0 -183
- package/src/core/LoadBearingDetector.js +0 -242
- package/src/core/NotificationService.js +0 -1032
- package/src/core/RapportOrchestrator.js +0 -632
- package/src/core/RelevanceDetector.js +0 -694
- package/src/core/StandardLifecycle.js +0 -244
- package/src/core/StandardsIngestion.js +0 -991
- package/src/core/TeamLoadBearingDetector.js +0 -431
- package/src/core/parsers/adrParser.js +0 -479
- package/src/core/parsers/cursorRulesParser.js +0 -564
- package/src/core/parsers/eslintParser.js +0 -439
- package/src/database/dbOperations.js +0 -105
- package/src/handlers/activity/activityGetMe.js +0 -98
- package/src/handlers/activity/activityGetTeam.js +0 -175
- package/src/handlers/admin/adminSetup.js +0 -216
- package/src/handlers/alerts/alertsAcknowledge.js +0 -92
- package/src/handlers/alerts/alertsGet.js +0 -250
- package/src/handlers/analytics/activitySummaryGet.js +0 -234
- package/src/handlers/analytics/coachingGet.js +0 -361
- package/src/handlers/analytics/convergenceGet.js +0 -236
- package/src/handlers/analytics/developerScoreGet.js +0 -137
- package/src/handlers/collaborators/collaboratorAdd.js +0 -200
- package/src/handlers/collaborators/collaboratorInvite.js +0 -219
- package/src/handlers/collaborators/collaboratorList.js +0 -82
- package/src/handlers/collaborators/collaboratorRemove.js +0 -128
- package/src/handlers/collaborators/inviteAccept.js +0 -122
- package/src/handlers/company/companyUsersDelete.js +0 -141
- package/src/handlers/company/companyUsersGet.js +0 -90
- package/src/handlers/company/companyUsersPost.js +0 -267
- package/src/handlers/company/companyUsersPut.js +0 -76
- package/src/handlers/context/contextGet.js +0 -57
- package/src/handlers/context/invariantsGet.js +0 -74
- package/src/handlers/context/loopsGet.js +0 -82
- package/src/handlers/context/notesCreate.js +0 -74
- package/src/handlers/context/purposeGet.js +0 -78
- package/src/handlers/correlations/correlationsDeveloperGet.js +0 -227
- package/src/handlers/correlations/correlationsGet.js +0 -93
- package/src/handlers/correlations/correlationsProjectGet.js +0 -153
- package/src/handlers/enterprise/controlTowerGet.js +0 -224
- package/src/handlers/enterprise/enterpriseAuditGet.js +0 -108
- package/src/handlers/enterprise/enterpriseContributorsGet.js +0 -85
- package/src/handlers/enterprise/enterpriseKnowledgeCategoriesGet.js +0 -53
- package/src/handlers/enterprise/enterpriseKnowledgeCreate.js +0 -77
- package/src/handlers/enterprise/enterpriseKnowledgeDelete.js +0 -71
- package/src/handlers/enterprise/enterpriseKnowledgeGet.js +0 -87
- package/src/handlers/enterprise/enterpriseKnowledgeUpdate.js +0 -122
- package/src/handlers/enterprise/enterpriseOnboardingComplete.js +0 -77
- package/src/handlers/enterprise/enterpriseOnboardingInvite.js +0 -138
- package/src/handlers/enterprise/enterpriseOnboardingSetup.js +0 -128
- package/src/handlers/enterprise/enterpriseOnboardingStatus.js +0 -88
- package/src/handlers/github/githubConnectionStatus.js +0 -49
- package/src/handlers/github/githubDiscoverPatterns.js +0 -621
- package/src/handlers/github/githubOAuthCallback.js +0 -178
- package/src/handlers/github/githubOAuthStart.js +0 -59
- package/src/handlers/github/githubPatternsReview.js +0 -76
- package/src/handlers/github/githubReposList.js +0 -105
- package/src/handlers/health/healthGet.js +0 -55
- package/src/handlers/helpers/auditLogger.js +0 -201
- package/src/handlers/helpers/checkSuperAdmin.js +0 -84
- package/src/handlers/helpers/decisionFrames.js +0 -29
- package/src/handlers/helpers/errorHandler.js +0 -49
- package/src/handlers/helpers/index.js +0 -138
- package/src/handlers/helpers/lambdaWrapper.js +0 -60
- package/src/handlers/helpers/mindmeldMcpCore.js +0 -1103
- package/src/handlers/helpers/predictiveCache.js +0 -51
- package/src/handlers/helpers/projectAccess.js +0 -88
- package/src/handlers/helpers/responseUtil.js +0 -55
- package/src/handlers/helpers/subscriptionTiers.js +0 -1168
- package/src/handlers/mcp/mcpHandler.js +0 -569
- package/src/handlers/mcp/mindmeldMcpHandler.js +0 -124
- package/src/handlers/mcp/mindmeldMcpStreamHandler.js +0 -342
- package/src/handlers/notifications/getPreferences.js +0 -84
- package/src/handlers/notifications/sendNotification.js +0 -170
- package/src/handlers/notifications/updatePreferences.js +0 -316
- package/src/handlers/patterns/patternEvaluatePromotionPost.js +0 -173
- package/src/handlers/patterns/patternUsagePost.js +0 -182
- package/src/handlers/patterns/patternViolationPost.js +0 -185
- package/src/handlers/projects/projectCreate.js +0 -248
- package/src/handlers/projects/projectDelete.js +0 -82
- package/src/handlers/projects/projectGet.js +0 -95
- package/src/handlers/projects/projectUpdate.js +0 -117
- package/src/handlers/reports/aiLeverage.js +0 -210
- package/src/handlers/reports/engineeringInvestment.js +0 -132
- package/src/handlers/reports/riskForecast.js +0 -206
- package/src/handlers/reports/standardsRoi.js +0 -254
- package/src/handlers/scheduled/analyzeCorrelations.js +0 -178
- package/src/handlers/scheduled/analyzeGitHistory.js +0 -510
- package/src/handlers/scheduled/generateAlerts.js +0 -135
- package/src/handlers/scheduled/maturityUpdateJob.js +0 -166
- package/src/handlers/scheduled/refreshActivity.js +0 -21
- package/src/handlers/scheduled/scanCompliance.js +0 -334
- package/src/handlers/sessions/sessionEndPost.js +0 -180
- package/src/handlers/sessions/sessionStandardsPost.js +0 -171
- package/src/handlers/standards/catalogGet.js +0 -185
- package/src/handlers/standards/catalogSync.js +0 -120
- package/src/handlers/standards/discoveriesGet.js +0 -89
- package/src/handlers/standards/projectStandardsGet.js +0 -129
- package/src/handlers/standards/projectStandardsPut.js +0 -151
- package/src/handlers/standards/standardsAuditGet.js +0 -65
- package/src/handlers/standards/standardsParseUpload.js +0 -149
- package/src/handlers/standards/standardsRelevantPost.js +0 -405
- package/src/handlers/standards/standardsTransition.js +0 -161
- package/src/handlers/stripe/addonManagePost.js +0 -240
- package/src/handlers/stripe/billingPortalPost.js +0 -93
- package/src/handlers/stripe/enterpriseCheckoutPost.js +0 -272
- package/src/handlers/stripe/seatsUpdatePost.js +0 -185
- package/src/handlers/stripe/subscriptionCancelDelete.js +0 -169
- package/src/handlers/stripe/subscriptionCreatePost.js +0 -221
- package/src/handlers/stripe/subscriptionUpdatePut.js +0 -163
- package/src/handlers/stripe/webhookPost.js +0 -482
- package/src/handlers/user/apiTokenCreate.js +0 -71
- package/src/handlers/user/apiTokenList.js +0 -64
- package/src/handlers/user/userSplashAck.js +0 -91
- package/src/handlers/user/userSplashGet.js +0 -211
- package/src/handlers/users/cognitoPostConfirmation.js +0 -186
- package/src/handlers/users/cognitoPreSignUp.js +0 -114
- package/src/handlers/users/userEntitlementsGet.js +0 -89
- package/src/handlers/users/userGet.js +0 -118
- package/src/handlers/users/userProfilePut.js +0 -77
- package/src/handlers/webhooks/githubWebhook.js +0 -215
package/scripts/harvest.js
DELETED
|
@@ -1,601 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
/**
|
|
3
|
-
* MindMeld CLI - Manually harvest patterns from recent git history
|
|
4
|
-
*
|
|
5
|
-
* For non-Claude-Code tools (Cursor, Aider, Ollama, etc.) that don't have
|
|
6
|
-
* a pre-compact hook. Analyzes recent commits to detect patterns.
|
|
7
|
-
*
|
|
8
|
-
* Usage:
|
|
9
|
-
* mindmeld harvest # Analyze last 10 commits
|
|
10
|
-
* mindmeld harvest --since 7d # Last 7 days
|
|
11
|
-
* mindmeld harvest --commits 20 # Last 20 commits
|
|
12
|
-
*
|
|
13
|
-
* @equilateral_ai/mindmeld v3.0.0
|
|
14
|
-
*/
|
|
15
|
-
|
|
16
|
-
const path = require('path');
|
|
17
|
-
const fs = require('fs').promises;
|
|
18
|
-
const { exec } = require('child_process');
|
|
19
|
-
const { promisify } = require('util');
|
|
20
|
-
|
|
21
|
-
const execAsync = promisify(exec);
|
|
22
|
-
|
|
23
|
-
function showHarvestHelp() {
|
|
24
|
-
console.log(`
|
|
25
|
-
MindMeld harvest - Capture patterns from recent git history
|
|
26
|
-
|
|
27
|
-
Usage:
|
|
28
|
-
mindmeld harvest [options]
|
|
29
|
-
|
|
30
|
-
Options:
|
|
31
|
-
--since <period> Time period: 1d, 7d, 30d (default: 7d)
|
|
32
|
-
--commits <n> Number of commits to analyze (default: 10)
|
|
33
|
-
--path <dir> Project path (default: current directory)
|
|
34
|
-
--dry-run Show what would be captured without saving
|
|
35
|
-
--help, -h Show this help
|
|
36
|
-
|
|
37
|
-
Examples:
|
|
38
|
-
mindmeld harvest # Analyze last 7 days
|
|
39
|
-
mindmeld harvest --since 1d # Just today's commits
|
|
40
|
-
mindmeld harvest --commits 20 # Last 20 commits
|
|
41
|
-
mindmeld harvest --dry-run # Preview without saving
|
|
42
|
-
|
|
43
|
-
What it does:
|
|
44
|
-
1. Reads recent git diffs and commit messages
|
|
45
|
-
2. Detects repeated patterns (file types, naming, structure)
|
|
46
|
-
3. Identifies correction patterns (reverts, fixes, amendments)
|
|
47
|
-
4. Records detected patterns in .mindmeld/patterns/
|
|
48
|
-
5. Patterns with enough repetition get promoted to standards
|
|
49
|
-
`);
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
/**
|
|
53
|
-
* Convert shorthand period (7d, 30d, 2w) to git-compatible format
|
|
54
|
-
*/
|
|
55
|
-
function parseGitSince(since) {
|
|
56
|
-
const match = since.match(/^(\d+)(d|w|m)$/);
|
|
57
|
-
if (!match) return since; // pass through if already in git format
|
|
58
|
-
const [, num, unit] = match;
|
|
59
|
-
const units = { d: 'days', w: 'weeks', m: 'months' };
|
|
60
|
-
return `${num} ${units[unit] || 'days'} ago`;
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
/**
|
|
64
|
-
* Get recent git activity
|
|
65
|
-
*/
|
|
66
|
-
async function getGitHistory(projectPath, options = {}) {
|
|
67
|
-
const since = parseGitSince(options.since || '7d');
|
|
68
|
-
const maxCommits = options.commits || 10;
|
|
69
|
-
|
|
70
|
-
// Get commit log with diffs
|
|
71
|
-
try {
|
|
72
|
-
const { stdout: log } = await execAsync(
|
|
73
|
-
`git log --since="${since}" -n ${maxCommits} --format="%H|%s|%an|%ai" --stat`,
|
|
74
|
-
{ cwd: projectPath, maxBuffer: 10 * 1024 * 1024 }
|
|
75
|
-
);
|
|
76
|
-
|
|
77
|
-
// For large repos, limit diff output to avoid buffer overflow
|
|
78
|
-
let diff = '';
|
|
79
|
-
try {
|
|
80
|
-
const { stdout } = await execAsync(
|
|
81
|
-
`git log --since="${since}" -n ${maxCommits} -p --diff-filter=M -- '*.js' '*.ts' '*.py' '*.java' '*.go' '*.rs'`,
|
|
82
|
-
{ cwd: projectPath, maxBuffer: 20 * 1024 * 1024 }
|
|
83
|
-
);
|
|
84
|
-
diff = stdout;
|
|
85
|
-
} catch (diffError) {
|
|
86
|
-
// If diff is too large, try with fewer commits
|
|
87
|
-
if (diffError.message.includes('maxBuffer')) {
|
|
88
|
-
const { stdout } = await execAsync(
|
|
89
|
-
`git log --since="${since}" -n 10 -p --diff-filter=M -- '*.js' '*.ts'`,
|
|
90
|
-
{ cwd: projectPath, maxBuffer: 10 * 1024 * 1024 }
|
|
91
|
-
);
|
|
92
|
-
diff = stdout;
|
|
93
|
-
}
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
return { log, diff };
|
|
97
|
-
} catch (error) {
|
|
98
|
-
if (error.message.includes('not a git repository')) {
|
|
99
|
-
throw new Error('not a git repository');
|
|
100
|
-
}
|
|
101
|
-
throw error;
|
|
102
|
-
}
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
/**
|
|
106
|
-
* Detect patterns from git history using regex analysis
|
|
107
|
-
*/
|
|
108
|
-
function detectPatterns(gitHistory) {
|
|
109
|
-
const patterns = [];
|
|
110
|
-
const { log, diff } = gitHistory;
|
|
111
|
-
|
|
112
|
-
// 1. Detect fix/correction patterns (commits that fix previous work)
|
|
113
|
-
const fixPatterns = log.match(/fix[:\s]|correct[:\s]|revert[:\s]|wrong|mistake|should be|instead of/gi) || [];
|
|
114
|
-
if (fixPatterns.length >= 2) {
|
|
115
|
-
patterns.push({
|
|
116
|
-
type: 'correction',
|
|
117
|
-
element: 'Repeated corrections detected',
|
|
118
|
-
evidence: `${fixPatterns.length} fix/correction commits in recent history`,
|
|
119
|
-
confidence: Math.min(0.9, fixPatterns.length * 0.15),
|
|
120
|
-
category: 'workflow'
|
|
121
|
-
});
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
// 2. Detect file type patterns (what's being edited most)
|
|
125
|
-
const fileExtensions = {};
|
|
126
|
-
const fileMatches = log.match(/\S+\.\w+/g) || [];
|
|
127
|
-
for (const file of fileMatches) {
|
|
128
|
-
const ext = path.extname(file);
|
|
129
|
-
if (ext && ext.length < 6) {
|
|
130
|
-
fileExtensions[ext] = (fileExtensions[ext] || 0) + 1;
|
|
131
|
-
}
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
// 3. Detect code patterns from diffs
|
|
135
|
-
const codePatterns = [];
|
|
136
|
-
|
|
137
|
-
// Connection pool vs single client
|
|
138
|
-
if (diff.includes('createPool') || diff.includes('Pool(')) {
|
|
139
|
-
codePatterns.push({
|
|
140
|
-
type: 'anti-pattern',
|
|
141
|
-
element: 'Connection pool usage detected',
|
|
142
|
-
evidence: 'createPool or Pool() found in diffs',
|
|
143
|
-
confidence: 0.7,
|
|
144
|
-
category: 'database'
|
|
145
|
-
});
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
// Console.log removal (cleanup pattern)
|
|
149
|
-
const consoleAdds = (diff.match(/^\+.*console\.log/gm) || []).length;
|
|
150
|
-
const consoleRemoves = (diff.match(/^-.*console\.log/gm) || []).length;
|
|
151
|
-
if (consoleRemoves > consoleAdds && consoleRemoves >= 3) {
|
|
152
|
-
codePatterns.push({
|
|
153
|
-
type: 'pattern',
|
|
154
|
-
element: 'Console.log cleanup pattern',
|
|
155
|
-
evidence: `${consoleRemoves} console.log removals vs ${consoleAdds} additions`,
|
|
156
|
-
confidence: 0.6,
|
|
157
|
-
category: 'code-quality'
|
|
158
|
-
});
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
// Error handling patterns
|
|
162
|
-
const tryCatchAdds = (diff.match(/^\+.*try\s*\{/gm) || []).length;
|
|
163
|
-
if (tryCatchAdds >= 3) {
|
|
164
|
-
codePatterns.push({
|
|
165
|
-
type: 'pattern',
|
|
166
|
-
element: 'Error handling additions',
|
|
167
|
-
evidence: `${tryCatchAdds} try/catch blocks added`,
|
|
168
|
-
confidence: 0.5,
|
|
169
|
-
category: 'error-handling'
|
|
170
|
-
});
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
// Type annotation additions (TypeScript patterns)
|
|
174
|
-
const typeAnnotations = (diff.match(/^\+.*:\s*(string|number|boolean|any|void|Promise|Array)/gm) || []).length;
|
|
175
|
-
if (typeAnnotations >= 5) {
|
|
176
|
-
codePatterns.push({
|
|
177
|
-
type: 'pattern',
|
|
178
|
-
element: 'Type annotations being added',
|
|
179
|
-
evidence: `${typeAnnotations} type annotations in recent diffs`,
|
|
180
|
-
confidence: 0.6,
|
|
181
|
-
category: 'typescript'
|
|
182
|
-
});
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
// Import organization patterns
|
|
186
|
-
const importChanges = (diff.match(/^\+\s*import\s/gm) || []).length;
|
|
187
|
-
if (importChanges >= 5) {
|
|
188
|
-
codePatterns.push({
|
|
189
|
-
type: 'pattern',
|
|
190
|
-
element: 'Import reorganization',
|
|
191
|
-
evidence: `${importChanges} import statements modified`,
|
|
192
|
-
confidence: 0.4,
|
|
193
|
-
category: 'code-organization'
|
|
194
|
-
});
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
patterns.push(...codePatterns);
|
|
198
|
-
|
|
199
|
-
return patterns;
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
/**
|
|
203
|
-
* Save detected patterns to .mindmeld/patterns/
|
|
204
|
-
*/
|
|
205
|
-
async function savePatterns(projectPath, patterns) {
|
|
206
|
-
const patternsDir = path.join(projectPath, '.mindmeld', 'patterns');
|
|
207
|
-
await fs.mkdir(patternsDir, { recursive: true });
|
|
208
|
-
|
|
209
|
-
const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
|
|
210
|
-
const harvestFile = path.join(patternsDir, `harvest-${timestamp}.json`);
|
|
211
|
-
|
|
212
|
-
const harvest = {
|
|
213
|
-
timestamp: new Date().toISOString(),
|
|
214
|
-
source: 'manual-harvest',
|
|
215
|
-
patterns: patterns,
|
|
216
|
-
projectPath: projectPath,
|
|
217
|
-
projectName: path.basename(projectPath)
|
|
218
|
-
};
|
|
219
|
-
|
|
220
|
-
await fs.writeFile(harvestFile, JSON.stringify(harvest, null, 2));
|
|
221
|
-
return harvestFile;
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
/**
|
|
225
|
-
* Main harvest execution
|
|
226
|
-
*/
|
|
227
|
-
async function harvest(options = {}) {
|
|
228
|
-
const projectPath = options.path || process.cwd();
|
|
229
|
-
const dryRun = options.dryRun || false;
|
|
230
|
-
|
|
231
|
-
console.error(`[MindMeld] Harvesting patterns from: ${projectPath}`);
|
|
232
|
-
console.error(`[MindMeld] Period: last ${options.since || '7d'}, max ${options.commits || 10} commits`);
|
|
233
|
-
console.error('');
|
|
234
|
-
|
|
235
|
-
// 1. Get git history
|
|
236
|
-
let gitHistory;
|
|
237
|
-
try {
|
|
238
|
-
gitHistory = await getGitHistory(projectPath, {
|
|
239
|
-
since: options.since || '7d',
|
|
240
|
-
commits: options.commits || 10
|
|
241
|
-
});
|
|
242
|
-
} catch (error) {
|
|
243
|
-
if (error.message === 'not a git repository') {
|
|
244
|
-
console.error('[MindMeld] Not a git repository. Harvest requires git history.');
|
|
245
|
-
process.exit(1);
|
|
246
|
-
}
|
|
247
|
-
throw error;
|
|
248
|
-
}
|
|
249
|
-
|
|
250
|
-
// 2. Detect patterns
|
|
251
|
-
const patterns = detectPatterns(gitHistory);
|
|
252
|
-
|
|
253
|
-
if (patterns.length === 0) {
|
|
254
|
-
console.error('[MindMeld] No patterns detected in recent history.');
|
|
255
|
-
console.error('[MindMeld] Try a longer period: mindmeld harvest --since 30d');
|
|
256
|
-
return;
|
|
257
|
-
}
|
|
258
|
-
|
|
259
|
-
// 3. Display results
|
|
260
|
-
console.error(`[MindMeld] Detected ${patterns.length} pattern(s):\n`);
|
|
261
|
-
for (const pattern of patterns) {
|
|
262
|
-
const confidence = (pattern.confidence * 100).toFixed(0);
|
|
263
|
-
console.error(` [${pattern.type}] ${pattern.element}`);
|
|
264
|
-
console.error(` Category: ${pattern.category} | Confidence: ${confidence}%`);
|
|
265
|
-
console.error(` Evidence: ${pattern.evidence}`);
|
|
266
|
-
console.error('');
|
|
267
|
-
}
|
|
268
|
-
|
|
269
|
-
// 4. Save (unless dry-run)
|
|
270
|
-
if (!dryRun) {
|
|
271
|
-
const savedPath = await savePatterns(projectPath, patterns);
|
|
272
|
-
console.error(`[MindMeld] Patterns saved to: ${path.relative(projectPath, savedPath)}`);
|
|
273
|
-
console.error(`[MindMeld] Patterns with 5+ detections will be promoted to provisional standards.`);
|
|
274
|
-
} else {
|
|
275
|
-
console.error('[MindMeld] Dry run - no patterns saved.');
|
|
276
|
-
}
|
|
277
|
-
|
|
278
|
-
// 5. Try LLM-powered detection if available
|
|
279
|
-
try {
|
|
280
|
-
const llmModule = require('../src/core/LLMPatternDetector');
|
|
281
|
-
if (llmModule && process.env.MINDMELD_USE_LLM !== 'false') {
|
|
282
|
-
console.error('\n[MindMeld] LLM-powered detection available for deeper analysis.');
|
|
283
|
-
console.error('[MindMeld] Set MINDMELD_USE_LLM=true for semantic pattern detection.');
|
|
284
|
-
}
|
|
285
|
-
} catch (error) {
|
|
286
|
-
// Expected: LLM module not available
|
|
287
|
-
if (error.code !== 'MODULE_NOT_FOUND') {
|
|
288
|
-
console.error('Unexpected error loading LLM module:', error.message);
|
|
289
|
-
}
|
|
290
|
-
}
|
|
291
|
-
}
|
|
292
|
-
|
|
293
|
-
/**
|
|
294
|
-
* Promote high-confidence patterns to local staging
|
|
295
|
-
* Writes to .mindmeld/patterns/{category}/ (local, never directly to community repo)
|
|
296
|
-
* Community promotion happens via API → PR to EquilateralAgents-Community-Standards
|
|
297
|
-
*/
|
|
298
|
-
async function promotePatterns(projectPath, patterns, options = {}) {
|
|
299
|
-
const threshold = options.threshold || 0.5;
|
|
300
|
-
const standardsDir = path.join(projectPath, '.mindmeld', 'patterns');
|
|
301
|
-
|
|
302
|
-
const eligible = patterns.filter(p => p.confidence >= threshold);
|
|
303
|
-
if (eligible.length === 0) return [];
|
|
304
|
-
|
|
305
|
-
await fs.mkdir(standardsDir, { recursive: true });
|
|
306
|
-
|
|
307
|
-
const promoted = [];
|
|
308
|
-
|
|
309
|
-
for (const pattern of eligible) {
|
|
310
|
-
const categoryDir = path.join(standardsDir, pattern.category);
|
|
311
|
-
await fs.mkdir(categoryDir, { recursive: true });
|
|
312
|
-
|
|
313
|
-
const filename = pattern.element
|
|
314
|
-
.toLowerCase()
|
|
315
|
-
.replace(/[^a-z0-9]+/g, '_')
|
|
316
|
-
.replace(/^_|_$/g, '') + '.md';
|
|
317
|
-
|
|
318
|
-
const filePath = path.join(categoryDir, filename);
|
|
319
|
-
|
|
320
|
-
// Don't overwrite existing standards
|
|
321
|
-
try {
|
|
322
|
-
await fs.access(filePath);
|
|
323
|
-
continue;
|
|
324
|
-
} catch (error) {
|
|
325
|
-
// Expected: file doesn't exist, safe to create
|
|
326
|
-
if (error.code !== 'ENOENT') {
|
|
327
|
-
console.error(`Unexpected error checking ${filePath}:`, error.message);
|
|
328
|
-
}
|
|
329
|
-
}
|
|
330
|
-
|
|
331
|
-
const content = [
|
|
332
|
-
`# ${pattern.element}`,
|
|
333
|
-
'',
|
|
334
|
-
`**Status**: provisional`,
|
|
335
|
-
`**Category**: ${pattern.category}`,
|
|
336
|
-
`**Confidence**: ${(pattern.confidence * 100).toFixed(0)}%`,
|
|
337
|
-
`**Source**: git-harvest`,
|
|
338
|
-
`**Detected**: ${new Date().toISOString().split('T')[0]}`,
|
|
339
|
-
'',
|
|
340
|
-
'## Evidence',
|
|
341
|
-
'',
|
|
342
|
-
`- ${pattern.evidence}`,
|
|
343
|
-
'',
|
|
344
|
-
'## Guidance',
|
|
345
|
-
'',
|
|
346
|
-
pattern.type === 'anti-pattern'
|
|
347
|
-
? `- Avoid: ${pattern.element}`
|
|
348
|
-
: `- Follow: ${pattern.element}`,
|
|
349
|
-
'',
|
|
350
|
-
'---',
|
|
351
|
-
'*Provisional standard - will be promoted to solidified after 5+ consistent detections.*'
|
|
352
|
-
].join('\n');
|
|
353
|
-
|
|
354
|
-
await fs.writeFile(filePath, content);
|
|
355
|
-
promoted.push({ pattern, path: filePath });
|
|
356
|
-
}
|
|
357
|
-
|
|
358
|
-
return promoted;
|
|
359
|
-
}
|
|
360
|
-
|
|
361
|
-
/**
|
|
362
|
-
* Harvest decisions from Claude Code plan files
|
|
363
|
-
* Matches plans to project by correlating session timestamps with plan file mtimes
|
|
364
|
-
*/
|
|
365
|
-
async function harvestPlans(projectPath) {
|
|
366
|
-
const os = require('os');
|
|
367
|
-
const homeDir = os.homedir();
|
|
368
|
-
const plansDir = path.join(homeDir, '.claude', 'plans');
|
|
369
|
-
const projectsDir = path.join(homeDir, '.claude', 'projects');
|
|
370
|
-
|
|
371
|
-
// Encode project path the same way Claude Code does
|
|
372
|
-
const encodedPath = projectPath.replace(/\//g, '-');
|
|
373
|
-
const sessionIndexPath = path.join(projectsDir, encodedPath, 'sessions-index.json');
|
|
374
|
-
|
|
375
|
-
// 1. Load sessions for this project
|
|
376
|
-
let sessions;
|
|
377
|
-
try {
|
|
378
|
-
const indexContent = await fs.readFile(sessionIndexPath, 'utf-8');
|
|
379
|
-
const index = JSON.parse(indexContent);
|
|
380
|
-
sessions = index.entries || [];
|
|
381
|
-
} catch (error) {
|
|
382
|
-
// Expected: no sessions file
|
|
383
|
-
if (error.code !== 'ENOENT' && !(error instanceof SyntaxError)) {
|
|
384
|
-
console.error('Unexpected error reading sessions:', error.message);
|
|
385
|
-
}
|
|
386
|
-
return []; // No sessions found for this project
|
|
387
|
-
}
|
|
388
|
-
|
|
389
|
-
if (sessions.length === 0) return [];
|
|
390
|
-
|
|
391
|
-
// 2. Get time window from sessions (earliest created → now, since current session may not be indexed)
|
|
392
|
-
const sessionTimes = sessions.map(s => new Date(s.created).getTime());
|
|
393
|
-
const windowStart = Math.min(...sessionTimes);
|
|
394
|
-
const windowEnd = Date.now();
|
|
395
|
-
|
|
396
|
-
// 3. Find plan files within the session time window
|
|
397
|
-
let planFiles;
|
|
398
|
-
try {
|
|
399
|
-
const entries = await fs.readdir(plansDir);
|
|
400
|
-
planFiles = [];
|
|
401
|
-
for (const entry of entries) {
|
|
402
|
-
if (!entry.endsWith('.md')) continue;
|
|
403
|
-
const filePath = path.join(plansDir, entry);
|
|
404
|
-
const stat = await fs.stat(filePath);
|
|
405
|
-
const mtime = stat.mtimeMs;
|
|
406
|
-
if (mtime >= windowStart && mtime <= windowEnd) {
|
|
407
|
-
planFiles.push({ path: filePath, name: entry, mtime });
|
|
408
|
-
}
|
|
409
|
-
}
|
|
410
|
-
} catch (error) {
|
|
411
|
-
// Expected: no plans directory
|
|
412
|
-
if (error.code !== 'ENOENT') {
|
|
413
|
-
console.error('Unexpected error reading plans:', error.message);
|
|
414
|
-
}
|
|
415
|
-
return []; // No plans directory
|
|
416
|
-
}
|
|
417
|
-
|
|
418
|
-
// 4. Content-match: only keep plans that reference files/terms from this project
|
|
419
|
-
const projectName = path.basename(projectPath).toLowerCase();
|
|
420
|
-
const relevantPlans = [];
|
|
421
|
-
|
|
422
|
-
for (const planFile of planFiles) {
|
|
423
|
-
try {
|
|
424
|
-
const content = await fs.readFile(planFile.path, 'utf-8');
|
|
425
|
-
const contentLower = content.toLowerCase();
|
|
426
|
-
|
|
427
|
-
// Score relevance by checking project indicators
|
|
428
|
-
let relevance = 0;
|
|
429
|
-
|
|
430
|
-
// Project name match (strongest signal)
|
|
431
|
-
if (contentLower.includes(projectName)) relevance += 5;
|
|
432
|
-
|
|
433
|
-
// Check if plan mentions files that exist in this project
|
|
434
|
-
const fileRefs = content.match(/`([^`]+\.(js|ts|json|yaml|yml|py|sql))`/g) || [];
|
|
435
|
-
for (const ref of fileRefs.slice(0, 10)) {
|
|
436
|
-
const cleanRef = ref.replace(/`/g, '');
|
|
437
|
-
try {
|
|
438
|
-
await fs.access(path.join(projectPath, cleanRef));
|
|
439
|
-
relevance += 3; // File exists in this project
|
|
440
|
-
} catch (error) {
|
|
441
|
-
// Expected: file doesn't exist here
|
|
442
|
-
if (error.code !== 'ENOENT') {
|
|
443
|
-
console.error(`Unexpected error checking ${cleanRef}:`, error.message);
|
|
444
|
-
}
|
|
445
|
-
}
|
|
446
|
-
}
|
|
447
|
-
|
|
448
|
-
// Check for project-specific paths
|
|
449
|
-
if (content.includes(projectPath)) relevance += 10;
|
|
450
|
-
|
|
451
|
-
if (relevance >= 3) {
|
|
452
|
-
planFile.relevance = relevance;
|
|
453
|
-
planFile.content = content;
|
|
454
|
-
relevantPlans.push(planFile);
|
|
455
|
-
}
|
|
456
|
-
} catch (error) {
|
|
457
|
-
// Expected: file not readable
|
|
458
|
-
if (error.code !== 'ENOENT' && error.code !== 'EACCES') {
|
|
459
|
-
console.error(`Unexpected error reading plan ${planFile.path}:`, error.message);
|
|
460
|
-
}
|
|
461
|
-
}
|
|
462
|
-
}
|
|
463
|
-
|
|
464
|
-
planFiles = relevantPlans;
|
|
465
|
-
|
|
466
|
-
if (planFiles.length === 0) return [];
|
|
467
|
-
|
|
468
|
-
// 5. Extract decisions from matched plan files
|
|
469
|
-
const decisions = [];
|
|
470
|
-
|
|
471
|
-
for (const planFile of planFiles) {
|
|
472
|
-
try {
|
|
473
|
-
const content = planFile.content || await fs.readFile(planFile.path, 'utf-8');
|
|
474
|
-
|
|
475
|
-
// Extract "Decisions" sections
|
|
476
|
-
const decisionPatterns = [
|
|
477
|
-
/## Decisions?\s*(?:Confirmed|Made)?\s*\n([\s\S]*?)(?=\n## |\n---|\Z)/gi,
|
|
478
|
-
/\| Decision \|[\s\S]*?\n((?:\|[^\n]+\n)+)/gi
|
|
479
|
-
];
|
|
480
|
-
|
|
481
|
-
for (const pattern of decisionPatterns) {
|
|
482
|
-
let match;
|
|
483
|
-
while ((match = pattern.exec(content)) !== null) {
|
|
484
|
-
const section = match[1] || match[0];
|
|
485
|
-
// Parse table rows or bullet points
|
|
486
|
-
const lines = section.split('\n').filter(l =>
|
|
487
|
-
l.includes('|') && !l.includes('---') && !l.includes('Decision') && l.trim().length > 5
|
|
488
|
-
);
|
|
489
|
-
|
|
490
|
-
for (const line of lines) {
|
|
491
|
-
const cells = line.split('|').map(c => c.trim()).filter(c => c.length > 0);
|
|
492
|
-
if (cells.length >= 2) {
|
|
493
|
-
decisions.push({
|
|
494
|
-
type: 'decision',
|
|
495
|
-
element: cells[0].replace(/\*\*/g, ''),
|
|
496
|
-
evidence: cells[1].replace(/\*\*/g, ''),
|
|
497
|
-
confidence: 0.8,
|
|
498
|
-
category: 'architecture',
|
|
499
|
-
source: planFile.name
|
|
500
|
-
});
|
|
501
|
-
}
|
|
502
|
-
}
|
|
503
|
-
}
|
|
504
|
-
}
|
|
505
|
-
|
|
506
|
-
// Extract "Recommendation:" lines
|
|
507
|
-
const recommendations = content.match(/\*\*Recommendation:?\*\*:?\s*(.+)/gi) || [];
|
|
508
|
-
for (const rec of recommendations) {
|
|
509
|
-
const cleaned = rec.replace(/\*\*Recommendation:?\*\*:?\s*/i, '').trim();
|
|
510
|
-
if (cleaned.length > 10) {
|
|
511
|
-
decisions.push({
|
|
512
|
-
type: 'decision',
|
|
513
|
-
element: cleaned,
|
|
514
|
-
evidence: `From plan: ${planFile.name}`,
|
|
515
|
-
confidence: 0.7,
|
|
516
|
-
category: 'architecture',
|
|
517
|
-
source: planFile.name
|
|
518
|
-
});
|
|
519
|
-
}
|
|
520
|
-
}
|
|
521
|
-
} catch (error) {
|
|
522
|
-
// Expected: file not readable
|
|
523
|
-
if (error.code !== 'ENOENT' && error.code !== 'EACCES') {
|
|
524
|
-
console.error(`Unexpected error processing plan:`, error.message);
|
|
525
|
-
}
|
|
526
|
-
}
|
|
527
|
-
}
|
|
528
|
-
|
|
529
|
-
// Deduplicate by element name (keep longest evidence)
|
|
530
|
-
const unique = {};
|
|
531
|
-
for (const d of decisions) {
|
|
532
|
-
if (!unique[d.element] || d.evidence.length > unique[d.element].evidence.length) {
|
|
533
|
-
unique[d.element] = d;
|
|
534
|
-
}
|
|
535
|
-
}
|
|
536
|
-
|
|
537
|
-
return Object.values(unique);
|
|
538
|
-
}
|
|
539
|
-
|
|
540
|
-
/**
|
|
541
|
-
* Promote plan decisions to standards
|
|
542
|
-
* Always writes to .mindmeld/decisions/ (local staging)
|
|
543
|
-
* Community promotion happens via API → PR to EquilateralAgents-Community-Standards
|
|
544
|
-
*/
|
|
545
|
-
async function promoteDecisions(projectPath, decisions, options = {}) {
|
|
546
|
-
if (decisions.length === 0) return [];
|
|
547
|
-
|
|
548
|
-
const decisionsDir = path.join(projectPath, '.mindmeld', 'decisions');
|
|
549
|
-
await fs.mkdir(decisionsDir, { recursive: true });
|
|
550
|
-
|
|
551
|
-
const promoted = [];
|
|
552
|
-
|
|
553
|
-
for (const decision of decisions) {
|
|
554
|
-
const filename = decision.element
|
|
555
|
-
.toLowerCase()
|
|
556
|
-
.replace(/[^a-z0-9]+/g, '_')
|
|
557
|
-
.replace(/^_|_$/g, '')
|
|
558
|
-
.substring(0, 50) + '.md';
|
|
559
|
-
|
|
560
|
-
const filePath = path.join(decisionsDir, filename);
|
|
561
|
-
|
|
562
|
-
// Don't overwrite existing
|
|
563
|
-
try {
|
|
564
|
-
await fs.access(filePath);
|
|
565
|
-
continue;
|
|
566
|
-
} catch (error) {
|
|
567
|
-
// Expected: file doesn't exist, safe to create
|
|
568
|
-
if (error.code !== 'ENOENT') {
|
|
569
|
-
console.error(`Unexpected error checking ${filePath}:`, error.message);
|
|
570
|
-
}
|
|
571
|
-
}
|
|
572
|
-
|
|
573
|
-
const content = [
|
|
574
|
-
`# ${decision.element}`,
|
|
575
|
-
'',
|
|
576
|
-
`**Status**: solidified`,
|
|
577
|
-
`**Category**: ${decision.category}`,
|
|
578
|
-
`**Confidence**: ${(decision.confidence * 100).toFixed(0)}%`,
|
|
579
|
-
`**Source**: plan-harvest`,
|
|
580
|
-
`**Detected**: ${new Date().toISOString().split('T')[0]}`,
|
|
581
|
-
'',
|
|
582
|
-
'## Decision',
|
|
583
|
-
'',
|
|
584
|
-
`- ${decision.evidence}`,
|
|
585
|
-
'',
|
|
586
|
-
'## Context',
|
|
587
|
-
'',
|
|
588
|
-
`Extracted from: ${decision.source}`,
|
|
589
|
-
'',
|
|
590
|
-
'---',
|
|
591
|
-
'*Architectural decision - do not revisit without explicit discussion.*'
|
|
592
|
-
].join('\n');
|
|
593
|
-
|
|
594
|
-
await fs.writeFile(filePath, content);
|
|
595
|
-
promoted.push({ decision, path: filePath });
|
|
596
|
-
}
|
|
597
|
-
|
|
598
|
-
return promoted;
|
|
599
|
-
}
|
|
600
|
-
|
|
601
|
-
module.exports = { harvest, promotePatterns, promoteDecisions, harvestPlans, getGitHistory, detectPatterns, savePatterns, showHarvestHelp };
|