@bryan-thompson/inspector-assessment-cli 1.13.1 → 1.14.0

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.
@@ -22,6 +22,9 @@ import { DEFAULT_ASSESSMENT_CONFIG, } from "../../client/lib/lib/assessmentTypes
22
22
  import { FULL_CLAUDE_CODE_CONFIG } from "../../client/lib/services/assessment/lib/claudeCodeBridge.js";
23
23
  import { createFormatter, } from "../../client/lib/lib/reportFormatters/index.js";
24
24
  import { generatePolicyComplianceReport } from "../../client/lib/services/assessment/PolicyComplianceGenerator.js";
25
+ import { compareAssessments } from "../../client/lib/lib/assessmentDiffer.js";
26
+ import { formatDiffAsMarkdown } from "../../client/lib/lib/reportFormatters/DiffReportFormatter.js";
27
+ import { AssessmentStateManager } from "./assessmentState.js";
25
28
  /**
26
29
  * Load server configuration from Claude Code's MCP settings
27
30
  */
@@ -296,6 +299,34 @@ async function runFullAssessment(options) {
296
299
  if (!options.jsonOnly) {
297
300
  console.log(`šŸ”§ Found ${tools.length} tool${tools.length !== 1 ? "s" : ""}`);
298
301
  }
302
+ // State management for resumable assessments
303
+ const stateManager = new AssessmentStateManager(options.serverName);
304
+ if (stateManager.exists() && !options.noResume) {
305
+ const summary = stateManager.getSummary();
306
+ if (summary) {
307
+ if (!options.jsonOnly) {
308
+ console.log(`\nšŸ“‹ Found interrupted session from ${summary.startedAt}`);
309
+ console.log(` Completed modules: ${summary.completedModules.length > 0 ? summary.completedModules.join(", ") : "none"}`);
310
+ }
311
+ if (options.resume) {
312
+ if (!options.jsonOnly) {
313
+ console.log(" Resuming from previous state...");
314
+ }
315
+ // Will use partial results later
316
+ }
317
+ else if (!options.jsonOnly) {
318
+ console.log(" Use --resume to continue or --no-resume to start fresh");
319
+ // Clear state and start fresh by default
320
+ stateManager.clear();
321
+ }
322
+ }
323
+ }
324
+ else if (options.noResume && stateManager.exists()) {
325
+ stateManager.clear();
326
+ if (!options.jsonOnly) {
327
+ console.log("šŸ—‘ļø Cleared previous assessment state");
328
+ }
329
+ }
299
330
  // Pre-flight validation checks
300
331
  if (options.preflightOnly) {
301
332
  const preflightResult = {
@@ -561,6 +592,18 @@ function parseArgs() {
561
592
  case "--preflight":
562
593
  options.preflightOnly = true;
563
594
  break;
595
+ case "--compare":
596
+ options.comparePath = args[++i];
597
+ break;
598
+ case "--diff-only":
599
+ options.diffOnly = true;
600
+ break;
601
+ case "--resume":
602
+ options.resume = true;
603
+ break;
604
+ case "--no-resume":
605
+ options.noResume = true;
606
+ break;
564
607
  case "--help":
565
608
  case "-h":
566
609
  printHelp();
@@ -608,6 +651,10 @@ Options:
608
651
  --format, -f <type> Output format: json (default) or markdown
609
652
  --include-policy Include policy compliance mapping in report (30 requirements)
610
653
  --preflight Run quick validation only (tools exist, manifest valid, server responds)
654
+ --compare <path> Compare current assessment against baseline JSON file
655
+ --diff-only Output only the comparison diff (requires --compare)
656
+ --resume Resume from previous interrupted assessment
657
+ --no-resume Force fresh start, clear any existing state
611
658
  --claude-enabled Enable Claude Code integration for intelligent analysis
612
659
  --full Enable all assessment modules (default)
613
660
  --json Output only JSON path (no console summary)
@@ -632,6 +679,8 @@ Examples:
632
679
  mcp-assess-full --server broken-mcp --claude-enabled
633
680
  mcp-assess-full --server my-server --source ./my-server --output ./results.json
634
681
  mcp-assess-full --server my-server --format markdown --include-policy
682
+ mcp-assess-full --server my-server --compare ./baseline.json
683
+ mcp-assess-full --server my-server --compare ./baseline.json --diff-only --format markdown
635
684
  `);
636
685
  }
637
686
  /**
@@ -648,6 +697,60 @@ async function main() {
648
697
  if (options.preflightOnly) {
649
698
  return;
650
699
  }
700
+ // Handle comparison mode
701
+ if (options.comparePath) {
702
+ if (!fs.existsSync(options.comparePath)) {
703
+ console.error(`Error: Baseline file not found: ${options.comparePath}`);
704
+ setTimeout(() => process.exit(1), 10);
705
+ return;
706
+ }
707
+ const baselineData = JSON.parse(fs.readFileSync(options.comparePath, "utf-8"));
708
+ const baseline = baselineData.functionality && baselineData.security
709
+ ? baselineData
710
+ : baselineData;
711
+ const diff = compareAssessments(baseline, results);
712
+ if (options.diffOnly) {
713
+ // Only output diff, not full assessment
714
+ if (options.format === "markdown") {
715
+ const diffPath = options.outputPath ||
716
+ `/tmp/inspector-diff-${options.serverName}.md`;
717
+ fs.writeFileSync(diffPath, formatDiffAsMarkdown(diff));
718
+ console.log(diffPath);
719
+ }
720
+ else {
721
+ const diffPath = options.outputPath ||
722
+ `/tmp/inspector-diff-${options.serverName}.json`;
723
+ fs.writeFileSync(diffPath, JSON.stringify(diff, null, 2));
724
+ console.log(diffPath);
725
+ }
726
+ const exitCode = diff.summary.overallChange === "regressed" ? 1 : 0;
727
+ setTimeout(() => process.exit(exitCode), 10);
728
+ return;
729
+ }
730
+ // Include diff in output alongside full assessment
731
+ if (!options.jsonOnly) {
732
+ console.log("\n" + "=".repeat(70));
733
+ console.log("VERSION COMPARISON");
734
+ console.log("=".repeat(70));
735
+ console.log(`Baseline: ${diff.baseline.version || "N/A"} (${diff.baseline.date})`);
736
+ console.log(`Current: ${diff.current.version || "N/A"} (${diff.current.date})`);
737
+ console.log(`Overall Change: ${diff.summary.overallChange.toUpperCase()}`);
738
+ console.log(`Modules Improved: ${diff.summary.modulesImproved}`);
739
+ console.log(`Modules Regressed: ${diff.summary.modulesRegressed}`);
740
+ if (diff.securityDelta.newVulnerabilities.length > 0) {
741
+ console.log(`\nāš ļø NEW VULNERABILITIES: ${diff.securityDelta.newVulnerabilities.length}`);
742
+ }
743
+ if (diff.securityDelta.fixedVulnerabilities.length > 0) {
744
+ console.log(`āœ… FIXED VULNERABILITIES: ${diff.securityDelta.fixedVulnerabilities.length}`);
745
+ }
746
+ if (diff.functionalityDelta.newBrokenTools.length > 0) {
747
+ console.log(`āŒ NEW BROKEN TOOLS: ${diff.functionalityDelta.newBrokenTools.length}`);
748
+ }
749
+ if (diff.functionalityDelta.fixedTools.length > 0) {
750
+ console.log(`āœ… FIXED TOOLS: ${diff.functionalityDelta.fixedTools.length}`);
751
+ }
752
+ }
753
+ }
651
754
  if (!options.jsonOnly) {
652
755
  displaySummary(results);
653
756
  }
@@ -0,0 +1,168 @@
1
+ /**
2
+ * Assessment State Manager
3
+ * File-based state management for resumable assessments.
4
+ *
5
+ * Purpose:
6
+ * - Allow long-running assessments to be paused and resumed
7
+ * - Checkpoint after each module completes
8
+ * - Recover from interruptions
9
+ */
10
+ import * as fs from "fs";
11
+ const STATE_VERSION = "1.0";
12
+ /**
13
+ * Manages assessment state persistence for resumable runs
14
+ */
15
+ export class AssessmentStateManager {
16
+ statePath;
17
+ constructor(serverName, stateDir = "/tmp") {
18
+ this.statePath = `${stateDir}/inspector-assessment-state-${serverName}.json`;
19
+ }
20
+ /**
21
+ * Check if a previous state exists
22
+ */
23
+ exists() {
24
+ return fs.existsSync(this.statePath);
25
+ }
26
+ /**
27
+ * Load state from disk
28
+ */
29
+ load() {
30
+ if (!this.exists())
31
+ return null;
32
+ try {
33
+ const data = fs.readFileSync(this.statePath, "utf-8");
34
+ const state = JSON.parse(data);
35
+ // Validate state version
36
+ if (state.version !== STATE_VERSION) {
37
+ console.warn(`[StateManager] State version mismatch: ${state.version} vs ${STATE_VERSION}, clearing`);
38
+ this.clear();
39
+ return null;
40
+ }
41
+ return state;
42
+ }
43
+ catch (error) {
44
+ console.warn("[StateManager] Failed to load state:", error);
45
+ return null;
46
+ }
47
+ }
48
+ /**
49
+ * Create a new state for a fresh assessment
50
+ */
51
+ create(serverName, config, toolsDiscovered) {
52
+ const state = {
53
+ sessionId: this.generateSessionId(),
54
+ serverName,
55
+ startedAt: new Date().toISOString(),
56
+ lastUpdatedAt: new Date().toISOString(),
57
+ config,
58
+ completedModules: [],
59
+ currentModule: null,
60
+ partialResults: {
61
+ serverName,
62
+ assessmentDate: new Date().toISOString(),
63
+ assessorVersion: "1.13.1", // Will be updated from package.json
64
+ },
65
+ toolsDiscovered,
66
+ version: STATE_VERSION,
67
+ };
68
+ this.save(state);
69
+ return state;
70
+ }
71
+ /**
72
+ * Save state to disk
73
+ */
74
+ save(state) {
75
+ state.lastUpdatedAt = new Date().toISOString();
76
+ fs.writeFileSync(this.statePath, JSON.stringify(state, null, 2));
77
+ }
78
+ /**
79
+ * Mark a module as starting
80
+ */
81
+ startModule(moduleName) {
82
+ const state = this.load();
83
+ if (state) {
84
+ state.currentModule = moduleName;
85
+ this.save(state);
86
+ }
87
+ }
88
+ /**
89
+ * Mark a module as complete and save its result
90
+ */
91
+ completeModule(moduleName, result) {
92
+ const state = this.load();
93
+ if (state) {
94
+ state.completedModules.push(moduleName);
95
+ state.currentModule = null;
96
+ // Store the result in partialResults
97
+ state.partialResults[moduleName] = result;
98
+ this.save(state);
99
+ }
100
+ }
101
+ /**
102
+ * Get list of completed modules
103
+ */
104
+ getCompletedModules() {
105
+ const state = this.load();
106
+ return state?.completedModules || [];
107
+ }
108
+ /**
109
+ * Check if a module was already completed
110
+ */
111
+ isModuleCompleted(moduleName) {
112
+ const state = this.load();
113
+ return state?.completedModules.includes(moduleName) || false;
114
+ }
115
+ /**
116
+ * Get partial results from previous run
117
+ */
118
+ getPartialResults() {
119
+ const state = this.load();
120
+ return state?.partialResults || null;
121
+ }
122
+ /**
123
+ * Clear state (delete state file)
124
+ */
125
+ clear() {
126
+ if (this.exists()) {
127
+ try {
128
+ fs.unlinkSync(this.statePath);
129
+ }
130
+ catch {
131
+ // Ignore errors on cleanup
132
+ }
133
+ }
134
+ }
135
+ /**
136
+ * Get state file path
137
+ */
138
+ getStatePath() {
139
+ return this.statePath;
140
+ }
141
+ /**
142
+ * Get state summary for display
143
+ */
144
+ getSummary() {
145
+ const state = this.load();
146
+ if (!state)
147
+ return null;
148
+ return {
149
+ sessionId: state.sessionId,
150
+ startedAt: state.startedAt,
151
+ completedModules: state.completedModules,
152
+ currentModule: state.currentModule,
153
+ toolsDiscovered: state.toolsDiscovered,
154
+ };
155
+ }
156
+ /**
157
+ * Generate unique session ID
158
+ */
159
+ generateSessionId() {
160
+ return `assess-${Date.now()}-${Math.random().toString(36).substring(2, 8)}`;
161
+ }
162
+ }
163
+ /**
164
+ * Factory function to create state manager
165
+ */
166
+ export function createStateManager(serverName, stateDir) {
167
+ return new AssessmentStateManager(serverName, stateDir);
168
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bryan-thompson/inspector-assessment-cli",
3
- "version": "1.13.1",
3
+ "version": "1.14.0",
4
4
  "description": "CLI for the Enhanced MCP Inspector with assessment capabilities",
5
5
  "license": "MIT",
6
6
  "author": "Bryan Thompson <bryan@triepod.ai>",