@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
|
@@ -1,405 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Standards Relevant Post Handler
|
|
3
|
-
*
|
|
4
|
-
* Returns top 10 relevant standards for a project based on detected characteristics.
|
|
5
|
-
* The session-start hook calls this endpoint with locally-detected project characteristics;
|
|
6
|
-
* the handler maps them to categories, queries the database, ranks results, and returns
|
|
7
|
-
* the most relevant standards for injection into the AI coding session.
|
|
8
|
-
*
|
|
9
|
-
* POST /api/standards/relevant
|
|
10
|
-
* Auth: Cognito JWT required
|
|
11
|
-
* Body: { characteristics, projectId?, preferences? }
|
|
12
|
-
*/
|
|
13
|
-
|
|
14
|
-
const { wrapHandler, executeQuery, createSuccessResponse, createErrorResponse, getPredictedStandards, logStandardsActivation, createFrame } = require('./helpers');
|
|
15
|
-
|
|
16
|
-
/**
|
|
17
|
-
* Category weights for relevance scoring
|
|
18
|
-
*/
|
|
19
|
-
const CATEGORY_WEIGHTS = {
|
|
20
|
-
// Code standard categories
|
|
21
|
-
'serverless-saas-aws': 1.0,
|
|
22
|
-
'frontend-development': 1.0,
|
|
23
|
-
'database': 0.9,
|
|
24
|
-
'backend': 0.9,
|
|
25
|
-
'compliance-security': 0.9,
|
|
26
|
-
'deployment': 0.8,
|
|
27
|
-
'testing': 0.7,
|
|
28
|
-
'real-time-systems': 0.7,
|
|
29
|
-
'well-architected': 0.7,
|
|
30
|
-
'cost-optimization': 0.7,
|
|
31
|
-
'multi-agent-orchestration': 0.1, // Infrastructure config, rarely needed in coding sessions
|
|
32
|
-
// Business domains — moderate weights, boosted by recency when relevant
|
|
33
|
-
'ip-strategy': 0.6,
|
|
34
|
-
'architecture-decisions': 0.8,
|
|
35
|
-
'go-to-market': 0.6,
|
|
36
|
-
'operations': 0.5,
|
|
37
|
-
'legal-process': 0.5,
|
|
38
|
-
'finance': 0.5,
|
|
39
|
-
'communication': 0.4,
|
|
40
|
-
'product-strategy': 0.6,
|
|
41
|
-
'investor-relations': 0.4,
|
|
42
|
-
};
|
|
43
|
-
|
|
44
|
-
/**
|
|
45
|
-
* Map project characteristics to relevant standard categories
|
|
46
|
-
*/
|
|
47
|
-
function mapCharacteristicsToCategories(characteristics) {
|
|
48
|
-
const categories = new Set();
|
|
49
|
-
|
|
50
|
-
if (characteristics.hasLambda || characteristics.hasSAM) {
|
|
51
|
-
categories.add('serverless-saas-aws');
|
|
52
|
-
categories.add('cost-optimization');
|
|
53
|
-
}
|
|
54
|
-
if (characteristics.hasReact) {
|
|
55
|
-
categories.add('frontend-development');
|
|
56
|
-
}
|
|
57
|
-
if (characteristics.hasDatabase) {
|
|
58
|
-
categories.add('database');
|
|
59
|
-
}
|
|
60
|
-
if (characteristics.hasMultiAgent) {
|
|
61
|
-
categories.add('multi-agent-orchestration');
|
|
62
|
-
}
|
|
63
|
-
if (characteristics.hasAPI) {
|
|
64
|
-
categories.add('backend');
|
|
65
|
-
}
|
|
66
|
-
if (characteristics.hasTests) {
|
|
67
|
-
categories.add('testing');
|
|
68
|
-
}
|
|
69
|
-
if (characteristics.hasSAM || characteristics.hasLambda || characteristics.hasAPI) {
|
|
70
|
-
categories.add('deployment');
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
// Always relevant
|
|
74
|
-
categories.add('compliance-security');
|
|
75
|
-
categories.add('well-architected');
|
|
76
|
-
|
|
77
|
-
return Array.from(categories);
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
/**
|
|
81
|
-
* Rank standards by relevance score
|
|
82
|
-
*/
|
|
83
|
-
function rankStandards(standards, recentCategories) {
|
|
84
|
-
return standards.map(standard => {
|
|
85
|
-
let score = 0;
|
|
86
|
-
|
|
87
|
-
// Base score from correlation
|
|
88
|
-
score += (standard.correlation || 1.0) * 40;
|
|
89
|
-
|
|
90
|
-
// Maturity score (includes business invariant lifecycle values)
|
|
91
|
-
const maturityScores = { enforced: 30, reinforced: 25, validated: 20, solidified: 15, recommended: 10, provisional: 5 };
|
|
92
|
-
score += maturityScores[standard.maturity] || 0;
|
|
93
|
-
|
|
94
|
-
// Category weight
|
|
95
|
-
const categoryWeight = CATEGORY_WEIGHTS[standard.category] || 0.5;
|
|
96
|
-
score += categoryWeight * 20;
|
|
97
|
-
|
|
98
|
-
// File applicability bonus
|
|
99
|
-
if (standard.applicable_files && standard.applicable_files.length > 0) {
|
|
100
|
-
score += 5;
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
// Cost impact bonus (critical patterns)
|
|
104
|
-
if (standard.cost_impact && standard.cost_impact.severity === 'critical') {
|
|
105
|
-
score += 10;
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
// Anti-patterns bonus (important to know what NOT to do)
|
|
109
|
-
if (standard.anti_patterns) {
|
|
110
|
-
const apCount = Array.isArray(standard.anti_patterns)
|
|
111
|
-
? standard.anti_patterns.length
|
|
112
|
-
: Object.keys(standard.anti_patterns).length;
|
|
113
|
-
if (apCount > 0) score += 5;
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
// Workflow bonus — workflows are high-value procedural knowledge
|
|
117
|
-
const isWorkflow = (standard.rule && standard.rule.startsWith('WORKFLOW:'))
|
|
118
|
-
|| (Array.isArray(standard.keywords) && standard.keywords.includes('workflow'));
|
|
119
|
-
if (isWorkflow) score += 10;
|
|
120
|
-
|
|
121
|
-
// Rationale bonus — business invariants with documented reasoning
|
|
122
|
-
if (standard.rationale) score += 5;
|
|
123
|
-
|
|
124
|
-
// Recency bonus — boost categories the user has been working in recently
|
|
125
|
-
// Scaled by category weight to prevent feedback loops (low-weight categories
|
|
126
|
-
// that got injected shouldn't bootstrap themselves back into the top 10)
|
|
127
|
-
if (recentCategories && recentCategories[standard.category]) {
|
|
128
|
-
const usageCount = recentCategories[standard.category];
|
|
129
|
-
let rawBonus;
|
|
130
|
-
if (usageCount >= 8) rawBonus = 25;
|
|
131
|
-
else if (usageCount >= 4) rawBonus = 18;
|
|
132
|
-
else rawBonus = 10;
|
|
133
|
-
score += rawBonus * categoryWeight;
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
// Recency decay: patterns not seen in 30+ days get penalized
|
|
137
|
-
if (standard.last_seen_at) {
|
|
138
|
-
const daysSinceLastSeen = (Date.now() - new Date(standard.last_seen_at).getTime()) / (1000 * 60 * 60 * 24);
|
|
139
|
-
if (daysSinceLastSeen > 30) {
|
|
140
|
-
score *= 0.5;
|
|
141
|
-
}
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
// Frequency boost: high-occurrence patterns are more valuable
|
|
145
|
-
const occurrenceCount = standard.occurrence_count || 1;
|
|
146
|
-
if (occurrenceCount > 50) {
|
|
147
|
-
score *= 1.5;
|
|
148
|
-
} else if (occurrenceCount > 10) {
|
|
149
|
-
score *= 1.2;
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
return {
|
|
153
|
-
...standard,
|
|
154
|
-
relevance_score: Math.round(score * 10) / 10
|
|
155
|
-
};
|
|
156
|
-
}).sort((a, b) => {
|
|
157
|
-
// Load-bearing standards always sort to the top
|
|
158
|
-
const aLB = a.load_bearing ? 1 : 0;
|
|
159
|
-
const bLB = b.load_bearing ? 1 : 0;
|
|
160
|
-
if (bLB !== aLB) return bLB - aLB;
|
|
161
|
-
return b.relevance_score - a.relevance_score;
|
|
162
|
-
});
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
/**
|
|
166
|
-
* Apply user preferences to filter standards
|
|
167
|
-
*/
|
|
168
|
-
function applyPreferences(standards, preferences) {
|
|
169
|
-
if (!preferences) return standards;
|
|
170
|
-
|
|
171
|
-
const enabledCategories = preferences.enabled_categories || {};
|
|
172
|
-
const standardOverrides = preferences.standard_overrides || {};
|
|
173
|
-
|
|
174
|
-
return standards.filter(standard => {
|
|
175
|
-
const category = standard.category;
|
|
176
|
-
const standardPath = `${category}/${standard.element}`;
|
|
177
|
-
|
|
178
|
-
// Individual override takes priority
|
|
179
|
-
if (standardPath in standardOverrides) {
|
|
180
|
-
return standardOverrides[standardPath] === true;
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
// Category-level setting
|
|
184
|
-
if (category in enabledCategories) {
|
|
185
|
-
return enabledCategories[category] === true;
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
// Default: include
|
|
189
|
-
return true;
|
|
190
|
-
});
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
async function getRelevantStandards({ body, requestContext }) {
|
|
194
|
-
const email = requestContext.authorizer?.claims?.email
|
|
195
|
-
|| requestContext.authorizer?.jwt?.claims?.email;
|
|
196
|
-
|
|
197
|
-
if (!email) {
|
|
198
|
-
return createErrorResponse(401, 'Authentication required');
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
// Verify active subscription and resolve company_id for tenant isolation
|
|
202
|
-
const subResult = await executeQuery(`
|
|
203
|
-
SELECT c.subscription_tier, c.subscription_status, ue.company_id
|
|
204
|
-
FROM rapport.users u
|
|
205
|
-
JOIN rapport.clients c ON u.client_id = c.client_id
|
|
206
|
-
LEFT JOIN rapport.user_entitlements ue ON u.email_address = ue.email_address
|
|
207
|
-
WHERE u.email_address = $1
|
|
208
|
-
LIMIT 1
|
|
209
|
-
`, [email]);
|
|
210
|
-
|
|
211
|
-
let companyId = null;
|
|
212
|
-
if (subResult.rows.length > 0) {
|
|
213
|
-
const { subscription_tier, subscription_status } = subResult.rows[0];
|
|
214
|
-
companyId = subResult.rows[0].company_id;
|
|
215
|
-
if (!subscription_tier || subscription_tier === 'free') {
|
|
216
|
-
return createErrorResponse(403, 'Active MindMeld subscription required. Subscribe at app.mindmeld.dev');
|
|
217
|
-
}
|
|
218
|
-
if (subscription_status === 'canceled') {
|
|
219
|
-
return createErrorResponse(403, 'Subscription canceled. Resubscribe at app.mindmeld.dev');
|
|
220
|
-
}
|
|
221
|
-
}
|
|
222
|
-
|
|
223
|
-
// Parse request body
|
|
224
|
-
let requestBody;
|
|
225
|
-
try {
|
|
226
|
-
requestBody = typeof body === 'string' ? JSON.parse(body) : body;
|
|
227
|
-
} catch (e) {
|
|
228
|
-
return createErrorResponse(400, 'Invalid request body');
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
const { characteristics, preferences } = requestBody || {};
|
|
232
|
-
|
|
233
|
-
if (!characteristics || typeof characteristics !== 'object') {
|
|
234
|
-
return createErrorResponse(400, 'characteristics object is required');
|
|
235
|
-
}
|
|
236
|
-
|
|
237
|
-
// Map characteristics to categories
|
|
238
|
-
const categories = mapCharacteristicsToCategories(characteristics);
|
|
239
|
-
|
|
240
|
-
if (categories.length === 0) {
|
|
241
|
-
return createSuccessResponse({
|
|
242
|
-
standards: [],
|
|
243
|
-
categories: [],
|
|
244
|
-
total_matched: 0
|
|
245
|
-
}, 'No relevant categories detected');
|
|
246
|
-
}
|
|
247
|
-
|
|
248
|
-
// Query recent session categories first (fast), then merge into categories for main query
|
|
249
|
-
const recentCategories = {};
|
|
250
|
-
try {
|
|
251
|
-
const recencyResult = await executeQuery(`
|
|
252
|
-
SELECT sp.category, COUNT(*) as usage_count
|
|
253
|
-
FROM rapport.session_standards ss
|
|
254
|
-
JOIN rapport.sessions s ON s.session_id = ss.session_id
|
|
255
|
-
JOIN rapport.standards_patterns sp ON sp.pattern_id = ss.standard_id
|
|
256
|
-
WHERE s.email_address = $1
|
|
257
|
-
AND s.started_at >= NOW() - INTERVAL '7 days'
|
|
258
|
-
GROUP BY sp.category
|
|
259
|
-
ORDER BY usage_count DESC
|
|
260
|
-
LIMIT 5
|
|
261
|
-
`, [email]);
|
|
262
|
-
for (const row of recencyResult.rows) {
|
|
263
|
-
recentCategories[row.category] = parseInt(row.usage_count, 10);
|
|
264
|
-
}
|
|
265
|
-
} catch (err) {
|
|
266
|
-
console.error('[standardsRelevant] Recency query failed:', err.message);
|
|
267
|
-
}
|
|
268
|
-
|
|
269
|
-
// Merge recency categories into query — recent activity should always be represented
|
|
270
|
-
for (const category of Object.keys(recentCategories)) {
|
|
271
|
-
if (!categories.includes(category)) {
|
|
272
|
-
categories.push(category);
|
|
273
|
-
}
|
|
274
|
-
}
|
|
275
|
-
|
|
276
|
-
// Query standards — tenant-isolated via get_effective_standards()
|
|
277
|
-
const maturityList = ['enforced', 'validated', 'recommended', 'provisional', 'solidified', 'reinforced'];
|
|
278
|
-
const result = await executeQuery(`
|
|
279
|
-
SELECT * FROM rapport.get_effective_standards($1, $2::varchar[], $3::varchar[])
|
|
280
|
-
ORDER BY
|
|
281
|
-
CASE
|
|
282
|
-
WHEN maturity = 'enforced' THEN 1
|
|
283
|
-
WHEN maturity = 'reinforced' THEN 2
|
|
284
|
-
WHEN maturity = 'validated' THEN 3
|
|
285
|
-
WHEN maturity = 'solidified' THEN 4
|
|
286
|
-
WHEN maturity = 'recommended' THEN 5
|
|
287
|
-
ELSE 6
|
|
288
|
-
END,
|
|
289
|
-
correlation DESC
|
|
290
|
-
`, [companyId, categories, maturityList]);
|
|
291
|
-
|
|
292
|
-
// Predictive cache: fetch standards with >80% activation rate for this project
|
|
293
|
-
let predictedStandards = [];
|
|
294
|
-
const projectId = requestBody.projectId;
|
|
295
|
-
if (projectId) {
|
|
296
|
-
try {
|
|
297
|
-
const predicted = await getPredictedStandards(projectId);
|
|
298
|
-
if (predicted.length > 0) {
|
|
299
|
-
const predictedIds = new Set(predicted.map(p => p.standard_id));
|
|
300
|
-
// Pull predicted standards from query results and mark them as pre-cached
|
|
301
|
-
predictedStandards = result.rows
|
|
302
|
-
.filter(s => predictedIds.has(s.pattern_id || s.standard_id))
|
|
303
|
-
.map(s => ({ ...s, relevance_score: 100, predicted: true }));
|
|
304
|
-
}
|
|
305
|
-
} catch (err) {
|
|
306
|
-
console.error('[standardsRelevant] Predictive cache lookup failed:', err.message);
|
|
307
|
-
}
|
|
308
|
-
}
|
|
309
|
-
|
|
310
|
-
// Rank by relevance with recency boost
|
|
311
|
-
let ranked = rankStandards(result.rows, recentCategories);
|
|
312
|
-
|
|
313
|
-
// Deduplicate by element name (same rule can be ingested with different path prefixes)
|
|
314
|
-
const seenElements = new Set();
|
|
315
|
-
ranked = ranked.filter(standard => {
|
|
316
|
-
const key = standard.element;
|
|
317
|
-
if (seenElements.has(key)) return false;
|
|
318
|
-
seenElements.add(key);
|
|
319
|
-
return true;
|
|
320
|
-
});
|
|
321
|
-
|
|
322
|
-
// Apply preferences if provided
|
|
323
|
-
if (preferences) {
|
|
324
|
-
ranked = applyPreferences(ranked, preferences);
|
|
325
|
-
}
|
|
326
|
-
|
|
327
|
-
// Return top 10 with diversity caps:
|
|
328
|
-
// - Max 2 per category (prevents single-category saturation)
|
|
329
|
-
// - Max 1 per standard title (prevents same-file rule pairs wasting slots)
|
|
330
|
-
// Predicted standards are included first (they skip scoring)
|
|
331
|
-
const MAX_PER_CATEGORY = 2;
|
|
332
|
-
const MAX_PER_TITLE = 1;
|
|
333
|
-
const top = [];
|
|
334
|
-
const categoryCounts = {};
|
|
335
|
-
const titleCounts = {};
|
|
336
|
-
const seenPredicted = new Set();
|
|
337
|
-
|
|
338
|
-
// Add predicted standards first — they have proven relevance
|
|
339
|
-
for (const standard of predictedStandards) {
|
|
340
|
-
const cat = standard.category;
|
|
341
|
-
const title = standard.title || standard.element;
|
|
342
|
-
const key = standard.element;
|
|
343
|
-
categoryCounts[cat] = (categoryCounts[cat] || 0) + 1;
|
|
344
|
-
titleCounts[title] = (titleCounts[title] || 0) + 1;
|
|
345
|
-
seenPredicted.add(key);
|
|
346
|
-
if (categoryCounts[cat] <= MAX_PER_CATEGORY && titleCounts[title] <= MAX_PER_TITLE) {
|
|
347
|
-
top.push(standard);
|
|
348
|
-
if (top.length >= 10) break;
|
|
349
|
-
}
|
|
350
|
-
}
|
|
351
|
-
|
|
352
|
-
// Fill remaining slots from scored standards
|
|
353
|
-
for (const standard of ranked) {
|
|
354
|
-
if (top.length >= 10) break;
|
|
355
|
-
if (seenPredicted.has(standard.element)) continue;
|
|
356
|
-
const cat = standard.category;
|
|
357
|
-
const title = standard.title || standard.element;
|
|
358
|
-
categoryCounts[cat] = (categoryCounts[cat] || 0) + 1;
|
|
359
|
-
titleCounts[title] = (titleCounts[title] || 0) + 1;
|
|
360
|
-
if (categoryCounts[cat] <= MAX_PER_CATEGORY && titleCounts[title] <= MAX_PER_TITLE) {
|
|
361
|
-
top.push(standard);
|
|
362
|
-
}
|
|
363
|
-
}
|
|
364
|
-
|
|
365
|
-
// Log activated standards for future predictions
|
|
366
|
-
if (projectId && top.length > 0) {
|
|
367
|
-
const activatedIds = top
|
|
368
|
-
.map(s => s.pattern_id || s.standard_id)
|
|
369
|
-
.filter(id => id != null);
|
|
370
|
-
logStandardsActivation(projectId, activatedIds).catch(err => {
|
|
371
|
-
console.error('[standardsRelevant] Activation logging failed:', err.message);
|
|
372
|
-
});
|
|
373
|
-
}
|
|
374
|
-
|
|
375
|
-
// Track decision frame for this standards selection
|
|
376
|
-
let frameId = null;
|
|
377
|
-
if (projectId && top.length > 0) {
|
|
378
|
-
try {
|
|
379
|
-
const standardIds = top
|
|
380
|
-
.map(s => s.pattern_id || s.standard_id)
|
|
381
|
-
.filter(id => id != null);
|
|
382
|
-
const avgScore = top.reduce((sum, s) => sum + (s.relevance_score || 0), 0) / top.length;
|
|
383
|
-
const confidence = Math.min(avgScore / 100, 1);
|
|
384
|
-
const frame = await createFrame({
|
|
385
|
-
projectId,
|
|
386
|
-
sessionId: requestBody.sessionId || null,
|
|
387
|
-
standardIds,
|
|
388
|
-
confidence,
|
|
389
|
-
context: { categories, characteristics, total_matched: result.rows.length }
|
|
390
|
-
});
|
|
391
|
-
frameId = frame.frame_id;
|
|
392
|
-
} catch (err) {
|
|
393
|
-
console.error('[standardsRelevant] Decision frame creation failed:', err.message);
|
|
394
|
-
}
|
|
395
|
-
}
|
|
396
|
-
|
|
397
|
-
return createSuccessResponse({
|
|
398
|
-
standards: top,
|
|
399
|
-
categories,
|
|
400
|
-
total_matched: result.rows.length,
|
|
401
|
-
frame_id: frameId
|
|
402
|
-
}, 'Relevant standards retrieved');
|
|
403
|
-
}
|
|
404
|
-
|
|
405
|
-
exports.handler = wrapHandler(getRelevantStandards);
|
|
@@ -1,161 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Standards Transition Handler
|
|
3
|
-
* Executes lifecycle state transitions on standards and discoveries
|
|
4
|
-
*
|
|
5
|
-
* POST /api/standards/transition
|
|
6
|
-
* Body: { standard_id, action: 'approve'|'reject'|'observe'|'disable'|'deprecate'|'delete'|'enable'|'cancel_deprecation', reason?, reason_code? }
|
|
7
|
-
* Auth: Cognito JWT required
|
|
8
|
-
*
|
|
9
|
-
* For discovery IDs (from onboarding_discoveries table):
|
|
10
|
-
* approve → creates pattern + marks discovery approved
|
|
11
|
-
* reject → marks discovery rejected
|
|
12
|
-
* observe → marks discovery as observing (deferred review)
|
|
13
|
-
*/
|
|
14
|
-
|
|
15
|
-
const { wrapHandler, executeQuery, createSuccessResponse, createErrorResponse } = require('./helpers');
|
|
16
|
-
const { StandardLifecycle } = require('./core/StandardLifecycle');
|
|
17
|
-
|
|
18
|
-
const lifecycle = new StandardLifecycle();
|
|
19
|
-
|
|
20
|
-
const DISCOVERY_ACTIONS = ['approve', 'reject', 'observe'];
|
|
21
|
-
|
|
22
|
-
/**
|
|
23
|
-
* Handle transitions for onboarding discoveries
|
|
24
|
-
*/
|
|
25
|
-
async function transitionDiscovery(discoveryId, action, email, reason, reasonCode) {
|
|
26
|
-
const discovery = await executeQuery(`
|
|
27
|
-
SELECT discovery_id, project_id, discovery_type, pattern_name, pattern_description, confidence, evidence, status
|
|
28
|
-
FROM rapport.onboarding_discoveries
|
|
29
|
-
WHERE discovery_id = $1
|
|
30
|
-
`, [discoveryId]);
|
|
31
|
-
|
|
32
|
-
if (discovery.rowCount === 0) {
|
|
33
|
-
return null; // Not a discovery either
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
const row = discovery.rows[0];
|
|
37
|
-
const oldStatus = row.status;
|
|
38
|
-
|
|
39
|
-
if (action === 'approve') {
|
|
40
|
-
// Create pattern from discovery
|
|
41
|
-
const patternId = `pat_${row.project_id}_${Date.now()}`;
|
|
42
|
-
await executeQuery(`
|
|
43
|
-
INSERT INTO rapport.patterns (
|
|
44
|
-
pattern_id, project_id, intent, constraints, outcome_criteria,
|
|
45
|
-
maturity, discovered_by, pattern_data
|
|
46
|
-
) VALUES ($1, $2, $3, $4, $5, 'provisional', $6, $7)
|
|
47
|
-
`, [
|
|
48
|
-
patternId,
|
|
49
|
-
row.project_id,
|
|
50
|
-
row.pattern_name,
|
|
51
|
-
JSON.stringify([row.pattern_description || '']),
|
|
52
|
-
JSON.stringify([`Discovered via onboarding: ${row.discovery_type}`]),
|
|
53
|
-
email,
|
|
54
|
-
JSON.stringify({
|
|
55
|
-
source: 'discovery_review',
|
|
56
|
-
discovery_type: row.discovery_type,
|
|
57
|
-
evidence: row.evidence
|
|
58
|
-
})
|
|
59
|
-
]);
|
|
60
|
-
|
|
61
|
-
await executeQuery(`
|
|
62
|
-
UPDATE rapport.onboarding_discoveries
|
|
63
|
-
SET status = 'approved', reviewed_at = NOW(), updated_at = NOW(), pattern_id = $1
|
|
64
|
-
WHERE discovery_id = $2
|
|
65
|
-
`, [patternId, discoveryId]);
|
|
66
|
-
|
|
67
|
-
return { old_state: oldStatus, new_state: 'approved', pattern_id: patternId };
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
if (action === 'reject') {
|
|
71
|
-
await executeQuery(`
|
|
72
|
-
UPDATE rapport.onboarding_discoveries
|
|
73
|
-
SET status = 'rejected', reviewed_at = NOW(), updated_at = NOW(), reason = $1, reason_code = $2
|
|
74
|
-
WHERE discovery_id = $3
|
|
75
|
-
`, [reason || null, reasonCode || null, discoveryId]);
|
|
76
|
-
|
|
77
|
-
return { old_state: oldStatus, new_state: 'rejected' };
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
if (action === 'observe') {
|
|
81
|
-
await executeQuery(`
|
|
82
|
-
UPDATE rapport.onboarding_discoveries
|
|
83
|
-
SET status = 'observing', updated_at = NOW()
|
|
84
|
-
WHERE discovery_id = $1
|
|
85
|
-
`, [discoveryId]);
|
|
86
|
-
|
|
87
|
-
return { old_state: oldStatus, new_state: 'observing' };
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
return null;
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
async function transitionStandard({ body, requestContext }) {
|
|
94
|
-
try {
|
|
95
|
-
const email = requestContext.authorizer?.claims?.email || requestContext.authorizer?.jwt?.claims?.email;
|
|
96
|
-
|
|
97
|
-
if (!email) {
|
|
98
|
-
return createErrorResponse(401, 'Authentication required');
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
const { standard_id: id, action, reason, reason_code: reasonCode } = body || {};
|
|
102
|
-
|
|
103
|
-
if (!id) {
|
|
104
|
-
return createErrorResponse(400, 'standard_id is required');
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
if (!action) {
|
|
108
|
-
return createErrorResponse(400, 'action is required', {
|
|
109
|
-
valid_actions: ['propose', 'approve', 'reject', 'observe', 'disable', 'deprecate', 'delete', 'enable', 'cancel_deprecation']
|
|
110
|
-
});
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
// Try standard lifecycle transition first
|
|
114
|
-
try {
|
|
115
|
-
const result = await lifecycle.transition(id, action, email, reason);
|
|
116
|
-
|
|
117
|
-
return createSuccessResponse({
|
|
118
|
-
standard_id: result.standard_id,
|
|
119
|
-
old_state: result.old_state,
|
|
120
|
-
new_state: result.new_state,
|
|
121
|
-
action: result.action,
|
|
122
|
-
audit_entry: result.audit_entry
|
|
123
|
-
}, `Standard transitioned from '${result.old_state}' to '${result.new_state}'`);
|
|
124
|
-
} catch (lifecycleError) {
|
|
125
|
-
// If pattern not found and action is valid for discoveries, try discovery transition
|
|
126
|
-
if (lifecycleError.message.includes('not found') && DISCOVERY_ACTIONS.includes(action)) {
|
|
127
|
-
const discoveryResult = await transitionDiscovery(id, action, email, reason, reasonCode);
|
|
128
|
-
|
|
129
|
-
if (discoveryResult) {
|
|
130
|
-
return createSuccessResponse({
|
|
131
|
-
standard_id: id,
|
|
132
|
-
old_state: discoveryResult.old_state,
|
|
133
|
-
new_state: discoveryResult.new_state,
|
|
134
|
-
action,
|
|
135
|
-
pattern_id: discoveryResult.pattern_id || null
|
|
136
|
-
}, `Discovery transitioned from '${discoveryResult.old_state}' to '${discoveryResult.new_state}'`);
|
|
137
|
-
}
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
// Re-throw if not handled
|
|
141
|
-
throw lifecycleError;
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
} catch (error) {
|
|
145
|
-
console.error('Standards Transition Error:', error);
|
|
146
|
-
|
|
147
|
-
if (error.message.includes('Invalid action')) {
|
|
148
|
-
return createErrorResponse(400, error.message);
|
|
149
|
-
}
|
|
150
|
-
if (error.message.includes('not found')) {
|
|
151
|
-
return createErrorResponse(404, error.message);
|
|
152
|
-
}
|
|
153
|
-
if (error.message.includes('Cannot perform')) {
|
|
154
|
-
return createErrorResponse(409, error.message);
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
return createErrorResponse(500, 'Failed to transition standard');
|
|
158
|
-
}
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
exports.handler = wrapHandler(transitionStandard);
|