@skyramp/mcp 0.0.49 → 0.0.51

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.
Files changed (25) hide show
  1. package/build/prompts/startTraceCollectionPrompts.js +4 -1
  2. package/build/prompts/testGenerationPrompt.js +4 -1
  3. package/build/prompts/testHealthPrompt.js +4 -1
  4. package/build/services/DriftAnalysisService.js +31 -10
  5. package/build/services/TestExecutionService.js +39 -1
  6. package/build/services/TestGenerationService.js +1 -1
  7. package/build/tools/executeSkyrampTestTool.js +5 -0
  8. package/build/tools/generate-tests/generateE2ERestTool.js +4 -1
  9. package/build/tools/generate-tests/generateIntegrationRestTool.js +5 -2
  10. package/build/tools/generate-tests/generateLoadRestTool.js +4 -1
  11. package/build/tools/generate-tests/generateScenarioRestTool.js +8 -1
  12. package/build/tools/test-maintenance/actionsTool.js +56 -45
  13. package/build/tools/test-maintenance/analyzeTestDriftTool.js +5 -4
  14. package/build/tools/test-maintenance/calculateHealthScoresTool.js +12 -33
  15. package/build/tools/test-maintenance/discoverTestsTool.js +3 -3
  16. package/build/tools/test-maintenance/executeBatchTestsTool.js +5 -4
  17. package/build/tools/test-maintenance/stateCleanupTool.js +13 -8
  18. package/build/tools/test-recommendation/analyzeRepositoryTool.js +5 -1
  19. package/build/tools/test-recommendation/mapTestsTool.js +29 -19
  20. package/build/tools/test-recommendation/recommendTestsTool.js +27 -30
  21. package/build/tools/trace/startTraceCollectionTool.js +73 -18
  22. package/build/tools/trace/stopTraceCollectionTool.js +1 -1
  23. package/build/types/TestTypes.js +7 -1
  24. package/build/utils/AnalysisStateManager.js +73 -62
  25. package/package.json +7 -2
@@ -3,45 +3,68 @@ import * as path from "path";
3
3
  import * as os from "os";
4
4
  import { logger } from "./logger.js";
5
5
  /**
6
- * Manages persistent state for test analysis workflow
6
+ * File prefix mapping for each state type
7
+ */
8
+ const STATE_FILE_PREFIXES = {
9
+ analysis: "skyramp-analysis", // For test maintenance workflow
10
+ recommendation: "skyramp-recommendation", // For test recommendation workflow
11
+ };
12
+ /**
13
+ * Generic State Manager for persisting workflow data
7
14
  * Reduces token usage by storing intermediate results in filesystem
15
+ *
16
+ * Supports two state types:
17
+ * - analysis: Test maintenance workflow (discovery, drift, execution, health)
18
+ * - recommendation: Test recommendation workflow (analyze repo, map tests, recommendations)
8
19
  */
9
- export class AnalysisStateManager {
20
+ export class StateManager {
10
21
  stateFile;
11
22
  sessionId;
23
+ stateType;
12
24
  /**
13
25
  * Create a new state manager
26
+ * @param stateType Type of state (analysis, recommendation)
14
27
  * @param sessionId Unique session identifier (defaults to timestamp)
15
28
  * @param stateDir Directory to store state files (defaults to /tmp)
16
29
  */
17
- constructor(sessionId, stateDir) {
30
+ constructor(stateType = "analysis", sessionId, stateDir) {
31
+ this.stateType = stateType;
18
32
  this.sessionId = sessionId || Date.now().toString();
19
33
  const baseDir = stateDir || os.tmpdir();
20
- this.stateFile = path.join(baseDir, `skyramp-analysis-${this.sessionId}.json`);
34
+ const prefix = STATE_FILE_PREFIXES[stateType];
35
+ this.stateFile = path.join(baseDir, `${prefix}-${this.sessionId}.json`);
21
36
  }
22
37
  /**
23
38
  * Create state manager from existing state file path
24
39
  */
25
40
  static fromStatePath(stateFilePath) {
26
41
  const basename = path.basename(stateFilePath, ".json");
27
- const sessionId = basename.replace("skyramp-analysis-", "");
28
42
  const stateDir = path.dirname(stateFilePath);
29
- return new AnalysisStateManager(sessionId, stateDir);
43
+ // Determine state type from filename
44
+ let stateType = "analysis";
45
+ let sessionId = basename;
46
+ for (const [type, prefix] of Object.entries(STATE_FILE_PREFIXES)) {
47
+ if (basename.startsWith(prefix)) {
48
+ stateType = type;
49
+ sessionId = basename.replace(`${prefix}-`, "");
50
+ break;
51
+ }
52
+ }
53
+ return new StateManager(stateType, sessionId, stateDir);
30
54
  }
31
55
  /**
32
- * Read current state from file
33
- * @returns Test analysis results, or empty array if file doesn't exist
56
+ * Read data from state file (excludes metadata)
34
57
  */
35
- async readState() {
58
+ async readData() {
36
59
  if (!this.exists()) {
37
60
  logger.debug(`State file does not exist: ${this.stateFile}`);
38
- return [];
61
+ return null;
39
62
  }
40
63
  try {
41
64
  const content = await fs.promises.readFile(this.stateFile, "utf-8");
42
- const state = JSON.parse(content);
43
- logger.debug(`Read ${state.tests.length} tests from state file: ${this.stateFile}`);
44
- return state.tests;
65
+ const { metadata, ...data } = JSON.parse(content);
66
+ logger.debug(`Read data from state file: ${this.stateFile}`);
67
+ return data;
45
68
  }
46
69
  catch (error) {
47
70
  logger.error(`Failed to read state file: ${error.message}`);
@@ -65,11 +88,10 @@ export class AnalysisStateManager {
65
88
  }
66
89
  }
67
90
  /**
68
- * Write test results to state file
69
- * @param tests Test analysis results
70
- * @param options Additional metadata options
91
+ * Write data to state file
92
+ * Data fields are spread at root level alongside metadata
71
93
  */
72
- async writeState(tests, options) {
94
+ async writeData(data, options) {
73
95
  try {
74
96
  // Read existing metadata if file exists
75
97
  let existingMetadata;
@@ -78,9 +100,10 @@ export class AnalysisStateManager {
78
100
  existingMetadata = existing?.metadata;
79
101
  }
80
102
  const state = {
81
- tests,
103
+ ...data,
82
104
  metadata: {
83
105
  sessionId: this.sessionId,
106
+ stateType: this.stateType,
84
107
  repositoryPath: options?.repositoryPath || existingMetadata?.repositoryPath,
85
108
  createdAt: existingMetadata?.createdAt || new Date().toISOString(),
86
109
  updatedAt: new Date().toISOString(),
@@ -88,49 +111,13 @@ export class AnalysisStateManager {
88
111
  },
89
112
  };
90
113
  await fs.promises.writeFile(this.stateFile, JSON.stringify(state, null, 2), "utf-8");
91
- logger.debug(`Wrote ${tests.length} tests to state file: ${this.stateFile}`);
114
+ logger.debug(`Wrote data to state file: ${this.stateFile}`);
92
115
  }
93
116
  catch (error) {
94
117
  logger.error(`Failed to write state file: ${error.message}`);
95
118
  throw new Error(`Failed to write state file: ${error.message}`);
96
119
  }
97
120
  }
98
- // /**
99
- // * Read state with filtering
100
- // * @param filter Function to filter tests
101
- // */
102
- // async readStateFiltered(
103
- // filter: (test: TestAnalysisResult) => boolean,
104
- // ): Promise<TestAnalysisResult[]> {
105
- // const allTests = await this.readState();
106
- // return allTests.filter(filter);
107
- // }
108
- // /**
109
- // * Read state with pagination
110
- // * @param page Page number (0-indexed)
111
- // * @param pageSize Number of tests per page
112
- // */
113
- // async readStatePaginated(
114
- // page: number,
115
- // pageSize: number = 100,
116
- // ): Promise<{
117
- // tests: TestAnalysisResult[];
118
- // hasMore: boolean;
119
- // total: number;
120
- // page: number;
121
- // pageSize: number;
122
- // }> {
123
- // const allTests = await this.readState();
124
- // const start = page * pageSize;
125
- // const end = start + pageSize;
126
- // return {
127
- // tests: allTests.slice(start, end),
128
- // hasMore: end < allTests.length,
129
- // total: allTests.length,
130
- // page,
131
- // pageSize,
132
- // };
133
- // }
134
121
  /**
135
122
  * Get the state file path
136
123
  */
@@ -143,6 +130,12 @@ export class AnalysisStateManager {
143
130
  getSessionId() {
144
131
  return this.sessionId;
145
132
  }
133
+ /**
134
+ * Get the state type
135
+ */
136
+ getStateType() {
137
+ return this.stateType;
138
+ }
146
139
  /**
147
140
  * Check if state file exists
148
141
  */
@@ -184,12 +177,17 @@ export class AnalysisStateManager {
184
177
  * Cleanup old state files
185
178
  * @param maxAgeHours Maximum age in hours
186
179
  * @param stateDir Directory to clean (defaults to /tmp)
180
+ * @param stateTypes Which state types to clean (defaults to all)
187
181
  * @returns Number of files deleted
188
182
  */
189
- static async cleanupOldStateFiles(maxAgeHours = 24, stateDir) {
183
+ static async cleanupOldStateFiles(maxAgeHours = 24, stateDir, stateTypes) {
190
184
  const baseDir = stateDir || os.tmpdir();
191
185
  const files = await fs.promises.readdir(baseDir);
192
- const stateFiles = files.filter((f) => f.startsWith("skyramp-analysis-"));
186
+ // Get prefixes to clean
187
+ const prefixesToClean = stateTypes
188
+ ? stateTypes.map((t) => STATE_FILE_PREFIXES[t])
189
+ : Object.values(STATE_FILE_PREFIXES);
190
+ const stateFiles = files.filter((f) => prefixesToClean.some((prefix) => f.startsWith(prefix)));
193
191
  let deletedCount = 0;
194
192
  const now = Date.now();
195
193
  const maxAge = maxAgeHours * 60 * 60 * 1000;
@@ -216,19 +214,32 @@ export class AnalysisStateManager {
216
214
  /**
217
215
  * List all state files in directory
218
216
  * @param stateDir Directory to search (defaults to /tmp)
217
+ * @param stateTypes Which state types to list (defaults to all)
219
218
  */
220
- static async listStateFiles(stateDir) {
219
+ static async listStateFiles(stateDir, stateTypes) {
221
220
  const baseDir = stateDir || os.tmpdir();
222
221
  const files = await fs.promises.readdir(baseDir);
223
- const stateFiles = files.filter((f) => f.startsWith("skyramp-analysis-"));
222
+ // Get prefixes to list
223
+ const prefixesToList = stateTypes
224
+ ? stateTypes.map((t) => STATE_FILE_PREFIXES[t])
225
+ : Object.values(STATE_FILE_PREFIXES);
226
+ const stateFiles = files.filter((f) => prefixesToList.some((prefix) => f.startsWith(prefix)));
224
227
  const fileInfos = await Promise.all(stateFiles.map(async (file) => {
225
228
  const filePath = path.join(baseDir, file);
226
229
  const stats = await fs.promises.stat(filePath);
227
- const sessionId = file
228
- .replace("skyramp-analysis-", "")
229
- .replace(".json", "");
230
+ // Determine state type and session ID
231
+ let stateType = "analysis";
232
+ let sessionId = file.replace(".json", "");
233
+ for (const [type, prefix] of Object.entries(STATE_FILE_PREFIXES)) {
234
+ if (file.startsWith(prefix)) {
235
+ stateType = type;
236
+ sessionId = file.replace(`${prefix}-`, "").replace(".json", "");
237
+ break;
238
+ }
239
+ }
230
240
  return {
231
241
  sessionId,
242
+ stateType,
232
243
  path: filePath,
233
244
  size: stats.size,
234
245
  createdAt: stats.birthtime,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@skyramp/mcp",
3
- "version": "0.0.49",
3
+ "version": "0.0.51",
4
4
  "main": "build/index.js",
5
5
  "type": "module",
6
6
  "bin": {
@@ -14,6 +14,8 @@
14
14
  "start": "node build/index.js",
15
15
  "test:startup": "time node build/index.js --help",
16
16
  "pretty": "npx prettier --write .",
17
+ "lint": "eslint src --ext .ts",
18
+ "lint:fix": "eslint src --ext .ts --fix",
17
19
  "test": "npx jest"
18
20
  },
19
21
  "files": [
@@ -43,7 +45,7 @@
43
45
  },
44
46
  "dependencies": {
45
47
  "@modelcontextprotocol/sdk": "^1.24.3",
46
- "@skyramp/skyramp": "^1.3.1",
48
+ "@skyramp/skyramp": "^1.3.3",
47
49
  "@playwright/test": "^1.55.0",
48
50
  "dockerode": "^4.0.6",
49
51
  "fast-glob": "^3.3.3",
@@ -55,6 +57,9 @@
55
57
  "@types/jest": "^29.5.14",
56
58
  "@types/mocha": "^10.0.10",
57
59
  "@types/node": "^22.15.19",
60
+ "@typescript-eslint/eslint-plugin": "^8.0.0",
61
+ "@typescript-eslint/parser": "^8.0.0",
62
+ "eslint": "^9.0.0",
58
63
  "jest": "^29.7.0",
59
64
  "ts-jest": "^29.3.4",
60
65
  "typescript": "^5.8.3"