@equilateral_ai/mindmeld 3.4.0 → 3.5.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/pre-compact.js +269 -21
- package/hooks/session-end.js +112 -3
- package/hooks/session-start.js +139 -34
- package/package.json +1 -1
- package/scripts/auth-login.js +45 -8
- package/src/core/StandardsIngestion.js +3 -1
- package/src/handlers/collaborators/collaboratorList.js +4 -10
- package/src/handlers/correlations/correlationsProjectGet.js +4 -13
- package/src/handlers/github/githubDiscoverPatterns.js +4 -8
- package/src/handlers/github/githubPatternsReview.js +4 -8
- package/src/handlers/helpers/decisionFrames.js +29 -0
- package/src/handlers/helpers/index.js +14 -0
- package/src/handlers/helpers/mindmeldMcpCore.js +1103 -0
- package/src/handlers/helpers/predictiveCache.js +51 -0
- package/src/handlers/helpers/projectAccess.js +88 -0
- package/src/handlers/mcp/mindmeldMcpHandler.js +8 -573
- package/src/handlers/mcp/mindmeldMcpStreamHandler.js +342 -0
- package/src/handlers/standards/discoveriesGet.js +4 -8
- package/src/handlers/standards/projectStandardsGet.js +5 -11
- package/src/handlers/standards/projectStandardsPut.js +19 -14
- package/src/handlers/standards/standardsParseUpload.js +4 -8
- package/src/handlers/standards/standardsRelevantPost.js +126 -29
- package/src/handlers/users/userGet.js +3 -3
package/hooks/session-start.js
CHANGED
|
@@ -427,7 +427,7 @@ async function loadAuthToken() {
|
|
|
427
427
|
* @returns {Promise<Object>} Cognito constructor options or empty object (uses prod defaults)
|
|
428
428
|
*/
|
|
429
429
|
async function loadCognitoConfig() {
|
|
430
|
-
// 1. Check .mindmeld/config.json first (
|
|
430
|
+
// 1. Check .mindmeld/config.json first (explicit project config, always wins)
|
|
431
431
|
try {
|
|
432
432
|
const mindmeldConfigPath = path.join(process.cwd(), '.mindmeld', 'config.json');
|
|
433
433
|
const content = await fs.readFile(mindmeldConfigPath, 'utf-8');
|
|
@@ -442,21 +442,28 @@ async function loadCognitoConfig() {
|
|
|
442
442
|
// No .mindmeld/config.json or no auth section
|
|
443
443
|
}
|
|
444
444
|
|
|
445
|
-
// 2. Fallback to .myworld.json
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
445
|
+
// 2. Fallback to .myworld.json — but only for consumer projects.
|
|
446
|
+
// If this IS the MindMeld source repo, .myworld.json has dev Cognito
|
|
447
|
+
// config that would authenticate against the wrong user pool.
|
|
448
|
+
const isSourceRepo = await isMindMeldSourceRepo();
|
|
449
|
+
if (!isSourceRepo) {
|
|
450
|
+
try {
|
|
451
|
+
const configPath = path.join(process.cwd(), '.myworld.json');
|
|
452
|
+
const content = await fs.readFile(configPath, 'utf-8');
|
|
453
|
+
const config = JSON.parse(content);
|
|
454
|
+
const auth = config.deployments?.backend?.auth;
|
|
455
|
+
if (auth?.domain && auth?.client_id) {
|
|
456
|
+
return {
|
|
457
|
+
cognitoDomain: `${auth.domain}.auth.us-east-2.amazoncognito.com`,
|
|
458
|
+
cognitoClientId: auth.client_id
|
|
459
|
+
};
|
|
460
|
+
}
|
|
461
|
+
} catch (error) {
|
|
462
|
+
// No .myworld.json
|
|
456
463
|
}
|
|
457
|
-
} catch (error) {
|
|
458
|
-
// No .myworld.json — use production defaults
|
|
459
464
|
}
|
|
465
|
+
|
|
466
|
+
// 3. Production defaults (AuthManager has these built-in)
|
|
460
467
|
return {};
|
|
461
468
|
}
|
|
462
469
|
|
|
@@ -483,15 +490,40 @@ function spawnBackgroundLogin() {
|
|
|
483
490
|
}
|
|
484
491
|
}
|
|
485
492
|
|
|
493
|
+
/**
|
|
494
|
+
* Detect if we're running inside the MindMeld source repository (developer context)
|
|
495
|
+
* vs. a consumer project that uses MindMeld for standards injection.
|
|
496
|
+
*
|
|
497
|
+
* When developing MindMeld itself, .myworld.json contains deployment config pointing
|
|
498
|
+
* at dev/staging environments. The hook should still use production for its own
|
|
499
|
+
* standards and auth — dev URLs are for the product, not for the hook's API calls.
|
|
500
|
+
*
|
|
501
|
+
* @returns {Promise<boolean>} True if this is the MindMeld source repo
|
|
502
|
+
*/
|
|
503
|
+
async function isMindMeldSourceRepo() {
|
|
504
|
+
try {
|
|
505
|
+
const configPath = path.join(process.cwd(), '.myworld.json');
|
|
506
|
+
const content = await fs.readFile(configPath, 'utf-8');
|
|
507
|
+
const config = JSON.parse(content);
|
|
508
|
+
const productName = (config.project?.product || '').toLowerCase();
|
|
509
|
+
return productName === 'mindmeld';
|
|
510
|
+
} catch (error) {
|
|
511
|
+
return false;
|
|
512
|
+
}
|
|
513
|
+
}
|
|
514
|
+
|
|
486
515
|
/**
|
|
487
516
|
* Load API configuration
|
|
488
|
-
* Priority: .mindmeld/config.json → .myworld.json → env var → production default
|
|
489
|
-
*
|
|
490
|
-
*
|
|
517
|
+
* Priority: .mindmeld/config.json → .myworld.json (consumer only) → env var → production default
|
|
518
|
+
*
|
|
519
|
+
* When running inside the MindMeld source repo, .myworld.json is skipped because it
|
|
520
|
+
* contains dev/staging deployment config for the product — not config for the hook's
|
|
521
|
+
* own API calls, which should always target production.
|
|
522
|
+
*
|
|
491
523
|
* @returns {Promise<{apiUrl: string}>}
|
|
492
524
|
*/
|
|
493
525
|
async function loadApiConfig() {
|
|
494
|
-
// 1. Check .mindmeld/config.json first (
|
|
526
|
+
// 1. Check .mindmeld/config.json first (explicit project config, always wins)
|
|
495
527
|
try {
|
|
496
528
|
const mindmeldConfigPath = path.join(process.cwd(), '.mindmeld', 'config.json');
|
|
497
529
|
const content = await fs.readFile(mindmeldConfigPath, 'utf-8');
|
|
@@ -503,20 +535,28 @@ async function loadApiConfig() {
|
|
|
503
535
|
// No .mindmeld/config.json or no apiUrl
|
|
504
536
|
}
|
|
505
537
|
|
|
506
|
-
// 2. Fallback to .myworld.json
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
538
|
+
// 2. Fallback to .myworld.json — but only for consumer projects.
|
|
539
|
+
// If this IS the MindMeld source repo, .myworld.json has dev deployment
|
|
540
|
+
// URLs that would send hook API calls to the wrong environment.
|
|
541
|
+
const isSourceRepo = await isMindMeldSourceRepo();
|
|
542
|
+
if (!isSourceRepo) {
|
|
543
|
+
try {
|
|
544
|
+
const configPath = path.join(process.cwd(), '.myworld.json');
|
|
545
|
+
const content = await fs.readFile(configPath, 'utf-8');
|
|
546
|
+
const config = JSON.parse(content);
|
|
547
|
+
const backend = config.deployments?.backend;
|
|
548
|
+
if (backend?.api?.base_url) {
|
|
549
|
+
return { apiUrl: backend.api.base_url };
|
|
550
|
+
}
|
|
551
|
+
} catch (error) {
|
|
552
|
+
// No .myworld.json
|
|
553
|
+
}
|
|
519
554
|
}
|
|
555
|
+
|
|
556
|
+
// 3. Environment variable or production default
|
|
557
|
+
return {
|
|
558
|
+
apiUrl: process.env.MINDMELD_API_URL || 'https://api.mindmeld.dev'
|
|
559
|
+
};
|
|
520
560
|
}
|
|
521
561
|
|
|
522
562
|
/**
|
|
@@ -565,7 +605,9 @@ async function fetchRelevantStandardsFromAPI(apiUrl, authToken, characteristics,
|
|
|
565
605
|
try {
|
|
566
606
|
const parsed = JSON.parse(data);
|
|
567
607
|
if (res.statusCode >= 400) {
|
|
568
|
-
|
|
608
|
+
const err = new Error(parsed.message || `HTTP ${res.statusCode}`);
|
|
609
|
+
err.statusCode = res.statusCode;
|
|
610
|
+
reject(err);
|
|
569
611
|
} else {
|
|
570
612
|
resolve(parsed.data?.standards || parsed.standards || []);
|
|
571
613
|
}
|
|
@@ -767,6 +809,14 @@ async function injectContext() {
|
|
|
767
809
|
// Subscription enforcement — do NOT fall through to file-based injection
|
|
768
810
|
console.error('[MindMeld] Active subscription required. Subscribe at app.mindmeld.dev');
|
|
769
811
|
return '';
|
|
812
|
+
} else if (standardsResult.status === 'rejected' &&
|
|
813
|
+
(standardsResult.reason.statusCode === 401 || standardsResult.reason.message === 'Unauthorized')) {
|
|
814
|
+
// Auth token expired or invalid — trigger re-auth and use file-based fallback
|
|
815
|
+
console.error('[MindMeld] Auth token expired. Triggering re-authentication...');
|
|
816
|
+
spawnBackgroundLogin();
|
|
817
|
+
const categories = mindmeld.relevanceDetector.mapCharacteristicsToCategories(characteristics);
|
|
818
|
+
relevantStandards = await mindmeld.relevanceDetector.loadStandardsFromFiles(categories, characteristics);
|
|
819
|
+
console.error(`[MindMeld] ${relevantStandards.length} standards from file fallback (scored)`);
|
|
770
820
|
} else {
|
|
771
821
|
if (standardsResult.status === 'rejected') {
|
|
772
822
|
console.error(`[MindMeld] API fallback: ${standardsResult.reason.message}`);
|
|
@@ -893,6 +943,20 @@ async function cacheSubagentContext(standards, teamPatterns, projectName) {
|
|
|
893
943
|
console.error(`[MindMeld] Cached subagent context (${sections.length} lines)`);
|
|
894
944
|
}
|
|
895
945
|
|
|
946
|
+
/**
|
|
947
|
+
* Get maturity tier prefix for a standard
|
|
948
|
+
* @param {string|undefined} maturityTier - The maturity_tier from the standard
|
|
949
|
+
* @returns {string} Prefix string based on tier
|
|
950
|
+
*/
|
|
951
|
+
function getMaturityPrefix(maturityTier) {
|
|
952
|
+
switch (maturityTier) {
|
|
953
|
+
case 'reinforced': return '**[MUST FOLLOW]** ';
|
|
954
|
+
case 'solidified': return '**[SHOULD FOLLOW]** ';
|
|
955
|
+
case 'provisional':
|
|
956
|
+
default: return '**[CONSIDER]** ';
|
|
957
|
+
}
|
|
958
|
+
}
|
|
959
|
+
|
|
896
960
|
/**
|
|
897
961
|
* Format context injection for Claude Code
|
|
898
962
|
* @param {object} data - Context data to inject
|
|
@@ -968,8 +1032,15 @@ function formatContextInjection(data) {
|
|
|
968
1032
|
const standards = [];
|
|
969
1033
|
const workflows = [];
|
|
970
1034
|
|
|
1035
|
+
const businessInvariants = [];
|
|
971
1036
|
if (relevantStandards && relevantStandards.length > 0) {
|
|
972
1037
|
for (const item of relevantStandards) {
|
|
1038
|
+
// Separate business invariants from code standards
|
|
1039
|
+
if (item.content_type === 'business_invariant') {
|
|
1040
|
+
businessInvariants.push(item);
|
|
1041
|
+
continue;
|
|
1042
|
+
}
|
|
1043
|
+
|
|
973
1044
|
// Detect workflows: check type flag, rule prefix, keywords, or structured examples
|
|
974
1045
|
const isWorkflow = item.type === 'workflow' ||
|
|
975
1046
|
(item.rule && item.rule.startsWith('WORKFLOW:')) ||
|
|
@@ -990,7 +1061,9 @@ function formatContextInjection(data) {
|
|
|
990
1061
|
sections.push('');
|
|
991
1062
|
|
|
992
1063
|
for (const standard of standards) {
|
|
993
|
-
|
|
1064
|
+
const tierPrefix = getMaturityPrefix(standard.maturity_tier);
|
|
1065
|
+
const lbPrefix = standard.load_bearing ? '[CRITICAL] ' : '';
|
|
1066
|
+
sections.push(`### ${tierPrefix}${lbPrefix}${standard.element}`);
|
|
994
1067
|
sections.push(`**Category**: ${standard.category}`);
|
|
995
1068
|
// Add fingerprint to rule text
|
|
996
1069
|
sections.push(`**Rule**: ${standard.rule} ${fingerprintStr}`);
|
|
@@ -1018,6 +1091,11 @@ function formatContextInjection(data) {
|
|
|
1018
1091
|
}
|
|
1019
1092
|
}
|
|
1020
1093
|
|
|
1094
|
+
if (standard.consequence_tier === 'IRREVERSIBLE' || standard.consequence_tier === 'EXTERNAL_SIDE_EFFECT') {
|
|
1095
|
+
sections.push('');
|
|
1096
|
+
sections.push('⚠️ CONSEQUENCE: This standard involves irreversible/external actions. Verify before proceeding.');
|
|
1097
|
+
}
|
|
1098
|
+
|
|
1021
1099
|
sections.push('');
|
|
1022
1100
|
}
|
|
1023
1101
|
}
|
|
@@ -1033,7 +1111,8 @@ function formatContextInjection(data) {
|
|
|
1033
1111
|
? workflow.examples[0]
|
|
1034
1112
|
: null;
|
|
1035
1113
|
|
|
1036
|
-
|
|
1114
|
+
const tierPrefix = getMaturityPrefix(workflow.maturity_tier);
|
|
1115
|
+
sections.push(`### ${tierPrefix}${workflow.element} ${fingerprintStr}`);
|
|
1037
1116
|
sections.push(`**Category**: ${workflow.category}`);
|
|
1038
1117
|
|
|
1039
1118
|
if (workflowData) {
|
|
@@ -1085,6 +1164,32 @@ function formatContextInjection(data) {
|
|
|
1085
1164
|
}
|
|
1086
1165
|
}
|
|
1087
1166
|
|
|
1167
|
+
// Business invariants (rendered with rationale and consequences)
|
|
1168
|
+
if (businessInvariants.length > 0) {
|
|
1169
|
+
sections.push('## Business Invariants');
|
|
1170
|
+
sections.push('');
|
|
1171
|
+
|
|
1172
|
+
for (const invariant of businessInvariants) {
|
|
1173
|
+
const tierPrefix = getMaturityPrefix(invariant.maturity_tier);
|
|
1174
|
+
sections.push(`### ${tierPrefix}${invariant.element}`);
|
|
1175
|
+
sections.push(`**Domain**: ${invariant.category}`);
|
|
1176
|
+
sections.push(`**Invariant**: ${invariant.rule} ${fingerprintStr}`);
|
|
1177
|
+
if (invariant.rationale) {
|
|
1178
|
+
sections.push(`**Rationale**: ${invariant.rationale}`);
|
|
1179
|
+
}
|
|
1180
|
+
if (invariant.consequences) {
|
|
1181
|
+
sections.push(`**If violated**: ${invariant.consequences}`);
|
|
1182
|
+
}
|
|
1183
|
+
if (invariant.exceptions && Array.isArray(invariant.exceptions) && invariant.exceptions.length > 0) {
|
|
1184
|
+
sections.push('**Exceptions**:');
|
|
1185
|
+
for (const ex of invariant.exceptions) {
|
|
1186
|
+
sections.push(`- ${ex}`);
|
|
1187
|
+
}
|
|
1188
|
+
}
|
|
1189
|
+
sections.push('');
|
|
1190
|
+
}
|
|
1191
|
+
}
|
|
1192
|
+
|
|
1088
1193
|
// Team patterns (high correlation only)
|
|
1089
1194
|
if (teamPatterns && teamPatterns.length > 0) {
|
|
1090
1195
|
sections.push('## Team Patterns');
|
package/package.json
CHANGED
package/scripts/auth-login.js
CHANGED
|
@@ -6,7 +6,9 @@
|
|
|
6
6
|
* when no auth token is found. Opens browser for Cognito PKCE login,
|
|
7
7
|
* waits for callback, saves tokens to ~/.mindmeld/auth.json.
|
|
8
8
|
*
|
|
9
|
-
*
|
|
9
|
+
* Uses .mindmeld/config.json or .myworld.json (consumer projects only) for
|
|
10
|
+
* Cognito config. When running inside the MindMeld source repo, skips
|
|
11
|
+
* .myworld.json dev config and uses production defaults.
|
|
10
12
|
*
|
|
11
13
|
* Usage:
|
|
12
14
|
* node scripts/auth-login.js
|
|
@@ -19,23 +21,58 @@ const path = require('path');
|
|
|
19
21
|
const { AuthManager } = require('../src/core/AuthManager');
|
|
20
22
|
|
|
21
23
|
/**
|
|
22
|
-
*
|
|
24
|
+
* Detect if we're running inside the MindMeld source repository
|
|
23
25
|
*/
|
|
24
|
-
function
|
|
26
|
+
function isMindMeldSourceRepo() {
|
|
25
27
|
try {
|
|
26
28
|
const configPath = path.join(process.cwd(), '.myworld.json');
|
|
27
29
|
const content = fs.readFileSync(configPath, 'utf-8');
|
|
28
30
|
const config = JSON.parse(content);
|
|
29
|
-
|
|
30
|
-
|
|
31
|
+
return (config.project?.product || '').toLowerCase() === 'mindmeld';
|
|
32
|
+
} catch (error) {
|
|
33
|
+
return false;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Load Cognito config — .mindmeld/config.json first, then .myworld.json
|
|
39
|
+
* for consumer projects only. MindMeld source repo uses production defaults.
|
|
40
|
+
*/
|
|
41
|
+
function loadCognitoConfig() {
|
|
42
|
+
// 1. Explicit project config (always wins)
|
|
43
|
+
try {
|
|
44
|
+
const mindmeldConfigPath = path.join(process.cwd(), '.mindmeld', 'config.json');
|
|
45
|
+
const content = fs.readFileSync(mindmeldConfigPath, 'utf-8');
|
|
46
|
+
const config = JSON.parse(content);
|
|
47
|
+
if (config.auth?.cognitoDomain && config.auth?.cognitoClientId) {
|
|
31
48
|
return {
|
|
32
|
-
cognitoDomain:
|
|
33
|
-
cognitoClientId: auth.
|
|
49
|
+
cognitoDomain: config.auth.cognitoDomain,
|
|
50
|
+
cognitoClientId: config.auth.cognitoClientId
|
|
34
51
|
};
|
|
35
52
|
}
|
|
36
53
|
} catch (error) {
|
|
37
|
-
// No .
|
|
54
|
+
// No .mindmeld/config.json or no auth section
|
|
38
55
|
}
|
|
56
|
+
|
|
57
|
+
// 2. .myworld.json — only for consumer projects
|
|
58
|
+
if (!isMindMeldSourceRepo()) {
|
|
59
|
+
try {
|
|
60
|
+
const configPath = path.join(process.cwd(), '.myworld.json');
|
|
61
|
+
const content = fs.readFileSync(configPath, 'utf-8');
|
|
62
|
+
const config = JSON.parse(content);
|
|
63
|
+
const auth = config.deployments?.backend?.auth;
|
|
64
|
+
if (auth?.domain && auth?.client_id) {
|
|
65
|
+
return {
|
|
66
|
+
cognitoDomain: `${auth.domain}.auth.us-east-2.amazoncognito.com`,
|
|
67
|
+
cognitoClientId: auth.client_id
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
} catch (error) {
|
|
71
|
+
// No .myworld.json
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// 3. Production defaults (AuthManager has these built-in)
|
|
39
76
|
return {};
|
|
40
77
|
}
|
|
41
78
|
|
|
@@ -921,7 +921,9 @@ class StandardsIngestion {
|
|
|
921
921
|
examples = EXCLUDED.examples,
|
|
922
922
|
cost_impact = EXCLUDED.cost_impact,
|
|
923
923
|
keywords = EXCLUDED.keywords,
|
|
924
|
-
last_updated = NOW()
|
|
924
|
+
last_updated = NOW(),
|
|
925
|
+
last_seen_at = NOW(),
|
|
926
|
+
occurrence_count = COALESCE(rapport.standards_patterns.occurrence_count, 0) + 1
|
|
925
927
|
`, [
|
|
926
928
|
pattern.pattern_id,
|
|
927
929
|
pattern.file_name,
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
* Auth: Cognito JWT required
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
|
-
const { wrapHandler, executeQuery, createSuccessResponse, createErrorResponse, handleError } = require('./helpers');
|
|
9
|
+
const { wrapHandler, executeQuery, createSuccessResponse, createErrorResponse, handleError, verifyProjectAccess } = require('./helpers');
|
|
10
10
|
|
|
11
11
|
/**
|
|
12
12
|
* List project collaborators
|
|
@@ -26,15 +26,9 @@ async function listCollaborators({ queryStringParameters: queryParams = {}, requ
|
|
|
26
26
|
return createErrorResponse(400, 'projectId is required');
|
|
27
27
|
}
|
|
28
28
|
|
|
29
|
-
//
|
|
30
|
-
const
|
|
31
|
-
|
|
32
|
-
FROM rapport.project_collaborators pc
|
|
33
|
-
WHERE pc.project_id = $1 AND pc.email_address = $2
|
|
34
|
-
`;
|
|
35
|
-
const accessCheck = await executeQuery(accessQuery, [projectId, email]);
|
|
36
|
-
|
|
37
|
-
if (accessCheck.rowCount === 0) {
|
|
29
|
+
// Verify user has access to project (collaborator or company member)
|
|
30
|
+
const projectAccess = await verifyProjectAccess(projectId, email);
|
|
31
|
+
if (!projectAccess) {
|
|
38
32
|
return createErrorResponse(403, 'You do not have access to this project');
|
|
39
33
|
}
|
|
40
34
|
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
* - Pattern effectiveness for project
|
|
14
14
|
*/
|
|
15
15
|
|
|
16
|
-
const { wrapHandler, executeQuery, createSuccessResponse, createErrorResponse } = require('./helpers');
|
|
16
|
+
const { wrapHandler, executeQuery, createSuccessResponse, createErrorResponse, verifyProjectAccess } = require('./helpers');
|
|
17
17
|
const { CorrelationAnalyzer } = require('./core/CorrelationAnalyzer');
|
|
18
18
|
|
|
19
19
|
exports.handler = wrapHandler(async (event, context) => {
|
|
@@ -33,21 +33,12 @@ exports.handler = wrapHandler(async (event, context) => {
|
|
|
33
33
|
const queryParams = event.queryStringParameters || {};
|
|
34
34
|
const lookbackDays = parseInt(queryParams.lookbackDays) || 30;
|
|
35
35
|
|
|
36
|
-
// Verify user has access to this project
|
|
37
|
-
const
|
|
38
|
-
|
|
39
|
-
FROM rapport.projects p
|
|
40
|
-
JOIN rapport.project_collaborators pc ON p.project_id = pc.project_id
|
|
41
|
-
WHERE p.project_id = $1
|
|
42
|
-
AND pc.email_address = $2
|
|
43
|
-
`, [projectId, email]);
|
|
44
|
-
|
|
45
|
-
if (accessResult.rows.length === 0) {
|
|
36
|
+
// Verify user has access to this project (collaborator or company member)
|
|
37
|
+
const project = await verifyProjectAccess(projectId, email);
|
|
38
|
+
if (!project) {
|
|
46
39
|
return createErrorResponse(403, 'Access denied to this project');
|
|
47
40
|
}
|
|
48
41
|
|
|
49
|
-
const project = accessResult.rows[0];
|
|
50
|
-
|
|
51
42
|
// Initialize analyzer
|
|
52
43
|
const analyzer = new CorrelationAnalyzer();
|
|
53
44
|
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
* - Maps tech stack to relevant YAML standards categories
|
|
13
13
|
*/
|
|
14
14
|
|
|
15
|
-
const { wrapHandler, executeQuery, createSuccessResponse, createErrorResponse } = require('./helpers');
|
|
15
|
+
const { wrapHandler, executeQuery, createSuccessResponse, createErrorResponse, verifyProjectAccess } = require('./helpers');
|
|
16
16
|
const { LLMPatternDetector } = require('./core/LLMPatternDetector');
|
|
17
17
|
const crypto = require('crypto');
|
|
18
18
|
const https = require('https');
|
|
@@ -247,13 +247,9 @@ async function githubDiscoverPatterns({ body, requestContext }) {
|
|
|
247
247
|
|
|
248
248
|
const targetBranch = branch || 'main';
|
|
249
249
|
|
|
250
|
-
// Verify user has access to project
|
|
251
|
-
const
|
|
252
|
-
|
|
253
|
-
WHERE project_id = $1 AND email_address = $2
|
|
254
|
-
`, [project_id, email]);
|
|
255
|
-
|
|
256
|
-
if (accessResult.rowCount === 0) {
|
|
250
|
+
// Verify user has access to project (collaborator or company member)
|
|
251
|
+
const projectAccess = await verifyProjectAccess(project_id, email);
|
|
252
|
+
if (!projectAccess) {
|
|
257
253
|
return createErrorResponse(403, 'Access denied to project');
|
|
258
254
|
}
|
|
259
255
|
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
* Body: { project_id, approvals: [{ discovery_id, approved }] }
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
|
-
const { wrapHandler, executeQuery, createSuccessResponse, createErrorResponse } = require('./helpers');
|
|
9
|
+
const { wrapHandler, executeQuery, createSuccessResponse, createErrorResponse, verifyProjectAccess } = require('./helpers');
|
|
10
10
|
|
|
11
11
|
async function githubPatternsReview({ body, requestContext }) {
|
|
12
12
|
try {
|
|
@@ -21,13 +21,9 @@ async function githubPatternsReview({ body, requestContext }) {
|
|
|
21
21
|
return createErrorResponse(400, 'project_id and approvals array are required');
|
|
22
22
|
}
|
|
23
23
|
|
|
24
|
-
// Verify user has access to project
|
|
25
|
-
const
|
|
26
|
-
|
|
27
|
-
WHERE project_id = $1 AND email_address = $2
|
|
28
|
-
`, [project_id, email]);
|
|
29
|
-
|
|
30
|
-
if (accessResult.rowCount === 0) {
|
|
24
|
+
// Verify user has access to project (collaborator or company member)
|
|
25
|
+
const projectAccess = await verifyProjectAccess(project_id, email);
|
|
26
|
+
if (!projectAccess) {
|
|
31
27
|
return createErrorResponse(403, 'Access denied to project');
|
|
32
28
|
}
|
|
33
29
|
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
const { executeQuery } = require('./index');
|
|
2
|
+
|
|
3
|
+
async function createFrame({ projectId, sessionId, standardIds, confidence, context }) {
|
|
4
|
+
const result = await executeQuery(`
|
|
5
|
+
INSERT INTO rapport.decision_frames (project_id, session_id, standard_ids, confidence, context)
|
|
6
|
+
VALUES ($1, $2, $3, $4, $5)
|
|
7
|
+
RETURNING frame_id, created_at
|
|
8
|
+
`, [projectId, sessionId || null, standardIds, confidence || 0, JSON.stringify(context || {})]);
|
|
9
|
+
return result.rows[0];
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
async function getFrame(frameId) {
|
|
13
|
+
const result = await executeQuery(`
|
|
14
|
+
SELECT * FROM rapport.decision_frames WHERE frame_id = $1
|
|
15
|
+
`, [frameId]);
|
|
16
|
+
return result.rows[0] || null;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
async function getProjectFrames(projectId, limit = 20) {
|
|
20
|
+
const result = await executeQuery(`
|
|
21
|
+
SELECT * FROM rapport.decision_frames
|
|
22
|
+
WHERE project_id = $1
|
|
23
|
+
ORDER BY created_at DESC
|
|
24
|
+
LIMIT $2
|
|
25
|
+
`, [projectId, limit]);
|
|
26
|
+
return result.rows;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
module.exports = { createFrame, getFrame, getProjectFrames };
|
|
@@ -44,6 +44,9 @@ const {
|
|
|
44
44
|
getAddonPriceId
|
|
45
45
|
} = require('./subscriptionTiers');
|
|
46
46
|
const checkSuperAdmin = require('./checkSuperAdmin');
|
|
47
|
+
const { verifyProjectAccess, verifyProjectRole } = require('./projectAccess');
|
|
48
|
+
const { getPredictedStandards, logStandardsActivation } = require('./predictiveCache');
|
|
49
|
+
const { createFrame, getFrame, getProjectFrames } = require('./decisionFrames');
|
|
47
50
|
const {
|
|
48
51
|
AuditEventType,
|
|
49
52
|
EntityType,
|
|
@@ -112,6 +115,17 @@ module.exports = {
|
|
|
112
115
|
|
|
113
116
|
// Authorization
|
|
114
117
|
checkSuperAdmin,
|
|
118
|
+
verifyProjectAccess,
|
|
119
|
+
verifyProjectRole,
|
|
120
|
+
|
|
121
|
+
// Predictive standards caching
|
|
122
|
+
getPredictedStandards,
|
|
123
|
+
logStandardsActivation,
|
|
124
|
+
|
|
125
|
+
// Decision frames
|
|
126
|
+
createFrame,
|
|
127
|
+
getFrame,
|
|
128
|
+
getProjectFrames,
|
|
115
129
|
|
|
116
130
|
// Audit logging
|
|
117
131
|
AuditEventType,
|