claudex-setup 1.10.0 → 1.10.2
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/CHANGELOG.md +11 -0
- package/README.md +4 -4
- package/package.json +1 -1
- package/src/analyze.js +1 -1
- package/src/domain-packs.js +5 -5
- package/src/index.js +2 -1
- package/src/mcp-packs.js +132 -25
- package/src/plans.js +21 -0
- package/src/setup.js +13 -0
- package/src/techniques.js +5 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,16 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [1.10.2] - 2026-04-02
|
|
4
|
+
|
|
5
|
+
### Fixed
|
|
6
|
+
- MCP recommendations are now less speculative: `postgres-mcp` requires explicit Postgres signals, `figma-mcp` only appears for design-system repos, and `mcp-security` is no longer auto-added just because multiple packs were suggested
|
|
7
|
+
- `sentry-mcp` now requires real observability signals or stricter operational domains instead of appearing for every frontend/backend repo
|
|
8
|
+
- design-system detection now respects `.storybook/` directories directly, improving frontend pack accuracy
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
- MCP preflight warnings for `setup`, `plan`, and `apply` when selected packs require missing environment variables
|
|
12
|
+
- user-facing docs now reflect the actual 22 detected stacks
|
|
13
|
+
|
|
3
14
|
## [1.10.0] - 2026-04-01
|
|
4
15
|
|
|
5
16
|
### Added
|
package/README.md
CHANGED
|
@@ -204,16 +204,16 @@ The exact applicable count can be lower on a given repo because stack-specific c
|
|
|
204
204
|
|
|
205
205
|
## Stack Detection
|
|
206
206
|
|
|
207
|
-
Auto-detects and tailors output for
|
|
207
|
+
Auto-detects and tailors output for 22 stacks:
|
|
208
208
|
|
|
209
209
|
| | |
|
|
210
210
|
|--|--|
|
|
211
211
|
| **Frontend** | React, Vue, Angular, Next.js, Svelte |
|
|
212
212
|
| **Backend** | Node.js, Python, Django, FastAPI |
|
|
213
213
|
| **Mobile** | Flutter, Swift, Kotlin |
|
|
214
|
-
| **Systems** | Rust, Go, Java, Ruby |
|
|
214
|
+
| **Systems** | Rust, Go, Java, Ruby, C++, Bazel |
|
|
215
215
|
| **Language** | TypeScript |
|
|
216
|
-
| **Infra** | Docker |
|
|
216
|
+
| **Infra** | Docker, Terraform, Kubernetes |
|
|
217
217
|
|
|
218
218
|
## GitHub Action
|
|
219
219
|
|
|
@@ -227,7 +227,7 @@ jobs:
|
|
|
227
227
|
runs-on: ubuntu-latest
|
|
228
228
|
steps:
|
|
229
229
|
- uses: actions/checkout@v4
|
|
230
|
-
- uses: DnaFin/claudex-setup@v1.10.
|
|
230
|
+
- uses: DnaFin/claudex-setup@v1.10.2
|
|
231
231
|
with:
|
|
232
232
|
threshold: 50
|
|
233
233
|
```
|
package/package.json
CHANGED
package/src/analyze.js
CHANGED
|
@@ -318,7 +318,7 @@ async function analyzeProject(options) {
|
|
|
318
318
|
const maturity = detectMaturity(assets);
|
|
319
319
|
const mainDirs = detectMainDirs(ctx);
|
|
320
320
|
const recommendedDomainPacks = detectDomainPacks(ctx, stacks, assets);
|
|
321
|
-
const recommendedMcpPacks = recommendMcpPacks(stacks, recommendedDomainPacks);
|
|
321
|
+
const recommendedMcpPacks = recommendMcpPacks(stacks, recommendedDomainPacks, { ctx, assets });
|
|
322
322
|
|
|
323
323
|
const report = {
|
|
324
324
|
mode,
|
package/src/domain-packs.js
CHANGED
|
@@ -243,14 +243,14 @@ function detectDomainPacks(ctx, stacks, assets = null) {
|
|
|
243
243
|
}
|
|
244
244
|
|
|
245
245
|
// Regulated-lite detection
|
|
246
|
-
const
|
|
247
|
-
|
|
246
|
+
const hasSecurityPolicy = ctx.files.includes('SECURITY.md');
|
|
247
|
+
const isRegulated = ctx.files.includes('COMPLIANCE.md') || ctx.hasDir('compliance') ||
|
|
248
248
|
ctx.hasDir('audit') || ctx.hasDir('policies') ||
|
|
249
249
|
(pkg.keywords && pkg.keywords.some(k => ['hipaa', 'fintech', 'compliance', 'regulated', 'sox', 'pci'].includes(k)));
|
|
250
250
|
if (isRegulated && !isEnterpriseGoverned) {
|
|
251
251
|
addMatch('regulated-lite', [
|
|
252
252
|
'Detected compliance or regulatory signals without full enterprise governance.',
|
|
253
|
-
ctx.files.includes('
|
|
253
|
+
ctx.files.includes('COMPLIANCE.md') ? 'COMPLIANCE.md present.' : null,
|
|
254
254
|
ctx.hasDir('compliance') ? 'Compliance directory detected.' : null,
|
|
255
255
|
]);
|
|
256
256
|
}
|
|
@@ -293,7 +293,7 @@ function detectDomainPacks(ctx, stacks, assets = null) {
|
|
|
293
293
|
// Design system detection
|
|
294
294
|
const isDesignSystem = deps.storybook || deps['@storybook/react'] || deps['@storybook/vue3'] ||
|
|
295
295
|
ctx.hasDir('tokens') || ctx.hasDir('design-tokens') ||
|
|
296
|
-
(ctx.hasDir('components') && ctx.
|
|
296
|
+
(ctx.hasDir('components') && ctx.hasDir('.storybook'));
|
|
297
297
|
if (isDesignSystem) {
|
|
298
298
|
addMatch('design-system', [
|
|
299
299
|
'Detected design system or component library signals.',
|
|
@@ -315,7 +315,7 @@ function detectDomainPacks(ctx, stacks, assets = null) {
|
|
|
315
315
|
}
|
|
316
316
|
|
|
317
317
|
// Security-focused detection
|
|
318
|
-
const isSecurityFocused =
|
|
318
|
+
const isSecurityFocused = hasSecurityPolicy &&
|
|
319
319
|
(hasBackend || deps.bcrypt || deps.jsonwebtoken || deps.passport || deps['next-auth']);
|
|
320
320
|
if (isSecurityFocused && !isRegulated) {
|
|
321
321
|
addMatch('security-focused', [
|
package/src/index.js
CHANGED
|
@@ -5,7 +5,7 @@ const { buildProposalBundle, applyProposalBundle } = require('./plans');
|
|
|
5
5
|
const { getGovernanceSummary } = require('./governance');
|
|
6
6
|
const { runBenchmark } = require('./benchmark');
|
|
7
7
|
const { DOMAIN_PACKS, detectDomainPacks } = require('./domain-packs');
|
|
8
|
-
const { MCP_PACKS, getMcpPack, mergeMcpServers, recommendMcpPacks } = require('./mcp-packs');
|
|
8
|
+
const { MCP_PACKS, getMcpPack, mergeMcpServers, getMcpPackPreflight, recommendMcpPacks } = require('./mcp-packs');
|
|
9
9
|
|
|
10
10
|
module.exports = {
|
|
11
11
|
audit,
|
|
@@ -20,5 +20,6 @@ module.exports = {
|
|
|
20
20
|
MCP_PACKS,
|
|
21
21
|
getMcpPack,
|
|
22
22
|
mergeMcpServers,
|
|
23
|
+
getMcpPackPreflight,
|
|
23
24
|
recommendMcpPacks,
|
|
24
25
|
};
|
package/src/mcp-packs.js
CHANGED
|
@@ -81,7 +81,7 @@ const MCP_PACKS = [
|
|
|
81
81
|
servers: {
|
|
82
82
|
docker: {
|
|
83
83
|
command: 'npx',
|
|
84
|
-
args: ['-y', '@
|
|
84
|
+
args: ['-y', '@hypnosis/docker-mcp-server'],
|
|
85
85
|
},
|
|
86
86
|
},
|
|
87
87
|
},
|
|
@@ -106,7 +106,7 @@ const MCP_PACKS = [
|
|
|
106
106
|
servers: {
|
|
107
107
|
linear: {
|
|
108
108
|
command: 'npx',
|
|
109
|
-
args: ['-y', '@linear
|
|
109
|
+
args: ['-y', '@mseep/linear-mcp'],
|
|
110
110
|
env: { LINEAR_API_KEY: '${LINEAR_API_KEY}' },
|
|
111
111
|
},
|
|
112
112
|
},
|
|
@@ -132,7 +132,7 @@ const MCP_PACKS = [
|
|
|
132
132
|
servers: {
|
|
133
133
|
slack: {
|
|
134
134
|
command: 'npx',
|
|
135
|
-
args: ['-y', '
|
|
135
|
+
args: ['-y', 'slack-mcp-server'],
|
|
136
136
|
env: { SLACK_BOT_TOKEN: '${SLACK_BOT_TOKEN}' },
|
|
137
137
|
},
|
|
138
138
|
},
|
|
@@ -145,7 +145,7 @@ const MCP_PACKS = [
|
|
|
145
145
|
servers: {
|
|
146
146
|
stripe: {
|
|
147
147
|
command: 'npx',
|
|
148
|
-
args: ['-y', '@stripe/mcp
|
|
148
|
+
args: ['-y', '@stripe/mcp'],
|
|
149
149
|
env: { STRIPE_API_KEY: '${STRIPE_API_KEY}' },
|
|
150
150
|
},
|
|
151
151
|
},
|
|
@@ -158,7 +158,7 @@ const MCP_PACKS = [
|
|
|
158
158
|
servers: {
|
|
159
159
|
figma: {
|
|
160
160
|
command: 'npx',
|
|
161
|
-
args: ['-y', '
|
|
161
|
+
args: ['-y', 'claude-talk-to-figma-mcp'],
|
|
162
162
|
env: { FIGMA_ACCESS_TOKEN: '${FIGMA_ACCESS_TOKEN}' },
|
|
163
163
|
},
|
|
164
164
|
},
|
|
@@ -183,7 +183,7 @@ const MCP_PACKS = [
|
|
|
183
183
|
servers: {
|
|
184
184
|
composio: {
|
|
185
185
|
command: 'npx',
|
|
186
|
-
args: ['-y', 'composio
|
|
186
|
+
args: ['-y', '@composio/mcp'],
|
|
187
187
|
env: { COMPOSIO_API_KEY: '${COMPOSIO_API_KEY}' },
|
|
188
188
|
},
|
|
189
189
|
},
|
|
@@ -208,7 +208,7 @@ const MCP_PACKS = [
|
|
|
208
208
|
servers: {
|
|
209
209
|
jira: {
|
|
210
210
|
command: 'npx',
|
|
211
|
-
args: ['-y', '
|
|
211
|
+
args: ['-y', 'jira-mcp'],
|
|
212
212
|
env: { ATLASSIAN_API_TOKEN: '${ATLASSIAN_API_TOKEN}', ATLASSIAN_EMAIL: '${ATLASSIAN_EMAIL}' },
|
|
213
213
|
},
|
|
214
214
|
},
|
|
@@ -221,7 +221,7 @@ const MCP_PACKS = [
|
|
|
221
221
|
servers: {
|
|
222
222
|
ga4: {
|
|
223
223
|
command: 'npx',
|
|
224
|
-
args: ['-y', '
|
|
224
|
+
args: ['-y', 'mcp-server-ga4'],
|
|
225
225
|
env: { GA4_PROPERTY_ID: '${GA4_PROPERTY_ID}' },
|
|
226
226
|
},
|
|
227
227
|
},
|
|
@@ -260,7 +260,7 @@ const MCP_PACKS = [
|
|
|
260
260
|
servers: {
|
|
261
261
|
zendesk: {
|
|
262
262
|
command: 'npx',
|
|
263
|
-
args: ['-y', 'zendesk-mcp
|
|
263
|
+
args: ['-y', 'zendesk-mcp'],
|
|
264
264
|
env: { ZENDESK_API_TOKEN: '${ZENDESK_API_TOKEN}', ZENDESK_SUBDOMAIN: '${ZENDESK_SUBDOMAIN}' },
|
|
265
265
|
},
|
|
266
266
|
},
|
|
@@ -273,7 +273,7 @@ const MCP_PACKS = [
|
|
|
273
273
|
servers: {
|
|
274
274
|
infisical: {
|
|
275
275
|
command: 'npx',
|
|
276
|
-
args: ['-y', '@infisical/mcp
|
|
276
|
+
args: ['-y', '@infisical/mcp'],
|
|
277
277
|
env: { INFISICAL_TOKEN: '${INFISICAL_TOKEN}' },
|
|
278
278
|
},
|
|
279
279
|
},
|
|
@@ -286,7 +286,7 @@ const MCP_PACKS = [
|
|
|
286
286
|
servers: {
|
|
287
287
|
shopify: {
|
|
288
288
|
command: 'npx',
|
|
289
|
-
args: ['-y', '
|
|
289
|
+
args: ['-y', 'shopify-mcp'],
|
|
290
290
|
env: { SHOPIFY_ACCESS_TOKEN: '${SHOPIFY_ACCESS_TOKEN}' },
|
|
291
291
|
},
|
|
292
292
|
},
|
|
@@ -299,7 +299,7 @@ const MCP_PACKS = [
|
|
|
299
299
|
servers: {
|
|
300
300
|
huggingface: {
|
|
301
301
|
command: 'npx',
|
|
302
|
-
args: ['-y', '
|
|
302
|
+
args: ['-y', 'huggingface-mcp-server'],
|
|
303
303
|
env: { HF_TOKEN: '${HF_TOKEN}' },
|
|
304
304
|
},
|
|
305
305
|
},
|
|
@@ -312,7 +312,7 @@ const MCP_PACKS = [
|
|
|
312
312
|
servers: {
|
|
313
313
|
blender: {
|
|
314
314
|
command: 'npx',
|
|
315
|
-
args: ['-y', 'blender-mcp
|
|
315
|
+
args: ['-y', '@glutamateapp/blender-mcp-ts'],
|
|
316
316
|
},
|
|
317
317
|
},
|
|
318
318
|
},
|
|
@@ -335,6 +335,71 @@ function clone(value) {
|
|
|
335
335
|
return JSON.parse(JSON.stringify(value));
|
|
336
336
|
}
|
|
337
337
|
|
|
338
|
+
function hasDependency(deps, name) {
|
|
339
|
+
return Object.prototype.hasOwnProperty.call(deps || {}, name);
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
function hasFileContentMatch(ctx, filePath, pattern) {
|
|
343
|
+
if (!ctx) return false;
|
|
344
|
+
const content = ctx.fileContent(filePath);
|
|
345
|
+
return !!(content && pattern.test(content));
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
function getProjectDependencies(ctx) {
|
|
349
|
+
if (!ctx) return {};
|
|
350
|
+
const pkg = ctx.jsonFile('package.json') || {};
|
|
351
|
+
return {
|
|
352
|
+
...(pkg.dependencies || {}),
|
|
353
|
+
...(pkg.devDependencies || {}),
|
|
354
|
+
};
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
function hasPostgresSignals(ctx, deps = {}) {
|
|
358
|
+
if (
|
|
359
|
+
hasDependency(deps, 'pg') ||
|
|
360
|
+
hasDependency(deps, 'postgres') ||
|
|
361
|
+
hasDependency(deps, 'pg-promise') ||
|
|
362
|
+
hasDependency(deps, 'postgres.js') ||
|
|
363
|
+
hasDependency(deps, 'slonik') ||
|
|
364
|
+
hasDependency(deps, '@neondatabase/serverless') ||
|
|
365
|
+
hasDependency(deps, '@vercel/postgres')
|
|
366
|
+
) {
|
|
367
|
+
return true;
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
return (
|
|
371
|
+
hasFileContentMatch(ctx, 'prisma/schema.prisma', /provider\s*=\s*["']postgresql["']/i) ||
|
|
372
|
+
hasFileContentMatch(ctx, 'docker-compose.yml', /\bpostgres\b/i) ||
|
|
373
|
+
hasFileContentMatch(ctx, 'docker-compose.yaml', /\bpostgres\b/i) ||
|
|
374
|
+
hasFileContentMatch(ctx, 'compose.yml', /\bpostgres\b/i) ||
|
|
375
|
+
hasFileContentMatch(ctx, 'compose.yaml', /\bpostgres\b/i) ||
|
|
376
|
+
hasFileContentMatch(ctx, '.env', /postgres(?:ql)?:\/\//i) ||
|
|
377
|
+
hasFileContentMatch(ctx, '.env.local', /postgres(?:ql)?:\/\//i) ||
|
|
378
|
+
hasFileContentMatch(ctx, '.env.example', /postgres(?:ql)?:\/\//i)
|
|
379
|
+
);
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
function hasObservabilitySignals(ctx, deps = {}) {
|
|
383
|
+
if (
|
|
384
|
+
hasDependency(deps, '@sentry/nextjs') ||
|
|
385
|
+
hasDependency(deps, '@sentry/node') ||
|
|
386
|
+
hasDependency(deps, '@sentry/react') ||
|
|
387
|
+
hasDependency(deps, '@sentry/vue') ||
|
|
388
|
+
hasDependency(deps, '@sentry/browser')
|
|
389
|
+
) {
|
|
390
|
+
return true;
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
return (
|
|
394
|
+
hasFileContentMatch(ctx, 'sentry.client.config.js', /\S/) ||
|
|
395
|
+
hasFileContentMatch(ctx, 'sentry.client.config.ts', /\S/) ||
|
|
396
|
+
hasFileContentMatch(ctx, 'sentry.server.config.js', /\S/) ||
|
|
397
|
+
hasFileContentMatch(ctx, 'sentry.server.config.ts', /\S/) ||
|
|
398
|
+
hasFileContentMatch(ctx, 'instrumentation.ts', /sentry/i) ||
|
|
399
|
+
hasFileContentMatch(ctx, 'instrumentation.js', /sentry/i)
|
|
400
|
+
);
|
|
401
|
+
}
|
|
402
|
+
|
|
338
403
|
function getMcpPack(key) {
|
|
339
404
|
return MCP_PACKS.find(pack => pack.key === key) || null;
|
|
340
405
|
}
|
|
@@ -360,9 +425,46 @@ function mergeMcpServers(existing = {}, packKeys = []) {
|
|
|
360
425
|
return merged;
|
|
361
426
|
}
|
|
362
427
|
|
|
363
|
-
function
|
|
428
|
+
function getRequiredEnvVars(packKeys = []) {
|
|
429
|
+
const required = new Set();
|
|
430
|
+
for (const key of normalizeMcpPackKeys(packKeys)) {
|
|
431
|
+
const pack = getMcpPack(key);
|
|
432
|
+
if (!pack) continue;
|
|
433
|
+
for (const serverConfig of Object.values(pack.servers || {})) {
|
|
434
|
+
for (const envKey of Object.keys(serverConfig.env || {})) {
|
|
435
|
+
required.add(envKey);
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
return [...required].sort();
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
function getMcpPackPreflight(packKeys = [], env = process.env) {
|
|
443
|
+
return normalizeMcpPackKeys(packKeys)
|
|
444
|
+
.map((key) => {
|
|
445
|
+
const pack = getMcpPack(key);
|
|
446
|
+
if (!pack) return null;
|
|
447
|
+
const requiredEnvVars = getRequiredEnvVars([key]);
|
|
448
|
+
if (requiredEnvVars.length === 0) return null;
|
|
449
|
+
const missingEnvVars = requiredEnvVars.filter((envKey) => {
|
|
450
|
+
const value = env && Object.prototype.hasOwnProperty.call(env, envKey) ? env[envKey] : '';
|
|
451
|
+
return !`${value || ''}`.trim();
|
|
452
|
+
});
|
|
453
|
+
return {
|
|
454
|
+
key,
|
|
455
|
+
label: pack.label,
|
|
456
|
+
requiredEnvVars,
|
|
457
|
+
missingEnvVars,
|
|
458
|
+
};
|
|
459
|
+
})
|
|
460
|
+
.filter(Boolean);
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
function recommendMcpPacks(stacks = [], domainPacks = [], options = {}) {
|
|
364
464
|
const recommended = new Set();
|
|
365
465
|
const stackKeys = new Set(stacks.map(stack => stack.key));
|
|
466
|
+
const ctx = options.ctx || null;
|
|
467
|
+
const deps = getProjectDependencies(ctx);
|
|
366
468
|
|
|
367
469
|
for (const pack of domainPacks) {
|
|
368
470
|
for (const key of pack.recommendedMcpPacks || []) {
|
|
@@ -384,8 +486,8 @@ function recommendMcpPacks(stacks = [], domainPacks = []) {
|
|
|
384
486
|
recommended.add('github-mcp');
|
|
385
487
|
}
|
|
386
488
|
|
|
387
|
-
// Postgres MCP
|
|
388
|
-
if (domainKeys.has('data-pipeline') || domainKeys.has('backend-api')) {
|
|
489
|
+
// Postgres MCP only when there are explicit Postgres signals
|
|
490
|
+
if ((domainKeys.has('data-pipeline') || domainKeys.has('backend-api')) && hasPostgresSignals(ctx, deps)) {
|
|
389
491
|
recommended.add('postgres-mcp');
|
|
390
492
|
}
|
|
391
493
|
|
|
@@ -404,13 +506,21 @@ function recommendMcpPacks(stacks = [], domainPacks = []) {
|
|
|
404
506
|
recommended.add('docker-mcp');
|
|
405
507
|
}
|
|
406
508
|
|
|
407
|
-
// Sentry
|
|
408
|
-
if (
|
|
509
|
+
// Sentry when the repo already shows observability signals or has stricter operational needs
|
|
510
|
+
if (
|
|
511
|
+
(domainKeys.has('backend-api') || domainKeys.has('frontend-ui')) &&
|
|
512
|
+
(
|
|
513
|
+
hasObservabilitySignals(ctx, deps) ||
|
|
514
|
+
domainKeys.has('enterprise-governed') ||
|
|
515
|
+
domainKeys.has('security-focused') ||
|
|
516
|
+
domainKeys.has('ecommerce')
|
|
517
|
+
)
|
|
518
|
+
) {
|
|
409
519
|
recommended.add('sentry-mcp');
|
|
410
520
|
}
|
|
411
521
|
|
|
412
|
-
// Figma
|
|
413
|
-
if (domainKeys.has('
|
|
522
|
+
// Figma only when design-system signals are present
|
|
523
|
+
if (domainKeys.has('design-system')) {
|
|
414
524
|
recommended.add('figma-mcp');
|
|
415
525
|
}
|
|
416
526
|
|
|
@@ -450,11 +560,6 @@ function recommendMcpPacks(stacks = [], domainPacks = []) {
|
|
|
450
560
|
recommended.add('infisical-secrets');
|
|
451
561
|
}
|
|
452
562
|
|
|
453
|
-
// Security scanner when 2+ MCP servers recommended
|
|
454
|
-
if (recommended.size >= 2) {
|
|
455
|
-
recommended.add('mcp-security');
|
|
456
|
-
}
|
|
457
|
-
|
|
458
563
|
return MCP_PACKS
|
|
459
564
|
.filter(pack => recommended.has(pack.key))
|
|
460
565
|
.map(pack => clone(pack));
|
|
@@ -465,5 +570,7 @@ module.exports = {
|
|
|
465
570
|
getMcpPack,
|
|
466
571
|
normalizeMcpPackKeys,
|
|
467
572
|
mergeMcpServers,
|
|
573
|
+
getRequiredEnvVars,
|
|
574
|
+
getMcpPackPreflight,
|
|
468
575
|
recommendMcpPacks,
|
|
469
576
|
};
|
package/src/plans.js
CHANGED
|
@@ -7,6 +7,7 @@ const { ProjectContext } = require('./context');
|
|
|
7
7
|
const { TECHNIQUES, STACKS } = require('./techniques');
|
|
8
8
|
const { TEMPLATES } = require('./setup');
|
|
9
9
|
const { buildSettingsForProfile } = require('./governance');
|
|
10
|
+
const { getMcpPackPreflight } = require('./mcp-packs');
|
|
10
11
|
const { writeActivityArtifact, writeRollbackArtifact } = require('./activity');
|
|
11
12
|
|
|
12
13
|
const TEMPLATE_DIR_MAP = {
|
|
@@ -374,6 +375,8 @@ async function buildProposalBundle(options) {
|
|
|
374
375
|
const ctx = new ProjectContext(options.dir);
|
|
375
376
|
const stacks = ctx.detectStacks(STACKS);
|
|
376
377
|
const report = await analyzeProject({ ...options, mode: 'augment' });
|
|
378
|
+
const mcpPreflightWarnings = getMcpPackPreflight(options.mcpPacks || [])
|
|
379
|
+
.filter(item => item.missingEnvVars.length > 0);
|
|
377
380
|
const groups = getFailedTemplateGroups(ctx, options.only || []);
|
|
378
381
|
const proposals = [];
|
|
379
382
|
|
|
@@ -406,6 +409,7 @@ async function buildProposalBundle(options) {
|
|
|
406
409
|
strengthsPreserved: report.strengthsPreserved,
|
|
407
410
|
topNextActions: report.topNextActions,
|
|
408
411
|
riskNotes: report.riskNotes,
|
|
412
|
+
mcpPreflightWarnings,
|
|
409
413
|
proposals,
|
|
410
414
|
};
|
|
411
415
|
}
|
|
@@ -422,6 +426,14 @@ function printProposalBundle(bundle, options = {}) {
|
|
|
422
426
|
console.log(` ${bundle.projectSummary.name} | maturity=${bundle.projectSummary.maturity} | score=${bundle.projectSummary.score}/100`);
|
|
423
427
|
console.log('');
|
|
424
428
|
|
|
429
|
+
if (bundle.mcpPreflightWarnings && bundle.mcpPreflightWarnings.length > 0) {
|
|
430
|
+
console.log(' MCP Preflight Warnings');
|
|
431
|
+
for (const warning of bundle.mcpPreflightWarnings) {
|
|
432
|
+
console.log(` - ${warning.label}: missing ${warning.missingEnvVars.join(', ')}`);
|
|
433
|
+
}
|
|
434
|
+
console.log('');
|
|
435
|
+
}
|
|
436
|
+
|
|
425
437
|
if (bundle.proposals.length === 0) {
|
|
426
438
|
console.log(' No templated proposals are needed right now.');
|
|
427
439
|
console.log('');
|
|
@@ -523,6 +535,8 @@ function resolvePlan(bundle, options) {
|
|
|
523
535
|
async function applyProposalBundle(options) {
|
|
524
536
|
const liveBundle = options.planFile ? null : await buildProposalBundle(options);
|
|
525
537
|
const bundle = resolvePlan(liveBundle, options);
|
|
538
|
+
const mcpPreflightWarnings = getMcpPackPreflight(options.mcpPacks || [])
|
|
539
|
+
.filter(item => item.missingEnvVars.length > 0);
|
|
526
540
|
const selectedIds = options.only && options.only.length > 0
|
|
527
541
|
? new Set(options.only)
|
|
528
542
|
: null;
|
|
@@ -589,6 +603,7 @@ async function applyProposalBundle(options) {
|
|
|
589
603
|
dryRun: options.dryRun === true,
|
|
590
604
|
rollbackArtifact: rollback ? rollback.relativePath : null,
|
|
591
605
|
activityArtifact: activity ? activity.relativePath : null,
|
|
606
|
+
mcpPreflightWarnings,
|
|
592
607
|
};
|
|
593
608
|
}
|
|
594
609
|
|
|
@@ -607,6 +622,12 @@ function printApplyResult(result, options = {}) {
|
|
|
607
622
|
console.log(` Applied proposal bundles: ${result.appliedProposalIds.join(', ') || 'none'}`);
|
|
608
623
|
console.log(` Created files: ${result.createdFiles.join(', ') || 'none'}`);
|
|
609
624
|
console.log(` Patched files: ${result.patchedFiles.join(', ') || 'none'}`);
|
|
625
|
+
if (result.mcpPreflightWarnings && result.mcpPreflightWarnings.length > 0) {
|
|
626
|
+
console.log(' MCP preflight warnings:');
|
|
627
|
+
for (const warning of result.mcpPreflightWarnings) {
|
|
628
|
+
console.log(` - ${warning.label}: missing ${warning.missingEnvVars.join(', ')}`);
|
|
629
|
+
}
|
|
630
|
+
}
|
|
610
631
|
if (result.rollbackArtifact) {
|
|
611
632
|
console.log(` Rollback: ${result.rollbackArtifact}`);
|
|
612
633
|
}
|
package/src/setup.js
CHANGED
|
@@ -9,6 +9,7 @@ const { TECHNIQUES, STACKS } = require('./techniques');
|
|
|
9
9
|
const { ProjectContext } = require('./context');
|
|
10
10
|
const { audit } = require('./audit');
|
|
11
11
|
const { buildSettingsForProfile } = require('./governance');
|
|
12
|
+
const { getMcpPackPreflight } = require('./mcp-packs');
|
|
12
13
|
|
|
13
14
|
// ============================================================
|
|
14
15
|
// Helper: detect project scripts from package.json
|
|
@@ -1117,6 +1118,8 @@ async function setup(options) {
|
|
|
1117
1118
|
const silent = options.silent === true;
|
|
1118
1119
|
const writtenFiles = [];
|
|
1119
1120
|
const preservedFiles = [];
|
|
1121
|
+
const mcpPreflightWarnings = getMcpPackPreflight(options.mcpPacks || [])
|
|
1122
|
+
.filter(item => item.missingEnvVars.length > 0);
|
|
1120
1123
|
|
|
1121
1124
|
function log(message = '') {
|
|
1122
1125
|
if (!silent) {
|
|
@@ -1243,6 +1246,15 @@ async function setup(options) {
|
|
|
1243
1246
|
}
|
|
1244
1247
|
|
|
1245
1248
|
log('');
|
|
1249
|
+
if (mcpPreflightWarnings.length > 0) {
|
|
1250
|
+
log('\x1b[33m MCP Preflight Warnings\x1b[0m');
|
|
1251
|
+
for (const warning of mcpPreflightWarnings) {
|
|
1252
|
+
log(` - ${warning.label}: missing ${warning.missingEnvVars.join(', ')}`);
|
|
1253
|
+
log(' \x1b[2m Settings were generated with placeholders, but this MCP server will not start until those env vars are set.\x1b[0m');
|
|
1254
|
+
}
|
|
1255
|
+
log('');
|
|
1256
|
+
}
|
|
1257
|
+
|
|
1246
1258
|
log(' Run \x1b[1mnpx claudex-setup audit\x1b[0m to check your score.');
|
|
1247
1259
|
log('');
|
|
1248
1260
|
|
|
@@ -1252,6 +1264,7 @@ async function setup(options) {
|
|
|
1252
1264
|
writtenFiles,
|
|
1253
1265
|
preservedFiles,
|
|
1254
1266
|
stacks,
|
|
1267
|
+
mcpPreflightWarnings,
|
|
1255
1268
|
};
|
|
1256
1269
|
}
|
|
1257
1270
|
|
package/src/techniques.js
CHANGED
|
@@ -696,8 +696,12 @@ const TECHNIQUES = {
|
|
|
696
696
|
id: 1801,
|
|
697
697
|
name: '2+ MCP servers for rich tooling',
|
|
698
698
|
check: (ctx) => {
|
|
699
|
+
let count = 0;
|
|
699
700
|
const settings = ctx.jsonFile('.claude/settings.local.json') || ctx.jsonFile('.claude/settings.json');
|
|
700
|
-
|
|
701
|
+
if (settings && settings.mcpServers) count += Object.keys(settings.mcpServers).length;
|
|
702
|
+
const mcpJson = ctx.jsonFile('.mcp.json');
|
|
703
|
+
if (mcpJson && mcpJson.mcpServers) count += Object.keys(mcpJson.mcpServers).length;
|
|
704
|
+
return count >= 2;
|
|
701
705
|
},
|
|
702
706
|
impact: 'medium',
|
|
703
707
|
rating: 4,
|