@equilateral_ai/mindmeld 3.5.3 → 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.
Files changed (139) hide show
  1. package/hooks/session-start.js +312 -85
  2. package/package.json +20 -14
  3. package/scripts/init-project.js +9 -23
  4. package/src/client/dbShim.js +16 -0
  5. package/src/core/AuthManager.js +3 -2
  6. package/src/handlers/helpers/dbOperations.js +9 -46
  7. package/src/index.js +2 -217
  8. package/src/utils/piiMask.js +16 -0
  9. package/scripts/harvest.js +0 -601
  10. package/scripts/inject.js +0 -409
  11. package/scripts/mcp-bridge.js +0 -220
  12. package/scripts/repo-analyzer.js +0 -870
  13. package/scripts/standards.js +0 -285
  14. package/src/collaboration/CollaborationPrompt.js +0 -460
  15. package/src/core/AlertEngine.js +0 -813
  16. package/src/core/AlertNotifier.js +0 -363
  17. package/src/core/CorrelationAnalyzer.js +0 -931
  18. package/src/core/CrossReferenceEngine.js +0 -624
  19. package/src/core/CurationEngine.js +0 -688
  20. package/src/core/DeprecationScheduler.js +0 -183
  21. package/src/core/LoadBearingDetector.js +0 -242
  22. package/src/core/NotificationService.js +0 -1032
  23. package/src/core/RapportOrchestrator.js +0 -632
  24. package/src/core/RelevanceDetector.js +0 -694
  25. package/src/core/StandardLifecycle.js +0 -244
  26. package/src/core/StandardsIngestion.js +0 -991
  27. package/src/core/TeamLoadBearingDetector.js +0 -431
  28. package/src/core/parsers/adrParser.js +0 -479
  29. package/src/core/parsers/cursorRulesParser.js +0 -564
  30. package/src/core/parsers/eslintParser.js +0 -439
  31. package/src/database/dbOperations.js +0 -105
  32. package/src/handlers/activity/activityGetMe.js +0 -98
  33. package/src/handlers/activity/activityGetTeam.js +0 -175
  34. package/src/handlers/admin/adminSetup.js +0 -216
  35. package/src/handlers/alerts/alertsAcknowledge.js +0 -92
  36. package/src/handlers/alerts/alertsGet.js +0 -250
  37. package/src/handlers/analytics/activitySummaryGet.js +0 -234
  38. package/src/handlers/analytics/coachingGet.js +0 -361
  39. package/src/handlers/analytics/convergenceGet.js +0 -236
  40. package/src/handlers/analytics/developerScoreGet.js +0 -137
  41. package/src/handlers/collaborators/collaboratorAdd.js +0 -200
  42. package/src/handlers/collaborators/collaboratorInvite.js +0 -219
  43. package/src/handlers/collaborators/collaboratorList.js +0 -82
  44. package/src/handlers/collaborators/collaboratorRemove.js +0 -128
  45. package/src/handlers/collaborators/inviteAccept.js +0 -122
  46. package/src/handlers/company/companyUsersDelete.js +0 -141
  47. package/src/handlers/company/companyUsersGet.js +0 -90
  48. package/src/handlers/company/companyUsersPost.js +0 -267
  49. package/src/handlers/company/companyUsersPut.js +0 -76
  50. package/src/handlers/context/contextGet.js +0 -57
  51. package/src/handlers/context/invariantsGet.js +0 -74
  52. package/src/handlers/context/loopsGet.js +0 -82
  53. package/src/handlers/context/notesCreate.js +0 -74
  54. package/src/handlers/context/purposeGet.js +0 -78
  55. package/src/handlers/correlations/correlationsDeveloperGet.js +0 -227
  56. package/src/handlers/correlations/correlationsGet.js +0 -93
  57. package/src/handlers/correlations/correlationsProjectGet.js +0 -153
  58. package/src/handlers/enterprise/controlTowerGet.js +0 -224
  59. package/src/handlers/enterprise/enterpriseAuditGet.js +0 -108
  60. package/src/handlers/enterprise/enterpriseContributorsGet.js +0 -85
  61. package/src/handlers/enterprise/enterpriseKnowledgeCategoriesGet.js +0 -53
  62. package/src/handlers/enterprise/enterpriseKnowledgeCreate.js +0 -77
  63. package/src/handlers/enterprise/enterpriseKnowledgeDelete.js +0 -71
  64. package/src/handlers/enterprise/enterpriseKnowledgeGet.js +0 -87
  65. package/src/handlers/enterprise/enterpriseKnowledgeUpdate.js +0 -122
  66. package/src/handlers/enterprise/enterpriseOnboardingComplete.js +0 -77
  67. package/src/handlers/enterprise/enterpriseOnboardingInvite.js +0 -138
  68. package/src/handlers/enterprise/enterpriseOnboardingSetup.js +0 -128
  69. package/src/handlers/enterprise/enterpriseOnboardingStatus.js +0 -88
  70. package/src/handlers/github/githubConnectionStatus.js +0 -49
  71. package/src/handlers/github/githubDiscoverPatterns.js +0 -621
  72. package/src/handlers/github/githubOAuthCallback.js +0 -178
  73. package/src/handlers/github/githubOAuthStart.js +0 -59
  74. package/src/handlers/github/githubPatternsReview.js +0 -76
  75. package/src/handlers/github/githubReposList.js +0 -105
  76. package/src/handlers/health/healthGet.js +0 -55
  77. package/src/handlers/helpers/auditLogger.js +0 -201
  78. package/src/handlers/helpers/checkSuperAdmin.js +0 -84
  79. package/src/handlers/helpers/decisionFrames.js +0 -29
  80. package/src/handlers/helpers/errorHandler.js +0 -49
  81. package/src/handlers/helpers/index.js +0 -138
  82. package/src/handlers/helpers/lambdaWrapper.js +0 -60
  83. package/src/handlers/helpers/mindmeldMcpCore.js +0 -1103
  84. package/src/handlers/helpers/predictiveCache.js +0 -51
  85. package/src/handlers/helpers/projectAccess.js +0 -88
  86. package/src/handlers/helpers/responseUtil.js +0 -55
  87. package/src/handlers/helpers/subscriptionTiers.js +0 -1168
  88. package/src/handlers/mcp/mcpHandler.js +0 -569
  89. package/src/handlers/mcp/mindmeldMcpHandler.js +0 -124
  90. package/src/handlers/mcp/mindmeldMcpStreamHandler.js +0 -342
  91. package/src/handlers/notifications/getPreferences.js +0 -84
  92. package/src/handlers/notifications/sendNotification.js +0 -170
  93. package/src/handlers/notifications/updatePreferences.js +0 -316
  94. package/src/handlers/patterns/patternEvaluatePromotionPost.js +0 -173
  95. package/src/handlers/patterns/patternUsagePost.js +0 -182
  96. package/src/handlers/patterns/patternViolationPost.js +0 -185
  97. package/src/handlers/projects/projectCreate.js +0 -248
  98. package/src/handlers/projects/projectDelete.js +0 -82
  99. package/src/handlers/projects/projectGet.js +0 -95
  100. package/src/handlers/projects/projectUpdate.js +0 -117
  101. package/src/handlers/reports/aiLeverage.js +0 -210
  102. package/src/handlers/reports/engineeringInvestment.js +0 -132
  103. package/src/handlers/reports/riskForecast.js +0 -206
  104. package/src/handlers/reports/standardsRoi.js +0 -254
  105. package/src/handlers/scheduled/analyzeCorrelations.js +0 -178
  106. package/src/handlers/scheduled/analyzeGitHistory.js +0 -510
  107. package/src/handlers/scheduled/generateAlerts.js +0 -135
  108. package/src/handlers/scheduled/maturityUpdateJob.js +0 -166
  109. package/src/handlers/scheduled/refreshActivity.js +0 -21
  110. package/src/handlers/scheduled/scanCompliance.js +0 -334
  111. package/src/handlers/sessions/sessionEndPost.js +0 -180
  112. package/src/handlers/sessions/sessionStandardsPost.js +0 -171
  113. package/src/handlers/standards/catalogGet.js +0 -185
  114. package/src/handlers/standards/catalogSync.js +0 -120
  115. package/src/handlers/standards/discoveriesGet.js +0 -89
  116. package/src/handlers/standards/projectStandardsGet.js +0 -129
  117. package/src/handlers/standards/projectStandardsPut.js +0 -151
  118. package/src/handlers/standards/standardsAuditGet.js +0 -65
  119. package/src/handlers/standards/standardsParseUpload.js +0 -149
  120. package/src/handlers/standards/standardsRelevantPost.js +0 -405
  121. package/src/handlers/standards/standardsTransition.js +0 -161
  122. package/src/handlers/stripe/addonManagePost.js +0 -240
  123. package/src/handlers/stripe/billingPortalPost.js +0 -93
  124. package/src/handlers/stripe/enterpriseCheckoutPost.js +0 -272
  125. package/src/handlers/stripe/seatsUpdatePost.js +0 -185
  126. package/src/handlers/stripe/subscriptionCancelDelete.js +0 -169
  127. package/src/handlers/stripe/subscriptionCreatePost.js +0 -221
  128. package/src/handlers/stripe/subscriptionUpdatePut.js +0 -163
  129. package/src/handlers/stripe/webhookPost.js +0 -482
  130. package/src/handlers/user/apiTokenCreate.js +0 -71
  131. package/src/handlers/user/apiTokenList.js +0 -64
  132. package/src/handlers/user/userSplashAck.js +0 -91
  133. package/src/handlers/user/userSplashGet.js +0 -211
  134. package/src/handlers/users/cognitoPostConfirmation.js +0 -186
  135. package/src/handlers/users/cognitoPreSignUp.js +0 -114
  136. package/src/handlers/users/userEntitlementsGet.js +0 -89
  137. package/src/handlers/users/userGet.js +0 -118
  138. package/src/handlers/users/userProfilePut.js +0 -77
  139. package/src/handlers/webhooks/githubWebhook.js +0 -215
@@ -101,6 +101,80 @@ async function detectGitRemote() {
101
101
  }
102
102
  }
103
103
 
104
+ /**
105
+ * Detect git HEAD SHA and branch for project identity (Spec §6.5)
106
+ * @returns {Promise<{commitSha: string|null, gitBranch: string|null}>}
107
+ */
108
+ async function detectGitMetadata() {
109
+ const { execSync } = require('child_process');
110
+ const opts = { cwd: process.cwd(), encoding: 'utf-8', timeout: 2000, stdio: ['pipe', 'pipe', 'pipe'] };
111
+ let commitSha = null;
112
+ let gitBranch = null;
113
+ try {
114
+ commitSha = execSync('git rev-parse HEAD', opts).trim() || null;
115
+ } catch (_) { /* not a git repo */ }
116
+ try {
117
+ gitBranch = execSync('git branch --show-current', opts).trim() || null;
118
+ } catch (_) { /* detached HEAD or not a git repo */ }
119
+ return { commitSha, gitBranch };
120
+ }
121
+
122
+ /**
123
+ * Read Claude Code memory files for drift detection (Spec §5.2)
124
+ * Reads MEMORY.md index + all referenced .md files from the project memory dir
125
+ * @returns {Promise<string|null>} Concatenated memory content or null
126
+ */
127
+ async function readClaudeMemory() {
128
+ const os = require('os');
129
+ const encodedCwd = process.cwd().replace(/\//g, '-');
130
+ const memoryDir = path.join(os.homedir(), '.claude', 'projects', encodedCwd, 'memory');
131
+
132
+ try {
133
+ await fs.access(memoryDir);
134
+ } catch (_) {
135
+ return null;
136
+ }
137
+
138
+ try {
139
+ const files = await fs.readdir(memoryDir);
140
+ const mdFiles = files.filter(f => f.endsWith('.md'));
141
+ if (mdFiles.length === 0) return null;
142
+
143
+ const contents = [];
144
+ // Read MEMORY.md first if it exists (it's the index)
145
+ if (mdFiles.includes('MEMORY.md')) {
146
+ const content = await fs.readFile(path.join(memoryDir, 'MEMORY.md'), 'utf-8');
147
+ contents.push(`--- MEMORY.md ---\n${content}`);
148
+ }
149
+ for (const file of mdFiles) {
150
+ if (file === 'MEMORY.md') continue;
151
+ try {
152
+ const content = await fs.readFile(path.join(memoryDir, file), 'utf-8');
153
+ contents.push(`--- ${file} ---\n${content}`);
154
+ } catch (_) { /* skip unreadable files */ }
155
+ }
156
+ return contents.join('\n\n');
157
+ } catch (err) {
158
+ console.error('[MindMeld] Memory read failed (non-fatal):', err.message);
159
+ return null;
160
+ }
161
+ }
162
+
163
+ /**
164
+ * Read CLAUDE.md files for drift detection
165
+ * Reads project-level CLAUDE.md and any parent CLAUDE.md files
166
+ * @returns {Promise<string|null>} Concatenated CLAUDE.md content or null
167
+ */
168
+ async function readClaudeMd() {
169
+ try {
170
+ const claudeMdPath = path.join(process.cwd(), 'CLAUDE.md');
171
+ const content = await fs.readFile(claudeMdPath, 'utf-8');
172
+ return content || null;
173
+ } catch (_) {
174
+ return null;
175
+ }
176
+ }
177
+
104
178
  /**
105
179
  * Get project name from git remote URL or directory name
106
180
  * @param {string|null} gitRemote - Git remote URL
@@ -628,6 +702,81 @@ async function fetchRelevantStandardsFromAPI(apiUrl, authToken, characteristics,
628
702
  });
629
703
  }
630
704
 
705
+ /**
706
+ * Call mindmeld_init_session via JSON-RPC to get banded injection (Band A/B/C).
707
+ * This converges the hook injection path with the MCP tool path — one deterministic
708
+ * banded output regardless of whether the MCP tool also fires during the session.
709
+ */
710
+ async function callBandedInitSession(apiUrl, authToken, params) {
711
+ if (!authToken) {
712
+ throw new Error('No auth token available');
713
+ }
714
+
715
+ const https = require('https');
716
+ const url = require('url');
717
+
718
+ const jsonRpcPayload = JSON.stringify({
719
+ jsonrpc: '2.0',
720
+ id: 1,
721
+ method: 'tools/call',
722
+ params: {
723
+ name: 'mindmeld_init_session',
724
+ arguments: params
725
+ }
726
+ });
727
+
728
+ const parsedUrl = url.parse(`${apiUrl}/api/mcp/mindmeld`);
729
+
730
+ const options = {
731
+ hostname: parsedUrl.hostname,
732
+ port: parsedUrl.port || 443,
733
+ path: parsedUrl.path,
734
+ method: 'POST',
735
+ headers: {
736
+ 'Content-Type': 'application/json',
737
+ 'Authorization': `Bearer ${authToken}`,
738
+ 'Content-Length': Buffer.byteLength(jsonRpcPayload)
739
+ },
740
+ timeout: 8000
741
+ };
742
+
743
+ return new Promise((resolve, reject) => {
744
+ const req = https.request(options, (res) => {
745
+ let data = '';
746
+ res.on('data', chunk => { data += chunk; });
747
+ res.on('end', () => {
748
+ try {
749
+ const rpcResponse = JSON.parse(data);
750
+ if (rpcResponse.error) {
751
+ const err = new Error(rpcResponse.error.message || 'JSON-RPC error');
752
+ err.statusCode = res.statusCode;
753
+ reject(err);
754
+ return;
755
+ }
756
+ const content = rpcResponse.result?.content;
757
+ if (!content || !content[0] || content[0].type !== 'text') {
758
+ reject(new Error('Unexpected init_session response shape'));
759
+ return;
760
+ }
761
+ const initResult = JSON.parse(content[0].text);
762
+ resolve(initResult);
763
+ } catch (e) {
764
+ reject(new Error('Invalid banded init_session response'));
765
+ }
766
+ });
767
+ });
768
+
769
+ req.on('error', reject);
770
+ req.on('timeout', () => {
771
+ req.destroy();
772
+ reject(new Error('Banded init_session timeout'));
773
+ });
774
+
775
+ req.write(jsonRpcPayload);
776
+ req.end();
777
+ });
778
+ }
779
+
631
780
  /**
632
781
  * Fetch weekly splash data from API
633
782
  * Returns splash summary with stats, or null if already acknowledged
@@ -744,11 +893,14 @@ async function injectContext() {
744
893
  }
745
894
 
746
895
  // 1. Parallel local reads (no network, no DB)
747
- const [fingerprintConfig, authToken, apiConfig, preferences] = await Promise.all([
896
+ const [fingerprintConfig, authToken, apiConfig, preferences, gitMetadata, memoryContent, claudeMdContent] = await Promise.all([
748
897
  loadFingerprintConfig(),
749
898
  loadAuthToken(),
750
899
  loadApiConfig(),
751
- loadStandardsPreferences()
900
+ loadStandardsPreferences(),
901
+ detectGitMetadata(),
902
+ readClaudeMemory(),
903
+ readClaudeMd()
752
904
  ]);
753
905
 
754
906
  // 2. Initialize MindmeldClient (used for project detection, session tracking, team context)
@@ -795,7 +947,9 @@ async function injectContext() {
795
947
  companyId: context.companyId,
796
948
  userEmail: context.userEmail,
797
949
  startedAt: new Date().toISOString(),
798
- transcriptPath: transcriptPath
950
+ transcriptPath: transcriptPath,
951
+ commitSha: gitMetadata.commitSha,
952
+ gitBranch: gitMetadata.gitBranch,
799
953
  };
800
954
  const mindmeldDir = path.join(process.cwd(), '.mindmeld');
801
955
  await fs.mkdir(mindmeldDir, { recursive: true });
@@ -842,89 +996,66 @@ async function injectContext() {
842
996
  console.error(`[MindMeld] Watcher spawn failed (non-fatal): ${err.message}`);
843
997
  }
844
998
 
845
- // 4. Detect project characteristics locally (file system only, no DB)
846
- const characteristics = await mindmeld.relevanceDetector.detectProjectCharacteristics();
999
+ // 4. Banded injection path (converged with MCP init_session)
1000
+ // Band B content gated on resolved companyId — null scope would mis-match snapshots
1001
+ const bandBGated = !!context.companyId;
1002
+ const initParams = {
1003
+ project_path: process.cwd(),
1004
+ commit_sha: gitMetadata.commitSha || undefined,
1005
+ git_branch: gitMetadata.gitBranch || undefined,
1006
+ git_root: gitMetadata.gitRoot || undefined,
1007
+ memory_content: bandBGated ? (memoryContent || undefined) : undefined,
1008
+ claude_md_content: bandBGated ? (claudeMdContent || undefined) : undefined
1009
+ };
847
1010
 
848
- // 5. Parallel API calls: standards + team context + splash
849
- const [standardsResult, projectContextResult, splashResult] = await Promise.allSettled([
850
- fetchRelevantStandardsFromAPI(apiConfig.apiUrl, authToken, characteristics, context.projectId, preferences),
851
- mindmeld.loadProjectContext(context.projectId),
1011
+ const [bandedResult, splashResult] = await Promise.allSettled([
1012
+ callBandedInitSession(apiConfig.apiUrl, authToken, initParams),
852
1013
  fetchSplashData(apiConfig.apiUrl, authToken)
853
1014
  ]);
854
1015
 
855
- // 6. Resolve standards: API → file-based fallback (only for network errors, not subscription blocks)
856
- let relevantStandards = [];
857
- if (standardsResult.status === 'fulfilled' && standardsResult.value.length > 0) {
858
- relevantStandards = standardsResult.value;
859
- console.error(`[MindMeld] ${relevantStandards.length} standards from API`);
860
- } else if (standardsResult.status === 'rejected' &&
861
- standardsResult.reason.message.includes('subscription required')) {
862
- // Subscription enforcement — do NOT fall through to file-based injection
863
- console.error('[MindMeld] Active subscription required. Subscribe at app.mindmeld.dev');
864
- return '';
865
- } else if (standardsResult.status === 'rejected' &&
866
- (standardsResult.reason.statusCode === 401 || standardsResult.reason.message === 'Unauthorized')) {
867
- // Auth token expired or invalid — trigger re-auth and use file-based fallback
868
- console.error('[MindMeld] Auth token expired. Triggering re-authentication...');
869
- spawnBackgroundLogin();
870
- const categories = mindmeld.relevanceDetector.mapCharacteristicsToCategories(characteristics);
871
- relevantStandards = await mindmeld.relevanceDetector.loadStandardsFromFiles(categories, characteristics);
872
- console.error(`[MindMeld] ${relevantStandards.length} standards from file fallback (scored)`);
873
- } else {
874
- if (standardsResult.status === 'rejected') {
875
- console.error(`[MindMeld] API fallback: ${standardsResult.reason.message}`);
876
- }
877
- const categories = mindmeld.relevanceDetector.mapCharacteristicsToCategories(characteristics);
878
- relevantStandards = await mindmeld.relevanceDetector.loadStandardsFromFiles(categories, characteristics);
879
- console.error(`[MindMeld] ${relevantStandards.length} standards from file fallback (scored)`);
880
- }
881
-
882
- // 7. Filter by project preferences
883
- if (preferences) {
884
- relevantStandards = filterStandardsByPreferences(relevantStandards, preferences);
885
- console.error(`[MindMeld] Filtered to ${relevantStandards.length} standards by preferences`);
886
- }
887
-
888
- // Take top 10 most relevant after filtering
889
- relevantStandards = relevantStandards.slice(0, 10);
890
-
891
- // 8. Record standards shown (fire-and-forget, non-blocking)
892
- mindmeld.recordStandardsShown(sessionId, relevantStandards, context.projectId, context.userEmail);
893
-
894
- // 9. Resolve team context from parallel API call
895
- const projectContext = projectContextResult.status === 'fulfilled' ? projectContextResult.value : null;
896
- const teamPatterns = projectContext && projectContext.patterns
897
- ? projectContext.patterns.filter(p => p.confidence > 0.7)
898
- : [];
899
- const recentLearning = projectContext && projectContext.recentLearning
900
- ? projectContext.recentLearning
901
- : [];
902
-
903
- // 10. Resolve splash data
904
1016
  const splashData = splashResult.status === 'fulfilled' ? splashResult.value : null;
905
-
906
- // Auto-acknowledge splash (fire-and-forget) so it doesn't show again this week
907
- if (splashData && splashData.show_splash && splashData.summary) {
1017
+ if (splashData?.show_splash && splashData?.summary) {
908
1018
  acknowledgeSplash(apiConfig.apiUrl, authToken, splashData.summary.week_start);
909
1019
  }
910
1020
 
911
- // 11. Build context injection with fingerprint
912
- const injection = formatContextInjection({
913
- project: context.projectName,
914
- sessionId: sessionId,
915
- collaborators: context.collaborators,
916
- relevantStandards: relevantStandards,
917
- teamPatterns: teamPatterns,
918
- recentLearning: recentLearning,
919
- fingerprint: fingerprintConfig,
920
- splash: splashData
921
- });
1021
+ // 6. Resolve injection: banded → flat fallback
1022
+ let injection;
1023
+ if (bandedResult.status === 'fulfilled' && bandedResult.value.formatted_injection) {
1024
+ const initResponse = bandedResult.value;
1025
+ const summary = initResponse.injection_summary || {};
1026
+ console.error(`[MindMeld] Banded injection: A=${summary.band_a_tokens || 0}t B=${summary.band_b_tokens || 0}t C=${summary.band_c_tokens || 0}t / ${summary.budget_tokens || 400}t budget`);
1027
+
1028
+ injection = formatConvergedInjection({
1029
+ bandedMarkdown: initResponse.formatted_injection,
1030
+ collaborators: context.collaborators,
1031
+ splash: splashData
1032
+ });
922
1033
 
923
- // 12. Cache condensed context for subagent injection
924
- try {
925
- await cacheSubagentContext(relevantStandards, teamPatterns, context.projectName);
926
- } catch (cacheError) {
927
- console.error('[MindMeld] Subagent context cache failed (non-fatal):', cacheError.message);
1034
+ try {
1035
+ const standardsForCache = (initResponse.injected_rules || []).map(r => ({
1036
+ pattern_id: r.rule_id, element: r.standard, rule: r.text,
1037
+ maturity: r.maturity, relevance_score: r.relevance_score
1038
+ }));
1039
+ await cacheSubagentContext(standardsForCache, [], context.projectName);
1040
+ } catch (cacheError) {
1041
+ console.error('[MindMeld] Subagent context cache failed (non-fatal):', cacheError.message);
1042
+ }
1043
+ } else {
1044
+ const bandedError = bandedResult.status === 'rejected' ? bandedResult.reason : null;
1045
+ if (bandedError) {
1046
+ if (bandedError.message.includes('subscription required')) {
1047
+ console.error('[MindMeld] Active subscription required. Subscribe at app.mindmeld.dev');
1048
+ return '';
1049
+ }
1050
+ if (bandedError.statusCode === 401 || bandedError.message === 'Unauthorized') {
1051
+ console.error('[MindMeld] Auth token expired. Triggering re-authentication...');
1052
+ spawnBackgroundLogin();
1053
+ }
1054
+ console.error(`[MindMeld] API unavailable: ${bandedError.message}`);
1055
+ } else {
1056
+ console.error('[MindMeld] API returned empty injection');
1057
+ }
1058
+ injection = '<!-- MindMeld: API unavailable — this session is proceeding without governance injection. Standards were not loaded. -->';
928
1059
  }
929
1060
 
930
1061
  const elapsed = Date.now() - startTime;
@@ -933,9 +1064,8 @@ async function injectContext() {
933
1064
  return injection;
934
1065
 
935
1066
  } catch (error) {
936
- // Graceful degradation - never block Claude Code session
937
1067
  console.error('[MindMeld] Hook error (non-fatal):', error.message);
938
- return '';
1068
+ return '<!-- MindMeld: hook error — this session is proceeding without governance injection. Standards were not loaded. -->';
939
1069
  }
940
1070
  }
941
1071
 
@@ -956,6 +1086,27 @@ async function checkMindmeldConfiguration() {
956
1086
  }
957
1087
  }
958
1088
 
1089
+ /**
1090
+ * Validate that a standard has enough content to be useful when injected.
1091
+ * Mirrors StandardsIngestion.isViableStandard() for the output path.
1092
+ */
1093
+ function isViableForInjection(standard) {
1094
+ const el = (standard.element || '').trim();
1095
+ const rl = (standard.rule || '').trim();
1096
+
1097
+ if (!el || !rl) return false;
1098
+ if (/^(USE|ALWAYS|NEVER|PREFER|AVOID|DO|DON'T|SET|ADD|RUN|CALL)$/i.test(el)) return false;
1099
+ if (/^(Standard Pattern|Core Principle)$/i.test(el) && rl.length < 30) return false;
1100
+ if (/^(ALWAYS|NEVER|USE|PREFER|AVOID)[:\s]*$/i.test(rl)) return false;
1101
+ if (rl.length < 15) return false;
1102
+
1103
+ // Word-count guard: element must have >=2 words or be a compound term (>=8 chars)
1104
+ const wordCount = el.split(/[\s.]+/).filter(w => w.length > 0).length;
1105
+ if (wordCount < 2 && el.length < 8) return false;
1106
+
1107
+ return true;
1108
+ }
1109
+
959
1110
  /**
960
1111
  * Cache condensed context for subagent injection
961
1112
  * Subagents don't get session-start hooks, so we cache the essentials
@@ -975,25 +1126,55 @@ async function cacheSubagentContext(standards, teamPatterns, projectName) {
975
1126
  sections.push('Follow these standards when writing or modifying code:');
976
1127
  sections.push('');
977
1128
 
978
- // Include standards as compact rules (no examples/anti-patterns to save tokens)
1129
+ let injectedCount = 0;
1130
+ let skippedCount = 0;
1131
+
979
1132
  if (standards && standards.length > 0) {
980
1133
  for (const s of standards) {
981
- sections.push(`- **${s.element}** [${s.category}]: ${s.rule}`);
1134
+ if (!isViableForInjection(s)) {
1135
+ skippedCount++;
1136
+ continue;
1137
+ }
1138
+
1139
+ const maturity = s.maturity_tier || s.maturity || 'validated';
1140
+ const prefix = maturity === 'reinforced' ? '[MUST]' :
1141
+ maturity === 'enforced' ? '[MUST]' :
1142
+ maturity === 'solidified' ? '[SHOULD]' : '[CONSIDER]';
1143
+
1144
+ sections.push(`### ${prefix} ${s.element}`);
1145
+ sections.push(`**Category**: ${s.category}`);
1146
+ sections.push(`**Rule**: ${s.rule}`);
1147
+
1148
+ // Include first anti-pattern if available — high signal-to-token ratio
1149
+ if (s.anti_patterns && s.anti_patterns.length > 0) {
1150
+ const ap = s.anti_patterns[0];
1151
+ const desc = typeof ap === 'string' ? ap : (ap.description || '');
1152
+ if (desc) {
1153
+ sections.push(`**Avoid**: ${desc}`);
1154
+ }
1155
+ }
1156
+
1157
+ sections.push('');
1158
+ injectedCount++;
982
1159
  }
983
- sections.push('');
984
1160
  }
985
1161
 
986
- // Include team patterns as compact rules
1162
+ // Include team patterns with validation
987
1163
  if (teamPatterns && teamPatterns.length > 0) {
988
1164
  sections.push('## Team Patterns');
989
1165
  for (const p of teamPatterns) {
1166
+ if (!isViableForInjection(p)) {
1167
+ skippedCount++;
1168
+ continue;
1169
+ }
990
1170
  sections.push(`- **${p.element}** (${(p.correlation * 100).toFixed(0)}% correlation): ${p.rule}`);
1171
+ injectedCount++;
991
1172
  }
992
1173
  sections.push('');
993
1174
  }
994
1175
 
995
1176
  await fs.writeFile(cachePath, sections.join('\n'));
996
- console.error(`[MindMeld] Cached subagent context (${sections.length} lines)`);
1177
+ console.error(`[MindMeld] Cached subagent context (${injectedCount} standards, ${skippedCount} skipped as non-viable)`);
997
1178
  }
998
1179
 
999
1180
  /**
@@ -1011,7 +1192,53 @@ function getMaturityPrefix(maturityTier) {
1011
1192
  }
1012
1193
 
1013
1194
  /**
1014
- * Format context injection for Claude Code
1195
+ * Wrap banded init_session output with hook-only sections (splash, team).
1196
+ * The banded markdown is self-contained (header, copyright, bands A/B/C, footer).
1197
+ * Hook-only content is inserted between the copyright block and the first band heading.
1198
+ */
1199
+ function formatConvergedInjection({ bandedMarkdown, collaborators, splash }) {
1200
+ const extraSections = [];
1201
+
1202
+ if (splash && splash.show_splash) {
1203
+ if (splash.is_greenfield && splash.tips && splash.tips.length > 0) {
1204
+ extraSections.push('## Getting Started with MindMeld');
1205
+ for (const tip of splash.tips) extraSections.push(`- ${tip}`);
1206
+ extraSections.push('');
1207
+ } else if (splash.summary) {
1208
+ const s = splash.summary;
1209
+ const formatDate = (dateStr) => {
1210
+ const d = new Date(dateStr + 'T00:00:00Z');
1211
+ return d.toLocaleDateString('en-US', { month: 'short', day: 'numeric', timeZone: 'UTC' });
1212
+ };
1213
+ const hours = Math.floor(s.total_duration_minutes / 60);
1214
+ const mins = s.total_duration_minutes % 60;
1215
+ const duration = hours > 0 ? `${hours}h ${mins}m` : `${mins}m`;
1216
+ extraSections.push(`## Weekly Recap (${formatDate(s.week_start)} - ${formatDate(s.week_end)})`);
1217
+ extraSections.push(`${s.sessions_count} sessions | ${duration} total | ${s.active_projects} project${s.active_projects !== 1 ? 's' : ''}`);
1218
+ extraSections.push(`${s.standards_injected} standards injected, ${s.standards_followed} followed, ${s.violations_detected} violation${s.violations_detected !== 1 ? 's' : ''}`);
1219
+ if (s.patterns_harvested > 0) extraSections.push(`${s.patterns_harvested} patterns harvested`);
1220
+ extraSections.push('');
1221
+ }
1222
+ }
1223
+
1224
+ if (collaborators && collaborators.length > 0) {
1225
+ extraSections.push('## Team');
1226
+ extraSections.push(collaborators.map(c => `- ${c.name} (${c.email})`).join('\n'));
1227
+ extraSections.push('');
1228
+ }
1229
+
1230
+ if (extraSections.length === 0) return bandedMarkdown;
1231
+
1232
+ const firstBandHeading = bandedMarkdown.indexOf('\n## ');
1233
+ if (firstBandHeading === -1) return bandedMarkdown;
1234
+
1235
+ return bandedMarkdown.slice(0, firstBandHeading + 1) +
1236
+ extraSections.join('\n') + '\n' +
1237
+ bandedMarkdown.slice(firstBandHeading + 1);
1238
+ }
1239
+
1240
+ /**
1241
+ * Format flat context injection for Claude Code (fallback when banded path unavailable)
1015
1242
  * @param {object} data - Context data to inject
1016
1243
  * @param {object} data.fingerprint - Fingerprint config {userId, companyId, tier}
1017
1244
  */
package/package.json CHANGED
@@ -1,21 +1,22 @@
1
1
  {
2
2
  "name": "@equilateral_ai/mindmeld",
3
- "version": "3.5.3",
3
+ "version": "4.0.1",
4
4
  "description": "Intelligent standards injection for AI coding sessions - context-aware, self-documenting, scales to large codebases",
5
5
  "main": "src/index.js",
6
6
  "bin": {
7
- "mindmeld": "./scripts/init-project.js"
7
+ "mindmeld": "scripts/init-project.js"
8
8
  },
9
9
  "files": [
10
- "src/",
10
+ "src/index.js",
11
+ "src/core/AuthManager.js",
12
+ "src/core/PatternValidator.js",
13
+ "src/core/LLMPatternDetector.js",
14
+ "src/client/dbShim.js",
15
+ "src/handlers/helpers/dbOperations.js",
16
+ "src/utils/piiMask.js",
11
17
  "hooks/",
12
18
  "scripts/init-project.js",
13
19
  "scripts/auth-login.js",
14
- "scripts/inject.js",
15
- "scripts/harvest.js",
16
- "scripts/repo-analyzer.js",
17
- "scripts/mcp-bridge.js",
18
- "scripts/standards.js",
19
20
  "README.md"
20
21
  ],
21
22
  "publishConfig": {
@@ -44,7 +45,9 @@
44
45
  "test:benchmark": "node scripts/test-claude-hooks.js --benchmark",
45
46
  "test:smoke": "jest --selectProjects smoke --testTimeout=15000",
46
47
  "test:e2e": "cd e2e && npx playwright test",
47
- "test:e2e:admin": "cd frontend/admin && npx playwright test"
48
+ "test:e2e:admin": "cd frontend/admin && npx playwright test",
49
+ "prepack": "mkdir -p src/handlers/helpers && cp src/client/dbShim.js src/handlers/helpers/dbOperations.js",
50
+ "postpack": "git checkout -- src/handlers/helpers/dbOperations.js"
48
51
  },
49
52
  "claudeCode": {
50
53
  "hooks": {
@@ -80,14 +83,17 @@
80
83
  },
81
84
  "homepage": "https://mindmeld.dev",
82
85
  "dependencies": {
83
- "@aws-sdk/client-bedrock-runtime": "^3.460.0",
84
- "@aws-sdk/client-ses": "^3.985.0",
85
- "js-yaml": "^4.1.1",
86
- "pg": "^8.18.0"
86
+ "js-yaml": "^4.1.1"
87
+ },
88
+ "optionalDependencies": {
89
+ "@aws-sdk/client-bedrock-runtime": "^3.460.0"
87
90
  },
88
91
  "devDependencies": {
89
92
  "@aws-sdk/client-s3": "^3.460.0",
90
- "jest": "^29.7.0"
93
+ "@aws-sdk/client-ses": "^3.985.0",
94
+ "dotenv": "^17.4.2",
95
+ "jest": "^29.7.0",
96
+ "pg": "^8.18.0"
91
97
  },
92
98
  "engines": {
93
99
  "node": ">=18.0.0"
@@ -702,14 +702,9 @@ if (args.command === 'init') {
702
702
  process.exit(1);
703
703
  });
704
704
  } else if (args.command === 'inject') {
705
- const { inject, showInjectHelp } = require('./inject');
706
- if (args.help) { showInjectHelp(); process.exit(0); }
707
- inject({ format: args.format, path: args.projectPath })
708
- .then(() => process.exit(0))
709
- .catch(error => {
710
- console.error('\n❌ Error:', error.message);
711
- process.exit(1);
712
- });
705
+ console.error('The inject subcommand has been removed in v4.0.0.');
706
+ console.error('Standards injection is now handled automatically by the session-start hook via the MindMeld API.');
707
+ process.exit(1);
713
708
  } else if (args.command === 'harvest') {
714
709
  const { harvest, showHarvestHelp } = require('./harvest');
715
710
  if (args.help) { showHarvestHelp(); process.exit(0); }
@@ -741,8 +736,9 @@ if (args.command === 'init') {
741
736
  process.exit(1);
742
737
  });
743
738
  } else if (args.command === 'standards') {
744
- // Delegate to standards.js
745
- require('./standards');
739
+ console.error('The standards subcommand has been removed in v4.0.0.');
740
+ console.error('Configure standards at https://app.mindmeld.dev');
741
+ process.exit(1);
746
742
  } else if (args.command === 'open') {
747
743
  openWebApp(args.projectPath || 'dashboard')
748
744
  .then(() => process.exit(0))
@@ -751,19 +747,9 @@ if (args.command === 'init') {
751
747
  process.exit(1);
752
748
  });
753
749
  } else if (args.command === 'mcp') {
754
- const { startBridge, resolveToken, showMcpHelp } = require('./mcp-bridge');
755
- if (args.help) { showMcpHelp(); process.exit(0); }
756
- const token = resolveToken(args.token);
757
- if (!token) {
758
- console.error('\nMindMeld MCP bridge requires an API token.\n');
759
- console.error('Set one of:');
760
- console.error(' 1. MINDMELD_TOKEN environment variable');
761
- console.error(' 2. --token CLI argument');
762
- console.error(' 3. Save to ~/.mindmeld/api-token\n');
763
- console.error('Create a token at: https://app.mindmeld.dev/api-tokens\n');
764
- process.exit(1);
765
- }
766
- startBridge(token);
750
+ console.error('The mcp subcommand has been removed in v4.0.0.');
751
+ console.error('Use the MindMeld MCP server directly via Claude Code settings.');
752
+ process.exit(1);
767
753
  } else {
768
754
  console.error(`Unknown command: ${args.command}`);
769
755
  console.error('Run "mindmeld --help" for usage.');
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Database operations shim for NPM client package.
3
+ * Core modules import dbOperations at the top level, but hooks never
4
+ * execute DB queries — they communicate via HTTP to the MindMeld API.
5
+ * This shim satisfies the require() without shipping pg or connection logic.
6
+ */
7
+
8
+ async function executeQuery() {
9
+ throw new Error('Database operations are not available in the client package. Use the MindMeld API instead.');
10
+ }
11
+
12
+ function getPool() {
13
+ return null;
14
+ }
15
+
16
+ module.exports = { executeQuery, getPool };
@@ -32,6 +32,7 @@ class AuthManager {
32
32
  region: options.cognitoRegion || 'us-east-2'
33
33
  },
34
34
  callbackPorts: options.callbackPorts || [9876, 9877, 9878, 9879, 9880],
35
+ // AEGIS review: path is config-driven (homedir + hardcoded), not HTTP user input — no traversal risk
35
36
  authFile: options.authFile || path.join(os.homedir(), '.mindmeld', 'auth.json')
36
37
  };
37
38
  }
@@ -58,8 +59,8 @@ class AuthManager {
58
59
  */
59
60
  async saveAuth(auth) {
60
61
  const dir = path.dirname(this.config.authFile);
61
- await fsPromises.mkdir(dir, { recursive: true });
62
- await fsPromises.writeFile(this.config.authFile, JSON.stringify(auth, null, 2));
62
+ await fsPromises.mkdir(dir, { recursive: true, mode: 0o700 });
63
+ await fsPromises.writeFile(this.config.authFile, JSON.stringify(auth, null, 2), { mode: 0o600 });
63
64
  }
64
65
 
65
66
  /**