infrawise 0.8.1 → 0.8.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.
@@ -39,7 +39,9 @@ function buildMarkdownReport(findings, projectName) {
39
39
  renderGroup('Verify (check intent)', '🔵', verify),
40
40
  '',
41
41
  '_Generated by [infrawise](https://github.com/Sidd27/infrawise) — MCP server for AWS infrastructure analysis_',
42
- ].filter((l) => l !== undefined).join('\n');
42
+ ]
43
+ .filter((l) => l !== undefined)
44
+ .join('\n');
43
45
  }
44
46
  function mkSpinner(text) {
45
47
  return ora({ text: chalk.dim(text), color: 'cyan' }).start();
@@ -56,8 +58,12 @@ export async function runAnalyze(options = {}) {
56
58
  process.exit(1);
57
59
  }
58
60
  const repoPath = options.repo ?? process.cwd();
59
- const minSeverity = options.severity ? SEVERITY_ORDER[options.severity] ?? 1 : 0;
60
- const awsCfg = { region: config.aws?.region, profile: config.aws?.profile, endpoint: config.aws?.endpoint };
61
+ const minSeverity = options.severity ? (SEVERITY_ORDER[options.severity] ?? 1) : 0;
62
+ const awsCfg = {
63
+ region: config.aws?.region,
64
+ profile: config.aws?.profile,
65
+ endpoint: config.aws?.endpoint,
66
+ };
61
67
  const dynamoMeta = [];
62
68
  const postgresMeta = [];
63
69
  const mysqlMeta = [];
@@ -72,7 +78,8 @@ export async function runAnalyze(options = {}) {
72
78
  s.succeed(chalk.green('DynamoDB') + chalk.dim(` ${result.length} table(s)`));
73
79
  }
74
80
  catch (err) {
75
- s.warn(chalk.yellow('DynamoDB skipped') + chalk.dim(` ${err instanceof Error ? err.message : String(err)}`));
81
+ s.warn(chalk.yellow('DynamoDB skipped') +
82
+ chalk.dim(` ${err instanceof Error ? err.message : String(err)}`));
76
83
  }
77
84
  }
78
85
  // ── PostgreSQL ──────────────────────────────────────────────────────────────
@@ -84,7 +91,8 @@ export async function runAnalyze(options = {}) {
84
91
  s.succeed(chalk.green('PostgreSQL') + chalk.dim(` ${result.length} table(s)`));
85
92
  }
86
93
  catch (err) {
87
- s.warn(chalk.yellow('PostgreSQL skipped') + chalk.dim(` ${err instanceof Error ? err.message : String(err)}`));
94
+ s.warn(chalk.yellow('PostgreSQL skipped') +
95
+ chalk.dim(` ${err instanceof Error ? err.message : String(err)}`));
88
96
  }
89
97
  }
90
98
  // ── MySQL ───────────────────────────────────────────────────────────────────
@@ -96,7 +104,8 @@ export async function runAnalyze(options = {}) {
96
104
  s.succeed(chalk.green('MySQL') + chalk.dim(` ${result.length} table(s)`));
97
105
  }
98
106
  catch (err) {
99
- s.warn(chalk.yellow('MySQL skipped') + chalk.dim(` ${err instanceof Error ? err.message : String(err)}`));
107
+ s.warn(chalk.yellow('MySQL skipped') +
108
+ chalk.dim(` ${err instanceof Error ? err.message : String(err)}`));
100
109
  }
101
110
  }
102
111
  // ── MongoDB ─────────────────────────────────────────────────────────────────
@@ -108,7 +117,8 @@ export async function runAnalyze(options = {}) {
108
117
  s.succeed(chalk.green('MongoDB') + chalk.dim(` ${result.length} collection(s)`));
109
118
  }
110
119
  catch (err) {
111
- s.warn(chalk.yellow('MongoDB skipped') + chalk.dim(` ${err instanceof Error ? err.message : String(err)}`));
120
+ s.warn(chalk.yellow('MongoDB skipped') +
121
+ chalk.dim(` ${err instanceof Error ? err.message : String(err)}`));
112
122
  }
113
123
  }
114
124
  // ── SQS ─────────────────────────────────────────────────────────────────────
@@ -120,7 +130,8 @@ export async function runAnalyze(options = {}) {
120
130
  s.succeed(chalk.green('SQS') + chalk.dim(` ${result.length} queue(s)`));
121
131
  }
122
132
  catch (err) {
123
- s.warn(chalk.yellow('SQS skipped') + chalk.dim(` ${err instanceof Error ? err.message : String(err)}`));
133
+ s.warn(chalk.yellow('SQS skipped') +
134
+ chalk.dim(` ${err instanceof Error ? err.message : String(err)}`));
124
135
  }
125
136
  }
126
137
  // ── SNS ─────────────────────────────────────────────────────────────────────
@@ -132,7 +143,8 @@ export async function runAnalyze(options = {}) {
132
143
  s.succeed(chalk.green('SNS') + chalk.dim(` ${result.length} topic(s)`));
133
144
  }
134
145
  catch (err) {
135
- s.warn(chalk.yellow('SNS skipped') + chalk.dim(` ${err instanceof Error ? err.message : String(err)}`));
146
+ s.warn(chalk.yellow('SNS skipped') +
147
+ chalk.dim(` ${err instanceof Error ? err.message : String(err)}`));
136
148
  }
137
149
  }
138
150
  // ── SSM Parameter Store ──────────────────────────────────────────────────────
@@ -141,10 +153,13 @@ export async function runAnalyze(options = {}) {
141
153
  try {
142
154
  const result = await extractSSMMetadata({ ...awsCfg, paths: config.ssm?.paths });
143
155
  servicesMeta.ssm = result;
144
- s.succeed(chalk.green('SSM') + chalk.dim(` ${result.length} parameter(s) `) + chalk.dim('(metadata only, no values)'));
156
+ s.succeed(chalk.green('SSM') +
157
+ chalk.dim(` ${result.length} parameter(s) `) +
158
+ chalk.dim('(metadata only, no values)'));
145
159
  }
146
160
  catch (err) {
147
- s.warn(chalk.yellow('SSM skipped') + chalk.dim(` ${err instanceof Error ? err.message : String(err)}`));
161
+ s.warn(chalk.yellow('SSM skipped') +
162
+ chalk.dim(` ${err instanceof Error ? err.message : String(err)}`));
148
163
  }
149
164
  }
150
165
  // ── Secrets Manager ──────────────────────────────────────────────────────────
@@ -153,10 +168,13 @@ export async function runAnalyze(options = {}) {
153
168
  try {
154
169
  const result = await extractSecretsMetadata(awsCfg);
155
170
  servicesMeta.secrets = result;
156
- s.succeed(chalk.green('Secrets Manager') + chalk.dim(` ${result.length} secret(s) `) + chalk.dim('(names/rotation only, no values)'));
171
+ s.succeed(chalk.green('Secrets Manager') +
172
+ chalk.dim(` ${result.length} secret(s) `) +
173
+ chalk.dim('(names/rotation only, no values)'));
157
174
  }
158
175
  catch (err) {
159
- s.warn(chalk.yellow('Secrets Manager skipped') + chalk.dim(` ${err instanceof Error ? err.message : String(err)}`));
176
+ s.warn(chalk.yellow('Secrets Manager skipped') +
177
+ chalk.dim(` ${err instanceof Error ? err.message : String(err)}`));
160
178
  }
161
179
  }
162
180
  // ── Lambda ───────────────────────────────────────────────────────────────────
@@ -168,7 +186,8 @@ export async function runAnalyze(options = {}) {
168
186
  s.succeed(chalk.green('Lambda') + chalk.dim(` ${result.length} function(s)`));
169
187
  }
170
188
  catch (err) {
171
- s.warn(chalk.yellow('Lambda skipped') + chalk.dim(` ${err instanceof Error ? err.message : String(err)}`));
189
+ s.warn(chalk.yellow('Lambda skipped') +
190
+ chalk.dim(` ${err instanceof Error ? err.message : String(err)}`));
172
191
  }
173
192
  }
174
193
  // ── EventBridge ──────────────────────────────────────────────────────────────
@@ -180,7 +199,8 @@ export async function runAnalyze(options = {}) {
180
199
  s.succeed(chalk.green('EventBridge') + chalk.dim(` ${result.length} rule(s)`));
181
200
  }
182
201
  catch (err) {
183
- s.warn(chalk.yellow('EventBridge skipped') + chalk.dim(` ${err instanceof Error ? err.message : String(err)}`));
202
+ s.warn(chalk.yellow('EventBridge skipped') +
203
+ chalk.dim(` ${err instanceof Error ? err.message : String(err)}`));
184
204
  }
185
205
  }
186
206
  // ── RDS ──────────────────────────────────────────────────────────────────────
@@ -192,7 +212,8 @@ export async function runAnalyze(options = {}) {
192
212
  s.succeed(chalk.green('RDS') + chalk.dim(` ${result.length} instance(s)`));
193
213
  }
194
214
  catch (err) {
195
- s.warn(chalk.yellow('RDS skipped') + chalk.dim(` ${err instanceof Error ? err.message : String(err)}`));
215
+ s.warn(chalk.yellow('RDS skipped') +
216
+ chalk.dim(` ${err instanceof Error ? err.message : String(err)}`));
196
217
  }
197
218
  }
198
219
  // ── S3 ────────────────────────────────────────────────────────────────────────
@@ -204,7 +225,8 @@ export async function runAnalyze(options = {}) {
204
225
  s.succeed(chalk.green('S3') + chalk.dim(` ${result.length} bucket(s)`));
205
226
  }
206
227
  catch (err) {
207
- s.warn(chalk.yellow('S3 skipped') + chalk.dim(` ${err instanceof Error ? err.message : String(err)}`));
228
+ s.warn(chalk.yellow('S3 skipped') +
229
+ chalk.dim(` ${err instanceof Error ? err.message : String(err)}`));
208
230
  }
209
231
  }
210
232
  // ── CloudWatch Logs ──────────────────────────────────────────────────────────
@@ -218,10 +240,12 @@ export async function runAnalyze(options = {}) {
218
240
  });
219
241
  servicesMeta.logs = result;
220
242
  const errorGroups = result.filter((lg) => lg.errorCount > 0).length;
221
- s.succeed(chalk.green('CloudWatch Logs') + chalk.dim(` ${result.length} group(s), ${errorGroups} with errors`));
243
+ s.succeed(chalk.green('CloudWatch Logs') +
244
+ chalk.dim(` ${result.length} group(s), ${errorGroups} with errors`));
222
245
  }
223
246
  catch (err) {
224
- s.warn(chalk.yellow('CloudWatch Logs skipped') + chalk.dim(` ${err instanceof Error ? err.message : String(err)}`));
247
+ s.warn(chalk.yellow('CloudWatch Logs skipped') +
248
+ chalk.dim(` ${err instanceof Error ? err.message : String(err)}`));
225
249
  }
226
250
  }
227
251
  // ── IaC schema (Terraform / CloudFormation / CDK) ────────────────────────────
@@ -230,16 +254,23 @@ export async function runAnalyze(options = {}) {
230
254
  const s = mkSpinner('Extracting IaC schema (Terraform / CloudFormation / CDK)...');
231
255
  try {
232
256
  const iacSchema = await extractIaCSchema(repoPath);
233
- const total = iacSchema.dynamoTables.length + iacSchema.rdsInstances.length +
234
- iacSchema.mongoClusters.length + iacSchema.queues.length + iacSchema.topics.length +
235
- iacSchema.lambdas.length + iacSchema.buckets.length + iacSchema.parameters.length +
236
- iacSchema.secrets.length + iacSchema.apiGateways.length;
257
+ const total = iacSchema.dynamoTables.length +
258
+ iacSchema.rdsInstances.length +
259
+ iacSchema.mongoClusters.length +
260
+ iacSchema.queues.length +
261
+ iacSchema.topics.length +
262
+ iacSchema.lambdas.length +
263
+ iacSchema.buckets.length +
264
+ iacSchema.parameters.length +
265
+ iacSchema.secrets.length +
266
+ iacSchema.apiGateways.length;
237
267
  iacDriftAnalyzer = new IaCDriftAnalyzer();
238
268
  iacDriftAnalyzer.setIaCSchema(iacSchema);
239
269
  s.succeed(chalk.green('IaC schema') + chalk.dim(` ${total} resource(s) across TF/CFN/CDK`));
240
270
  }
241
271
  catch (err) {
242
- s.warn(chalk.yellow('IaC scan skipped') + chalk.dim(` ${err instanceof Error ? err.message : String(err)}`));
272
+ s.warn(chalk.yellow('IaC scan skipped') +
273
+ chalk.dim(` ${err instanceof Error ? err.message : String(err)}`));
243
274
  }
244
275
  }
245
276
  // ── Repository scan ──────────────────────────────────────────────────────────
@@ -248,10 +279,12 @@ export async function runAnalyze(options = {}) {
248
279
  const s = mkSpinner(`Scanning ${path.basename(repoPath)} for service usage...`);
249
280
  try {
250
281
  operations = await scanRepository(repoPath);
251
- s.succeed(chalk.green('Repository scanned') + chalk.dim(` ${operations.length} service operation(s) found`));
282
+ s.succeed(chalk.green('Repository scanned') +
283
+ chalk.dim(` ${operations.length} service operation(s) found`));
252
284
  }
253
285
  catch (err) {
254
- s.warn(chalk.yellow('Repository scan failed') + chalk.dim(` ${err instanceof Error ? err.message : String(err)}`));
286
+ s.warn(chalk.yellow('Repository scan failed') +
287
+ chalk.dim(` ${err instanceof Error ? err.message : String(err)}`));
255
288
  operations = [];
256
289
  }
257
290
  }
@@ -260,59 +293,58 @@ export async function runAnalyze(options = {}) {
260
293
  {
261
294
  const s = mkSpinner('Building infrastructure graph...');
262
295
  graph = buildGraph(operations, dynamoMeta, postgresMeta, mysqlMeta, mongoMeta, servicesMeta);
263
- s.succeed(chalk.green('Graph built') + chalk.dim(` ${graph.nodes.length} nodes, ${graph.edges.length} edges`));
296
+ s.succeed(chalk.green('Graph built') +
297
+ chalk.dim(` ${graph.nodes.length} nodes, ${graph.edges.length} edges`));
264
298
  }
265
299
  // ── Run analyzers ────────────────────────────────────────────────────────────
266
300
  let findings;
267
301
  {
268
302
  const s = mkSpinner('Running analyzers...');
269
303
  const analyzers = [
270
- ...(config.dynamodb?.enabled === true ? [
271
- new FullTableScanAnalyzer(),
272
- new MissingGSIAnalyzer(),
273
- new HotPartitionAnalyzer(),
274
- ] : []),
275
- ...(config.postgres?.enabled ? [
276
- new MissingIndexAnalyzer(),
277
- new NplusOneAnalyzer(),
278
- new LargeSelectAnalyzer(),
279
- ] : []),
280
- ...(config.mysql?.enabled ? [
281
- new MissingMySQLIndexAnalyzer(),
282
- new MySQLFullTableScanAnalyzer(),
283
- ] : []),
284
- ...(config.mongodb?.enabled ? [
285
- new MissingMongoIndexAnalyzer(),
286
- new MongoCollectionScanAnalyzer(),
287
- ] : []),
288
- ...(config.sqs?.enabled === true ? [
289
- new MissingDLQAnalyzer(),
290
- new UnencryptedQueueAnalyzer(),
291
- new LargeQueueBacklogAnalyzer(),
292
- ] : []),
293
- ...(config.secretsManager?.enabled === true ? [
294
- new MissingSecretRotationAnalyzer(),
295
- ] : []),
296
- ...(config.cloudwatchLogs?.enabled ? [
297
- new MissingLogRetentionAnalyzer(),
298
- ] : []),
299
- ...(config.lambda?.enabled === true ? [
300
- new LambdaDefaultMemoryAnalyzer(),
301
- new LambdaHighTimeoutAnalyzer(),
302
- new LambdaMissingTriggerDLQAnalyzer(),
303
- ] : []),
304
- ...(config.rds?.enabled === true ? [
305
- new RDSPubliclyAccessibleAnalyzer(),
306
- new RDSNoBackupAnalyzer(),
307
- new RDSUnencryptedAnalyzer(),
308
- new RDSNoDeletionProtectionAnalyzer(),
309
- new RDSNoMultiAZAnalyzer(),
310
- ] : []),
311
- ...(config.s3?.enabled === true ? [
312
- new S3PublicAccessAnalyzer(),
313
- new S3MissingVersioningAnalyzer(),
314
- new S3UnencryptedAnalyzer(),
315
- ] : []),
304
+ ...(config.dynamodb?.enabled === true
305
+ ? [new FullTableScanAnalyzer(), new MissingGSIAnalyzer(), new HotPartitionAnalyzer()]
306
+ : []),
307
+ ...(config.postgres?.enabled
308
+ ? [new MissingIndexAnalyzer(), new NplusOneAnalyzer(), new LargeSelectAnalyzer()]
309
+ : []),
310
+ ...(config.mysql?.enabled
311
+ ? [new MissingMySQLIndexAnalyzer(), new MySQLFullTableScanAnalyzer()]
312
+ : []),
313
+ ...(config.mongodb?.enabled
314
+ ? [new MissingMongoIndexAnalyzer(), new MongoCollectionScanAnalyzer()]
315
+ : []),
316
+ ...(config.sqs?.enabled === true
317
+ ? [
318
+ new MissingDLQAnalyzer(),
319
+ new UnencryptedQueueAnalyzer(),
320
+ new LargeQueueBacklogAnalyzer(),
321
+ ]
322
+ : []),
323
+ ...(config.secretsManager?.enabled === true ? [new MissingSecretRotationAnalyzer()] : []),
324
+ ...(config.cloudwatchLogs?.enabled ? [new MissingLogRetentionAnalyzer()] : []),
325
+ ...(config.lambda?.enabled === true
326
+ ? [
327
+ new LambdaDefaultMemoryAnalyzer(),
328
+ new LambdaHighTimeoutAnalyzer(),
329
+ new LambdaMissingTriggerDLQAnalyzer(),
330
+ ]
331
+ : []),
332
+ ...(config.rds?.enabled === true
333
+ ? [
334
+ new RDSPubliclyAccessibleAnalyzer(),
335
+ new RDSNoBackupAnalyzer(),
336
+ new RDSUnencryptedAnalyzer(),
337
+ new RDSNoDeletionProtectionAnalyzer(),
338
+ new RDSNoMultiAZAnalyzer(),
339
+ ]
340
+ : []),
341
+ ...(config.s3?.enabled === true
342
+ ? [
343
+ new S3PublicAccessAnalyzer(),
344
+ new S3MissingVersioningAnalyzer(),
345
+ new S3UnencryptedAnalyzer(),
346
+ ]
347
+ : []),
316
348
  ...(iacDriftAnalyzer ? [iacDriftAnalyzer] : []),
317
349
  ];
318
350
  findings = await runAllAnalyzers(graph, analyzers);
@@ -322,14 +354,22 @@ export async function runAnalyze(options = {}) {
322
354
  writeCache('graph', graph);
323
355
  writeCache('findings', findings);
324
356
  writeCache('operations', operations);
325
- writeCache('meta', { dynamoMeta, postgresMeta, mysqlMeta, mongoMeta, servicesMeta });
357
+ writeCache('meta', {
358
+ dynamoMeta,
359
+ postgresMeta,
360
+ mysqlMeta,
361
+ mongoMeta,
362
+ servicesMeta,
363
+ });
326
364
  // ── Output ────────────────────────────────────────────────────────────────────
327
365
  const displayFindings = minSeverity > 0
328
366
  ? findings.filter((f) => (SEVERITY_ORDER[f.severity] ?? 0) >= minSeverity)
329
367
  : findings;
330
368
  console.log('');
331
369
  if (displayFindings.length === 0) {
332
- const msg = minSeverity > 0 ? `No ${options.severity} (or higher) severity issues found.` : 'Your infrastructure looks clean.';
370
+ const msg = minSeverity > 0
371
+ ? `No ${options.severity} (or higher) severity issues found.`
372
+ : 'Your infrastructure looks clean.';
333
373
  console.log(` ${chalk.green.bold('✓ No issues found!')} ${chalk.dim(msg)}`);
334
374
  }
335
375
  else {
@@ -380,35 +420,42 @@ export async function runCodeRefresh(repoPath, config) {
380
420
  }
381
421
  const graph = buildGraph(operations, dynamoMeta, postgresMeta, mysqlMeta, mongoMeta, servicesMeta);
382
422
  const analyzers = [
383
- ...(config.dynamodb?.enabled === true ? [
384
- new FullTableScanAnalyzer(), new MissingGSIAnalyzer(), new HotPartitionAnalyzer(),
385
- ] : []),
386
- ...(config.postgres?.enabled ? [
387
- new MissingIndexAnalyzer(), new NplusOneAnalyzer(), new LargeSelectAnalyzer(),
388
- ] : []),
389
- ...(config.mysql?.enabled ? [
390
- new MissingMySQLIndexAnalyzer(), new MySQLFullTableScanAnalyzer(),
391
- ] : []),
392
- ...(config.mongodb?.enabled ? [
393
- new MissingMongoIndexAnalyzer(), new MongoCollectionScanAnalyzer(),
394
- ] : []),
395
- ...(config.sqs?.enabled === true ? [
396
- new MissingDLQAnalyzer(), new UnencryptedQueueAnalyzer(), new LargeQueueBacklogAnalyzer(),
397
- ] : []),
423
+ ...(config.dynamodb?.enabled === true
424
+ ? [new FullTableScanAnalyzer(), new MissingGSIAnalyzer(), new HotPartitionAnalyzer()]
425
+ : []),
426
+ ...(config.postgres?.enabled
427
+ ? [new MissingIndexAnalyzer(), new NplusOneAnalyzer(), new LargeSelectAnalyzer()]
428
+ : []),
429
+ ...(config.mysql?.enabled
430
+ ? [new MissingMySQLIndexAnalyzer(), new MySQLFullTableScanAnalyzer()]
431
+ : []),
432
+ ...(config.mongodb?.enabled
433
+ ? [new MissingMongoIndexAnalyzer(), new MongoCollectionScanAnalyzer()]
434
+ : []),
435
+ ...(config.sqs?.enabled === true
436
+ ? [new MissingDLQAnalyzer(), new UnencryptedQueueAnalyzer(), new LargeQueueBacklogAnalyzer()]
437
+ : []),
398
438
  ...(config.secretsManager?.enabled === true ? [new MissingSecretRotationAnalyzer()] : []),
399
439
  ...(config.cloudwatchLogs?.enabled ? [new MissingLogRetentionAnalyzer()] : []),
400
- ...(config.lambda?.enabled === true ? [
401
- new LambdaDefaultMemoryAnalyzer(), new LambdaHighTimeoutAnalyzer(),
402
- ] : []),
403
- ...(config.rds?.enabled === true ? [
404
- new RDSPubliclyAccessibleAnalyzer(), new RDSNoBackupAnalyzer(), new RDSUnencryptedAnalyzer(),
405
- new RDSNoDeletionProtectionAnalyzer(), new RDSNoMultiAZAnalyzer(),
406
- ] : []),
407
- ...(config.s3?.enabled === true ? [
408
- new S3PublicAccessAnalyzer(),
409
- new S3MissingVersioningAnalyzer(),
410
- new S3UnencryptedAnalyzer(),
411
- ] : []),
440
+ ...(config.lambda?.enabled === true
441
+ ? [new LambdaDefaultMemoryAnalyzer(), new LambdaHighTimeoutAnalyzer()]
442
+ : []),
443
+ ...(config.rds?.enabled === true
444
+ ? [
445
+ new RDSPubliclyAccessibleAnalyzer(),
446
+ new RDSNoBackupAnalyzer(),
447
+ new RDSUnencryptedAnalyzer(),
448
+ new RDSNoDeletionProtectionAnalyzer(),
449
+ new RDSNoMultiAZAnalyzer(),
450
+ ]
451
+ : []),
452
+ ...(config.s3?.enabled === true
453
+ ? [
454
+ new S3PublicAccessAnalyzer(),
455
+ new S3MissingVersioningAnalyzer(),
456
+ new S3UnencryptedAnalyzer(),
457
+ ]
458
+ : []),
412
459
  ...(iacDriftAnalyzer ? [iacDriftAnalyzer] : []),
413
460
  ];
414
461
  const findings = await runAllAnalyzers(graph, analyzers);
@@ -25,7 +25,10 @@ export async function runAuth() {
25
25
  },
26
26
  ]);
27
27
  console.log('');
28
- const spin = ora({ text: chalk.dim(`Validating "${selectedProfile}"...`), color: 'cyan' }).start();
28
+ const spin = ora({
29
+ text: chalk.dim(`Validating "${selectedProfile}"...`),
30
+ color: 'cyan',
31
+ }).start();
29
32
  const testConfig = {
30
33
  project: 'auth-test',
31
34
  aws: { profile: selectedProfile, region: 'us-east-1' },
@@ -147,10 +147,12 @@ export async function runDev(options = {}) {
147
147
  try {
148
148
  const { graph, findings } = await runCodeRefresh(repoPath, config);
149
149
  setGraphState(graph, findings, config);
150
- spin.succeed(chalk.green('Analysis refreshed') + chalk.dim(` ${graph.nodes.length} nodes · ${findings.length} finding(s)`));
150
+ spin.succeed(chalk.green('Analysis refreshed') +
151
+ chalk.dim(` ${graph.nodes.length} nodes · ${findings.length} finding(s)`));
151
152
  }
152
153
  catch (err) {
153
- spin.warn(chalk.yellow('Refresh failed') + chalk.dim(` ${err instanceof Error ? err.message : String(err)}`));
154
+ spin.warn(chalk.yellow('Refresh failed') +
155
+ chalk.dim(` ${err instanceof Error ? err.message : String(err)}`));
154
156
  }
155
157
  finally {
156
158
  refreshing = false;
@@ -58,7 +58,9 @@ export async function runDoctor(options = {}) {
58
58
  }
59
59
  catch (err) {
60
60
  return {
61
- name: 'Config validation', status: 'fail', message: 'Invalid config',
61
+ name: 'Config validation',
62
+ status: 'fail',
63
+ message: 'Invalid config',
62
64
  detail: err instanceof Error ? err.message : String(err),
63
65
  };
64
66
  }
@@ -75,7 +77,11 @@ export async function runDoctor(options = {}) {
75
77
  detail: hasCreds ? undefined : 'Run: aws configure',
76
78
  };
77
79
  }));
78
- const awsCfg = { region: config?.aws?.region, profile: config?.aws?.profile, endpoint: config?.aws?.endpoint };
80
+ const awsCfg = {
81
+ region: config?.aws?.region,
82
+ profile: config?.aws?.profile,
83
+ endpoint: config?.aws?.endpoint,
84
+ };
79
85
  // DynamoDB
80
86
  results.push(await runCheck('Testing DynamoDB access...', async () => {
81
87
  if (!config)
@@ -84,11 +90,16 @@ export async function runDoctor(options = {}) {
84
90
  return { name: 'DynamoDB', status: 'skip', message: 'Disabled in config' };
85
91
  try {
86
92
  await probeDynamoAccess(config);
87
- return { name: 'DynamoDB', status: 'pass', message: `Connected (profile: ${config.aws?.profile ?? 'default'})` };
93
+ return {
94
+ name: 'DynamoDB',
95
+ status: 'pass',
96
+ message: `Connected (profile: ${config.aws?.profile ?? 'default'})`,
97
+ };
88
98
  }
89
99
  catch (err) {
90
100
  return {
91
- name: 'DynamoDB', status: 'warn',
101
+ name: 'DynamoDB',
102
+ status: 'warn',
92
103
  message: err instanceof Error ? err.message : String(err),
93
104
  detail: 'Check IAM: dynamodb:ListTables, dynamodb:DescribeTable',
94
105
  };
@@ -104,7 +115,8 @@ export async function runDoctor(options = {}) {
104
115
  }
105
116
  catch (err) {
106
117
  return {
107
- name: 'SQS', status: 'warn',
118
+ name: 'SQS',
119
+ status: 'warn',
108
120
  message: err instanceof Error ? err.message : String(err),
109
121
  detail: 'Check IAM: sqs:ListQueues, sqs:GetQueueAttributes',
110
122
  };
@@ -120,7 +132,8 @@ export async function runDoctor(options = {}) {
120
132
  }
121
133
  catch (err) {
122
134
  return {
123
- name: 'SNS', status: 'warn',
135
+ name: 'SNS',
136
+ status: 'warn',
124
137
  message: err instanceof Error ? err.message : String(err),
125
138
  detail: 'Check IAM: sns:ListTopics, sns:GetTopicAttributes, sns:ListSubscriptionsByTopic',
126
139
  };
@@ -136,7 +149,8 @@ export async function runDoctor(options = {}) {
136
149
  }
137
150
  catch (err) {
138
151
  return {
139
- name: 'SSM', status: 'warn',
152
+ name: 'SSM',
153
+ status: 'warn',
140
154
  message: err instanceof Error ? err.message : String(err),
141
155
  detail: 'Check IAM: ssm:DescribeParameters',
142
156
  };
@@ -148,11 +162,16 @@ export async function runDoctor(options = {}) {
148
162
  return { name: 'Secrets Manager', status: 'skip', message: 'Disabled in config' };
149
163
  try {
150
164
  await validateSecretsAccess(awsCfg);
151
- return { name: 'Secrets Manager', status: 'pass', message: 'Connected (names/rotation only)' };
165
+ return {
166
+ name: 'Secrets Manager',
167
+ status: 'pass',
168
+ message: 'Connected (names/rotation only)',
169
+ };
152
170
  }
153
171
  catch (err) {
154
172
  return {
155
- name: 'Secrets Manager', status: 'warn',
173
+ name: 'Secrets Manager',
174
+ status: 'warn',
156
175
  message: err instanceof Error ? err.message : String(err),
157
176
  detail: 'Check IAM: secretsmanager:ListSecrets',
158
177
  };
@@ -168,7 +187,8 @@ export async function runDoctor(options = {}) {
168
187
  }
169
188
  catch (err) {
170
189
  return {
171
- name: 'Lambda', status: 'warn',
190
+ name: 'Lambda',
191
+ status: 'warn',
172
192
  message: err instanceof Error ? err.message : String(err),
173
193
  detail: 'Check IAM: lambda:ListFunctions',
174
194
  };
@@ -184,7 +204,8 @@ export async function runDoctor(options = {}) {
184
204
  }
185
205
  catch (err) {
186
206
  return {
187
- name: 'EventBridge', status: 'warn',
207
+ name: 'EventBridge',
208
+ status: 'warn',
188
209
  message: err instanceof Error ? err.message : String(err),
189
210
  detail: 'Check IAM: events:ListRules, events:ListTargetsByRule',
190
211
  };
@@ -200,7 +221,8 @@ export async function runDoctor(options = {}) {
200
221
  }
201
222
  catch (err) {
202
223
  return {
203
- name: 'S3', status: 'warn',
224
+ name: 'S3',
225
+ status: 'warn',
204
226
  message: err instanceof Error ? err.message : String(err),
205
227
  detail: 'Check IAM: s3:ListBuckets, s3:GetBucketNotificationConfiguration, s3:GetBucketVersioning, s3:GetBucketEncryption, s3:GetPublicAccessBlock',
206
228
  };
@@ -216,7 +238,8 @@ export async function runDoctor(options = {}) {
216
238
  }
217
239
  catch (err) {
218
240
  return {
219
- name: 'CloudWatch Logs', status: 'warn',
241
+ name: 'CloudWatch Logs',
242
+ status: 'warn',
220
243
  message: err instanceof Error ? err.message : String(err),
221
244
  detail: 'Check IAM: logs:DescribeLogGroups, logs:FilterLogEvents',
222
245
  };
@@ -230,13 +253,18 @@ export async function runDoctor(options = {}) {
230
253
  try {
231
254
  const ok = await validatePostgresAccess(config.postgres.connectionString);
232
255
  return {
233
- name: 'PostgreSQL', status: ok ? 'pass' : 'fail',
256
+ name: 'PostgreSQL',
257
+ status: ok ? 'pass' : 'fail',
234
258
  message: ok ? 'Connected' : 'Cannot connect',
235
259
  detail: ok ? undefined : 'Check connection string and security group',
236
260
  };
237
261
  }
238
262
  catch (err) {
239
- return { name: 'PostgreSQL', status: 'fail', message: err instanceof Error ? err.message : String(err) };
263
+ return {
264
+ name: 'PostgreSQL',
265
+ status: 'fail',
266
+ message: err instanceof Error ? err.message : String(err),
267
+ };
240
268
  }
241
269
  }));
242
270
  // MySQL
@@ -247,13 +275,18 @@ export async function runDoctor(options = {}) {
247
275
  try {
248
276
  const ok = await validateMySQLAccess(config.mysql.connectionString);
249
277
  return {
250
- name: 'MySQL', status: ok ? 'pass' : 'fail',
278
+ name: 'MySQL',
279
+ status: ok ? 'pass' : 'fail',
251
280
  message: ok ? 'Connected' : 'Cannot connect',
252
281
  detail: ok ? undefined : 'Check connection string, host, port 3306',
253
282
  };
254
283
  }
255
284
  catch (err) {
256
- return { name: 'MySQL', status: 'fail', message: err instanceof Error ? err.message : String(err) };
285
+ return {
286
+ name: 'MySQL',
287
+ status: 'fail',
288
+ message: err instanceof Error ? err.message : String(err),
289
+ };
257
290
  }
258
291
  }));
259
292
  // MongoDB
@@ -264,13 +297,18 @@ export async function runDoctor(options = {}) {
264
297
  try {
265
298
  const ok = await validateMongoAccess(config.mongodb.connectionString);
266
299
  return {
267
- name: 'MongoDB', status: ok ? 'pass' : 'fail',
300
+ name: 'MongoDB',
301
+ status: ok ? 'pass' : 'fail',
268
302
  message: ok ? 'Connected' : 'Cannot connect',
269
303
  detail: ok ? undefined : 'Check connection string and port 27017',
270
304
  };
271
305
  }
272
306
  catch (err) {
273
- return { name: 'MongoDB', status: 'fail', message: err instanceof Error ? err.message : String(err) };
307
+ return {
308
+ name: 'MongoDB',
309
+ status: 'fail',
310
+ message: err instanceof Error ? err.message : String(err),
311
+ };
274
312
  }
275
313
  }));
276
314
  // Project type
@@ -288,11 +326,13 @@ export async function runDoctor(options = {}) {
288
326
  results.push(await runCheck('Detecting IaC files...', async () => {
289
327
  const cwd = process.cwd();
290
328
  const hasTfFile = (dir) => fs.existsSync(dir) && fs.readdirSync(dir).some((f) => f.endsWith('.tf'));
291
- const hasTF = hasTfFile(cwd) || fs.readdirSync(cwd).some((entry) => {
292
- const full = path.join(cwd, entry);
293
- return fs.statSync(full).isDirectory() && hasTfFile(full);
294
- });
295
- const hasCFN = fs.existsSync(path.join(cwd, 'template.yaml')) || fs.existsSync(path.join(cwd, 'template.json'));
329
+ const hasTF = hasTfFile(cwd) ||
330
+ fs.readdirSync(cwd).some((entry) => {
331
+ const full = path.join(cwd, entry);
332
+ return fs.statSync(full).isDirectory() && hasTfFile(full);
333
+ });
334
+ const hasCFN = fs.existsSync(path.join(cwd, 'template.yaml')) ||
335
+ fs.existsSync(path.join(cwd, 'template.json'));
296
336
  const hasCDK = fs.existsSync(path.join(cwd, 'cdk.json'));
297
337
  const hasCDKOut = fs.existsSync(path.join(cwd, 'cdk.out'));
298
338
  const found = [];