@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.
@@ -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 (always points to prod)
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 (may point to dev — for backwards compatibility)
446
- try {
447
- const configPath = path.join(process.cwd(), '.myworld.json');
448
- const content = await fs.readFile(configPath, 'utf-8');
449
- const config = JSON.parse(content);
450
- const auth = config.deployments?.backend?.auth;
451
- if (auth?.domain && auth?.client_id) {
452
- return {
453
- cognitoDomain: `${auth.domain}.auth.us-east-2.amazoncognito.com`,
454
- cognitoClientId: auth.client_id
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
- * The hook should always talk to the production MindMeld API for standards/patterns,
490
- * regardless of which environment the developer is building against.
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 (always points to prod)
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 (may point to dev)
507
- try {
508
- const configPath = path.join(process.cwd(), '.myworld.json');
509
- const content = await fs.readFile(configPath, 'utf-8');
510
- const config = JSON.parse(content);
511
- const backend = config.deployments?.backend;
512
- return {
513
- apiUrl: backend?.api?.base_url || 'https://api.mindmeld.dev'
514
- };
515
- } catch (error) {
516
- return {
517
- apiUrl: process.env.MINDMELD_API_URL || 'https://api.mindmeld.dev'
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
- reject(new Error(parsed.message || `HTTP ${res.statusCode}`));
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
- sections.push(`### ${standard.element}`);
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
- sections.push(`### ${workflow.element} ${fingerprintStr}`);
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@equilateral_ai/mindmeld",
3
- "version": "3.4.0",
3
+ "version": "3.5.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": {
@@ -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
- * Reads .myworld.json from CWD to detect dev vs prod Cognito pool.
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
- * Load Cognito config from .myworld.json if present
24
+ * Detect if we're running inside the MindMeld source repository
23
25
  */
24
- function loadCognitoConfig() {
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
- const auth = config.deployments?.backend?.auth;
30
- if (auth?.domain && auth?.client_id) {
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: `${auth.domain}.auth.us-east-2.amazoncognito.com`,
33
- cognitoClientId: auth.client_id
49
+ cognitoDomain: config.auth.cognitoDomain,
50
+ cognitoClientId: config.auth.cognitoClientId
34
51
  };
35
52
  }
36
53
  } catch (error) {
37
- // No .myworld.json use production defaults
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
- // Check user has access to project
30
- const accessQuery = `
31
- SELECT pc.role
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 accessResult = await executeQuery(`
38
- SELECT p.project_id, p.project_name, p.company_id
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 accessResult = await executeQuery(`
252
- SELECT role FROM rapport.project_collaborators
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 accessResult = await executeQuery(`
26
- SELECT role FROM rapport.project_collaborators
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,