monora-ai 2.1.0 → 2.1.3
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/README.md +333 -159
- package/dist/aims_governance.d.ts +238 -0
- package/dist/aims_governance.d.ts.map +1 -0
- package/dist/aims_governance.js +922 -0
- package/dist/alerts.d.ts +16 -0
- package/dist/alerts.d.ts.map +1 -1
- package/dist/alerts.js +16 -0
- package/dist/api.d.ts +6 -0
- package/dist/api.d.ts.map +1 -1
- package/dist/api.js +6 -0
- package/dist/assessment.d.ts +85 -0
- package/dist/assessment.d.ts.map +1 -1
- package/dist/assessment.js +506 -13
- package/dist/attribution.d.ts +44 -3
- package/dist/attribution.d.ts.map +1 -1
- package/dist/attribution.js +197 -10
- package/dist/autodetect.d.ts +68 -0
- package/dist/autodetect.d.ts.map +1 -1
- package/dist/autodetect.js +639 -0
- package/dist/bias.d.ts +130 -0
- package/dist/bias.d.ts.map +1 -0
- package/dist/bias.js +223 -0
- package/dist/cli/diagnostics.d.ts +5 -1
- package/dist/cli/diagnostics.d.ts.map +1 -1
- package/dist/cli/diagnostics.js +23 -6
- package/dist/cli/doctor.d.ts +25 -0
- package/dist/cli/doctor.d.ts.map +1 -0
- package/dist/cli/doctor.js +381 -0
- package/dist/cli/fix.d.ts +16 -0
- package/dist/cli/fix.d.ts.map +1 -0
- package/dist/cli/fix.js +284 -0
- package/dist/cli/init.d.ts +57 -0
- package/dist/cli/init.d.ts.map +1 -0
- package/dist/cli/init.js +205 -0
- package/dist/cli.js +1550 -176
- package/dist/complianceTargets.d.ts +111 -0
- package/dist/complianceTargets.d.ts.map +1 -0
- package/dist/complianceTargets.js +521 -0
- package/dist/config.d.ts +261 -16
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +381 -32
- package/dist/config_migrations.d.ts.map +1 -1
- package/dist/config_migrations.js +38 -1
- package/dist/config_schema.d.ts +2490 -1035
- package/dist/config_schema.d.ts.map +1 -1
- package/dist/config_schema.js +233 -64
- package/dist/context.d.ts +34 -0
- package/dist/context.d.ts.map +1 -1
- package/dist/context.js +118 -7
- package/dist/control_backbone.d.ts +122 -0
- package/dist/control_backbone.d.ts.map +1 -0
- package/dist/control_backbone.js +698 -0
- package/dist/data-governance.d.ts +187 -0
- package/dist/data-governance.d.ts.map +1 -0
- package/dist/data-governance.js +424 -0
- package/dist/dataResidency.d.ts +44 -0
- package/dist/dataResidency.d.ts.map +1 -0
- package/dist/dataResidency.js +203 -0
- package/dist/dispatcher.d.ts.map +1 -1
- package/dist/dispatcher.js +17 -5
- package/dist/evidence_store.d.ts +103 -0
- package/dist/evidence_store.d.ts.map +1 -0
- package/dist/evidence_store.js +459 -0
- package/dist/executiveSummary.d.ts +15 -0
- package/dist/executiveSummary.d.ts.map +1 -1
- package/dist/executiveSummary.js +135 -22
- package/dist/identity.d.ts +143 -0
- package/dist/identity.d.ts.map +1 -0
- package/dist/identity.js +231 -0
- package/dist/impact-assessment.d.ts +350 -0
- package/dist/impact-assessment.d.ts.map +1 -0
- package/dist/impact-assessment.js +580 -0
- package/dist/index.d.ts +20 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +247 -5
- package/dist/instrumentation.d.ts +1 -1
- package/dist/instrumentation.d.ts.map +1 -1
- package/dist/instrumentation.js +123 -22
- package/dist/integrations/anthropic.d.ts +3 -0
- package/dist/integrations/anthropic.d.ts.map +1 -1
- package/dist/integrations/anthropic.js +282 -80
- package/dist/integrations/governance.d.ts +33 -0
- package/dist/integrations/governance.d.ts.map +1 -0
- package/dist/integrations/governance.js +208 -0
- package/dist/integrations/langchain.d.ts +4 -0
- package/dist/integrations/langchain.d.ts.map +1 -1
- package/dist/integrations/langchain.js +362 -142
- package/dist/integrations/openai.d.ts +9 -0
- package/dist/integrations/openai.d.ts.map +1 -1
- package/dist/integrations/openai.js +673 -73
- package/dist/iso42001_consolidation.d.ts +16 -0
- package/dist/iso42001_consolidation.d.ts.map +1 -0
- package/dist/iso42001_consolidation.js +413 -0
- package/dist/iso42001_workflows.d.ts +263 -0
- package/dist/iso42001_workflows.d.ts.map +1 -0
- package/dist/iso42001_workflows.js +781 -0
- package/dist/lifecycle.d.ts +299 -0
- package/dist/lifecycle.d.ts.map +1 -0
- package/dist/lifecycle.js +624 -0
- package/dist/lineage.d.ts +2 -2
- package/dist/lineage.d.ts.map +1 -1
- package/dist/lineage.js +9 -16
- package/dist/middleware/express.d.ts.map +1 -1
- package/dist/middleware/express.js +18 -3
- package/dist/middleware/nextjs.js +2 -2
- package/dist/model.d.ts +143 -0
- package/dist/model.d.ts.map +1 -0
- package/dist/model.js +371 -0
- package/dist/onboarding.d.ts +42 -0
- package/dist/onboarding.d.ts.map +1 -0
- package/dist/onboarding.js +1022 -0
- package/dist/oversight.d.ts +264 -0
- package/dist/oversight.d.ts.map +1 -0
- package/dist/oversight.js +497 -0
- package/dist/presets.js +7 -7
- package/dist/quotas.d.ts +171 -0
- package/dist/quotas.d.ts.map +1 -0
- package/dist/quotas.js +259 -0
- package/dist/register.d.ts +13 -0
- package/dist/register.d.ts.map +1 -0
- package/dist/register.js +99 -0
- package/dist/registry.d.ts +1 -0
- package/dist/registry.d.ts.map +1 -1
- package/dist/registry.js +7 -0
- package/dist/registryData.json +43 -6
- package/dist/report.d.ts +2 -1
- package/dist/report.d.ts.map +1 -1
- package/dist/report.js +189 -2
- package/dist/reporting.d.ts +125 -0
- package/dist/reporting.d.ts.map +1 -1
- package/dist/reporting.js +192 -2
- package/dist/resources.d.ts +285 -0
- package/dist/resources.d.ts.map +1 -0
- package/dist/resources.js +643 -0
- package/dist/risk.d.ts +120 -0
- package/dist/risk.d.ts.map +1 -0
- package/dist/risk.js +220 -0
- package/dist/runtime.d.ts +73 -0
- package/dist/runtime.d.ts.map +1 -1
- package/dist/runtime.js +415 -18
- package/dist/schemaInference.d.ts +92 -0
- package/dist/schemaInference.d.ts.map +1 -0
- package/dist/schemaInference.js +466 -0
- package/dist/schema_validation.js +2 -2
- package/dist/schemas/config.schema.json +118 -4
- package/dist/security_report.js +4 -4
- package/dist/signing.d.ts +1 -1
- package/dist/signing.d.ts.map +1 -1
- package/dist/signing.js +4 -0
- package/dist/sinks/file.d.ts +19 -1
- package/dist/sinks/file.d.ts.map +1 -1
- package/dist/sinks/file.js +82 -13
- package/dist/sinks/https.d.ts +10 -0
- package/dist/sinks/https.d.ts.map +1 -1
- package/dist/sinks/https.js +76 -16
- package/dist/sinks/stdout.d.ts +1 -0
- package/dist/sinks/stdout.d.ts.map +1 -1
- package/dist/sinks/stdout.js +12 -1
- package/dist/spec.d.ts +159 -0
- package/dist/spec.d.ts.map +1 -0
- package/dist/spec.js +391 -0
- package/dist/stakeholders.d.ts +199 -0
- package/dist/stakeholders.d.ts.map +1 -0
- package/dist/stakeholders.js +398 -0
- package/dist/standards.d.ts.map +1 -1
- package/dist/standards.js +160 -2
- package/dist/standards_ingest.d.ts.map +1 -1
- package/dist/standards_ingest.js +1 -4
- package/dist/telemetry.d.ts +16 -2
- package/dist/telemetry.d.ts.map +1 -1
- package/dist/telemetry.js +77 -14
- package/dist/templates/controls/iso42001_control_catalog.json +1443 -0
- package/dist/traced_emitter.d.ts.map +1 -1
- package/dist/traced_emitter.js +19 -9
- package/dist/trust_package.d.ts +19 -1
- package/dist/trust_package.d.ts.map +1 -1
- package/dist/trust_package.js +89 -2
- package/dist/verify.d.ts.map +1 -1
- package/dist/verify.js +9 -2
- package/dist/wal.d.ts.map +1 -1
- package/dist/wal.js +2 -1
- package/package.json +14 -1
- package/scripts/postinstall.js +105 -210
- package/templates/controls/iso42001_control_catalog.json +1443 -0
package/dist/autodetect.js
CHANGED
|
@@ -1,6 +1,12 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
/**
|
|
3
3
|
* Auto-detection utilities for zero-config initialization.
|
|
4
|
+
*
|
|
5
|
+
* Provides intelligent project analysis for streamlined setup:
|
|
6
|
+
* - Framework detection (Next.js, Express, FastAPI, etc.)
|
|
7
|
+
* - Cloud platform detection (Vercel, AWS, GCP, etc.)
|
|
8
|
+
* - AI SDK detection with model suggestions
|
|
9
|
+
* - Optional AI-powered analysis using Claude/GPT
|
|
4
10
|
*/
|
|
5
11
|
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
6
12
|
if (k2 === undefined) k2 = k;
|
|
@@ -41,8 +47,162 @@ exports.detectEnvironment = detectEnvironment;
|
|
|
41
47
|
exports.detectServiceName = detectServiceName;
|
|
42
48
|
exports.autoDetectConfig = autoDetectConfig;
|
|
43
49
|
exports.selectPresetForEnvironment = selectPresetForEnvironment;
|
|
50
|
+
exports.detectFramework = detectFramework;
|
|
51
|
+
exports.analyzeProjectWithAI = analyzeProjectWithAI;
|
|
52
|
+
exports.buildSmartConfig = buildSmartConfig;
|
|
44
53
|
const fs = __importStar(require("fs"));
|
|
45
54
|
const path = __importStar(require("path"));
|
|
55
|
+
const logger_1 = require("./logger");
|
|
56
|
+
const AI_ANALYSIS_ENV_FLAG = 'MONORA_AI_ANALYSIS_ENABLED';
|
|
57
|
+
const UNSAFE_KEYS = new Set(['__proto__', 'constructor', 'prototype']);
|
|
58
|
+
const SECRET_KEY_PATTERN = /api[_-]?key|token|secret|password|authorization|bearer|private[_-]?key|client[_-]?secret|access[_-]?key/i;
|
|
59
|
+
const ALLOWED_SUGGESTED_CONFIG_KEYS = new Set([
|
|
60
|
+
'version',
|
|
61
|
+
'config_version',
|
|
62
|
+
'preset',
|
|
63
|
+
'defaults',
|
|
64
|
+
'sinks',
|
|
65
|
+
'immutability',
|
|
66
|
+
'attestation',
|
|
67
|
+
'registry',
|
|
68
|
+
'instrumentation',
|
|
69
|
+
'reporting',
|
|
70
|
+
'data_handling',
|
|
71
|
+
'policies',
|
|
72
|
+
'alerts',
|
|
73
|
+
'error_handling',
|
|
74
|
+
'buffering',
|
|
75
|
+
'identity',
|
|
76
|
+
'risk_register',
|
|
77
|
+
'bias',
|
|
78
|
+
'human_oversight',
|
|
79
|
+
'tracing',
|
|
80
|
+
'telemetry',
|
|
81
|
+
'redaction',
|
|
82
|
+
'data_residency',
|
|
83
|
+
'data_residency_action',
|
|
84
|
+
'wal',
|
|
85
|
+
'signing',
|
|
86
|
+
'ai_act',
|
|
87
|
+
]);
|
|
88
|
+
function isAiAnalysisEnabled() {
|
|
89
|
+
const value = String(process.env[AI_ANALYSIS_ENV_FLAG] || '').toLowerCase();
|
|
90
|
+
return value === 'true' || value === '1' || value === 'yes';
|
|
91
|
+
}
|
|
92
|
+
function isPlainObject(value) {
|
|
93
|
+
if (!value || typeof value !== 'object') {
|
|
94
|
+
return false;
|
|
95
|
+
}
|
|
96
|
+
const proto = Object.getPrototypeOf(value);
|
|
97
|
+
return proto === Object.prototype || proto === null;
|
|
98
|
+
}
|
|
99
|
+
function redactSensitiveStrings(input) {
|
|
100
|
+
if (!input) {
|
|
101
|
+
return input;
|
|
102
|
+
}
|
|
103
|
+
let output = input;
|
|
104
|
+
output = output.replace(/(api[_-]?key|token|secret|password|authorization|bearer|private[_-]?key)\s*([:=]\s*)(['"`]?)[^\s'"`]+\3/gi, '$1$2$3[REDACTED]$3');
|
|
105
|
+
output = output.replace(/(['"`])(sk-[A-Za-z0-9]{10,}|AKIA[0-9A-Z]{16}|ASIA[0-9A-Z]{16}|AIza[0-9A-Za-z_-]{35}|ghp_[0-9A-Za-z]{36}|xox[baprs]-[0-9A-Za-z-]{10,}|ya29\.[0-9A-Za-z_-]+)\1/g, '$1[REDACTED]$1');
|
|
106
|
+
return output;
|
|
107
|
+
}
|
|
108
|
+
function sanitizeJsonValue(value, depth = 0) {
|
|
109
|
+
if (depth > 6) {
|
|
110
|
+
return '[REDACTED]';
|
|
111
|
+
}
|
|
112
|
+
if (value === null || typeof value === 'number' || typeof value === 'boolean') {
|
|
113
|
+
return value;
|
|
114
|
+
}
|
|
115
|
+
if (typeof value === 'string') {
|
|
116
|
+
return redactSensitiveStrings(value);
|
|
117
|
+
}
|
|
118
|
+
if (Array.isArray(value)) {
|
|
119
|
+
return value.map((item) => sanitizeJsonValue(item, depth + 1));
|
|
120
|
+
}
|
|
121
|
+
if (isPlainObject(value)) {
|
|
122
|
+
const output = {};
|
|
123
|
+
for (const [key, child] of Object.entries(value)) {
|
|
124
|
+
if (UNSAFE_KEYS.has(key)) {
|
|
125
|
+
continue;
|
|
126
|
+
}
|
|
127
|
+
if (SECRET_KEY_PATTERN.test(key)) {
|
|
128
|
+
output[key] = '[REDACTED]';
|
|
129
|
+
continue;
|
|
130
|
+
}
|
|
131
|
+
const sanitized = sanitizeJsonValue(child, depth + 1);
|
|
132
|
+
output[key] = sanitized;
|
|
133
|
+
}
|
|
134
|
+
return output;
|
|
135
|
+
}
|
|
136
|
+
return '[REDACTED]';
|
|
137
|
+
}
|
|
138
|
+
function sanitizePackageJsonContent(content) {
|
|
139
|
+
if (!content) {
|
|
140
|
+
return '';
|
|
141
|
+
}
|
|
142
|
+
try {
|
|
143
|
+
const parsed = JSON.parse(content);
|
|
144
|
+
const sanitized = sanitizeJsonValue(parsed);
|
|
145
|
+
return redactSensitiveStrings(JSON.stringify(sanitized, null, 2));
|
|
146
|
+
}
|
|
147
|
+
catch {
|
|
148
|
+
return redactSensitiveStrings(content);
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
function sanitizeSuggestedValue(value, depth = 0) {
|
|
152
|
+
if (depth > 6) {
|
|
153
|
+
return undefined;
|
|
154
|
+
}
|
|
155
|
+
if (value === null || typeof value === 'string' || typeof value === 'number' || typeof value === 'boolean') {
|
|
156
|
+
return value;
|
|
157
|
+
}
|
|
158
|
+
if (typeof value === 'function' || Array.isArray(value)) {
|
|
159
|
+
return undefined;
|
|
160
|
+
}
|
|
161
|
+
if (isPlainObject(value)) {
|
|
162
|
+
const output = {};
|
|
163
|
+
for (const [key, child] of Object.entries(value)) {
|
|
164
|
+
if (UNSAFE_KEYS.has(key)) {
|
|
165
|
+
continue;
|
|
166
|
+
}
|
|
167
|
+
const sanitized = sanitizeSuggestedValue(child, depth + 1);
|
|
168
|
+
if (sanitized !== undefined) {
|
|
169
|
+
output[key] = sanitized;
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
return output;
|
|
173
|
+
}
|
|
174
|
+
return undefined;
|
|
175
|
+
}
|
|
176
|
+
function sanitizeSuggestedConfig(input) {
|
|
177
|
+
if (!isPlainObject(input)) {
|
|
178
|
+
return {};
|
|
179
|
+
}
|
|
180
|
+
const output = {};
|
|
181
|
+
for (const [key, value] of Object.entries(input)) {
|
|
182
|
+
if (UNSAFE_KEYS.has(key) || !ALLOWED_SUGGESTED_CONFIG_KEYS.has(key)) {
|
|
183
|
+
continue;
|
|
184
|
+
}
|
|
185
|
+
const sanitized = sanitizeSuggestedValue(value);
|
|
186
|
+
if (sanitized !== undefined) {
|
|
187
|
+
output[key] = sanitized;
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
return output;
|
|
191
|
+
}
|
|
192
|
+
function safeDeepMerge(base, updates) {
|
|
193
|
+
for (const [key, value] of Object.entries(updates)) {
|
|
194
|
+
if (UNSAFE_KEYS.has(key)) {
|
|
195
|
+
continue;
|
|
196
|
+
}
|
|
197
|
+
if (isPlainObject(value) && isPlainObject(base[key])) {
|
|
198
|
+
safeDeepMerge(base[key], value);
|
|
199
|
+
continue;
|
|
200
|
+
}
|
|
201
|
+
if (value !== undefined && !Array.isArray(value) && typeof value !== 'function') {
|
|
202
|
+
base[key] = value;
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
}
|
|
46
206
|
/**
|
|
47
207
|
* Detect which LLM SDKs are installed.
|
|
48
208
|
*/
|
|
@@ -209,3 +369,482 @@ function selectPresetForEnvironment(env) {
|
|
|
209
369
|
};
|
|
210
370
|
return presetMap[env.toLowerCase()] || 'development';
|
|
211
371
|
}
|
|
372
|
+
/**
|
|
373
|
+
* Detect framework, cloud platform, and AI SDKs from project files.
|
|
374
|
+
*
|
|
375
|
+
* This performs comprehensive project analysis without requiring user input.
|
|
376
|
+
*/
|
|
377
|
+
function detectFramework() {
|
|
378
|
+
const cwd = process.cwd();
|
|
379
|
+
const result = {
|
|
380
|
+
framework: null,
|
|
381
|
+
projectType: 'unknown',
|
|
382
|
+
cloudPlatform: null,
|
|
383
|
+
aiSdks: [],
|
|
384
|
+
suggestedModels: [],
|
|
385
|
+
runtime: 'node',
|
|
386
|
+
entryPoint: null,
|
|
387
|
+
typescript: false,
|
|
388
|
+
packageManager: 'npm',
|
|
389
|
+
};
|
|
390
|
+
// Read package.json for dependency analysis
|
|
391
|
+
const packageJsonPath = path.join(cwd, 'package.json');
|
|
392
|
+
let packageJson = {};
|
|
393
|
+
if (fs.existsSync(packageJsonPath)) {
|
|
394
|
+
try {
|
|
395
|
+
packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf-8'));
|
|
396
|
+
}
|
|
397
|
+
catch {
|
|
398
|
+
// Ignore parse errors
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
const allDeps = {
|
|
402
|
+
...packageJson.dependencies,
|
|
403
|
+
...packageJson.devDependencies,
|
|
404
|
+
};
|
|
405
|
+
// Detect TypeScript
|
|
406
|
+
result.typescript = 'typescript' in allDeps || fs.existsSync(path.join(cwd, 'tsconfig.json'));
|
|
407
|
+
// Detect package manager
|
|
408
|
+
if (fs.existsSync(path.join(cwd, 'pnpm-lock.yaml'))) {
|
|
409
|
+
result.packageManager = 'pnpm';
|
|
410
|
+
}
|
|
411
|
+
else if (fs.existsSync(path.join(cwd, 'yarn.lock'))) {
|
|
412
|
+
result.packageManager = 'yarn';
|
|
413
|
+
}
|
|
414
|
+
else if (fs.existsSync(path.join(cwd, 'bun.lockb'))) {
|
|
415
|
+
result.packageManager = 'bun';
|
|
416
|
+
}
|
|
417
|
+
// Detect framework from config files and dependencies
|
|
418
|
+
const frameworkDetectors = [
|
|
419
|
+
// Next.js
|
|
420
|
+
{
|
|
421
|
+
check: () => fs.existsSync(path.join(cwd, 'next.config.js')) ||
|
|
422
|
+
fs.existsSync(path.join(cwd, 'next.config.mjs')) ||
|
|
423
|
+
fs.existsSync(path.join(cwd, 'next.config.ts')) ||
|
|
424
|
+
'next' in allDeps,
|
|
425
|
+
framework: 'next',
|
|
426
|
+
type: 'web-app',
|
|
427
|
+
},
|
|
428
|
+
// Nuxt
|
|
429
|
+
{
|
|
430
|
+
check: () => fs.existsSync(path.join(cwd, 'nuxt.config.js')) ||
|
|
431
|
+
fs.existsSync(path.join(cwd, 'nuxt.config.ts')) ||
|
|
432
|
+
'nuxt' in allDeps,
|
|
433
|
+
framework: 'nuxt',
|
|
434
|
+
type: 'web-app',
|
|
435
|
+
},
|
|
436
|
+
// Remix
|
|
437
|
+
{
|
|
438
|
+
check: () => '@remix-run/node' in allDeps || '@remix-run/react' in allDeps,
|
|
439
|
+
framework: 'remix',
|
|
440
|
+
type: 'web-app',
|
|
441
|
+
},
|
|
442
|
+
// NestJS
|
|
443
|
+
{
|
|
444
|
+
check: () => '@nestjs/core' in allDeps,
|
|
445
|
+
framework: 'nest',
|
|
446
|
+
type: 'api',
|
|
447
|
+
},
|
|
448
|
+
// Fastify
|
|
449
|
+
{
|
|
450
|
+
check: () => 'fastify' in allDeps,
|
|
451
|
+
framework: 'fastify',
|
|
452
|
+
type: 'api',
|
|
453
|
+
},
|
|
454
|
+
// Express
|
|
455
|
+
{
|
|
456
|
+
check: () => 'express' in allDeps,
|
|
457
|
+
framework: 'express',
|
|
458
|
+
type: 'api',
|
|
459
|
+
},
|
|
460
|
+
// Koa
|
|
461
|
+
{
|
|
462
|
+
check: () => 'koa' in allDeps,
|
|
463
|
+
framework: 'koa',
|
|
464
|
+
type: 'api',
|
|
465
|
+
},
|
|
466
|
+
// Hono
|
|
467
|
+
{
|
|
468
|
+
check: () => 'hono' in allDeps,
|
|
469
|
+
framework: 'hono',
|
|
470
|
+
type: 'api',
|
|
471
|
+
},
|
|
472
|
+
];
|
|
473
|
+
for (const detector of frameworkDetectors) {
|
|
474
|
+
if (detector.check()) {
|
|
475
|
+
result.framework = detector.framework;
|
|
476
|
+
result.projectType = detector.type;
|
|
477
|
+
break;
|
|
478
|
+
}
|
|
479
|
+
}
|
|
480
|
+
// Detect if it's a library or CLI
|
|
481
|
+
if (!result.framework) {
|
|
482
|
+
if (packageJson.bin) {
|
|
483
|
+
result.projectType = 'cli';
|
|
484
|
+
}
|
|
485
|
+
else if (packageJson.main || packageJson.module || packageJson.exports) {
|
|
486
|
+
result.projectType = 'library';
|
|
487
|
+
}
|
|
488
|
+
}
|
|
489
|
+
// Detect monorepo
|
|
490
|
+
if (fs.existsSync(path.join(cwd, 'lerna.json')) ||
|
|
491
|
+
fs.existsSync(path.join(cwd, 'pnpm-workspace.yaml')) ||
|
|
492
|
+
packageJson.workspaces) {
|
|
493
|
+
result.projectType = 'monorepo';
|
|
494
|
+
}
|
|
495
|
+
// Detect cloud platform
|
|
496
|
+
const cloudDetectors = [
|
|
497
|
+
{ check: () => fs.existsSync(path.join(cwd, 'vercel.json')) || !!process.env.VERCEL, platform: 'vercel' },
|
|
498
|
+
{ check: () => fs.existsSync(path.join(cwd, 'netlify.toml')) || !!process.env.NETLIFY, platform: 'netlify' },
|
|
499
|
+
{ check: () => fs.existsSync(path.join(cwd, 'fly.toml')), platform: 'fly' },
|
|
500
|
+
{ check: () => fs.existsSync(path.join(cwd, 'railway.json')) || !!process.env.RAILWAY_ENVIRONMENT, platform: 'railway' },
|
|
501
|
+
{ check: () => fs.existsSync(path.join(cwd, 'render.yaml')), platform: 'render' },
|
|
502
|
+
{ check: () => fs.existsSync(path.join(cwd, 'serverless.yml')) || fs.existsSync(path.join(cwd, 'serverless.yaml')), platform: 'aws-lambda' },
|
|
503
|
+
{ check: () => fs.existsSync(path.join(cwd, 'app.yaml')) && fs.readFileSync(path.join(cwd, 'app.yaml'), 'utf-8').includes('runtime:'), platform: 'gcp-appengine' },
|
|
504
|
+
{ check: () => !!process.env.AWS_LAMBDA_FUNCTION_NAME || !!process.env.AWS_EXECUTION_ENV, platform: 'aws-lambda' },
|
|
505
|
+
{ check: () => !!process.env.GOOGLE_CLOUD_PROJECT || !!process.env.GCP_PROJECT, platform: 'gcp' },
|
|
506
|
+
{ check: () => !!process.env.AZURE_FUNCTIONS_ENVIRONMENT, platform: 'azure' },
|
|
507
|
+
{ check: () => !!process.env.DYNO, platform: 'heroku' },
|
|
508
|
+
];
|
|
509
|
+
for (const detector of cloudDetectors) {
|
|
510
|
+
try {
|
|
511
|
+
if (detector.check()) {
|
|
512
|
+
result.cloudPlatform = detector.platform;
|
|
513
|
+
break;
|
|
514
|
+
}
|
|
515
|
+
}
|
|
516
|
+
catch {
|
|
517
|
+
// Ignore file read errors
|
|
518
|
+
}
|
|
519
|
+
}
|
|
520
|
+
// Detect AI SDKs with versions
|
|
521
|
+
const aiSdkMap = {
|
|
522
|
+
'openai': 'openai',
|
|
523
|
+
'@anthropic-ai/sdk': 'anthropic',
|
|
524
|
+
'anthropic': 'anthropic',
|
|
525
|
+
'@ai-sdk/openai': 'vercel-ai-openai',
|
|
526
|
+
'@ai-sdk/anthropic': 'vercel-ai-anthropic',
|
|
527
|
+
'ai': 'vercel-ai',
|
|
528
|
+
'langchain': 'langchain',
|
|
529
|
+
'@langchain/core': 'langchain',
|
|
530
|
+
'@langchain/openai': 'langchain-openai',
|
|
531
|
+
'@langchain/anthropic': 'langchain-anthropic',
|
|
532
|
+
'llamaindex': 'llamaindex',
|
|
533
|
+
'cohere-ai': 'cohere',
|
|
534
|
+
'@google/generative-ai': 'google-genai',
|
|
535
|
+
'@mistralai/mistralai': 'mistral',
|
|
536
|
+
};
|
|
537
|
+
for (const [pkg, name] of Object.entries(aiSdkMap)) {
|
|
538
|
+
if (pkg in allDeps) {
|
|
539
|
+
const version = allDeps[pkg];
|
|
540
|
+
// Avoid duplicates (e.g., anthropic and @anthropic-ai/sdk)
|
|
541
|
+
if (!result.aiSdks.find(sdk => sdk.name === name)) {
|
|
542
|
+
result.aiSdks.push({ name, version: typeof version === 'string' ? version : undefined });
|
|
543
|
+
}
|
|
544
|
+
}
|
|
545
|
+
}
|
|
546
|
+
// Generate suggested models based on detected SDKs
|
|
547
|
+
// Use broad patterns to match all model variants (gpt-4, gpt-4o, gpt-4o-mini, etc.)
|
|
548
|
+
const sdkNames = result.aiSdks.map(sdk => sdk.name);
|
|
549
|
+
if (sdkNames.includes('openai') || sdkNames.includes('vercel-ai-openai') || sdkNames.includes('langchain-openai')) {
|
|
550
|
+
result.suggestedModels.push('gpt-*', 'o1-*', 'o3-*', 'chatgpt-*');
|
|
551
|
+
}
|
|
552
|
+
if (sdkNames.includes('anthropic') || sdkNames.includes('vercel-ai-anthropic') || sdkNames.includes('langchain-anthropic')) {
|
|
553
|
+
result.suggestedModels.push('claude-*');
|
|
554
|
+
}
|
|
555
|
+
if (sdkNames.includes('google-genai')) {
|
|
556
|
+
result.suggestedModels.push('gemini-*');
|
|
557
|
+
}
|
|
558
|
+
if (sdkNames.includes('mistral')) {
|
|
559
|
+
result.suggestedModels.push('mistral-*', 'codestral-*');
|
|
560
|
+
}
|
|
561
|
+
if (sdkNames.includes('cohere')) {
|
|
562
|
+
result.suggestedModels.push('command-*');
|
|
563
|
+
}
|
|
564
|
+
if (sdkNames.includes('deepseek')) {
|
|
565
|
+
result.suggestedModels.push('deepseek-*');
|
|
566
|
+
}
|
|
567
|
+
// Detect entry point
|
|
568
|
+
const entryPoints = [
|
|
569
|
+
'src/index.ts', 'src/index.js', 'src/main.ts', 'src/main.js',
|
|
570
|
+
'index.ts', 'index.js', 'server.ts', 'server.js', 'app.ts', 'app.js',
|
|
571
|
+
'src/app.ts', 'src/app.js', 'src/server.ts', 'src/server.js',
|
|
572
|
+
];
|
|
573
|
+
for (const entry of entryPoints) {
|
|
574
|
+
if (fs.existsSync(path.join(cwd, entry))) {
|
|
575
|
+
result.entryPoint = entry;
|
|
576
|
+
break;
|
|
577
|
+
}
|
|
578
|
+
}
|
|
579
|
+
// Check for Python files (mixed project)
|
|
580
|
+
const hasPython = fs.existsSync(path.join(cwd, 'requirements.txt')) ||
|
|
581
|
+
fs.existsSync(path.join(cwd, 'pyproject.toml')) ||
|
|
582
|
+
fs.existsSync(path.join(cwd, 'setup.py'));
|
|
583
|
+
if (hasPython && Object.keys(packageJson).length > 0) {
|
|
584
|
+
result.runtime = 'mixed';
|
|
585
|
+
}
|
|
586
|
+
else if (hasPython && Object.keys(packageJson).length === 0) {
|
|
587
|
+
result.runtime = 'python';
|
|
588
|
+
}
|
|
589
|
+
return result;
|
|
590
|
+
}
|
|
591
|
+
/**
|
|
592
|
+
* Analyze project using AI (Claude or GPT) for intelligent configuration suggestions.
|
|
593
|
+
*
|
|
594
|
+
* This is optional and requires an API key to be available.
|
|
595
|
+
* Returns null if no AI API key is found or analysis fails.
|
|
596
|
+
*/
|
|
597
|
+
async function analyzeProjectWithAI() {
|
|
598
|
+
const cwd = process.cwd();
|
|
599
|
+
logger_1.logger.warning('AI analysis invoked; project metadata may be sent to external providers when explicitly enabled.');
|
|
600
|
+
if (!isAiAnalysisEnabled()) {
|
|
601
|
+
logger_1.logger.warning(`AI analysis is disabled. Set ${AI_ANALYSIS_ENV_FLAG}=true to enable external analysis.`);
|
|
602
|
+
return null;
|
|
603
|
+
}
|
|
604
|
+
// Check for available API keys
|
|
605
|
+
const anthropicKey = process.env.ANTHROPIC_API_KEY;
|
|
606
|
+
const openaiKey = process.env.OPENAI_API_KEY;
|
|
607
|
+
if (!anthropicKey && !openaiKey) {
|
|
608
|
+
return null;
|
|
609
|
+
}
|
|
610
|
+
// Gather project context
|
|
611
|
+
let packageJsonContent = '';
|
|
612
|
+
let entryFileContent = '';
|
|
613
|
+
let fileStructure = '';
|
|
614
|
+
try {
|
|
615
|
+
const packageJsonPath = path.join(cwd, 'package.json');
|
|
616
|
+
if (fs.existsSync(packageJsonPath)) {
|
|
617
|
+
packageJsonContent = sanitizePackageJsonContent(fs.readFileSync(packageJsonPath, 'utf-8'));
|
|
618
|
+
}
|
|
619
|
+
// Get file structure (limited depth)
|
|
620
|
+
fileStructure = getDirectoryStructure(cwd, 2);
|
|
621
|
+
// Read entry point if exists
|
|
622
|
+
const detection = detectFramework();
|
|
623
|
+
if (detection.entryPoint) {
|
|
624
|
+
const entryPath = path.join(cwd, detection.entryPoint);
|
|
625
|
+
if (fs.existsSync(entryPath)) {
|
|
626
|
+
const content = fs.readFileSync(entryPath, 'utf-8');
|
|
627
|
+
// Limit to first 200 lines
|
|
628
|
+
entryFileContent = redactSensitiveStrings(content.split('\n').slice(0, 200).join('\n'));
|
|
629
|
+
}
|
|
630
|
+
}
|
|
631
|
+
}
|
|
632
|
+
catch {
|
|
633
|
+
// Ignore file read errors
|
|
634
|
+
}
|
|
635
|
+
const prompt = `Analyze this Node.js/TypeScript project for AI governance configuration.
|
|
636
|
+
|
|
637
|
+
Project structure:
|
|
638
|
+
${fileStructure}
|
|
639
|
+
|
|
640
|
+
package.json:
|
|
641
|
+
${packageJsonContent.slice(0, 2000)}
|
|
642
|
+
|
|
643
|
+
Entry point (first 200 lines):
|
|
644
|
+
${entryFileContent.slice(0, 3000)}
|
|
645
|
+
|
|
646
|
+
Based on this project, suggest:
|
|
647
|
+
1. Which Monora preset to use (development, poc, staging, production, enterprise)
|
|
648
|
+
2. Any specific configuration overrides needed
|
|
649
|
+
3. Compliance requirements (SOC2, HIPAA, GDPR, ISO42001, EU_AI_ACT)
|
|
650
|
+
|
|
651
|
+
Respond in JSON format:
|
|
652
|
+
{
|
|
653
|
+
"suggestedPreset": "string",
|
|
654
|
+
"suggestedConfig": { "key": "value" },
|
|
655
|
+
"reasoning": "brief explanation",
|
|
656
|
+
"confidence": 0.0-1.0,
|
|
657
|
+
"complianceHints": ["SOC2", "GDPR"]
|
|
658
|
+
}`;
|
|
659
|
+
try {
|
|
660
|
+
logger_1.logger.warning('Sending sanitized project context to external AI provider for analysis.');
|
|
661
|
+
if (anthropicKey) {
|
|
662
|
+
return await callAnthropicForAnalysis(anthropicKey, prompt);
|
|
663
|
+
}
|
|
664
|
+
else if (openaiKey) {
|
|
665
|
+
return await callOpenAIForAnalysis(openaiKey, prompt);
|
|
666
|
+
}
|
|
667
|
+
}
|
|
668
|
+
catch {
|
|
669
|
+
// AI analysis failed, return null
|
|
670
|
+
return null;
|
|
671
|
+
}
|
|
672
|
+
return null;
|
|
673
|
+
}
|
|
674
|
+
/**
|
|
675
|
+
* Get directory structure as a string (limited depth).
|
|
676
|
+
*/
|
|
677
|
+
function getDirectoryStructure(dir, maxDepth, currentDepth = 0, prefix = '') {
|
|
678
|
+
if (currentDepth >= maxDepth) {
|
|
679
|
+
return '';
|
|
680
|
+
}
|
|
681
|
+
const ignoreDirs = ['node_modules', '.git', 'dist', 'build', '.next', '__pycache__', '.venv', 'venv'];
|
|
682
|
+
let result = '';
|
|
683
|
+
try {
|
|
684
|
+
const entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
685
|
+
const filteredEntries = entries
|
|
686
|
+
.filter(e => !ignoreDirs.includes(e.name) && !e.name.startsWith('.'))
|
|
687
|
+
.slice(0, 20); // Limit entries
|
|
688
|
+
for (const entry of filteredEntries) {
|
|
689
|
+
const icon = entry.isDirectory() ? '📁' : '📄';
|
|
690
|
+
result += `${prefix}${icon} ${entry.name}\n`;
|
|
691
|
+
if (entry.isDirectory()) {
|
|
692
|
+
result += getDirectoryStructure(path.join(dir, entry.name), maxDepth, currentDepth + 1, prefix + ' ');
|
|
693
|
+
}
|
|
694
|
+
}
|
|
695
|
+
}
|
|
696
|
+
catch {
|
|
697
|
+
// Ignore permission errors
|
|
698
|
+
}
|
|
699
|
+
return result;
|
|
700
|
+
}
|
|
701
|
+
/**
|
|
702
|
+
* Call Anthropic API for project analysis.
|
|
703
|
+
*/
|
|
704
|
+
async function callAnthropicForAnalysis(apiKey, prompt) {
|
|
705
|
+
try {
|
|
706
|
+
const response = await fetch('https://api.anthropic.com/v1/messages', {
|
|
707
|
+
method: 'POST',
|
|
708
|
+
headers: {
|
|
709
|
+
'Content-Type': 'application/json',
|
|
710
|
+
'x-api-key': apiKey,
|
|
711
|
+
'anthropic-version': '2023-06-01',
|
|
712
|
+
},
|
|
713
|
+
body: JSON.stringify({
|
|
714
|
+
model: 'claude-haiku-4-5-20251001',
|
|
715
|
+
max_tokens: 1024,
|
|
716
|
+
messages: [{ role: 'user', content: prompt }],
|
|
717
|
+
}),
|
|
718
|
+
});
|
|
719
|
+
if (!response.ok) {
|
|
720
|
+
return null;
|
|
721
|
+
}
|
|
722
|
+
const data = await response.json();
|
|
723
|
+
const text = data.content?.[0]?.text;
|
|
724
|
+
if (!text) {
|
|
725
|
+
return null;
|
|
726
|
+
}
|
|
727
|
+
// Extract JSON from response
|
|
728
|
+
const jsonMatch = text.match(/\{[\s\S]*\}/);
|
|
729
|
+
if (!jsonMatch) {
|
|
730
|
+
return null;
|
|
731
|
+
}
|
|
732
|
+
const parsed = JSON.parse(jsonMatch[0]);
|
|
733
|
+
return {
|
|
734
|
+
suggestedPreset: parsed.suggestedPreset || 'development',
|
|
735
|
+
suggestedConfig: parsed.suggestedConfig || {},
|
|
736
|
+
reasoning: parsed.reasoning || '',
|
|
737
|
+
confidence: typeof parsed.confidence === 'number' ? parsed.confidence : 0.5,
|
|
738
|
+
complianceHints: Array.isArray(parsed.complianceHints) ? parsed.complianceHints : [],
|
|
739
|
+
};
|
|
740
|
+
}
|
|
741
|
+
catch {
|
|
742
|
+
return null;
|
|
743
|
+
}
|
|
744
|
+
}
|
|
745
|
+
/**
|
|
746
|
+
* Call OpenAI API for project analysis.
|
|
747
|
+
*/
|
|
748
|
+
async function callOpenAIForAnalysis(apiKey, prompt) {
|
|
749
|
+
try {
|
|
750
|
+
const response = await fetch('https://api.openai.com/v1/chat/completions', {
|
|
751
|
+
method: 'POST',
|
|
752
|
+
headers: {
|
|
753
|
+
'Content-Type': 'application/json',
|
|
754
|
+
'Authorization': `Bearer ${apiKey}`,
|
|
755
|
+
},
|
|
756
|
+
body: JSON.stringify({
|
|
757
|
+
model: 'gpt-4o-mini',
|
|
758
|
+
max_tokens: 1024,
|
|
759
|
+
messages: [{ role: 'user', content: prompt }],
|
|
760
|
+
}),
|
|
761
|
+
});
|
|
762
|
+
if (!response.ok) {
|
|
763
|
+
return null;
|
|
764
|
+
}
|
|
765
|
+
const data = await response.json();
|
|
766
|
+
const text = data.choices?.[0]?.message?.content;
|
|
767
|
+
if (!text) {
|
|
768
|
+
return null;
|
|
769
|
+
}
|
|
770
|
+
// Extract JSON from response
|
|
771
|
+
const jsonMatch = text.match(/\{[\s\S]*\}/);
|
|
772
|
+
if (!jsonMatch) {
|
|
773
|
+
return null;
|
|
774
|
+
}
|
|
775
|
+
const parsed = JSON.parse(jsonMatch[0]);
|
|
776
|
+
return {
|
|
777
|
+
suggestedPreset: parsed.suggestedPreset || 'development',
|
|
778
|
+
suggestedConfig: parsed.suggestedConfig || {},
|
|
779
|
+
reasoning: parsed.reasoning || '',
|
|
780
|
+
confidence: typeof parsed.confidence === 'number' ? parsed.confidence : 0.5,
|
|
781
|
+
complianceHints: Array.isArray(parsed.complianceHints) ? parsed.complianceHints : [],
|
|
782
|
+
};
|
|
783
|
+
}
|
|
784
|
+
catch {
|
|
785
|
+
return null;
|
|
786
|
+
}
|
|
787
|
+
}
|
|
788
|
+
/**
|
|
789
|
+
* Build a smart configuration based on all detection results.
|
|
790
|
+
*/
|
|
791
|
+
function buildSmartConfig(detection, aiAnalysis, attribution) {
|
|
792
|
+
const env = detectEnvironment();
|
|
793
|
+
const preset = aiAnalysis?.suggestedPreset || selectPresetForEnvironment(env);
|
|
794
|
+
const config = {
|
|
795
|
+
config_version: '1.0.0',
|
|
796
|
+
preset,
|
|
797
|
+
defaults: {
|
|
798
|
+
service_name: detectServiceName() || 'monora-app',
|
|
799
|
+
environment: env,
|
|
800
|
+
},
|
|
801
|
+
// Default to file-only sink for cleaner output
|
|
802
|
+
// Users can add stdout sink manually if needed via: { type: 'stdout', format: 'pretty' }
|
|
803
|
+
sinks: [
|
|
804
|
+
{ type: 'file', path: './monora_events.jsonl', rotation: 'none' },
|
|
805
|
+
],
|
|
806
|
+
};
|
|
807
|
+
if (['dev', 'development', 'poc'].includes(env)) {
|
|
808
|
+
config.telemetry = { enabled: true, backend: 'minimal' };
|
|
809
|
+
}
|
|
810
|
+
// Add model allowlist from detection
|
|
811
|
+
if (detection.suggestedModels.length > 0) {
|
|
812
|
+
config.policies = {
|
|
813
|
+
model_allowlist: detection.suggestedModels,
|
|
814
|
+
enforce: true,
|
|
815
|
+
};
|
|
816
|
+
}
|
|
817
|
+
// Enable instrumentation for detected SDKs
|
|
818
|
+
if (detection.aiSdks.length > 0) {
|
|
819
|
+
config.instrumentation = {
|
|
820
|
+
enabled: true,
|
|
821
|
+
targets: detection.aiSdks.map(sdk => sdk.name),
|
|
822
|
+
};
|
|
823
|
+
}
|
|
824
|
+
// Add attribution
|
|
825
|
+
if (attribution.email || attribution.company || attribution.role) {
|
|
826
|
+
config.attribution = {
|
|
827
|
+
enabled: true,
|
|
828
|
+
project: {
|
|
829
|
+
email: attribution.email,
|
|
830
|
+
company: attribution.company,
|
|
831
|
+
role: attribution.role,
|
|
832
|
+
},
|
|
833
|
+
};
|
|
834
|
+
}
|
|
835
|
+
// Apply AI suggestions
|
|
836
|
+
if (aiAnalysis) {
|
|
837
|
+
const sanitizedSuggestedConfig = sanitizeSuggestedConfig(aiAnalysis.suggestedConfig);
|
|
838
|
+
safeDeepMerge(config, sanitizedSuggestedConfig);
|
|
839
|
+
// Enable compliance features based on hints
|
|
840
|
+
if (aiAnalysis.complianceHints.includes('SOC2')) {
|
|
841
|
+
config.identity = { enabled: true, require_identity: false };
|
|
842
|
+
config.risk_register = { enabled: true };
|
|
843
|
+
}
|
|
844
|
+
if (aiAnalysis.complianceHints.includes('ISO42001') || aiAnalysis.complianceHints.includes('EU_AI_ACT')) {
|
|
845
|
+
config.bias = { enabled: true };
|
|
846
|
+
config.human_oversight = { enabled: true };
|
|
847
|
+
}
|
|
848
|
+
}
|
|
849
|
+
return config;
|
|
850
|
+
}
|