mstro-app 0.3.7 → 0.3.9

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 (131) hide show
  1. package/README.md +4 -8
  2. package/bin/mstro.js +54 -15
  3. package/dist/server/cli/headless/claude-invoker.d.ts.map +1 -1
  4. package/dist/server/cli/headless/claude-invoker.js +18 -9
  5. package/dist/server/cli/headless/claude-invoker.js.map +1 -1
  6. package/dist/server/cli/headless/headless-logger.d.ts +10 -0
  7. package/dist/server/cli/headless/headless-logger.d.ts.map +1 -0
  8. package/dist/server/cli/headless/headless-logger.js +66 -0
  9. package/dist/server/cli/headless/headless-logger.js.map +1 -0
  10. package/dist/server/cli/headless/mcp-config.d.ts.map +1 -1
  11. package/dist/server/cli/headless/mcp-config.js +6 -5
  12. package/dist/server/cli/headless/mcp-config.js.map +1 -1
  13. package/dist/server/cli/headless/runner.d.ts.map +1 -1
  14. package/dist/server/cli/headless/runner.js +4 -0
  15. package/dist/server/cli/headless/runner.js.map +1 -1
  16. package/dist/server/cli/headless/stall-assessor.d.ts +21 -0
  17. package/dist/server/cli/headless/stall-assessor.d.ts.map +1 -1
  18. package/dist/server/cli/headless/stall-assessor.js +74 -20
  19. package/dist/server/cli/headless/stall-assessor.js.map +1 -1
  20. package/dist/server/cli/headless/tool-watchdog.d.ts +0 -12
  21. package/dist/server/cli/headless/tool-watchdog.d.ts.map +1 -1
  22. package/dist/server/cli/headless/tool-watchdog.js +30 -9
  23. package/dist/server/cli/headless/tool-watchdog.js.map +1 -1
  24. package/dist/server/cli/headless/types.d.ts +8 -1
  25. package/dist/server/cli/headless/types.d.ts.map +1 -1
  26. package/dist/server/cli/improvisation-session-manager.d.ts +16 -0
  27. package/dist/server/cli/improvisation-session-manager.d.ts.map +1 -1
  28. package/dist/server/cli/improvisation-session-manager.js +94 -11
  29. package/dist/server/cli/improvisation-session-manager.js.map +1 -1
  30. package/dist/server/index.js +0 -4
  31. package/dist/server/index.js.map +1 -1
  32. package/dist/server/mcp/bouncer-cli.d.ts +3 -0
  33. package/dist/server/mcp/bouncer-cli.d.ts.map +1 -0
  34. package/dist/server/mcp/bouncer-cli.js +54 -0
  35. package/dist/server/mcp/bouncer-cli.js.map +1 -0
  36. package/dist/server/mcp/bouncer-integration.d.ts +2 -0
  37. package/dist/server/mcp/bouncer-integration.d.ts.map +1 -1
  38. package/dist/server/mcp/bouncer-integration.js +55 -39
  39. package/dist/server/mcp/bouncer-integration.js.map +1 -1
  40. package/dist/server/mcp/bouncer-sandbox.d.ts +60 -0
  41. package/dist/server/mcp/bouncer-sandbox.d.ts.map +1 -0
  42. package/dist/server/mcp/bouncer-sandbox.js +182 -0
  43. package/dist/server/mcp/bouncer-sandbox.js.map +1 -0
  44. package/dist/server/mcp/security-patterns.d.ts +6 -12
  45. package/dist/server/mcp/security-patterns.d.ts.map +1 -1
  46. package/dist/server/mcp/security-patterns.js +197 -10
  47. package/dist/server/mcp/security-patterns.js.map +1 -1
  48. package/dist/server/services/plan/composer.d.ts +4 -0
  49. package/dist/server/services/plan/composer.d.ts.map +1 -0
  50. package/dist/server/services/plan/composer.js +181 -0
  51. package/dist/server/services/plan/composer.js.map +1 -0
  52. package/dist/server/services/plan/dependency-resolver.d.ts +28 -0
  53. package/dist/server/services/plan/dependency-resolver.d.ts.map +1 -0
  54. package/dist/server/services/plan/dependency-resolver.js +152 -0
  55. package/dist/server/services/plan/dependency-resolver.js.map +1 -0
  56. package/dist/server/services/plan/executor.d.ts +91 -0
  57. package/dist/server/services/plan/executor.d.ts.map +1 -0
  58. package/dist/server/services/plan/executor.js +545 -0
  59. package/dist/server/services/plan/executor.js.map +1 -0
  60. package/dist/server/services/plan/parser.d.ts +11 -0
  61. package/dist/server/services/plan/parser.d.ts.map +1 -0
  62. package/dist/server/services/plan/parser.js +415 -0
  63. package/dist/server/services/plan/parser.js.map +1 -0
  64. package/dist/server/services/plan/state-reconciler.d.ts +2 -0
  65. package/dist/server/services/plan/state-reconciler.d.ts.map +1 -0
  66. package/dist/server/services/plan/state-reconciler.js +105 -0
  67. package/dist/server/services/plan/state-reconciler.js.map +1 -0
  68. package/dist/server/services/plan/types.d.ts +120 -0
  69. package/dist/server/services/plan/types.d.ts.map +1 -0
  70. package/dist/server/services/plan/types.js +4 -0
  71. package/dist/server/services/plan/types.js.map +1 -0
  72. package/dist/server/services/plan/watcher.d.ts +14 -0
  73. package/dist/server/services/plan/watcher.d.ts.map +1 -0
  74. package/dist/server/services/plan/watcher.js +69 -0
  75. package/dist/server/services/plan/watcher.js.map +1 -0
  76. package/dist/server/services/websocket/file-explorer-handlers.js +20 -0
  77. package/dist/server/services/websocket/file-explorer-handlers.js.map +1 -1
  78. package/dist/server/services/websocket/handler.d.ts +0 -1
  79. package/dist/server/services/websocket/handler.d.ts.map +1 -1
  80. package/dist/server/services/websocket/handler.js +28 -2
  81. package/dist/server/services/websocket/handler.js.map +1 -1
  82. package/dist/server/services/websocket/plan-handlers.d.ts +6 -0
  83. package/dist/server/services/websocket/plan-handlers.d.ts.map +1 -0
  84. package/dist/server/services/websocket/plan-handlers.js +494 -0
  85. package/dist/server/services/websocket/plan-handlers.js.map +1 -0
  86. package/dist/server/services/websocket/quality-handlers.d.ts +4 -0
  87. package/dist/server/services/websocket/quality-handlers.d.ts.map +1 -0
  88. package/dist/server/services/websocket/quality-handlers.js +470 -0
  89. package/dist/server/services/websocket/quality-handlers.js.map +1 -0
  90. package/dist/server/services/websocket/quality-persistence.d.ts +45 -0
  91. package/dist/server/services/websocket/quality-persistence.d.ts.map +1 -0
  92. package/dist/server/services/websocket/quality-persistence.js +187 -0
  93. package/dist/server/services/websocket/quality-persistence.js.map +1 -0
  94. package/dist/server/services/websocket/quality-service.d.ts +54 -0
  95. package/dist/server/services/websocket/quality-service.d.ts.map +1 -0
  96. package/dist/server/services/websocket/quality-service.js +816 -0
  97. package/dist/server/services/websocket/quality-service.js.map +1 -0
  98. package/dist/server/services/websocket/session-handlers.d.ts.map +1 -1
  99. package/dist/server/services/websocket/session-handlers.js +23 -0
  100. package/dist/server/services/websocket/session-handlers.js.map +1 -1
  101. package/dist/server/services/websocket/types.d.ts +2 -2
  102. package/dist/server/services/websocket/types.d.ts.map +1 -1
  103. package/package.json +3 -2
  104. package/server/cli/headless/claude-invoker.ts +21 -9
  105. package/server/cli/headless/headless-logger.ts +78 -0
  106. package/server/cli/headless/mcp-config.ts +6 -5
  107. package/server/cli/headless/runner.ts +4 -0
  108. package/server/cli/headless/stall-assessor.ts +101 -20
  109. package/server/cli/headless/tool-watchdog.ts +18 -9
  110. package/server/cli/headless/types.ts +10 -1
  111. package/server/cli/improvisation-session-manager.ts +118 -11
  112. package/server/index.ts +0 -4
  113. package/server/mcp/bouncer-cli.ts +73 -0
  114. package/server/mcp/bouncer-integration.ts +66 -44
  115. package/server/mcp/bouncer-sandbox.ts +214 -0
  116. package/server/mcp/security-patterns.ts +206 -10
  117. package/server/services/plan/composer.ts +199 -0
  118. package/server/services/plan/dependency-resolver.ts +179 -0
  119. package/server/services/plan/executor.ts +604 -0
  120. package/server/services/plan/parser.ts +459 -0
  121. package/server/services/plan/state-reconciler.ts +132 -0
  122. package/server/services/plan/types.ts +164 -0
  123. package/server/services/plan/watcher.ts +73 -0
  124. package/server/services/websocket/file-explorer-handlers.ts +20 -0
  125. package/server/services/websocket/handler.ts +28 -2
  126. package/server/services/websocket/plan-handlers.ts +592 -0
  127. package/server/services/websocket/quality-handlers.ts +570 -0
  128. package/server/services/websocket/quality-persistence.ts +250 -0
  129. package/server/services/websocket/quality-service.ts +975 -0
  130. package/server/services/websocket/session-handlers.ts +26 -0
  131. package/server/services/websocket/types.ts +62 -2
@@ -0,0 +1,250 @@
1
+ // Copyright (c) 2025-present Mstro, Inc. All rights reserved.
2
+ // Licensed under the MIT License. See LICENSE file for details.
3
+
4
+ /**
5
+ * Quality Persistence — Persists quality config, reports, and history
6
+ * to .mstro/quality/ in the working directory.
7
+ *
8
+ * Files:
9
+ * .mstro/quality/config.json — Directory list (paths + labels)
10
+ * .mstro/quality/reports/<slug>.json — Latest full report per directory
11
+ * .mstro/quality/history.json — Score history entries for trend tracking
12
+ */
13
+
14
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
15
+ import { join } from 'node:path';
16
+ import type { QualityResults } from './quality-service.js';
17
+
18
+ // ============================================================================
19
+ // Types
20
+ // ============================================================================
21
+
22
+ export interface QualityDirectoryConfig {
23
+ path: string;
24
+ label: string;
25
+ }
26
+
27
+ interface QualityConfig {
28
+ directories: QualityDirectoryConfig[];
29
+ }
30
+
31
+ export interface HistoryDirectoryEntry {
32
+ path: string;
33
+ score: number;
34
+ grade: string;
35
+ }
36
+
37
+ export interface QualityHistoryEntry {
38
+ timestamp: string;
39
+ overall: number;
40
+ grade: string;
41
+ directories: HistoryDirectoryEntry[];
42
+ }
43
+
44
+ interface QualityHistory {
45
+ entries: QualityHistoryEntry[];
46
+ }
47
+
48
+ export interface QualityPersistedState {
49
+ directories: QualityDirectoryConfig[];
50
+ reports: Record<string, QualityResults>;
51
+ history: QualityHistoryEntry[];
52
+ }
53
+
54
+ // ============================================================================
55
+ // Helpers
56
+ // ============================================================================
57
+
58
+ const MAX_HISTORY_ENTRIES = 100;
59
+
60
+ function slugify(dirPath: string): string {
61
+ if (dirPath === '.' || dirPath === './') return '_root';
62
+ return dirPath.replace(/[/\\]/g, '_').replace(/^_+|_+$/g, '') || '_root';
63
+ }
64
+
65
+ function ensureDir(dir: string): void {
66
+ if (!existsSync(dir)) {
67
+ mkdirSync(dir, { recursive: true });
68
+ }
69
+ }
70
+
71
+ function readJson<T>(filePath: string, fallback: T): T {
72
+ try {
73
+ if (existsSync(filePath)) {
74
+ return JSON.parse(readFileSync(filePath, 'utf-8')) as T;
75
+ }
76
+ } catch {
77
+ // Corrupted or unreadable — return fallback
78
+ }
79
+ return fallback;
80
+ }
81
+
82
+ function writeJson(filePath: string, data: unknown): void {
83
+ try {
84
+ writeFileSync(filePath, JSON.stringify(data, null, 2));
85
+ } catch (error) {
86
+ console.error('[QualityPersistence] Error writing:', filePath, error);
87
+ }
88
+ }
89
+
90
+ // ============================================================================
91
+ // Quality Persistence
92
+ // ============================================================================
93
+
94
+ export class QualityPersistence {
95
+ private qualityDir: string;
96
+ private reportsDir: string;
97
+ private configPath: string;
98
+ private historyPath: string;
99
+
100
+ constructor(workingDir: string) {
101
+ this.qualityDir = join(workingDir, '.mstro', 'quality');
102
+ this.reportsDir = join(this.qualityDir, 'reports');
103
+ this.configPath = join(this.qualityDir, 'config.json');
104
+ this.historyPath = join(this.qualityDir, 'history.json');
105
+ ensureDir(this.reportsDir);
106
+ }
107
+
108
+ // ---- Config (directory list) ----
109
+
110
+ loadConfig(): QualityDirectoryConfig[] {
111
+ const config = readJson<QualityConfig>(this.configPath, { directories: [] });
112
+ return config.directories;
113
+ }
114
+
115
+ saveConfig(directories: QualityDirectoryConfig[]): void {
116
+ writeJson(this.configPath, { directories });
117
+ }
118
+
119
+ addDirectory(path: string, label: string): void {
120
+ const dirs = this.loadConfig();
121
+ if (!dirs.some((d) => d.path === path)) {
122
+ dirs.push({ path, label });
123
+ this.saveConfig(dirs);
124
+ }
125
+ }
126
+
127
+ removeDirectory(path: string): void {
128
+ const dirs = this.loadConfig().filter((d) => d.path !== path);
129
+ this.saveConfig(dirs);
130
+ }
131
+
132
+ // ---- Reports (latest per directory) ----
133
+
134
+ loadReport(dirPath: string): QualityResults | null {
135
+ const slug = slugify(dirPath);
136
+ const reportPath = join(this.reportsDir, `${slug}.json`);
137
+ return readJson<QualityResults | null>(reportPath, null);
138
+ }
139
+
140
+ saveReport(dirPath: string, results: QualityResults): void {
141
+ const slug = slugify(dirPath);
142
+ const reportPath = join(this.reportsDir, `${slug}.json`);
143
+ writeJson(reportPath, results);
144
+ }
145
+
146
+ loadAllReports(directories: QualityDirectoryConfig[]): Record<string, QualityResults> {
147
+ const reports: Record<string, QualityResults> = {};
148
+ for (const dir of directories) {
149
+ const report = this.loadReport(dir.path);
150
+ if (report) {
151
+ reports[dir.path] = report;
152
+ }
153
+ }
154
+ return reports;
155
+ }
156
+
157
+ // ---- History (trend tracking) ----
158
+
159
+ loadHistory(): QualityHistoryEntry[] {
160
+ const history = readJson<QualityHistory>(this.historyPath, { entries: [] });
161
+ return history.entries;
162
+ }
163
+
164
+ appendHistory(results: QualityResults, dirPath: string): void {
165
+ const history = this.loadHistory();
166
+
167
+ // Find or create entry for this timestamp batch
168
+ // If the last entry was within 60 seconds, merge into it (for multi-dir scans)
169
+ const now = new Date();
170
+ const lastEntry = history[history.length - 1];
171
+ const lastTime = lastEntry ? new Date(lastEntry.timestamp).getTime() : 0;
172
+ const mergeWindow = 60_000; // 60 seconds
173
+
174
+ const dirEntry: HistoryDirectoryEntry = {
175
+ path: dirPath,
176
+ score: results.overall,
177
+ grade: results.grade,
178
+ };
179
+
180
+ if (lastEntry && now.getTime() - lastTime < mergeWindow) {
181
+ // Merge: update or add this directory in the last entry
182
+ const existing = lastEntry.directories.findIndex((d) => d.path === dirPath);
183
+ if (existing >= 0) {
184
+ lastEntry.directories[existing] = dirEntry;
185
+ } else {
186
+ lastEntry.directories.push(dirEntry);
187
+ }
188
+ // Recompute overall as average of all directories in this entry
189
+ const totalScore = lastEntry.directories.reduce((sum, d) => sum + d.score, 0);
190
+ lastEntry.overall = Math.round(totalScore / lastEntry.directories.length);
191
+ lastEntry.grade = gradeFromScore(lastEntry.overall);
192
+ lastEntry.timestamp = now.toISOString();
193
+ } else {
194
+ // New entry
195
+ history.push({
196
+ timestamp: now.toISOString(),
197
+ overall: results.overall,
198
+ grade: results.grade,
199
+ directories: [dirEntry],
200
+ });
201
+ }
202
+
203
+ // Trim to max entries
204
+ while (history.length > MAX_HISTORY_ENTRIES) {
205
+ history.shift();
206
+ }
207
+
208
+ writeJson(this.historyPath, { entries: history });
209
+ }
210
+
211
+ // ---- Code Review (persisted per directory) ----
212
+
213
+ loadCodeReview(dirPath: string): { findings: Record<string, unknown>[]; summary: string; timestamp: string } | null {
214
+ const slug = slugify(dirPath);
215
+ const reviewPath = join(this.reportsDir, `${slug}-review.json`);
216
+ return readJson<{ findings: Record<string, unknown>[]; summary: string; timestamp: string } | null>(reviewPath, null);
217
+ }
218
+
219
+ saveCodeReview(dirPath: string, findings: Record<string, unknown>[], summary: string): void {
220
+ const slug = slugify(dirPath);
221
+ const reviewPath = join(this.reportsDir, `${slug}-review.json`);
222
+ writeJson(reviewPath, { findings, summary, timestamp: new Date().toISOString() });
223
+ }
224
+
225
+ // ---- Full state load ----
226
+
227
+ loadState(): QualityPersistedState {
228
+ const directories = this.loadConfig();
229
+ const reports = this.loadAllReports(directories);
230
+ const history = this.loadHistory();
231
+
232
+ // Merge persisted code reviews into reports
233
+ for (const dir of directories) {
234
+ const review = this.loadCodeReview(dir.path);
235
+ if (review && reports[dir.path]) {
236
+ reports[dir.path] = { ...reports[dir.path], codeReview: review.findings as unknown as QualityResults['codeReview'] };
237
+ }
238
+ }
239
+
240
+ return { directories, reports, history };
241
+ }
242
+ }
243
+
244
+ function gradeFromScore(score: number): string {
245
+ if (score >= 90) return 'A';
246
+ if (score >= 80) return 'B';
247
+ if (score >= 70) return 'C';
248
+ if (score >= 60) return 'D';
249
+ return 'F';
250
+ }