mstro-app 0.4.34 → 0.4.35

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 (91) hide show
  1. package/dist/server/cli/headless/claude-invoker-stream.d.ts.map +1 -1
  2. package/dist/server/cli/headless/claude-invoker-stream.js +63 -0
  3. package/dist/server/cli/headless/claude-invoker-stream.js.map +1 -1
  4. package/dist/server/cli/headless/haiku-assessments.d.ts.map +1 -1
  5. package/dist/server/cli/headless/haiku-assessments.js +10 -5
  6. package/dist/server/cli/headless/haiku-assessments.js.map +1 -1
  7. package/dist/server/cli/improvisation-retry.d.ts.map +1 -1
  8. package/dist/server/cli/improvisation-retry.js +17 -2
  9. package/dist/server/cli/improvisation-retry.js.map +1 -1
  10. package/dist/server/cli/improvisation-session-manager.d.ts +1 -0
  11. package/dist/server/cli/improvisation-session-manager.d.ts.map +1 -1
  12. package/dist/server/cli/improvisation-session-manager.js +13 -5
  13. package/dist/server/cli/improvisation-session-manager.js.map +1 -1
  14. package/dist/server/cli/improvisation-types.d.ts +1 -0
  15. package/dist/server/cli/improvisation-types.d.ts.map +1 -1
  16. package/dist/server/cli/improvisation-types.js.map +1 -1
  17. package/dist/server/services/websocket/git-head-watcher.d.ts +25 -0
  18. package/dist/server/services/websocket/git-head-watcher.d.ts.map +1 -0
  19. package/dist/server/services/websocket/git-head-watcher.js +136 -0
  20. package/dist/server/services/websocket/git-head-watcher.js.map +1 -0
  21. package/dist/server/services/websocket/git-worktree-handlers.js +37 -5
  22. package/dist/server/services/websocket/git-worktree-handlers.js.map +1 -1
  23. package/dist/server/services/websocket/handler-context.d.ts +2 -0
  24. package/dist/server/services/websocket/handler-context.d.ts.map +1 -1
  25. package/dist/server/services/websocket/handler.d.ts +3 -1
  26. package/dist/server/services/websocket/handler.d.ts.map +1 -1
  27. package/dist/server/services/websocket/handler.js +18 -6
  28. package/dist/server/services/websocket/handler.js.map +1 -1
  29. package/dist/server/services/websocket/plan-board-handlers.d.ts +1 -0
  30. package/dist/server/services/websocket/plan-board-handlers.d.ts.map +1 -1
  31. package/dist/server/services/websocket/plan-board-handlers.js +94 -0
  32. package/dist/server/services/websocket/plan-board-handlers.js.map +1 -1
  33. package/dist/server/services/websocket/plan-handlers.d.ts.map +1 -1
  34. package/dist/server/services/websocket/plan-handlers.js +3 -1
  35. package/dist/server/services/websocket/plan-handlers.js.map +1 -1
  36. package/dist/server/services/websocket/quality-persistence.d.ts +7 -0
  37. package/dist/server/services/websocket/quality-persistence.d.ts.map +1 -1
  38. package/dist/server/services/websocket/quality-persistence.js +15 -7
  39. package/dist/server/services/websocket/quality-persistence.js.map +1 -1
  40. package/dist/server/services/websocket/quality-review-agent.d.ts.map +1 -1
  41. package/dist/server/services/websocket/quality-review-agent.js +2 -13
  42. package/dist/server/services/websocket/quality-review-agent.js.map +1 -1
  43. package/dist/server/services/websocket/quality-service.d.ts +12 -3
  44. package/dist/server/services/websocket/quality-service.d.ts.map +1 -1
  45. package/dist/server/services/websocket/quality-service.js +101 -81
  46. package/dist/server/services/websocket/quality-service.js.map +1 -1
  47. package/dist/server/services/websocket/quality-tools.d.ts.map +1 -1
  48. package/dist/server/services/websocket/quality-tools.js +6 -1
  49. package/dist/server/services/websocket/quality-tools.js.map +1 -1
  50. package/dist/server/services/websocket/quality-types.d.ts +15 -2
  51. package/dist/server/services/websocket/quality-types.d.ts.map +1 -1
  52. package/dist/server/services/websocket/quality-types.js.map +1 -1
  53. package/dist/server/services/websocket/session-handlers.js +2 -2
  54. package/dist/server/services/websocket/session-handlers.js.map +1 -1
  55. package/dist/server/services/websocket/tab-handlers.d.ts.map +1 -1
  56. package/dist/server/services/websocket/tab-handlers.js +9 -2
  57. package/dist/server/services/websocket/tab-handlers.js.map +1 -1
  58. package/dist/server/services/websocket/types.d.ts +2 -2
  59. package/dist/server/services/websocket/types.d.ts.map +1 -1
  60. package/package.json +2 -1
  61. package/server/cli/headless/claude-invoker-stream.ts +63 -0
  62. package/server/cli/headless/haiku-assessments.ts +10 -5
  63. package/server/cli/improvisation-retry.ts +18 -2
  64. package/server/cli/improvisation-session-manager.ts +13 -5
  65. package/server/cli/improvisation-types.ts +1 -0
  66. package/server/services/plan/agents/assess-stall.md +21 -0
  67. package/server/services/plan/agents/check-injection.md +36 -0
  68. package/server/services/plan/agents/classify-error.md +29 -0
  69. package/server/services/plan/agents/detect-context-loss.md +29 -0
  70. package/server/services/plan/agents/execute-issue.md +42 -0
  71. package/server/services/plan/agents/plan-coordinator.md +71 -0
  72. package/server/services/plan/agents/retry-task.md +26 -0
  73. package/server/services/plan/agents/review-code.md +4 -1
  74. package/server/services/plan/agents/review-criteria.md +53 -0
  75. package/server/services/plan/agents/review-custom.md +4 -1
  76. package/server/services/plan/agents/review-quality.md +4 -1
  77. package/server/services/plan/agents/verify-review.md +56 -0
  78. package/server/services/websocket/git-head-watcher.ts +120 -0
  79. package/server/services/websocket/git-worktree-handlers.ts +40 -6
  80. package/server/services/websocket/handler-context.ts +2 -0
  81. package/server/services/websocket/handler.ts +19 -6
  82. package/server/services/websocket/plan-board-handlers.ts +116 -0
  83. package/server/services/websocket/plan-handlers.ts +3 -1
  84. package/server/services/websocket/quality-persistence.ts +23 -7
  85. package/server/services/websocket/quality-review-agent.ts +2 -12
  86. package/server/services/websocket/quality-service.ts +116 -99
  87. package/server/services/websocket/quality-tools.ts +6 -1
  88. package/server/services/websocket/quality-types.ts +17 -2
  89. package/server/services/websocket/session-handlers.ts +2 -2
  90. package/server/services/websocket/tab-handlers.ts +8 -2
  91. package/server/services/websocket/types.ts +7 -2
@@ -5,11 +5,11 @@ import { extname } from 'node:path';
5
5
  import { analyzeComplexity, analyzeFunctionLength } from './quality-complexity.js';
6
6
  import { analyzeLinting } from './quality-linting.js';
7
7
  import { collectSourceFiles, detectEcosystem, runCommand, type SourceFile } from './quality-tools.js';
8
- import { type CategoryScore, type Ecosystem, FILE_LENGTH_THRESHOLD, hasInstalledToolInCategory, type QualityFinding, type QualityResults, type ScanProgress, TOTAL_STEPS } from './quality-types.js';
8
+ import { type CategoryPenalty, type CategoryScore, type Ecosystem, FILE_LENGTH_THRESHOLD, hasInstalledToolInCategory, type QualityFinding, type QualityResults, type ScanProgress, type ScoreBreakdown, TOTAL_STEPS } from './quality-types.js';
9
9
 
10
10
  export { detectEcosystem, detectTools, installTools } from './quality-tools.js';
11
11
  // Re-export public API for backward compatibility
12
- export type { CategoryScore, QualityFinding, QualityResults, QualityTool, ScanProgress } from './quality-types.js';
12
+ export type { CategoryPenalty, CategoryScore, QualityFinding, QualityResults, QualityTool, ScanProgress, ScoreBreakdown } from './quality-types.js';
13
13
 
14
14
  // ============================================================================
15
15
  // Formatting Analysis
@@ -124,7 +124,7 @@ function analyzeFileLength(files: SourceFile[]): { score: number; findings: Qual
124
124
  }
125
125
 
126
126
  // ============================================================================
127
- // Scoring
127
+ // Deterministic Scoring — penalty-density exponential decay
128
128
  // ============================================================================
129
129
 
130
130
  function computeGrade(score: number): string {
@@ -135,66 +135,107 @@ function computeGrade(score: number): string {
135
135
  return 'F';
136
136
  }
137
137
 
138
- interface CategoryWeights {
139
- linting: number;
140
- formatting: number;
141
- complexity: number;
142
- fileLength: number;
143
- functionLength: number;
144
- aiReview: number;
145
- }
138
+ const SEVERITY_WEIGHT: Record<string, number> = {
139
+ critical: 10,
140
+ high: 5,
141
+ medium: 2,
142
+ low: 0.5,
143
+ };
146
144
 
147
- const DEFAULT_WEIGHTS: CategoryWeights = {
148
- linting: 0.25,
149
- formatting: 0.10,
150
- complexity: 0.20,
151
- fileLength: 0.12,
152
- functionLength: 0.13,
153
- aiReview: 0.20,
145
+ const CATEGORY_MULTIPLIER: Record<string, number> = {
146
+ security: 2.0,
147
+ bugs: 1.5,
148
+ architecture: 1.2,
149
+ logic: 1.2,
150
+ performance: 1.0,
151
+ oop: 0.8,
152
+ maintainability: 0.8,
153
+ complexity: 0.7,
154
+ lint: 0.5,
155
+ linting: 0.5,
156
+ format: 0.3,
157
+ 'file-length': 0.3,
158
+ 'function-length': 0.3,
154
159
  };
155
160
 
156
- // ============================================================================
157
- // AI Code Review Score
158
- // ============================================================================
161
+ const OVERALL_DECAY = 0.09;
162
+ const CATEGORY_DECAY = 0.20;
159
163
 
160
- const SEVERITY_PENALTY: Record<string, number> = {
161
- critical: 10.0,
162
- high: 5.0,
163
- medium: 2.0,
164
- low: 0.5,
165
- };
164
+ function findingPenalty(f: { severity: string; category: string }): number {
165
+ return (SEVERITY_WEIGHT[f.severity] ?? 2) * (CATEGORY_MULTIPLIER[f.category] ?? 1.0);
166
+ }
167
+
168
+ export function computeFormulaScore(
169
+ allFindings: Array<{ severity: string; category: string }>,
170
+ totalLines: number,
171
+ ): { score: number; breakdown: ScoreBreakdown } {
172
+ const kloc = Math.max(totalLines / 1000, 1.0);
173
+
174
+ if (allFindings.length === 0) {
175
+ return {
176
+ score: 100,
177
+ breakdown: { penaltyDensity: 0, totalPenalty: 0, issueDensity: 0, kloc, categoryPenalties: [] },
178
+ };
179
+ }
180
+
181
+ const byCategory = new Map<string, { penalty: number; count: number }>();
182
+ let totalPenalty = 0;
183
+
184
+ for (const f of allFindings) {
185
+ const p = findingPenalty(f);
186
+ totalPenalty += p;
187
+ const existing = byCategory.get(f.category);
188
+ if (existing) {
189
+ existing.penalty += p;
190
+ existing.count++;
191
+ } else {
192
+ byCategory.set(f.category, { penalty: p, count: 1 });
193
+ }
194
+ }
195
+
196
+ const penaltyDensity = totalPenalty / kloc;
197
+ const score = Math.round(100 * Math.exp(-OVERALL_DECAY * penaltyDensity));
198
+
199
+ const categoryPenalties: CategoryPenalty[] = [];
200
+ for (const [cat, data] of byCategory) {
201
+ const catDensity = data.penalty / kloc;
202
+ const catScore = Math.round(100 * Math.exp(-CATEGORY_DECAY * catDensity));
203
+ categoryPenalties.push({
204
+ category: cat,
205
+ score: catScore,
206
+ grade: computeGrade(catScore),
207
+ penalty: Math.round(data.penalty * 10) / 10,
208
+ findingCount: data.count,
209
+ });
210
+ }
166
211
 
167
- /** Exponential decay constant higher = harsher scoring */
168
- const AI_REVIEW_DECAY = 0.10;
212
+ categoryPenalties.sort((a, b) => a.score - b.score);
169
213
 
214
+ return {
215
+ score,
216
+ breakdown: {
217
+ penaltyDensity: Math.round(penaltyDensity * 100) / 100,
218
+ totalPenalty: Math.round(totalPenalty * 10) / 10,
219
+ issueDensity: Math.round((allFindings.length / kloc) * 100) / 100,
220
+ kloc: Math.round(kloc * 10) / 10,
221
+ categoryPenalties,
222
+ },
223
+ };
224
+ }
225
+
226
+ /** @deprecated — use computeFormulaScore instead */
170
227
  export function computeAiReviewScore(
171
228
  findings: Array<{ severity: string }>,
172
229
  totalLines: number,
173
230
  ): number {
174
231
  if (findings.length === 0) return 100;
175
-
176
232
  const effectiveKloc = Math.max(totalLines / 1000, 1.0);
177
233
  const totalPenalty = findings.reduce(
178
- (sum, f) => sum + (SEVERITY_PENALTY[f.severity] ?? 2.0),
234
+ (sum, f) => sum + (SEVERITY_WEIGHT[f.severity] ?? 2.0),
179
235
  0,
180
236
  );
181
237
  const penaltyDensity = totalPenalty / effectiveKloc;
182
- return Math.round(100 * Math.exp(-AI_REVIEW_DECAY * penaltyDensity));
183
- }
184
-
185
- function computeOverallScore(categories: CategoryScore[]): number {
186
- const available = categories.filter((c) => c.available);
187
- if (available.length === 0) return 0;
188
-
189
- const totalWeight = available.reduce((sum, c) => sum + c.weight, 0);
190
- let weighted = 0;
191
- for (const cat of available) {
192
- const effectiveWeight = cat.weight / totalWeight;
193
- cat.effectiveWeight = effectiveWeight;
194
- weighted += cat.score * effectiveWeight;
195
- }
196
-
197
- return Math.round(Math.max(0, Math.min(100, weighted)));
238
+ return Math.round(100 * Math.exp(-0.10 * penaltyDensity));
198
239
  }
199
240
 
200
241
  // ============================================================================
@@ -250,64 +291,58 @@ export async function runQualityScan(
250
291
  // Step 7: Compute scores
251
292
  progress('Computing scores', 7);
252
293
 
294
+ const allFindings = [
295
+ ...lintResult.findings,
296
+ ...fmtResult.findings,
297
+ ...complexityResult.findings,
298
+ ...fileLengthResult.findings,
299
+ ...funcLengthResult.findings,
300
+ ];
301
+
302
+ const totalLines = files.reduce((sum, f) => sum + f.lines, 0);
303
+ const { score: overall, breakdown } = computeFormulaScore(allFindings, totalLines);
304
+
253
305
  const categories: CategoryScore[] = [
254
306
  {
255
307
  name: 'Linting',
256
308
  score: lintResult.score,
257
- weight: DEFAULT_WEIGHTS.linting,
258
- effectiveWeight: DEFAULT_WEIGHTS.linting,
309
+ weight: 0,
310
+ effectiveWeight: 0,
259
311
  available: lintResult.available,
260
312
  issueCount: lintResult.issueCount,
261
313
  },
262
314
  {
263
315
  name: 'Formatting',
264
316
  score: fmtResult.score,
265
- weight: DEFAULT_WEIGHTS.formatting,
266
- effectiveWeight: DEFAULT_WEIGHTS.formatting,
317
+ weight: 0,
318
+ effectiveWeight: 0,
267
319
  available: fmtResult.available,
268
320
  issueCount: fmtResult.issueCount,
269
321
  },
270
322
  {
271
323
  name: 'Complexity',
272
324
  score: complexityResult.score,
273
- weight: DEFAULT_WEIGHTS.complexity,
274
- effectiveWeight: DEFAULT_WEIGHTS.complexity,
325
+ weight: 0,
326
+ effectiveWeight: 0,
275
327
  available: complexityResult.available,
276
328
  issueCount: complexityResult.issueCount,
277
329
  },
278
330
  {
279
331
  name: 'File Length',
280
332
  score: fileLengthResult.score,
281
- weight: DEFAULT_WEIGHTS.fileLength,
282
- effectiveWeight: DEFAULT_WEIGHTS.fileLength,
333
+ weight: 0,
334
+ effectiveWeight: 0,
283
335
  available: true,
284
336
  issueCount: fileLengthResult.issueCount,
285
337
  },
286
338
  {
287
339
  name: 'Function Length',
288
340
  score: funcLengthResult.score,
289
- weight: DEFAULT_WEIGHTS.functionLength,
290
- effectiveWeight: DEFAULT_WEIGHTS.functionLength,
341
+ weight: 0,
342
+ effectiveWeight: 0,
291
343
  available: true,
292
344
  issueCount: funcLengthResult.issueCount,
293
345
  },
294
- {
295
- name: 'AI Review',
296
- score: 0,
297
- weight: DEFAULT_WEIGHTS.aiReview,
298
- effectiveWeight: DEFAULT_WEIGHTS.aiReview,
299
- available: false,
300
- issueCount: 0,
301
- },
302
- ];
303
-
304
- const overall = computeOverallScore(categories);
305
- const allFindings = [
306
- ...lintResult.findings,
307
- ...fmtResult.findings,
308
- ...complexityResult.findings,
309
- ...fileLengthResult.findings,
310
- ...funcLengthResult.findings,
311
346
  ];
312
347
 
313
348
  return {
@@ -317,9 +352,10 @@ export async function runQualityScan(
317
352
  findings: allFindings.slice(0, 200),
318
353
  codeReview: [],
319
354
  analyzedFiles: files.length,
320
- totalLines: files.reduce((sum, f) => sum + f.lines, 0),
355
+ totalLines,
321
356
  timestamp: new Date().toISOString(),
322
357
  ecosystem: ecosystems,
358
+ scoreBreakdown: breakdown,
323
359
  };
324
360
  }
325
361
 
@@ -329,39 +365,20 @@ export async function runQualityScan(
329
365
 
330
366
  /**
331
367
  * Recompute the overall score after AI code review findings become available.
332
- * Returns a new QualityResults with the AI Review category enabled and score updated.
368
+ * Merges CLI + AI findings and runs the deterministic penalty-density formula.
333
369
  */
334
370
  export function recomputeWithAiReview(
335
371
  results: QualityResults,
336
- aiFindings: Array<{ severity: string }>,
372
+ aiFindings: Array<{ severity: string; category: string }>,
337
373
  ): QualityResults {
338
- const aiScore = computeAiReviewScore(aiFindings, results.totalLines);
339
-
340
- // Update or add the AI Review category
341
- const categories = results.categories.map((cat) => ({ ...cat }));
342
- const aiCatIndex = categories.findIndex((c) => c.name === 'AI Review');
343
- const aiCategory: CategoryScore = {
344
- name: 'AI Review',
345
- score: aiScore,
346
- weight: DEFAULT_WEIGHTS.aiReview,
347
- effectiveWeight: DEFAULT_WEIGHTS.aiReview,
348
- available: true,
349
- issueCount: aiFindings.length,
350
- };
351
-
352
- if (aiCatIndex >= 0) {
353
- categories[aiCatIndex] = aiCategory;
354
- } else {
355
- categories.push(aiCategory);
356
- }
357
-
358
- const overall = computeOverallScore(categories);
374
+ const allFindings = [...results.findings, ...aiFindings];
375
+ const { score: overall, breakdown } = computeFormulaScore(allFindings, results.totalLines);
359
376
 
360
377
  return {
361
378
  ...results,
362
379
  overall,
363
380
  grade: computeGrade(overall),
364
- categories,
365
381
  codeReview: results.codeReview,
382
+ scoreBreakdown: breakdown,
366
383
  };
367
384
  }
@@ -65,6 +65,7 @@ async function checkToolInstalled(check: string[], cwd: string): Promise<boolean
65
65
  return new Promise((resolve) => {
66
66
  const proc = spawn(check[0], check.slice(1), {
67
67
  cwd,
68
+ shell: true,
68
69
  stdio: ['ignore', 'pipe', 'pipe'],
69
70
  timeout: 10000,
70
71
  });
@@ -109,13 +110,16 @@ export async function installTools(
109
110
  if (tool.installCommand.startsWith('(')) continue;
110
111
  const commands = tool.installCommand.split(' || ');
111
112
  let installed = false;
113
+ let lastStderr = '';
112
114
  for (const cmd of commands) {
113
115
  const parts = cmd.trim().split(' ');
114
116
  const result = await runCommand(parts[0], parts.slice(1), dirPath);
115
117
  if (result.exitCode === 0) { installed = true; break; }
118
+ lastStderr = result.stderr;
116
119
  }
117
120
  if (!installed) {
118
- failures.push(`${tool.name}: all install methods failed`);
121
+ const detail = lastStderr ? ` (${lastStderr.trim().split('\n').pop()})` : '';
122
+ failures.push(`${tool.name}: install failed${detail}`);
119
123
  }
120
124
  }
121
125
 
@@ -241,6 +245,7 @@ export function runCommand(cmd: string, args: string[], cwd: string): Promise<{
241
245
  return new Promise((resolve) => {
242
246
  const proc = spawn(cmd, args, {
243
247
  cwd,
248
+ shell: true,
244
249
  stdio: ['ignore', 'pipe', 'pipe'],
245
250
  timeout: 120000,
246
251
  });
@@ -35,6 +35,22 @@ export interface QualityFinding {
35
35
  verificationNote?: string;
36
36
  }
37
37
 
38
+ export interface CategoryPenalty {
39
+ category: string;
40
+ score: number;
41
+ grade: string;
42
+ penalty: number;
43
+ findingCount: number;
44
+ }
45
+
46
+ export interface ScoreBreakdown {
47
+ penaltyDensity: number;
48
+ totalPenalty: number;
49
+ issueDensity: number;
50
+ kloc: number;
51
+ categoryPenalties: CategoryPenalty[];
52
+ }
53
+
38
54
  export interface QualityResults {
39
55
  overall: number;
40
56
  grade: string;
@@ -45,8 +61,7 @@ export interface QualityResults {
45
61
  totalLines: number;
46
62
  timestamp: string;
47
63
  ecosystem: string[];
48
- /** AI-generated rationale for the score */
49
- scoreRationale?: string;
64
+ scoreBreakdown?: ScoreBreakdown;
50
65
  }
51
66
 
52
67
  export interface ScanProgress {
@@ -104,8 +104,8 @@ export function setupSessionListeners(ctx: HandlerContext, session: Improvisatio
104
104
  ctx.send(ws, { type: 'thinking', tabId, data: { text } });
105
105
  });
106
106
 
107
- session.on('onMovementStart', (sequenceNumber: number, prompt: string) => {
108
- ctx.send(ws, { type: 'movementStart', tabId, data: { sequenceNumber, prompt, timestamp: Date.now(), executionStartTimestamp: session.executionStartTimestamp } });
107
+ session.on('onMovementStart', (sequenceNumber: number, prompt: string, isAutoContinue?: boolean) => {
108
+ ctx.send(ws, { type: 'movementStart', tabId, data: { sequenceNumber, prompt, timestamp: Date.now(), executionStartTimestamp: session.executionStartTimestamp, isAutoContinue } });
109
109
  ctx.broadcastToAll({ type: 'tabStateChanged', data: { tabId, isExecuting: true, executionStartTimestamp: session.executionStartTimestamp } });
110
110
  });
111
111
 
@@ -51,8 +51,14 @@ export function handleGetActiveTabs(ctx: HandlerContext, ws: WSContext, workingD
51
51
  const tabs: Record<string, unknown> = {};
52
52
  for (const [tabId, regTab] of Object.entries(allTabs)) {
53
53
  const session = ctx.sessions.get(regTab.sessionId);
54
- const worktreePath = ctx.gitDirectories.get(tabId);
55
- const worktreeBranch = ctx.gitBranches.get(tabId);
54
+ let worktreePath = ctx.gitDirectories.get(tabId);
55
+ let worktreeBranch = ctx.gitBranches.get(tabId);
56
+ if (!worktreePath && regTab.worktreePath) {
57
+ worktreePath = regTab.worktreePath;
58
+ worktreeBranch = regTab.worktreeBranch;
59
+ ctx.gitDirectories.set(tabId, worktreePath);
60
+ if (worktreeBranch) ctx.gitBranches.set(tabId, worktreeBranch);
61
+ }
56
62
  tabs[tabId] = session
57
63
  ? buildActiveTabData(regTab, session, worktreePath, worktreeBranch)
58
64
  : buildInactiveTabData(regTab, worktreePath, worktreeBranch);
@@ -170,7 +170,9 @@ export interface WebSocketMessage {
170
170
  | 'deployUsageReport'
171
171
  | 'deployAiHealthUpdate'
172
172
  // Skill discovery
173
- | 'listSkills';
173
+ | 'listSkills'
174
+ // Chat-to-board (cross-view skill actions)
175
+ | 'chatToBoard';
174
176
  tabId?: string;
175
177
  terminalId?: string;
176
178
  // biome-ignore lint/suspicious/noExplicitAny: message envelope carries heterogeneous payloads
@@ -262,6 +264,7 @@ export interface WebSocketResponse {
262
264
  | 'gitWorktreeCreatedAndAssigned'
263
265
  | 'gitWorktreeRemoved'
264
266
  | 'tabWorktreeSwitched'
267
+ | 'gitBranchChanged'
265
268
  | 'gitWorktreePushed'
266
269
  | 'gitWorktreePRCreated'
267
270
  // Merge response types
@@ -350,7 +353,9 @@ export interface WebSocketResponse {
350
353
  | 'deployUsageReportAck'
351
354
  | 'deployAiHealthAck'
352
355
  // Skill discovery response types
353
- | 'skillsList';
356
+ | 'skillsList'
357
+ // Chat-to-board response types
358
+ | 'chatToBoardCreated';
354
359
  tabId?: string;
355
360
  terminalId?: string;
356
361
  // biome-ignore lint/suspicious/noExplicitAny: message envelope carries heterogeneous payloads