@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.
- package/build/assess-full.js +103 -0
- package/build/assessmentState.js +168 -0
- package/package.json +1 -1
package/build/assess-full.js
CHANGED
|
@@ -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